API documentation
Send a PDF, Word document or image, get structured JSON back.
Introduction
You describe the data you want as a category: a list of fields such as invoice number, date or line items, set up in the dashboard. Parsevix reads the document, fills those fields and returns them as JSON, together with the text it read and where each value was found.
The API is served over HTTPS from one base URL. Requests and responses are JSON, except file uploads, which are multipart/form-data.
https://parsevix.com/api/v1Every API key belongs to one project. A key sees and changes only that project's documents, categories and webhooks.
Besides calling the API you can receive results through webhooks, connect Zapier, or send documents to a project's email address.
Conventions
- API responses use camelCase field names. Webhook and callback bodies use snake_case.
- Timestamps are UTC, in ISO 8601 (
2026-10-25T10:12:06.791Z). - Error messages are in English, German, Turkish, French, Spanish, Italian, Dutch, Polish or Portuguese, following the
Accept-Languageheader.
Quickstart
- 1
Create an API key
In the dashboard, open your project, go to API Keys and create a key. It is shown once; store it, for example as the environment variable
PARSEVIX_API_KEY. The Free plan includes one key, paid plans more (see Limits and credits). - 2
Define a category
Under Categories, create a category for your document type and add the fields you need, or start from a built-in template. The examples on this page use a category named
Supplier invoice. - 3
Send a document
POST /extractuploads the file and waits for the result. The response contains the filled fields underextracted.
To process many documents without holding requests open, upload asynchronously and receive results through webhooks.
curl https://parsevix.com/api/v1/extract \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-F file=@invoice.pdf \
-F category="Supplier invoice"{
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"fileName": "invoice.pdf",
"status": "Completed",
"category": "Supplier invoice",
"processingMode": "specific",
"confidenceScore": 0.97,
"pageCount": 1,
"language": "en",
"retained": false,
"extracted": {
"invoice_number": "F-2026-88343",
"invoice_date": "2026-10-03",
"due_date": "2026-10-17",
"currency": "GBP",
"seller": {
"name": "Northgate Systems Ltd",
"vat_id": "GB566163740"
},
"line_items": [
{
"description": "LED panel light",
"quantity": 4,
"unit_price": 542.55,
"total": 2170.2
},
{
"description": "Office chair",
"quantity": 1,
"unit_price": 475.86,
"total": 475.86
}
],
"gross_total": 7848.88,
"_field_blocks": {
"invoice_number": [
{
"page": 1,
"x": 35.04,
"y": 378.24,
"width": 159.84,
"height": 11.04,
"text": "Invoice number: F-2026-88343"
}
],
"seller.name": [
{
"page": 1,
"x": 34.08,
"y": 558.24,
"width": 120.48,
"height": 12.96,
"text": "Northgate Systems Ltd"
}
]
}
}
}Video tutorials
Five short videos show the main steps in the dashboard and your first API request. Each has voice-over and subtitles in nine languages: choose them under the gear icon in the player.
The videos play from YouTube (youtube-nocookie.com). Nothing is loaded from YouTube until you click play; then Google receives your IP address and may store cookies. See our Privacy Policy.
Authentication
Send your API key in the X-API-Key header with every request.
X-API-Key: psvx_…A key works only on /api/v1; it cannot sign in to the dashboard. Keys are created and revoked under API Keys in the project. A revoked key stops working immediately.
Keep keys on your server. Anyone who has a key can upload documents and read the project's results, and the uploads use your credits.
When a key is refused
| Status | Code | Meaning |
|---|---|---|
| 401 | apikey.missing | No X-API-Key header was sent. |
| 401 | apikey.invalid | The key does not exist or was revoked. |
| 403 | apikey.plan_disabled | The account's plan does not include API access. |
| 403 | apikey.paused_by_plan | The key or its project is paused because the account's plan allows fewer keys or projects. |
| 403 | apikey.account_unavailable | The account is closed or scheduled for deletion. |
| 403 | apikey.project_unavailable | The key's project was deleted. |
| 429 | apikey.too_many_invalid | More than 30 invalid keys came from your network within 10 minutes. Wait Retry-After seconds; a key that worked recently is not blocked. |
{
"error": {
"code": "apikey.invalid",
"message": "The API key is not valid. It may have been revoked; create a new one in the project under API Keys."
}
}Processing modes
Each upload is read in one of four ways.
- Automatic category (
auto-classify). Parsevix picks the best-matching category of the project, then extracts its fields. Used when you send no category, orcategory=auto. - Classify only (
classify-only). Parsevix picks the best-matching category and extracts nothing, for sorting documents by type. It reads the first 3 pages and costs 1 credit per document. The fields can be extracted later. See Classify a document. - Specific category (
specific). You name the category; its fields are extracted. - Without a schema (
auto-entity). No category is used. Parsevix returns the fields it finds, named after the document's own labels. See Extract without a schema.
The document's language is detected automatically. The optional language parameter is only a fallback for documents with too little text to detect.
Extract a document
/api/v1/extractUploads a document and waits until it is processed. The response contains the extracted fields.
filefilerequired- The document: PDF, DOCX, PNG, JPEG or TIFF, up to 10 MB and 30 pages.
categorystringoptional- Name of a category in the project. Leave it out, or send
auto, to let Parsevix choose. languagestringoptional- Two-letter ISO 639-1 code, such as
de. Used only if the language cannot be detected.
Waiting and limits
- The request stays open until the result is ready, for at most 285 seconds. Set your HTTP client's timeout above that.
- If processing takes longer, you get
504withdocument.sync_timeoutand thedocumentId. Processing continues; the result goes to your webhooks. - If processing fails, you get
422withdocument.sync_failed.reasonsays why; see failure reasons. - Each API key can have 5 requests open at once. More return
429withdocument.sync_concurrency_limit. A request you close, or one that times out, keeps its place until its document is processed. For bulk uploads use asynchronous upload.
Response
idstring- Document id.
fileNamestring- The uploaded file's name.
statusstringCompleted.categorystring | null- The category used;
nullwithout a schema. processingModestringauto-classify,specificorauto-entity.confidenceScorenumber- Between 0 and 1: how certain the extraction is.
pageCountinteger- Pages processed; each costs one credit.
languagestring- Detected language, ISO 639-1.
retainedboolean- Whether Parsevix keeps the file and result. See Data retention.
extractedobject- The extracted fields. See The result.
curl https://parsevix.com/api/v1/extract \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-F file=@invoice.pdf \
-F category="Supplier invoice"{
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"fileName": "invoice.pdf",
"status": "Completed",
"category": "Supplier invoice",
"processingMode": "specific",
"confidenceScore": 0.97,
"pageCount": 1,
"language": "en",
"retained": false,
"extracted": {
"invoice_number": "F-2026-88343",
"invoice_date": "2026-10-03",
"due_date": "2026-10-17",
"currency": "GBP",
"seller": {
"name": "Northgate Systems Ltd",
"vat_id": "GB566163740"
},
"line_items": [
{
"description": "LED panel light",
"quantity": 4,
"unit_price": 542.55,
"total": 2170.2
},
{
"description": "Office chair",
"quantity": 1,
"unit_price": 475.86,
"total": 475.86
}
],
"gross_total": 7848.88,
"_field_blocks": {
"invoice_number": [
{
"page": 1,
"x": 35.04,
"y": 378.24,
"width": 159.84,
"height": 11.04,
"text": "Invoice number: F-2026-88343"
}
],
"seller.name": [
{
"page": 1,
"x": 34.08,
"y": 558.24,
"width": 120.48,
"height": 12.96,
"text": "Northgate Systems Ltd"
}
]
}
}
}{
"error": {
"code": "document.sync_failed",
"message": "The file is damaged or is not a valid PDF, Word document or image.",
"reason": "file_unreadable",
"documentId": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581"
}
}{
"error": {
"code": "document.sync_timeout",
"message": "Extraction did not finish within 285 seconds. Large documents may need to be split.",
"documentId": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581"
}
}Extract without a schema
/api/v1/extract-autoLike POST /extract, but without a category: Parsevix returns whatever fields it finds in the document.
filefilerequired- The document: PDF, DOCX, PNG, JPEG or TIFF, up to 10 MB and 30 pages.
languagestringoptional- Two-letter ISO 639-1 code. Used only if the language cannot be detected.
The output stays in the document's language. Keys are the document's own labels in snake_case (an invoice in German gives rechnungsnummer), and values are copied as printed: dates in the document's format, amounts as strings such as "2.430,09".
Use it to explore a new document type. For data you process automatically, a category gives you fixed keys and normalised values.
The response has the same fields as POST /extract, with processingMode auto-entity and category null.
curl https://parsevix.com/api/v1/extract-auto \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-F file=@invoice.pdfClassify a document
/api/v1/classifyUploads a document and waits until Parsevix has chosen one of the project's categories. No fields are extracted. Use it to sort or route documents by type.
The candidates are the project's own categories, not the built-in templates. Parsevix reads the first 3 pages, so a classification costs 1 credit however long the document is.
filefilerequired- The document: PDF, DOCX, PNG, JPEG or TIFF, up to 10 MB and 30 pages.
languagestringoptional- Two-letter ISO 639-1 code. Used only if the language cannot be detected.
Waiting and errors work as for POST /extract: at most 285 seconds, 504 with document.sync_timeout if it takes longer, 422 with document.sync_failed if the document cannot be processed. The 5 open requests per key are shared by both endpoints.
Response
idstring- Document id.
fileNamestring- The uploaded file's name.
statusstringCompleted.categorystring- The category Parsevix chose.
processingModestringclassify-only.pageCountinteger- Pages in the document. The first 3 were read.
languagestring- Detected language, ISO 639-1.
retainedboolean- Whether Parsevix keeps the file. Only a kept document can be extracted later.
Extracting later
POST /documents/{id}/extract extracts the document's fields, as the category it was given or as one you name. It costs the document's pages less the 1 credit already paid: a 5-page document costs 4 more, 5 in total.
That needs the file, so the project must keep API uploads (see Data retention). Documents sent from the dashboard or by email are always kept. Asynchronous uploads can be classified too: processingMode=classify-only on POST /documents.
curl https://parsevix.com/api/v1/classify \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-F file=@invoice.pdf{
"id": "01a172b4-5d2e-7c61-8f0a-3b9d6e1c4a27",
"fileName": "invoice.pdf",
"status": "Completed",
"category": "Supplier invoice",
"processingMode": "classify-only",
"pageCount": 1,
"language": "en",
"retained": true
}Upload asynchronously
/api/v1/documentsQueues a document and answers immediately with 202 and the document record. Use it for bulk uploads and for clients that cannot keep a request open, such as Zapier.
filefilerequired- The document: PDF, DOCX, PNG, JPEG or TIFF, up to 10 MB and 30 pages.
categorystringoptional- Name of a category. Leave it out, or send
auto, to let Parsevix choose. processingModestringoptionalauto-classify,specific,auto-entityorclassify-only. Optional: by default it follows fromcategory.classify-onlycannot be combined with a category.languagestringoptional- Two-letter ISO 639-1 code. Used only if the language cannot be detected.
callbackUrlstringoptional- A public
httpsURL. Parsevix sends the result there once, as aPOSTwith the same body as a webhook event.
Getting the result
- Webhooks of the project receive
extraction.completedorextraction.failed. Aclassify-onlyupload sendsclassification.completedinstead ofextraction.completed. - callbackUrl, if given, receives the same event, with the same retries. It is not signed, so put a secret token in the URL and check it.
- Polling
GET /documents/{id}shows the status, and the result only if the project keeps API uploads (see Data retention).
curl https://parsevix.com/api/v1/documents \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-F file=@invoice.pdf \
-F category="Supplier invoice" \
-F callbackUrl="https://example.com/parsevix/callback?token=a-secret-only-you-know"{
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"status": "Pending",
"categoryName": "Supplier invoice",
"processingMode": "specific",
"fileName": "invoice.pdf",
"fileSize": 22579,
"source": "api",
"discardFiles": true,
"createdAt": "2026-10-25T10:12:06.791Z",
"completedAt": null,
"reprocessCount": 0,
"lastReprocessedAt": null,
"approvalStatus": "Unapproved",
"isTrainingExample": false
}The result
extracted
An object keyed by the category's field names (fieldName in the category schema). Objects and lists follow the schema.
Dates are YYYY-MM-DD. Numbers are JSON numbers, without currency symbols or thousand separators. A field the document doesn't contain is left out or null.
Personal data such as names and IBANs is returned as written in the document. It is masked only on the way to the AI model and restored before you receive it.
_field_blocks
Where each value was found. Keys are field paths such as seller.name or line_items[0].total; each entry lists the text lines with page, box and text. A value that could not be located is missing from it. Ignore keys starting with _ if you only need the data.
Coordinates
- PDF and DOCX: PDF points (1/72 inch). The origin is the bottom-left corner of the page;
yis the top edge of the box, measured from the bottom. - Images: pixels. The origin is the top-left corner;
yis the top edge, measured from the top.
{
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"fileName": "invoice.pdf",
"status": "Completed",
"category": "Supplier invoice",
"processingMode": "specific",
"confidenceScore": 0.97,
"pageCount": 1,
"language": "en",
"retained": false,
"extracted": {
"invoice_number": "F-2026-88343",
"invoice_date": "2026-10-03",
"due_date": "2026-10-17",
"currency": "GBP",
"seller": {
"name": "Northgate Systems Ltd",
"vat_id": "GB566163740"
},
"line_items": [
{
"description": "LED panel light",
"quantity": 4,
"unit_price": 542.55,
"total": 2170.2
},
{
"description": "Office chair",
"quantity": 1,
"unit_price": 475.86,
"total": 475.86
}
],
"gross_total": 7848.88,
"_field_blocks": {
"invoice_number": [
{
"page": 1,
"x": 35.04,
"y": 378.24,
"width": 159.84,
"height": 11.04,
"text": "Invoice number: F-2026-88343"
}
],
"seller.name": [
{
"page": 1,
"x": 34.08,
"y": 558.24,
"width": 120.48,
"height": 12.96,
"text": "Northgate Systems Ltd"
}
]
}
}
}Documents
Documents of the project, from every source: the dashboard, the API and email.
List documents
/api/v1/documentsNewest first. Lists only stored documents: API uploads appear only if the project keeps them.
pageintegeroptional- Page number, from 1.
pageSizeintegeroptional- 1 to 100; default 20.
statusstringoptionalPending,Processing,CompletedorFailed.categorystringoptional- Category name.
searchstringoptional- Part of the file name.
fileTypestringoptionalpdf,docxorimage.sourcestringoptionaldashboard,apioremail.processingModestringoptionalauto-classify,specific,auto-entityorclassify-only.
Get a document
/api/v1/documents/{id}The document with its result. outputDeanonymized is the extracted data, the same object as extracted. A classify-only document has its category in categoryName and no outputDeanonymized. A failed document has errorCode and errorMessage. If the project does not keep API uploads, the status is available but the result is not.
Reprocess a document
/api/v1/documents/{id}/reprocessRuns the extraction again, for example after you changed the category's fields. Optional JSON body: category (a name, or auto) and processingMode. Answers 202; the result arrives like an asynchronous upload's. Credits are charged again. A classify-only document is classified again (1 credit) unless you name a category. Not possible for API uploads whose files were not kept (document.files_not_retained).
Extract a classified document
/api/v1/documents/{id}/extractFor a document uploaded with classify-only: extracts its fields as the category it was given, or as the category named in the optional JSON body. Costs the document's pages less the 1 credit the classification cost. Answers 202; the result arrives like an asynchronous upload's, as extraction.completed. For any other document it answers 400 with document.not_classify_only; use reprocess instead.
Delete a document
/api/v1/documents/{id}Deletes the document, its file and its results. Answers 204.
An id that doesn't exist in the key's project returns 404 with document.not_found.
curl "https://parsevix.com/api/v1/documents?status=Completed&pageSize=20" \
-H "X-API-Key: $PARSEVIX_API_KEY"{
"data": [
{
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"status": "Completed",
"categoryName": "Supplier invoice",
"processingMode": "specific",
"fileName": "invoice.pdf",
"fileSize": 22579,
"source": "api",
"discardFiles": false,
"createdAt": "2026-10-25T10:12:06.791Z",
"completedAt": "2026-10-25T10:12:15.212Z",
"reprocessCount": 0,
"lastReprocessedAt": null,
"approvalStatus": "Unapproved",
"isTrainingExample": false
}
],
"totalCount": 1,
"page": 1,
"pageSize": 20
}curl https://parsevix.com/api/v1/documents/01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581 \
-H "X-API-Key: $PARSEVIX_API_KEY"{
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"status": "Completed",
"categoryName": "Supplier invoice",
"processingMode": "specific",
"fileName": "invoice.pdf",
"fileSize": 22579,
"source": "api",
"language": "en",
"confidenceScore": 0.97,
"pageCount": 1,
"outputDeanonymized": {
"invoice_number": "F-2026-88343",
"invoice_date": "2026-10-03",
"due_date": "2026-10-17",
"currency": "GBP",
"seller": {
"name": "Northgate Systems Ltd",
"vat_id": "GB566163740"
},
"line_items": [
{
"description": "LED panel light",
"quantity": 4,
"unit_price": 542.55,
"total": 2170.2
},
{
"description": "Office chair",
"quantity": 1,
"unit_price": 475.86,
"total": 475.86
}
],
"gross_total": 7848.88,
"_field_blocks": {
"invoice_number": [
{
"page": 1,
"x": 35.04,
"y": 378.24,
"width": 159.84,
"height": 11.04,
"text": "Invoice number: F-2026-88343"
}
],
"seller.name": [
{
"page": 1,
"x": 34.08,
"y": 558.24,
"width": 120.48,
"height": 12.96,
"text": "Northgate Systems Ltd"
}
]
}
},
"errorMessage": null,
"errorCode": null,
"createdAt": "2026-10-25T10:12:06.791Z",
"completedAt": "2026-10-25T10:12:15.212Z"
}curl https://parsevix.com/api/v1/documents/01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581/reprocess \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"category": "auto"}'curl https://parsevix.com/api/v1/documents/01a172b4-5d2e-7c61-8f0a-3b9d6e1c4a27/extract \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "01a172b4-5d2e-7c61-8f0a-3b9d6e1c4a27",
"status": "Pending",
"categoryName": "Supplier invoice",
"processingMode": "specific",
"fileName": "invoice.pdf",
"fileSize": 22579,
"source": "api",
"discardFiles": false,
"createdAt": "2026-10-25T10:12:06.791Z",
"completedAt": null,
"reprocessCount": 1,
"lastReprocessedAt": "2026-10-25T11:04:12.530Z",
"approvalStatus": "Unapproved",
"isTrainingExample": false
}curl -X DELETE https://parsevix.com/api/v1/documents/01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581 \
-H "X-API-Key: $PARSEVIX_API_KEY"Categories
The project's categories, including the built-in templates (isGlobal: true). Categories are created and edited in the dashboard.
List categories
/api/v1/categoriesGet a category's schema
/api/v1/categories/{name}The fields a category extracts. fieldName is the key in extracted. fieldType is string, number, date, boolean, object or array; objects and arrays have their fields in children. The name in the path is not case-sensitive.
curl https://parsevix.com/api/v1/categories \
-H "X-API-Key: $PARSEVIX_API_KEY"[
{
"id": 1,
"name": "Invoice",
"description": "Invoices and billing documents with seller/buyer info, line items, and totals",
"isGlobal": true,
"fieldCount": 48
},
{
"id": 54,
"name": "Supplier invoice",
"description": "Commercial invoice: seller, buyer, line items and totals.",
"isGlobal": false,
"fieldCount": 26
}
]curl "https://parsevix.com/api/v1/categories/Supplier%20invoice" \
-H "X-API-Key: $PARSEVIX_API_KEY"{
"id": 54,
"name": "Supplier invoice",
"description": "Commercial invoice: seller, buyer, line items and totals.",
"fields": [
{
"fieldName": "invoice_number",
"displayName": "Invoice number",
"fieldType": "string",
"description": null,
"children": []
},
{
"fieldName": "invoice_date",
"displayName": "Invoice date",
"fieldType": "date",
"description": null,
"children": []
},
{
"fieldName": "seller",
"displayName": "Seller",
"fieldType": "object",
"description": null,
"children": [
{
"fieldName": "name",
"displayName": "Name",
"fieldType": "string",
"description": null,
"children": []
},
{
"fieldName": "vat_id",
"displayName": "VAT / tax id",
"fieldType": "string",
"description": null,
"children": []
}
]
}
]
}Key and credits
/api/v1/meThe key's project and the account's credit balance. Credits belong to the account and are shared by all its projects; resetsOn is the start of the next period. On an annual plan the period is the billing year and monthlyLimit holds the whole year's credits.
curl https://parsevix.com/api/v1/me \
-H "X-API-Key: $PARSEVIX_API_KEY"{
"projectId": "01a16af3-0b1f-7f2f-8282-b3824309d921",
"projectName": "Accounts payable",
"projectSlug": "accounts-payable",
"keyName": "ERP connector",
"keyPrefix": "psvx_nyskxlv",
"email": "you@example.com",
"credits": {
"period": "2026-10-04",
"resetsOn": "2026-11-04",
"monthlyLimit": 1000,
"used": 312,
"remaining": 688,
"planName": "Growth"
}
}Manage webhooks
Webhooks can be managed in the dashboard (Webhooks) or through these endpoints.
Create a webhook
/api/v1/webhooksurlstringrequired- A public
httpsURL. Addresses in private networks are refused. eventsstring[]required- One or more of
extraction.completed,extraction.failedandclassification.completed. filtersobjectoptional- Optional.
sources(dashboard,api,email) andcategories(names) limit which documents send events.
The response contains the signing secret once. Store it to verify signatures.
List webhooks
/api/v1/webhooksDelete a webhook
/api/v1/webhooks/{id}Sample events
/api/v1/webhooks/samplesThe latest documents of the project as event bodies, for testing your receiver. Query: event (required), limit (1–10, default 3), category. If there is no such document yet, a sample is built from the category schema.
curl https://parsevix.com/api/v1/webhooks \
-H "X-API-Key: $PARSEVIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/parsevix/webhook",
"events": ["extraction.completed", "extraction.failed"],
"filters": { "sources": ["api", "email"] }
}'{
"secret": "d0726faeb5c655d349fc30ed226e93e531ac2499800aacde100e83554b48701c",
"id": "01a17295-c761-7b8f-b173-388bc754b1ba",
"url": "https://example.com/parsevix/webhook",
"events": [
"extraction.completed",
"extraction.failed"
],
"filters": {
"sources": [
"api",
"email"
],
"categories": null
},
"isActive": true,
"disabledAt": null,
"disabledReason": null,
"managedBy": null,
"createdAt": "2026-10-25T10:23:09.311Z"
}curl "https://parsevix.com/api/v1/webhooks/samples?event=extraction.completed&limit=1" \
-H "X-API-Key: $PARSEVIX_API_KEY"Webhooks
When a document is finished, Parsevix sends a POST with a JSON body to each of the project's webhooks. Webhooks receive documents from every source: dashboard, API and email.
Events
| Event | Description |
|---|---|
| extraction.completed | A document was processed. data holds the extracted fields. |
| classification.completed | A classify-only document got its category: document.category, with the document's pages in document.page_count. There is no data. |
| extraction.failed | A document could not be processed, also a classify-only one. error_code is one of the failure reasons; error is the message shown in the dashboard. |
| test.ping | Sent by the Test button on a webhook's page in the dashboard. |
Headers
| Header | Description |
|---|---|
| X-Webhook-Id | Event id. The same in every attempt; use it to ignore duplicates. Also the body's id. |
| X-Webhook-Event | The event name. |
| X-Webhook-Timestamp | Unix time of this attempt, in seconds. |
| X-Webhook-Signature | sha256= followed by the hex HMAC-SHA256 of {timestamp}.{raw body}, keyed with the webhook's secret. |
| X-Webhook-Attempt | Attempt number, from 1. |
Verify the signature
Compute the HMAC over the raw request body exactly as received, before parsing it. Reject events whose timestamp is more than 5 minutes old.
Responding
- Answer with a
2xxstatus within 10 seconds, then process the event. Anything else counts as a failure. Redirects are not followed. - Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours: 7 attempts over about 33 hours.
- Answering
410 Goneswitches the webhook off at once. It is also switched off when three events have failed every attempt and no delivery has succeeded for 3 days. The owner gets an email; turn it back on in the dashboard. Events in between are not sent again. - The secret can be rotated in the dashboard. The old secret stops working at once.
{
"id": "01a172a0-3f1e-7d42-9b6c-8e2f5a7d1c90",
"event": "extraction.completed",
"timestamp": "2026-10-25T10:12:21.592Z",
"project_id": "01a16af3-0b1f-7f2f-8282-b3824309d921",
"project_name": "Accounts payable",
"project_slug": "accounts-payable",
"document": {
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"file_name": "invoice.pdf",
"category": "Supplier invoice",
"source": "api",
"processing_mode": "specific",
"language": "en",
"confidence_score": 0.97
},
"data": {
"invoice_number": "F-2026-88343",
"invoice_date": "2026-10-03",
"due_date": "2026-10-17",
"currency": "GBP",
"seller": {
"name": "Northgate Systems Ltd",
"vat_id": "GB566163740"
},
"line_items": [
{
"description": "LED panel light",
"quantity": 4,
"unit_price": 542.55,
"total": 2170.2
},
{
"description": "Office chair",
"quantity": 1,
"unit_price": 475.86,
"total": 475.86
}
],
"gross_total": 7848.88,
"_field_blocks": {
"invoice_number": [
{
"page": 1,
"x": 35.04,
"y": 378.24,
"width": 159.84,
"height": 11.04,
"text": "Invoice number: F-2026-88343"
}
],
"seller.name": [
{
"page": 1,
"x": 34.08,
"y": 558.24,
"width": 120.48,
"height": 12.96,
"text": "Northgate Systems Ltd"
}
]
}
}
}{
"id": "01a172b5-8e47-7a13-b2c9-4f6d0a8e3b51",
"event": "classification.completed",
"timestamp": "2026-10-25T10:14:02.334Z",
"project_id": "01a16af3-0b1f-7f2f-8282-b3824309d921",
"project_name": "Accounts payable",
"project_slug": "accounts-payable",
"document": {
"id": "01a172b4-5d2e-7c61-8f0a-3b9d6e1c4a27",
"file_name": "invoice.pdf",
"category": "Supplier invoice",
"source": "api",
"processing_mode": "classify-only",
"language": "en",
"page_count": 1
}
}{
"id": "01a172a1-0c55-7e18-a3d4-6b9f2e8c1d07",
"event": "extraction.failed",
"timestamp": "2026-10-25T10:12:23.711Z",
"project_id": "01a16af3-0b1f-7f2f-8282-b3824309d921",
"project_name": "Accounts payable",
"project_slug": "accounts-payable",
"document": {
"id": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581",
"file_name": "invoice.pdf",
"category": "Supplier invoice",
"source": "api",
"processing_mode": "specific"
},
"error_code": "file_unreadable",
"error": "The file is damaged or is not a valid PDF, Word document or image."
}import hashlib
import hmac
import time
def verify(raw_body: bytes, headers, secret: str) -> bool:
timestamp = headers["X-Webhook-Timestamp"]
signature = headers["X-Webhook-Signature"] # "sha256=<hex>"
if abs(time.time() - int(timestamp)) > 300:
return False # older than 5 minutes: possibly replayed
expected = hmac.new(
secret.encode(),
timestamp.encode() + b"." + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(signature, "sha256=" + expected)Zapier
The Parsevix app for Zapier connects with an API key of a project. Add it through the invite link on the project's Zapier page in the dashboard or here.
Triggers
- Extraction Completed: a document was processed. Optionally only one category or source.
- Extraction Failed: a document could not be processed.
- Document Classified: a document sent only to be classified got its category, for example from an email address set to classify only.
Actions
- Extract Document sends a file from an earlier step, optionally with a category, and passes the extracted fields to the next steps. With a category chosen, its fields can be mapped while you build the Zap.
- Classify Document sends a file and passes the category Parsevix chose to the next steps, without extracting fields. Use it to route documents by type.
Zaps you turn on appear on the project's Zapier page and under Webhooks.
From the Starter plan on, every project has an email address under Email inbox. Each attachment of a mail sent to it becomes a document; the results appear in the dashboard and are sent to your webhooks with source email.
Addresses look like name+inbox@inbox.parsevix.com. Depending on the plan, a project can have more than one, for example one per supplier or department.
Per address
- Attachments: extract every attachment as one fixed category; let Parsevix choose the category, then extract; or only choose the category (
classify-only, 1 credit per document), with the fields extracted later if you need them. - Allowed senders: only mail from these domains is accepted, and only if it passes DKIM or SPF for that domain, so a forged sender is rejected.
What is processed
- PDF, DOCX, PNG, JPEG and TIFF attachments, including those inside forwarded mails. Images shown in the mail body, such as signature logos, are skipped.
- A mail may be up to 30 MB, and its first 20 attachments become documents. One address takes up to 1,000 mails and makes up to 1,000 documents in 24 hours; past that, mail is refused and attachments are listed as not imported. Receiving is free; each document costs credits like any other.
- The same mail delivered twice (same Message-ID) is processed once.
Errors
Errors have an HTTP status and a body with a stable code and a readable message. Branch on code; messages can change.
Status codes
| Status | Meaning |
|---|---|
| 400 | The request is invalid: a missing or wrong parameter, an unsupported file, too few credits. validation.failed lists the problems per parameter in fields. |
| 401 | No API key, or not a valid one. |
| 403 | The key is valid but may not be used. See Authentication. |
| 404 | The document, category, webhook or endpoint does not exist in this project. |
| 413 | The upload is larger than the request limit. |
| 422 | POST /extract or POST /classify ran, but the document could not be processed. |
| 429 | Rate limit or concurrency limit reached. Wait Retry-After seconds. |
| 5xx | A server error (with a reference to quote to support), maintenance (503) or a timeout (504). |
Common codes
| Code | Meaning |
|---|---|
| validation.file_required | No file was sent. |
| document.content_not_supported | The file's content is not PDF, DOCX, PNG, JPEG or TIFF, whatever its name says. |
| credit.file_size_exceeds_plan | The file is larger than the plan allows. |
| validation.language_invalid | language is not a two-letter code. |
| document.callback_url_* | callbackUrl is not a public https URL of at most 2,000 characters. |
| credit.insufficient_credits | Not enough credits left this period. |
| document.refund_pending | A refund of the subscription is being reviewed; nothing is processed until it is settled. |
| category.not_found | No category with that name in the project. |
| document.classify_only_category | classify-only was sent together with a category. The mode chooses the category itself. |
| document.not_classify_only | POST /documents/{id}/extract was called for a document not uploaded with classify-only. |
| document.files_not_retained | The API upload's files were not kept, so it cannot be reprocessed. |
| webhook.url_* | The webhook URL is not a public https URL. |
| *.limit_reached | A plan limit was reached, such as the number of webhooks per project. |
Failure reasons
Why a document failed: reason in the 422 from POST /extract or POST /classify, error_code in extraction.failed, errorCode on the document.
| Code | Meaning |
|---|---|
| too_many_pages | More than 30 pages. Split the file. |
| sync_too_many_pages | More pages than POST /extract and POST /classify accept. |
| pdf_encrypted | The PDF is password-protected. |
| file_unreadable | The file is damaged or not a valid document. |
| no_text | No text was found: blank, or the scan is unreadable. |
| no_categories | The category was to be chosen automatically, but the project has no categories. |
| no_schema_fields | The category has no fields. |
| no_fields_found | None of the category's fields was found in the document; it is probably in the wrong category. Reprocess it with the right one. |
| no_data_found | Automatic extraction found no data in the document. |
| insufficient_credits | The document needs more credits than are left this period. Nothing was charged; reprocess it after an upgrade or the reset. |
| refund_pending | A refund of the subscription was being reviewed. If it is declined, reprocess the document. |
| masking_failed | Personal data could not be masked, so the document was not sent to the AI model. Retry. |
| worker_unavailable | Processing was temporarily unavailable. Retry. |
| ai_failed | The AI model failed temporarily. Retry. |
| processing_failed | An internal error. Retry; contact support if it repeats. |
| processing_timeout | Processing stopped before it finished (for example during a restart). Retry. |
The last five are temporary: reprocess the document or send it again. Failed runs are not charged per page.
{
"error": {
"code": "validation.failed",
"message": "The file field is required.",
"fields": {
"file": [
"The file field is required."
]
}
}
}{
"error": {
"code": "document.sync_failed",
"message": "The file is damaged or is not a valid PDF, Word document or image.",
"reason": "file_unreadable",
"documentId": "01a1728b-cc26-7ba9-ad3c-dcf5e6d8e581"
}
}Limits and credits
Files
- PDF, DOCX, PNG, JPEG and TIFF (also multi-page). The type is taken from the file's content, not its name.
- Up to 10 MB and 30 pages per document.
Credits
- Each processed page costs 1 credit. Choosing the category automatically costs nothing extra.
- Classifying only (
classify-only) costs 1 credit per document. Extracting such a document later costs its pages less that credit, so the total is still 1 credit per page. - A failed run costs nothing for its pages. Reprocessing costs credits again. Receiving email is free.
- When the credits of a period are used up, uploads are refused with
credit.insufficient_creditsuntil the next period or an upgrade. A document's credits are reserved once its pages are counted; if they no longer fit, for example after many parallel uploads, the document fails withinsufficient_creditsand nothing is charged.
Request rate
Each API key may make a number of requests per minute, depending on the plan. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix time). Over the limit you get 429 with Retry-After.
By plan
| Plan | Requests / minute | Credits / month | API keys | Webhooks / project |
|---|---|---|---|---|
| Free | 10 | 20 | 1 | 1 |
| Starter | 30 | 500 | 2 | 2 |
| Growth | 60 | 2,000 | 3 | 5 |
| Pro | 120 | 6,000 | 10 | 10 |
| Business | 300 | 20,000 | unlimited | 25 |
Also: POST /extract and POST /classify wait at most 285 seconds and allow 5 open requests per key between them.
{
"error": {
"code": "apikey.rate_limited",
"message": "Too many requests for this API key: the limit is 60 per minute. Retry in 18 s.",
"limit": 60,
"retryAfterSeconds": 18
}
}Data retention
What Parsevix keeps of an API upload is a project setting: API uploads, under Project settings. It is Delete after processing by default; Keep like dashboard uploads keeps them.
- Delete after processing: the file is deleted when processing ends and the result is not stored. The API response, webhook or callback is the only copy. Such documents are not listed and cannot be reprocessed or downloaded;
retainedisfalse. - Keep like dashboard uploads: files and results are stored like dashboard uploads, until you delete them.
Dashboard and email uploads are always stored. Personal data is masked before any text reaches the AI model; see Security.
