Invoice Data Extraction Node SDK
Official Node.js SDK for Invoice Data Extraction. Handles file upload, extraction submission, polling, and result download so you can go from local files to structured output in a few lines of code.
- Node.js 18 or later
- ESM only
Install
npm install @invoicedataextraction/sdk
This package is ESM only. Your project's package.json must include "type": "module" (or use .mjs file extensions). TypeScript declarations are included.
Quick Start
import InvoiceDataExtraction from "@invoicedataextraction/sdk";
const client = new InvoiceDataExtraction({
api_key: process.env.INVOICE_DATA_EXTRACTION_API_KEY,
});
const result = await client.extract({
folder_path: "./invoices",
prompt: "Extract invoice number and total",
output_structure: "per_invoice",
download: {
formats: ["xlsx"],
output_path: "./output",
},
console_output: true, // remove to disable console logging
});
extract(...) uploads every file in the folder, submits the extraction, polls until it finishes, and downloads the results. The returned result is the final polling response from the API. Check result.pages.failed_count to verify that all uploaded pages were processed successfully — if greater than 0, inspect result.pages.failed to see which files and pages failed. When console_output is enabled, failed pages are logged automatically.
Generate an API key from your dashboard. Every account includes 50 free pages per month. Additional credits can be purchase on a pay-as-you-go basis with no subscription needed.
Constructor
import InvoiceDataExtraction from "@invoicedataextraction/sdk";
const client = new InvoiceDataExtraction({
api_key: process.env.INVOICE_DATA_EXTRACTION_API_KEY,
});
| Parameter | Required | Description |
|---|---|---|
api_key | Yes | Your API key. |
base_url | No | API base URL. Defaults to https://api.invoicedataextraction.com/v1. Only needed for testing or non-production environments. |
extract(...)
Run a complete extraction in a single call. Pass a folder path or an array of file paths, tell the SDK what to extract, and optionally download the extracted data to disk as Excel, CSV, or JSON. The method returns the extraction task results — credits deducted, successful and failed pages, and AI uncertainty notes. The SDK handles upload, submission, polling, and download internally.
Underlying API workflow: upload session → submit extraction → poll for results → download output. See File limits for size and count constraints.
const result = await client.extract({
folder_path: "./invoices",
prompt: "Extract invoice number, date, vendor name, and total amount",
output_structure: "per_invoice",
download: {
formats: ["xlsx", "json"],
output_path: "./output",
},
console_output: true, // remove to disable console logging
});
Parameters
| Parameter | Required | Description |
|---|---|---|
folder_path | One of folder_path or files | Path to a local folder. The SDK uploads every supported file in the folder (.pdf, .jpg, .jpeg, .png). Not recursive. |
files | One of folder_path or files | Array of local file paths to upload. Supported types: .pdf, .jpg, .jpeg, .png. |
prompt | Yes | Extraction instructions. String or object — see Prompt below. |
output_structure | Yes | Controls how the extracted data is structured — see Output structure below. |
task_name | No | Your label for this extraction (3–40 characters). Appears in the web dashboard. If omitted, the SDK generates one as extraction_YYYYMMDD_HHMMSS. |
exclude_columns | No | Array of system-generated columns to exclude from output. By default, a "Source File" column is added to every row indicating which uploaded file/page the data was extracted from. If your workflow requires an exact output structure, you can exclude it. Valid values: "source_file". |
download | No | Download options — see Download below. If omitted, no files are downloaded. |
polling | No | Polling options — see Polling below. |
console_output | No | Boolean. When true, the SDK logs progress to the console during upload, polling, and download. Off by default. |
on_update | No | Callback function for lifecycle updates — see on_update below. |
Output structure
Controls how the extracted data is structured:
| Value | Meaning |
|---|---|
automatic | The AI decides based on your prompt and documents. |
per_invoice | Each invoice becomes a single row (spreadsheet/CSV) or object (JSON). |
per_line_item | Each individual product/service listed within an invoice becomes its own row (spreadsheet/CSV) or object (JSON). |
Prompt
The prompt tells the AI what data to extract. It can be a string or an object.
String — describe what you want in natural language (max 2,500 characters):
prompt: "Extract invoice number, date, vendor name, and total amount"
With a string, the AI chooses output field names based on your instructions.
Object — use an object when you need exact output field names. Each name is guaranteed to appear exactly as written in the extracted data. You can also add optional per-field and general instructions:
prompt: {
fields: [
{ name: "Invoice Number" },
{ name: "Invoice Date", prompt: "The date the invoice was issued, NOT the due date" },
{ name: "Vendor Name" },
{ name: "Total Amount", prompt: "No currency symbol, 2 decimal places" },
],
general_prompt: "Extract one record per invoice or credit note. Ignore email cover letters. Dates should be in YYYY-MM-DD format.",
}
Each item in fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The name for this data point in the output (2–50 characters). Prefer clear, descriptive names (e.g., "Invoice Number", not "Field A"). |
prompt | string | No | Specific instructions for extracting this data point (3–600 characters). Use this to clarify ambiguities or instruct special handling. |
| Field | Type | Required | Description |
|---|---|---|---|
general_prompt | string | No | Instructions that apply to the full task and across all fields (max 1,500 characters). Use this to provide special handling instructions, specify output formatting, or describe the extraction goal. |
fields must be a non-empty array.
For guidance on writing effective prompts, see the Extraction Guide.
Download
When download is provided, the SDK downloads output files after a successful extraction.
download: {
formats: ["xlsx", "csv", "json"],
output_path: "./output",
}
| Field | Required | Description |
|---|---|---|
formats | Yes | Array of output formats to download. One or more of "xlsx", "csv", "json". |
output_path | Yes | Destination folder for downloaded files. Created automatically if it doesn't exist. |
Downloaded files are named {task_name}_{timestamp}.{format}.
Auto-download is a best-effort convenience. If the extraction completed but a download fails, the SDK surfaces a warning through console_output / on_update and still returns the completed extraction response. You can retry the download later using downloadOutput(...).
Auto-download does not overwrite existing files. If a generated file path already exists, the SDK skips that file and surfaces a warning.
Returns
extract(...) returns the terminal polling response from the API unchanged — for both successful and failed extractions.
Verifying results: When extract(...) returns a completed extraction, check result.pages.failed_count. If it's 0, every uploaded page was processed successfully and is included in the output. If it's greater than 0, inspect result.pages.failed to see which specific files and pages failed — those pages are not included in the output. This is the primary check to confirm that everything you submitted was extracted without issue.
Completed:
{
"success": true,
"status": "completed",
"extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"credits_deducted": 25,
"output_structure": "per_invoice",
"output_expires_at": "2026-07-14T10:30:00Z",
"pages": {
"successful_count": 10,
"failed_count": 2,
"successful": [
{ "file_name": "invoice-1.pdf", "page": 1 }
],
"failed": [
{ "file_name": "damaged.pdf", "page": 1 }
]
},
"ai_uncertainty_notes": [],
"output": {
"xlsx_url": "https://...",
"csv_url": "https://...",
"json_url": "https://..."
}
}
| Field | Description |
|---|---|
credits_deducted | Credits charged for this extraction (one credit per successful page). |
output_structure | The output structure used: "per_invoice" or "per_line_item". If you submitted "automatic", this tells you what the AI chose. |
output_expires_at | ISO 8601 timestamp when generated output files will be deleted by the 90-day retention policy. After this time, output.*_url will be null and the output endpoint returns OUTPUT_EXPIRED. |
pages.successful_count | Number of pages successfully processed. |
pages.failed_count | Number of pages that failed processing. |
pages.successful | List of successfully processed pages. Each item has file_name (the uploaded file name) and page (the page number within that file). |
pages.failed | List of pages that failed processing. Same shape as successful. |
ai_uncertainty_notes | Areas where the AI made assumptions due to ambiguity in your prompt. Empty array if none. Each note has a topic, a description of what was assumed, and a suggested_prompt_additions array of prompt additions you can use to remove the ambiguity in future extractions. Each suggestion has a purpose (why you'd add it) and instructions (prompt text you can add). |
output | Presigned download URLs for each format (xlsx_url, csv_url, json_url). Each URL expires after 5 minutes — use downloadOutput(...) or getDownloadUrl(...) for a fresh URL. URLs are null once output_expires_at has passed (the underlying files have been deleted). |
File uploads are all-or-nothing — if extract(...) returns without throwing, every file was uploaded successfully. The only failures to check for are in pages.failed, which lists pages that failed during extraction processing. If pages.failed_count is 0, all uploaded files and pages were processed successfully.
Failed:
When the extraction task itself fails, extract(...) returns the failed polling response — it does not throw. The failure details are in the returned response body, not on error.body.
{
"success": false,
"status": "failed",
"extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits to process this extraction.",
"retryable": false,
"details": { "credits_required": 25, "credits_balance": 15, "credits_reserved": 10 }
}
}
See the API docs for the full list of task failure codes.
When extract(...) throws
extract(...) only throws before a terminal extraction response is available — for example if upload, submission, or polling fails due to invalid input, network errors, or a polling timeout. These are SDK/API errors and are read from error.body as described in Errors.
Staged Workflow
extract(...) runs the full pipeline in one call. If you need control over individual steps — for example, uploading files in one part of your system and triggering extraction in another, running multiple extractions against the same uploaded files, or fitting each step into your own error handling and retry logic — use these methods instead:
const upload = await client.uploadFiles({
files: ["./invoice1.pdf", "./invoice2.pdf"],
console_output: true,
});
const submitted = await client.submitExtraction({
upload_session_id: upload.upload_session_id,
file_ids: upload.file_ids,
prompt: "Extract invoice number and total",
output_structure: "per_invoice",
});
const result = await client.waitForExtractionToFinish({
extraction_id: submitted.extraction_id,
console_output: true,
});
// Verify all pages were processed
if (result.pages.failed_count > 0) {
console.log("Some pages failed processing:", result.pages.failed);
}
await client.downloadOutput({
extraction_id: submitted.extraction_id,
format: "xlsx",
file_path: "./output/invoices.xlsx",
});
uploadFiles(...)
Upload local files without starting an extraction. Use this when you want to upload once and submit extractions separately — for example, to run different prompts against the same files, or to upload in one part of your system and extract in another.
Underlying API workflow: create upload session → upload file parts → complete each file. See File limits for size and count constraints.
| Parameter | Required | Description |
|---|---|---|
folder_path | One of folder_path or files | Path to a local folder. The SDK uploads every supported file in the folder (.pdf, .jpg, .jpeg, .png). Not recursive. |
files | One of folder_path or files | Array of local file paths to upload. Supported types: .pdf, .jpg, .jpeg, .png. |
upload_session_id | No | Your own session ID. If omitted, the SDK generates one. If an upload fails partway through, that session cannot be resumed — start a new upload with a fresh session ID. |
console_output | No | Boolean. When true, the SDK logs upload progress to the console. |
on_update | No | Callback for upload lifecycle updates — see on_update. |
Returns
{
"upload_session_id": "session_a1b2c3d4-...",
"file_ids": ["file_abc123", "file_def456"]
}
Pass upload_session_id and file_ids to submitExtraction(...) to start an extraction.
File uploads are all-or-nothing. If any file fails to upload, the method throws immediately — there is no partial success state. If uploadFiles(...) returns without throwing, every file was uploaded successfully.
The API checks your credit balance when the upload session is created. If you don't have enough credits, uploadFiles(...) throws INSUFFICIENT_CREDITS before any files are uploaded.
submitExtraction(...)
Submit an extraction task for files that have already been uploaded. The method returns immediately — it does not wait for the extraction to finish.
Underlying API endpoint: POST /extractions.
| Parameter | Required | Description |
|---|---|---|
upload_session_id | Yes | The upload session ID returned by uploadFiles(...). |
file_ids | Yes | Array of file IDs returned by uploadFiles(...). |
prompt | Yes | Extraction instructions. String or object — see Prompt. |
output_structure | Yes | Controls how the extracted data is structured — see Output structure. |
task_name | No | Your label for this extraction (3–40 characters). Appears in the web dashboard. If omitted, the SDK generates one as extraction_YYYYMMDD_HHMMSS. |
exclude_columns | No | Array of system-generated columns to exclude from output. By default, a "Source File" column is added to every row indicating which uploaded file/page the data was extracted from. If your workflow requires an exact output structure, you can exclude it. Valid values: "source_file". |
submission_id | No | Your own idempotency ID for this submission. If omitted, the SDK generates one. If a request fails or times out, retry with the same submission_id to safely retrieve the existing task instead of creating a duplicate. |
Returns
{
"success": true,
"extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"submission_state": "received"
}
The task is now queued for processing. Use extraction_id to poll for results with waitForExtractionToFinish(...) or checkExtraction(...). Submitted tasks also appear in the web dashboard where you can view progress and results.
waitForExtractionToFinish(...)
Poll an extraction until it reaches a terminal state (completed or failed). Use this after submitExtraction(...) when you want the SDK to handle the polling loop for you.
Underlying API endpoint: GET /extractions/{extraction_id} (polled repeatedly).
| Parameter | Required | Description |
|---|---|---|
extraction_id | Yes | The extraction ID returned by submitExtraction(...). |
polling | No | Polling options — see Polling. |
console_output | No | Boolean. When true, the SDK logs polling progress to the console. |
on_update | No | Callback for waiting lifecycle updates — see on_update. |
Returns
Returns the terminal polling response from the API unchanged — the same shape documented for extract(...) returns.
When the extraction completes, you get the full result with credits_deducted, pages, ai_uncertainty_notes, and output URLs. Check result.pages.failed_count to verify all pages were processed — if greater than 0, some pages failed and are not included in the output. When it fails, you get the failed response with error.code and error.message. In both cases the response is returned, not thrown.
If polling.timeout_ms is set and the extraction hasn't finished in time, the method throws SDK_TIMEOUT_ERROR. The extraction may still be processing — you can check later with checkExtraction(...) or from the web dashboard.
downloadOutput(...)
Download a single output file for a completed extraction to disk. Use this for manual downloads after using the staged workflow, or to retry a failed auto-download from extract(...).
Underlying API workflow: request a fresh presigned download URL → download the file → write to disk.
| Parameter | Required | Description |
|---|---|---|
extraction_id | Yes | The extraction ID whose output you want to download. |
format | Yes | A single output format: "xlsx", "csv", or "json". |
file_path | Yes | Full destination file path on disk. The file extension must match the requested format. The parent directory is created automatically if it doesn't exist. |
downloadOutput(...) does not overwrite existing files. If file_path already exists, the SDK throws SDK_FILESYSTEM_ERROR with guidance to choose a new path or remove the existing file.
The extraction must be completed before downloading. If the requested format was not generated, or the extraction hasn't finished, the method throws OUTPUT_NOT_AVAILABLE. If the extraction completed but its generated output files have aged out of the 90-day retention window, the method throws OUTPUT_EXPIRED — see Output expiry.
Returns
{
"success": true,
"extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"format": "xlsx",
"file_path": "./output/invoices.xlsx"
}
checkExtraction(...)
Check the current status of a submitted extraction without polling. Use this when you want a single point-in-time status check — for example, in a job queue where you check periodically on your own schedule rather than having the SDK poll using waitForExtractionToFinish(...).
checkExtraction(...) returns the polling shape — minimal for processing and failed, full for completed. If you want the full record for an extraction in any state (including the original prompt and options, the full error.message for failed extractions, and available_outputs instead of signed URLs), use getExtraction(...) instead.
Underlying API endpoint: GET /extractions/{extraction_id}.
| Parameter | Required | Description |
|---|---|---|
extraction_id | Yes | The extraction ID to check. |
Returns
Returns the current polling response from the API unchanged. The response may represent a processing, completed, or failed extraction — the same shapes documented for extract(...) returns. A processing response includes a progress field (0–100) indicating approximate completion.
For terminal failed tasks the response is { success: false, status: "failed", extraction_id, error }. The task itself failed but the request succeeded — the SDK does not throw.
getDownloadUrl(...)
Request a fresh presigned download URL for an extraction's output. Use this when you want to handle the download yourself rather than using downloadOutput(...).
Underlying API endpoint: GET /extractions/{extraction_id}/output?format={format}.
| Parameter | Required | Description |
|---|---|---|
extraction_id | Yes | The extraction ID whose output you want to download. |
format | Yes | A single output format: "xlsx", "csv", or "json". |
Returns
{
"download_url": "https://storage.example.com/...?X-Amz-Signature=...",
"format": "xlsx",
"expires_in_seconds": 300
}
The URL is a temporary, pre-authenticated link. Make a plain GET request to it — no Authorization header needed. It expires after 5 minutes.
The extraction must be completed before requesting a download URL. If the requested format was not generated, or the extraction hasn't finished, the method throws OUTPUT_NOT_AVAILABLE. If the extraction completed but its generated output files have aged out of the 90-day retention window, the method throws OUTPUT_EXPIRED — see Output expiry.
deleteExtraction(...)
Permanently delete an extraction, its output files, and its uploaded source files. Use this when you need to remove data immediately rather than waiting for automatic data retention. Extractions that are currently being processed cannot be deleted.
If you created multiple extractions from the same upload session, deleting one will not affect the others — source files are only removed when no other extraction is using them.
Underlying API endpoint: DELETE /extractions/{extraction_id}.
| Parameter | Required | Description |
|---|---|---|
extraction_id | Yes | The extraction ID to delete. |
Returns
Returns the API response unchanged.
getCreditsBalance()
Check your current credit balance and reserved credits.
Underlying API endpoint: GET /credits/balance.
This method takes no arguments.
Returns
{
"success": true,
"credits_balance": 150,
"credits_reserved": 10
}
| Field | Description |
|---|---|
credits_balance | Your total credit balance (paid + free credits). |
credits_reserved | Credits reserved by extractions currently being processed. Your usable balance is credits_balance minus credits_reserved. |
listExtractions(...)
Retrieve a paginated list of your extractions, with optional filters. Items use a slim shape designed for browsing — for the full record (including the original prompt, options, full pages, ai_uncertainty_notes, and the full failure message/details), call getExtraction(...) for a specific item.
listExtractions(...) returns a single page. To iterate every matching extraction without writing the cursor loop yourself, use iterateExtractions(...).
Underlying API endpoint: GET /extractions.
const page = await client.listExtractions({
status: "completed",
submission_method: "api",
limit: 50,
});
for (const item of page.extractions) {
console.log(item.extraction_id, item.task_name, item.created_at);
}
if (page.has_more) {
const nextPage = await client.listExtractions({
status: "completed",
submission_method: "api",
limit: 50,
cursor: page.next_cursor,
});
}
Parameters
All filters are optional. Omit options entirely to list every extraction visible to your API key.
| Parameter | Required | Description |
|---|---|---|
status | No | One of "processing", "completed", or "failed". Filter by current status. |
submission_method | No | "api" or "web_app". Filter by how the extraction was submitted. The web_app value matches the database column verbatim. |
created_after | No | ISO 8601 string or Date. Returns extractions created on or after this timestamp. Date values are serialized via toISOString() before being sent. |
created_before | No | ISO 8601 string or Date. Returns extractions created on or before this timestamp. |
limit | No | Integer from 1 to 100. The number of items to return per page. |
cursor | No | Opaque pagination token returned as next_cursor from a previous page. Treat it as a string and pass it back unchanged. |
scope | No | "own" or "team". Only relevant for Team accounts. Team admins default to team-visible history; pass "own" to list only your own extractions. Non-admins can omit it. |
For team admins, omitting scope returns team-visible history, equivalent to the dashboard's Team tasks view. Use scope: "own" when you want only your own extractions. scope: "team" is accepted for explicitness, but only team admins can use it.
Returns
{
"success": true,
"extractions": [
{
"extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"submission_id": "sub_abc",
"task_name": "March invoices",
"status": "completed",
"created_at": "2026-04-15T10:30:00Z",
"submission_method": "api",
"file_count": 3,
"file_names_preview": ["a.pdf", "b.pdf", "c.pdf"],
"file_names_truncated": false,
"output_structure": "per_invoice",
"credits_deducted": 5,
"available_outputs": ["xlsx", "csv", "json"],
"output_expires_at": "2026-07-14T10:30:00Z"
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wNC0xNVQxMDozMDowMFoiLCJpIjoxMjM0fQ"
}
When no extractions match, the SDK returns { success: true, extractions: [], has_more: false, next_cursor: null }.
List item shape
Every list item includes these fields:
| Field | Description |
|---|---|
extraction_id | The extraction's UUID. Use this with getExtraction(...), getDownloadUrl(...), etc. |
submission_id | Your idempotency ID from submitExtraction(...), or null for web-app submissions. |
task_name | The label you gave the extraction at submission, or null. |
status | "processing", "completed", or "failed". |
created_at | ISO 8601 timestamp. |
submission_method | "api" or "web_app". |
file_count | Total number of files uploaded for this extraction. |
file_names_preview | The first up to 5 file names, in submission order. Use getExtraction(...) to retrieve the full list. |
file_names_truncated | true when file_count > file_names_preview.length (i.e., the extraction has more files than fit in the preview). |
output_structure | "per_invoice", "per_line_item", "automatic" (only while an automatic run is still resolving). |
Status-specific fields:
- Completed items add
credits_deducted(number),available_outputs(an array of"xlsx" | "csv" | "json"indicating which formats can currently be downloaded — empty when the output has aged pastoutput_expires_at), andoutput_expires_at(ISO 8601 string). - Processing items add
progress(0-100). - Failed items add
errorwith the slim{ code, retryable }shape — for the full message and details, callgetExtraction(...)on that extraction.
When a team admin lists team-visible history, every item also includes submitted_by: { email: string | null } identifying the team member who created the extraction. The field is absent in own-only listings. The SDK never exposes a user ID.
iterateExtractions(...)
Auto-paginating async iterator over listExtractions(...). Yields one extraction summary record at a time and transparently fetches the next page when the current one is exhausted. Use this when you want to process every matching extraction without writing the cursor loop yourself.
Underlying API endpoint: GET /extractions (paged).
for await (const extraction of client.iterateExtractions({ status: "completed" })) {
console.log(extraction.extraction_id, extraction.task_name);
}
Parameters
Identical to listExtractions(...), including scope for team-admin listing behavior. A caller-provided cursor is used as the starting point — the iterator manages cursor advancement from that point on.
Behavior
- Yields individual records, not pages. The iterator yields each
ExtractionListItemdirectly, in the same order aslistExtractions(...)would return them. - Pages are fetched lazily. The iterator does not request the next page until every item from the current page has been yielded. Breaking out of
for awaitearly (or otherwise terminating the iterator) prevents the next page from being fetched. - Filters are preserved across pages. The original options you pass are reused for every page; only
cursoradvances. - Mid-stream errors propagate. If a page request fails, the iterator throws on the corresponding
next()call and the consumer'sfor awaitrethrows. Items already yielded remain yielded. - Defensive guard on bad pagination state. If the API ever returns
has_more: truewithout a usablenext_cursor, the iterator throwsSDK_HTTP_ERRORrather than risking an infinite loop.
Validation runs eagerly: iterateExtractions(invalidOptions) throws synchronously at the call site before the iterator is created. You don't need to start iterating to discover bad input.
Return type
AsyncIterableIterator<ExtractionListItem> — usable with for await ... of, Array.fromAsync(...), or by calling .next() directly. The iterator implements Symbol.asyncIterator and returns itself.
getExtraction(...)
Retrieve a single extraction's full record. The record is the same regardless of state — processing, completed, or failed extractions are all returned with success: true and the failure details (when present) on extraction.error.
Use this when you want the full picture of an extraction: the original prompt and options, the complete file list, the full failure error (message and details, not just code and retryable), and available_outputs so you can decide what to download.
getExtraction(...) is record retrieval — distinct from checkExtraction(...), which wraps the polling endpoint and is intended for "is it done yet?" checks against in-flight extractions.
Underlying API endpoint: GET /extractions/{extraction_id}/details.
const { extraction } = await client.getExtraction({
extraction_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
});
if (extraction.status === "failed") {
console.error(extraction.error.code, extraction.error.message);
} else if (extraction.status === "completed") {
console.log("Available formats:", extraction.available_outputs);
}
Parameters
| Parameter | Required | Description |
|---|---|---|
extraction_id | Yes | The extraction ID to retrieve. |
Returns
{
"success": true,
"extraction": {
"extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"submission_id": "sub_abc",
"task_name": "March invoices",
"status": "completed",
"created_at": "2026-04-15T10:30:00Z",
"submission_method": "api",
"file_count": 3,
"file_names": ["a.pdf", "b.pdf", "c.pdf"],
"output_structure": "per_invoice",
"prompt": "Extract invoice number, date, vendor, total",
"options": { "exclude_columns": [] },
"credits_deducted": 5,
"available_outputs": ["xlsx", "csv", "json"],
"output_expires_at": "2026-07-14T10:30:00Z",
"pages": {
"successful_count": 6,
"failed_count": 0,
"successful": [{ "file_name": "a.pdf", "page": 1 }],
"failed": []
},
"ai_uncertainty_notes": []
}
}
Record shape
Every record includes these fields:
| Field | Description |
|---|---|
extraction_id, submission_id, task_name, created_at, submission_method, output_structure | Same semantics as the list item shape. |
file_count | Total number of files uploaded. |
file_names | Full list of file names, in submission order (no truncation). |
prompt | The original prompt you submitted: a string, a structured object ({ fields, general_prompt? }), an empty string for web-app submissions with no explicit prompt. |
options | Always { exclude_columns: [...] }, even when nothing was excluded. |
Status-specific fields:
- Completed records add
credits_deducted,available_outputs,output_expires_at, fullpages(withsuccessfulandfailedarrays), andai_uncertainty_notes(same shape as onextract(...)returns). - Processing records add
progress(0-100). - Failed records add
errorwith the full shape{ code, message, retryable, details }—getExtraction(...)exposes the full failure information regardless of when the extraction failed.
For team-admin lookups, the record may also include submitted_by: { email: string | null }.
The details endpoint never includes signed download URLs — call downloadOutput(...) or getDownloadUrl(...) to actually download files.
getExtraction(...) does not throw on failed extractions
A failed extraction is a valid record. getExtraction(...) returns it like any other state, with the failure details on extraction.error. It only throws for request-level failures: invalid input, authentication errors, EXTRACTION_NOT_FOUND, network failures, etc.
Common workflow: list, then download
Browsing past extractions and downloading their output is a two-step pattern. List/details responses don't include signed download URLs. Instead, use listExtractions(...) or iterateExtractions(...) to find the extraction you want, then call getDownloadUrl(...) (or downloadOutput(...)) for a fresh signed URL when you're ready to download:
for await (const extraction of client.iterateExtractions({
status: "completed",
submission_method: "api",
})) {
if (extraction.task_name?.startsWith("March invoices")) {
if (extraction.available_outputs.includes("xlsx")) {
await client.downloadOutput({
extraction_id: extraction.extraction_id,
format: "xlsx",
file_path: `./march/${extraction.extraction_id}.xlsx`,
});
} else {
// available_outputs is empty when output_expires_at has passed —
// the underlying file has been deleted by the 90-day retention policy.
console.warn(`Output no longer available for ${extraction.extraction_id}`);
}
break;
}
}
Working with Output Files
You can control the structure and formatting of all output files in two main ways:
- use
output_structureto choose the top-level record shape, such asper_invoiceorper_line_item - use your prompt to describe the fields, grouping, and overall structure you want, such as "one row per product" or "one row per PO"
You can also use your prompt to:
- specify missing-value placeholders, such as empty string,
N/A, or0 - specify formatting requirements, such as
YYYY-MM-DD, digits only, or no currency symbol - specify the intended output type, such as text, number, date, datetime, boolean, currency, or percentage
These instructions may appear differently across JSON, CSV, and XLSX outputs, but they all affect how the final export is produced.
At a high level:
- JSON output is string-based.
- CSV is text-based.
- XLSX can use native spreadsheet cell types when values can be safely interpreted.
Working with JSON Output
JSON value typing
In the JSON output file, extracted field values are returned as strings.
- Standard fields are returned as strings.
- If you ask for a field to contain JSON, that field is returned as a string containing valid JSON.
- All values inside that JSON are also strings.
If you need numbers, booleans, or dates as typed values, parse them in your own code. If you plan to parse a value, state the formatting clearly in your prompt. For example:
- "Do not include currency symbol"
- "Use digits only"
- "Return true or false"
- "Use YYYY-MM-DD format"
Structured JSON fields
You can ask for a field to return structured JSON.
Example prompt:
"prompt": {
"fields": [
{ "name": "Invoice Number" },
{
"name": "Line Items",
"prompt": "Return a JSON array with keys description, quantity, unit_price, and amount. Use digits only for quantity. Use a full stop as the decimal separator. Do not include currency symbols in unit_price or amount. Do not use thousands separators. Use an empty string when a value is missing."
}
]
}
Example JSON output value:
"Line Items": "[{\"description\":\"Widget\",\"quantity\":\"2\",\"unit_price\":\"9.99\",\"amount\":\"19.98\"}]"
In the example above, Line Items is a string whose content is valid JSON.
Use nested line-item JSON like above, mainly for smaller or simpler cases, such as when there are only a few line items and you want a single invoice-level object.
Recommended approach for line items
If you need detailed line item extraction, prefer output_structure: "per_line_item" instead of returning line items inside a nested JSON field.
This is strongly recommended when:
- invoices may contain around 7 or more line items
- line items need detailed per-field instructions
- you want the most reliable line item extraction
In per_line_item, define invoice-level fields and line-item fields as separate top-level fields.
Many workflows can use the per_line_item output directly, with one row/object per line item.
If your workflow needs a nested structure such as { invoice_fields..., line_items: [...] }, include your own stable invoice identifier such as Invoice Number so you can group related line item rows back into invoices in your own system.
Do not rely on Source File alone to group rows into invoices. Source File helps you trace where a row came from, but it is not a stable invoice identifier.
Example prompt for the recommended approach:
{
"prompt": {
"fields": [
{ "name": "Invoice Number" },
{ "name": "Invoice Date", "prompt": "Use YYYY-MM-DD format" },
{ "name": "Vendor Name" },
{ "name": "Line Item Description" },
{ "name": "Line Item Quantity", "prompt": "Use digits only" },
{ "name": "Line Item Unit Price" },
{ "name": "Line Item Amount" }
],
"general_prompt": "For amount fields don't use thousands separators, use full stops as the decimal separator and do not include currency symbols."
},
"output_structure": "per_line_item"
}
Example JSON output rows:
[
{
"Invoice Number": "INV-1001",
"Invoice Date": "2025-01-15",
"Vendor Name": "Acme Ltd",
"Line Item Description": "Widget A",
"Line Item Quantity": "2",
"Line Item Unit Price": "9.99",
"Line Item Amount": "19.98"
},
{
"Invoice Number": "INV-1001",
"Invoice Date": "2025-01-15",
"Vendor Name": "Acme Ltd",
"Line Item Description": "Widget B",
"Line Item Quantity": "1",
"Line Item Unit Price": "5.00",
"Line Item Amount": "5.00"
}
]
Both rows above belong to the same invoice because they share the same Invoice Number. If your workflow needs one record per line item, you can use the rows as-is. If your workflow needs a nested invoice structure, you can group rows that share the same invoice identifier to build your own { invoice_fields..., line_items: [...] } structure.
CSV Output
CSV is a plain-text export. Every value in the CSV file is written as text.
XLSX Output
XLSX uses the most appropriate spreadsheet cell type for each value by default, and follows explicit prompt instructions where provided.
File Limits
| Type | Max size |
|---|---|
| 150 MB | |
| JPG / JPEG / PNG | 5 MB |
| Total batch size | 2 GB |
| Max files per session | 6,000 |
Applies to extract(...) and uploadFiles(...).
Polling
Several methods accept a polling option to control how the SDK polls for extraction status.
| Field | Default | Description |
|---|---|---|
interval_ms | 10000 | Milliseconds between polls. Minimum 5000. |
timeout_ms | null | Maximum time to wait in milliseconds. null means no timeout. |
Used by: extract(...), waitForExtractionToFinish(...).
on_update
Optional callback that receives lifecycle updates across all stages. Use this when you want to handle progress reporting yourself — for example to update a UI, feed a progress bar, or route updates to your own logging instead of the built-in console_output.
on_update({ stage, level, message, progress, extraction_id })
| Field | Description |
|---|---|
stage | Current lifecycle stage: "upload", "submission", "waiting", "download", or "completion". |
level | "info", "warn", or "error". |
message | Human-readable status message. |
progress | Numeric progress when available, otherwise null. |
extraction_id | The extraction ID once available, otherwise null. |
Used by: extract(...), uploadFiles(...), waitForExtractionToFinish(...).
Output expiry
There are two unrelated time limits on output files:
| Limit | What expires | Duration | What to do |
|---|---|---|---|
| Signed download URL | The presigned URL itself | 5 minutes | Request a fresh URL via downloadOutput(...) or getDownloadUrl(...) |
| Output file retention | The generated file in storage | 90 days from created_at | Re-run the extraction; the original output is gone |
output_expires_at (on completed responses) tells you when the underlying file will be deleted. After that timestamp:
output.xlsx_url,output.csv_url,output.json_urlon polling/extract responses arenull.available_outputson list/details responses is an empty array.getDownloadUrl(...)anddownloadOutput(...)throwOUTPUT_EXPIRED.
OUTPUT_NOT_AVAILABLE is a different error: it means the extraction either hasn't completed, or the requested format was never generated for it. OUTPUT_EXPIRED means the output existed but has aged out of retention.
Conventions
- Method names are camelCase:
extract(...),uploadFiles(...),submitExtraction(...). - All option keys are snake_case:
api_key,folder_path,output_structure,task_name,upload_session_id,file_ids,console_output,on_update,file_path. Do not use camelCase equivalents likeapiKeyorfolderPath. - Response fields are snake_case, matching the API exactly. The SDK returns the same JSON shapes as the raw API — if you have the API docs, those response examples are valid for the SDK too.
- The
filesparameter accepts local file paths as strings only. Buffers, streams, and browser file objects are not supported in v1. - ESM only — use
import InvoiceDataExtraction from "@invoicedataextraction/sdk", notrequire().
Rate Limits
All API endpoints are rate limited per API key. The SDK automatically retries rate-limited requests, but you should be aware of the limits if you are making many calls. Sustained overuse will result in a RATE_LIMITED error.
| Endpoints | Limit |
|---|---|
| Upload endpoints (create session, get part URLs, complete upload) | 600 requests per minute |
| Submit extraction | 30 requests per minute |
| Poll extraction status | 120 requests per minute |
| List extractions | 60 requests per minute |
| Get extraction details | 60 requests per minute |
| Download output | 30 requests per minute |
| Delete extraction | 30 requests per minute |
| Check credit balance | 60 requests per minute |
Errors
SDK methods use normal JavaScript promise rejection behavior:
- On failure, a method rejects by throwing a normal JavaScript
Error. - The structured error body is available on
error.body. error.bodyuses the same JSON error shape as the API.
Error body shape:
{
"success": false,
"error": {
"code": "SOME_ERROR_CODE",
"message": "Human-readable message.",
"retryable": false,
"details": null
}
}
Read the error like this:
try {
await client.checkExtraction({ extraction_id });
} catch (error) {
const sdkError = error?.body;
if (!sdkError?.error) {
throw error;
}
console.log(sdkError.error.code);
console.log(sdkError.error.message);
console.log(sdkError.error.retryable);
console.log(sdkError.error.details);
}
Every error includes a code (machine-readable), message (human-readable), and retryable (whether retrying may succeed). The message is descriptive enough to act on directly in most cases. details provides additional context when available — for example, INVALID_INPUT errors include a details.issues array with the specific validation problems.
INVALID_INPUT can come from either the SDK (caught before the request is sent) or the API. Handle it the same way in both cases.
Authentication errors (UNAUTHENTICATED, API_KEY_EXPIRED, API_KEY_REVOKED) indicate a problem with your API key — generate a new one from your dashboard.
The SDK automatically retries RATE_LIMITED and transient INTERNAL_ERROR responses, but will surface them if retries are exhausted.
Method-specific errors like EXTRACTION_NOT_FOUND, OUTPUT_NOT_AVAILABLE, OUTPUT_EXPIRED, EXTRACTION_IN_PROGRESS, and INSUFFICIENT_CREDITS are documented in the relevant method sections above. For full endpoint-level error details, see the API docs.
Extraction task failure codes
When an extraction task itself fails, the failure code comes from the API-owned extraction failure taxonomy documented in the REST API docs. In the SDK, that code appears on result.error.code from extract(...), checkExtraction(...), and waitForExtractionToFinish(...), or on extraction.error.code from getExtraction(...).
Branch on error.code, error.retryable, and any returned error.details directly. The SDK does not provide subclasses for individual extraction failure codes.
Task failure vs SDK/API failure:
- After an extraction task has been accepted, the task itself can still finish with
status: "failed". - That is a task outcome, not an SDK error.
checkExtraction(...),waitForExtractionToFinish(...), andextract(...)return the polling response body for task states such asprocessing,completed, andfailed.- When a task ends with
status: "failed", the failure details are in the returned response body, not onerror.body. error.bodyis only used when the SDK method/request itself fails — validation errors, authentication errors, network failures, timeouts, or other operational failures.
SDK-specific error codes:
| Code | When the SDK uses it |
|---|---|
SDK_FILESYSTEM_ERROR | A local filesystem operation failed, such as reading an input file, creating a directory, or writing a downloaded file. |
SDK_NETWORK_ERROR | A network request failed before the SDK received a valid HTTP response. |
SDK_HTTP_ERROR | The SDK received an unexpected HTTP response shape, such as a non-JSON response or another response that does not match the documented contract. |
SDK_TIMEOUT_ERROR | waitForExtractionToFinish(...) timed out before the extraction finished. |
SDK_DOWNLOAD_ERROR | An SDK-managed download step failed. |
SDK_UPLOAD_ERROR | An SDK-managed upload orchestration step failed. |
Method to API Endpoint Mapping
| SDK Method | Underlying API |
|---|---|
extract(...) | uploadFiles(...) → submitExtraction(...) → waitForExtractionToFinish(...) → downloadOutput(...) |
uploadFiles(...) | POST /uploads/sessions → POST /uploads/sessions/{id}/parts → POST /uploads/sessions/{id}/complete |
submitExtraction(...) | POST /extractions |
waitForExtractionToFinish(...) | GET /extractions/{extraction_id} (polled) |
downloadOutput(...) | GET /extractions/{extraction_id}/output?format={format} → presigned URL download |
checkExtraction(...) | GET /extractions/{extraction_id} |
getDownloadUrl(...) | GET /extractions/{extraction_id}/output?format={format} |
deleteExtraction(...) | DELETE /extractions/{extraction_id} |
getCreditsBalance() | GET /credits/balance |
listExtractions(...) | GET /extractions |
iterateExtractions(...) | GET /extractions (auto-paginated) |
getExtraction(...) | GET /extractions/{extraction_id}/details |