Invoice Data Extraction Uploads API
Register the files of one upload session, upload each file in parts to signed URLs, and complete each file. Every upload endpoint shares one rate limit of 600 requests per minute per API key.
Register the files of one upload session, upload each file in parts to signed URLs, and complete each file. Every upload endpoint shares one rate limit of 600 requests per minute per API key.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/invoicedataextraction-com:invoicedataextraction-com-uploads-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Invoice Data Extraction Uploads API
version: 1.0.0
summary: Turn invoices and other financial documents into structured rows and spreadsheets.
description: 'Upload files, submit an extraction that says in plain words what to extract, wait for it
to finish, and read the rows as JSON or download an XLSX, CSV or JSON file. An extraction
submitted with `options.ask_questions` can stop and ask when the documents leave something
unsettled; the caller answers and the extraction continues.
This document is the contract as the API enforces it. The prose reference, with the same
facts told in order, is https://invoicedataextraction.com/docs/api (Markdown:
https://invoicedataextraction.com/docs/api.md). What changed and when, and how the API is
versioned, is https://invoicedataextraction.com/docs/changelog. A guide for AI agents and the
people directing them is https://invoicedataextraction.com/docs/agents.
Every request may carry `X-SDK-Name` (up to 32 characters) naming the client it comes from,
and `X-SDK-Version` with its version; the official SDKs send `node` and `python`.'
termsOfService: https://invoicedataextraction.com/terms-of-service
contact:
name: Invoice Data Extraction support
email: support@invoicedataextraction.com
url: https://invoicedataextraction.com/contact
servers:
- url: https://api.invoicedataextraction.com/v1
security:
- bearerAuth: []
tags:
- name: Uploads
description: Register the files of one upload session, upload each file in parts to signed URLs, and complete each file. Every upload endpoint shares one rate limit of 600 requests per minute per API key.
paths:
/uploads/sessions:
post:
tags:
- Uploads
operationId: createUploadSession
summary: Create an upload session
description: 'Registers one or more files (1 to 6,000) and returns the part size to use when
uploading each of them. Every file needs at least one available credit at this point,
so an account with fewer available credits than files is refused with
`INSUFFICIENT_CREDITS`.
Idempotent: a retry with the same `upload_session_id` and the same files returns the
existing session. The same `upload_session_id` with different files is
`SESSION_ALREADY_INITIALIZED`.
Rate limit: 600 requests per minute per API key, shared by the upload endpoints.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUploadSessionRequest'
example:
upload_session_id: sess_001
files:
- file_id: file_001
file_name: invoice-1.pdf
file_size_bytes: 120450
- file_id: file_002
file_name: receipt.jpg
file_size_bytes: 84200
responses:
'200':
description: The session, with the part size to upload each file in.
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUploadSessionResponse'
example:
success: true
upload_session_id: sess_001
files:
- file_id: file_001
file_name: invoice-1.pdf
part_size: 8388608
- file_id: file_002
file_name: receipt.jpg
part_size: 8388608
'400':
description: '`INVALID_INPUT` (the body does not match the schema; `details.issues` names each problem), `DUPLICATE_FILE_NAME`, `DUPLICATE_FILE_ID`, `FILE_TOO_LARGE` (a PDF over 150 MB or an image over 5 MB; `details` names the file and the limit) or `TOTAL_UPLOAD_SIZE_LIMIT_EXCEEDED` (the files together exceed 2 GB).'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'401':
$ref: '#/components/responses/Unauthenticated'
'402':
description: '`INSUFFICIENT_CREDITS`: fewer available credits than files. `details` carries `credits_balance`, `credits_reserved` and `file_count`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'409':
description: '`SESSION_ALREADY_INITIALIZED`: this `upload_session_id` is in use with different files.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
/uploads/sessions/{upload_session_id}/parts:
post:
tags:
- Uploads
operationId: getUploadPartUrls
summary: Get signed upload URLs for a file's parts
description: 'Returns one signed URL per requested part number. `PUT` the raw bytes of each part to
its URL with no extra headers, and keep the `ETag` response header of each `PUT`
(quotes included) for completing the file.
`total_parts = ceil(file_size_bytes / part_size)`; a file smaller than `part_size` is
one part. Up to 1,000 part numbers per request, each from 1 to 10,000; a URL is valid
for 15 minutes from issue, so for a very large file request URLs in batches just before
uploading each batch.
Rate limit: 600 requests per minute per API key, shared by the upload endpoints.'
parameters:
- $ref: '#/components/parameters/UploadSessionId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GetUploadPartUrlsRequest'
examples:
singlePart:
summary: A file smaller than the part size
value:
file_id: file_001
part_numbers:
- 1
multiPart:
summary: A 20 MB file at an 8 MB part size
value:
file_id: file_002
part_numbers:
- 1
- 2
- 3
responses:
'200':
description: The signed URLs, one per part number requested.
content:
application/json:
schema:
$ref: '#/components/schemas/GetUploadPartUrlsResponse'
example:
success: true
upload_session_id: sess_001
file_id: file_001
file_name: invoice-1.pdf
part_size: 8388608
part_urls:
- part_number: 1
url: https://storage.example.com/...?X-Amz-Signature=...
'400':
$ref: '#/components/responses/InvalidInput'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
description: '`FILE_NOT_FOUND`: this `file_id` was not registered when the session was created.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'409':
description: '`FILE_NOT_UPLOADABLE`: the file has already been completed or aborted.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
/uploads/sessions/{upload_session_id}/complete:
post:
tags:
- Uploads
operationId: completeFileUpload
summary: Complete a file's upload
description: 'Finalises one file from the ETags of its uploaded parts. Call it once per file. A file
that is already complete returns success again, so a retry after a dropped connection
is safe.
Rate limit: 600 requests per minute per API key, shared by the upload endpoints.'
parameters:
- $ref: '#/components/parameters/UploadSessionId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CompleteFileUploadRequest'
example:
file_id: file_001
parts:
- part_number: 1
e_tag: '"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"'
responses:
'200':
description: The file is complete and can be named in an extraction.
content:
application/json:
schema:
$ref: '#/components/schemas/CompleteFileUploadResponse'
example:
success: true
upload_session_id: sess_001
file_id: file_001
file_name: invoice-1.pdf
'400':
description: '`INVALID_INPUT`, or `INVALID_COMPLETION_PARTS` (the parts do not cover 1 to the part count with their ETags; `details.reason` says which check failed).'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
description: '`FILE_NOT_FOUND`: this `file_id` was not registered when the session was created.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'409':
description: '`FILE_ABORTED` (the file was aborted; upload it in a new session) or `UPLOAD_ID_NOT_FOUND` (the session is no longer available).'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'422':
description: '`OBJECT_SIZE_MISMATCH`: the uploaded bytes differ from the declared `file_size_bytes`; `details` carries `declared_size_bytes` and `actual_size_bytes`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
'502':
description: '`UPLOAD_COMPLETE_FAILED`: completing the file failed on our side; retryable.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
/uploads/import:
post:
tags:
- Uploads
operationId: importAttachedFiles
summary: Import files attached in an assistant's conversation
description: 'In place of creating a session, uploading parts and completing each file, when the
files are attached in a conversation with an assistant that gives your app a download
link for each one, as ChatGPT does: creates the session, uploads every file (1 to 100)
from its link and completes it, in one call. The session''s limits apply, and every
file needs one available credit. The links expire a few minutes after the assistant
gives them. When any link cannot be read, nothing is created and nothing is charged.
Files are independent: one that cannot be uploaded carries its `error` and is listed
in `failed_file_ids`, and the completed files can be submitted. A retry with the same
`upload_session_id` and files continues the session, and a file already completed is
not uploaded again.
Rate limit: 20 requests per minute per API key.'
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- upload_session_id
- files
properties:
upload_session_id:
type: string
pattern: ^[A-Za-z0-9._:-]{1,200}$
description: Your identifier for the session; a retry with the same one and the same files continues it.
files:
type: array
minItems: 1
maxItems: 100
items:
type: object
additionalProperties: false
required:
- file_id
- file_name
- url
properties:
file_id:
type: string
pattern: ^[A-Za-z0-9._:-]{1,200}$
file_name:
type: string
description: The file name, ending in .pdf, .jpg, .jpeg or .png.
url:
type: string
format: uri
description: The download link the assistant gave for the attached file. Links from ChatGPT are accepted.
example:
upload_session_id: sess_002
files:
- file_id: file_001
file_name: invoice-1.pdf
url: https://files.oaiusercontent.com/file-abc123
responses:
'200':
description: The session, with each file's outcome.
content:
application/json:
schema:
type: object
required:
- success
- upload_session_id
- files
- completed_file_ids
- failed_file_ids
properties:
success:
type: boolean
upload_session_id:
type: string
files:
type: array
items:
type: object
required:
- file_id
- file_name
- success
properties:
file_id:
type: string
file_name:
type: string
success:
type: boolean
error:
type: object
description: Present when the file could not be uploaded.
properties:
code:
type: string
message:
type: string
retryable:
type: boolean
details:
type: object
nullable: true
completed_file_ids:
type: array
items:
type: string
failed_file_ids:
type: array
items:
type: string
example:
success: true
upload_session_id: sess_002
files:
- file_id: file_001
file_name: invoice-1.pdf
success: true
completed_file_ids:
- file_001
failed_file_ids: []
'400':
description: '`INVALID_INPUT` (a link that is not an assistant''s download link for an attached file among them), or the session''s own refusals (`DUPLICATE_FILE_NAME`, `DUPLICATE_FILE_ID`, `FILE_TOO_LARGE`, `TOTAL_UPLOAD_SIZE_LIMIT_EXCEEDED`).'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'401':
$ref: '#/components/responses/Unauthenticated'
'402':
description: '`INSUFFICIENT_CREDITS`: fewer available credits than files.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'409':
description: '`SESSION_ALREADY_INITIALIZED`: this `upload_session_id` is in use.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'422':
description: '`FILE_LINK_UNREADABLE`: a link could not be read, most often because it expired; `details.file_names` names the files. Attach them again and import with the new links.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
components:
responses:
InternalError:
description: '`INTERNAL_ERROR`: retry after a short delay.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
Unauthenticated:
description: '`UNAUTHENTICATED`, `API_KEY_EXPIRED` or `API_KEY_REVOKED`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
RateLimited:
description: '`RATE_LIMITED`: wait `details.retry_after_seconds` (also the `Retry-After` header), then retry.'
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
InvalidInput:
description: '`INVALID_INPUT`: the request does not match the contract; `details.issues` lists each problem with its `path`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
example:
success: false
error:
code: INVALID_INPUT
message: Request validation failed. Check details for specific issues.
retryable: false
details:
issues:
- message: 'file_name must end with a supported extension: .pdf, .jpg, .jpeg, or .png.'
path:
- files
- 0
- file_name
schemas:
CreateUploadSessionResponse:
type: object
required:
- success
- upload_session_id
- files
properties:
success:
type: boolean
const: true
upload_session_id:
$ref: '#/components/schemas/ClientId'
files:
type: array
items:
type: object
required:
- file_id
- file_name
- part_size
properties:
file_id:
$ref: '#/components/schemas/ClientId'
file_name:
type: string
part_size:
type: integer
description: The part size in bytes to split the file into; the same for every file in the session (8,388,608 today). A file smaller than this is one part.
UploadSessionFileRequest:
type: object
additionalProperties: false
required:
- file_id
- file_name
- file_size_bytes
properties:
file_id:
$ref: '#/components/schemas/ClientId'
file_name:
type: string
minLength: 1
maxLength: 200
description: The file name with its extension, one of `.pdf`, `.jpg`, `.jpeg`, `.png`; no control characters, slashes or surrounding whitespace. Unique within the session.
file_size_bytes:
type: integer
minimum: 1
description: The exact size in bytes. PDFs up to 150 MB (157,286,400 bytes) and up to 5,000 pages; images up to 5 MB (5,242,880 bytes); all files of a session together up to 2 GB.
ErrorEnvelope:
type: object
required:
- success
- error
properties:
success:
type: boolean
const: false
error:
$ref: '#/components/schemas/ErrorObject'
ErrorCode:
type: string
description: The codes an error envelope can carry, from any endpoint.
enum:
- UNAUTHENTICATED
- API_KEY_EXPIRED
- API_KEY_REVOKED
- INVALID_INPUT
- EXTRACTION_NOT_FOUND
- INTERNAL_ERROR
- SUBMISSIONS_PAUSED
- SUBMISSION_ID_ALREADY_USED
- DUPLICATE_FILE_NAME
- DUPLICATE_FILE_ID
- FILE_TOO_LARGE
- TOTAL_UPLOAD_SIZE_LIMIT_EXCEEDED
- SESSION_ALREADY_INITIALIZED
- FILE_NOT_FOUND
- FILE_NOT_UPLOADABLE
- FILE_ABORTED
- INVALID_COMPLETION_PARTS
- OBJECT_SIZE_MISMATCH
- UPLOAD_ID_NOT_FOUND
- INSUFFICIENT_CREDITS
- UPLOAD_COMPLETE_FAILED
- OUTPUT_NOT_AVAILABLE
- OUTPUT_EXPIRED
- EXTRACTION_IN_PROGRESS
- EXTRACTION_NOT_CANCELLABLE
- NOT_FOUND
- FORBIDDEN
- RATE_LIMITED
CreateUploadSessionRequest:
type: object
additionalProperties: false
required:
- upload_session_id
- files
properties:
upload_session_id:
$ref: '#/components/schemas/ClientId'
files:
type: array
minItems: 1
maxItems: 6000
items:
$ref: '#/components/schemas/UploadSessionFileRequest'
GetUploadPartUrlsResponse:
type: object
required:
- success
- upload_session_id
- file_id
- file_name
- part_size
- part_urls
properties:
success:
type: boolean
const: true
upload_session_id:
$ref: '#/components/schemas/ClientId'
file_id:
$ref: '#/components/schemas/ClientId'
file_name:
type: string
part_size:
type: integer
part_urls:
type: array
items:
type: object
required:
- part_number
- url
properties:
part_number:
type: integer
url:
type: string
format: uri
description: '`PUT` the part''s raw bytes here within 15 minutes; keep the `ETag` response header.'
CompleteFileUploadRequest:
type: object
additionalProperties: false
required:
- file_id
- parts
properties:
file_id:
$ref: '#/components/schemas/ClientId'
parts:
type: array
minItems: 1
maxItems: 10000
items:
type: object
additionalProperties: false
required:
- part_number
- e_tag
properties:
part_number:
type: integer
minimum: 1
maximum: 10000
e_tag:
type: string
minLength: 1
description: The `ETag` header of the part's `PUT` response, quotes included.
CompleteFileUploadResponse:
type: object
required:
- success
- upload_session_id
- file_id
- file_name
properties:
success:
type: boolean
const: true
upload_session_id:
$ref: '#/components/schemas/ClientId'
file_id:
$ref: '#/components/schemas/ClientId'
file_name:
type: string
status:
type: string
const: completed
description: Present when this call completed the file; absent when the file was already complete.
ErrorObject:
type: object
required:
- code
- message
- retryable
- details
properties:
code:
$ref: '#/components/schemas/ErrorCode'
message:
type: string
description: What happened and what to do next, in plain words.
retryable:
type: boolean
description: '`true` when the same request may succeed after a short delay; `false` when the request itself must change.'
details:
description: 'Always present: `null`, or an object with error-specific context. `INVALID_INPUT` carries `issues`, each with a `message` and a `path`; `RATE_LIMITED` carries `retry_after_seconds`.'
oneOf:
- type: object
additionalProperties: true
- type: 'null'
GetUploadPartUrlsRequest:
type: object
additionalProperties: false
required:
- file_id
- part_numbers
properties:
file_id:
$ref: '#/components/schemas/ClientId'
part_numbers:
type: array
minItems: 1
maxItems: 1000
items:
type: integer
minimum: 1
maximum: 10000
description: 1-indexed part numbers; duplicates are ignored.
ClientId:
type: string
pattern: ^[A-Za-z0-9._:-]{1,200}$
description: 'An identifier you choose: 1 to 200 characters from letters, digits, dots, underscores, colons and hyphens. Used for `upload_session_id`, `file_id` and `submission_id`.'
parameters:
UploadSessionId:
name: upload_session_id
in: path
required: true
description: The `upload_session_id` you chose when creating the session.
schema:
$ref: '#/components/schemas/ClientId'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'An API key created at https://invoicedataextraction.com/dashboard?view=API, sent as `Authorization: Bearer YOUR_API_KEY`. Every account includes 50 free pages a month. A team admin''s key sees the team''s extractions by default (the `scope` parameter).'
externalDocs:
description: REST API reference
url: https://invoicedataextraction.com/docs/api