API v1 documentation
Endpoint reference
Every document API endpoint with parameters, responses, fields, and copyable examples.
OpenAPI coverage
The operation list below is read from the published OpenAPI contract. Use it as a compact index when comparing generated clients, request methods, operation IDs, and documented response codes.
| Method | Path | Operation | Responses |
|---|---|---|---|
| GET | /api/v1/ping |
Check API authentication ping |
200
429
|
| GET | /api/v1/documents |
List documents listDocuments |
200
429
|
| POST | /api/v1/documents |
Upload a document uploadDocument |
201
422
409
429
503
402
|
| POST | /api/v1/x402/extractions |
Upload a document with x402 payment createAnonymousX402Extraction |
200
201
400
402
409
422
429
503
|
| GET | /api/v1/x402/extractions/{document} |
Retrieve an anonymous x402 extraction getAnonymousX402Extraction |
200
403
404
429
|
| GET | /api/v1/documents/{document} |
Retrieve a document getDocument |
200
403
404
429
|
| DELETE | /api/v1/documents/{document} |
Delete a document deleteDocument |
204
403
404
409
429
|
| GET | /api/v1/documents/{document}/thumbnail |
Download document thumbnail getDocumentThumbnail |
200
403
404
429
|
| GET | /api/v1/documents/{document}/previews |
List document previews listDocumentPreviews |
200
403
404
429
|
| GET | /api/v1/documents/{document}/extractions |
List document extractions listDocumentExtractions |
200
403
404
429
|
| GET | /api/v1/documents/{document}/extraction-runs/{extractionRun}/pages/{physicalPage}/preview |
Download an extraction evidence page preview downloadDocumentExtractionEvidencePagePreview |
200
403
404
429
|
| GET | /api/v1/documents/{document}/exports/{projection} |
Download a document profile export downloadDocumentProfileExport |
200
403
404
409
429
|
| GET | /api/v1/documents/exports/{projection} |
Download a deterministic batch profile export downloadDocumentProfileExportBatch |
200
403
404
409
422
429
|
| POST | /api/v1/documents/{document}/extractions/reviews |
Review a profile result reviewDocumentExtraction |
201
403
404
409
422
429
|
| GET | /api/v1/previews/{preview} |
Download a preview file getPreview |
200
403
404
429
|
Common headers
Each request is scoped to the account behind the API token. Upload retries should include an idempotency key so a network retry does not create another document.
| Header | Required | Value | Description |
|---|---|---|---|
Authorization |
Yes | Bearer <token> |
Account-scoped API token created in the exdata app. The token must have the ability required by the endpoint. |
Idempotency-Key |
Uploads and review writes | String, max 255 characters | Unique key for one logical write. Upload replay compares the caller-supplied file and fields, including mode, lane, and profile; later changes to profile settings or prices do not alter the stored replay. Review replay compares the complete decision payload. |
If-Match |
Review writes only | Profile-result ETag | Optimistic concurrency guard returned by the extractions endpoint. A stale value returns 409 extraction_version_conflict. |
X-Request-ID |
No | String, max 128 characters | Optional caller request ID. exdata echoes this value, or a generated request ID, in error responses. |
/api/v1/ping
Ping
Use this endpoint for a lightweight token check during integration setup, deploy checks, or uptime probes that should not upload files.
| Parameter | Location | Required | Description |
|---|---|---|---|
| None | - | No | Only the bearer token is required. |
| Status | Response | Description |
|---|---|---|
200 | JSON object | Authentication succeeded. |
401 | Error envelope | Bearer token is missing, invalid, or revoked. |
429 | Error envelope | Read rate limit exceeded. |
curl -sS "https://www.exdata.app/api/v1/ping" \
-H "Authorization: Bearer $EXDATA_API_TOKEN"
{
"status": true
}
/api/v1/documents
List documents
Returns paginated documents for the token's account. Use this endpoint for status dashboards, reconciliation, integration workflows, and scheduled backfills.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
sort | Query | No | created_at or -created_at | Sort by creation time. Use -created_at for newest first. |
per_page | Query | No | Integer, 1-100 | Number of documents per page. Defaults to 100. |
extraction_profile | Query | No | String | Filter by the profile selected at upload, for example de.energy.supply-invoice. |
review_status | Query | No | Profile outcome | Filter by the current outcome of the effective specialized profile result, such as needs_review. |
handoff_status | Query | No | Handoff status | Filter by the current downstream projection state. |
| Status | Response | Description |
|---|---|---|
200 | Paginated document list | data contains document objects. links and meta contain Laravel pagination data. |
403 | Error envelope | The token does not have documents:read. |
422 | Validation error | A query parameter is not valid. |
429 | Error envelope | Read rate limit exceeded. |
curl -sS "https://www.exdata.app/api/v1/documents?sort=-created_at&per_page=25" \
-H "Authorization: Bearer $EXDATA_API_TOKEN"
{
"data": [
{
"id": 123,
"mode": "live",
"processing_mode": "extraction",
"processing_lane": "standard",
"status": "completed",
"processing_stage": "completed",
"processing_error": null,
"blocked_reason": null,
"scanner_status": "clean",
"scanner_provider": "default",
"scanner_message": null,
"filename": "invoice-re-2026-1048.pdf",
"file_format": "pdf",
"file_size": 240123,
"locale": "en",
"custom_types": ["invoice"],
"requester": "accounts-payable",
"origin": "api",
"ai_processing": true,
"is_e_invoice": false,
"thumbnail": "https://www.exdata.app/api/v1/documents/123/thumbnail",
"previews": [
{
"id": 987,
"filename": "page-1.png",
"file_format": "png",
"file_size": 94812,
"preview": "https://www.exdata.app/api/v1/previews/987"
}
],
"latest_extraction_run": {
"id": 456,
"mode": "live",
"processing_lane": "standard",
"source": "api",
"status": "completed",
"blocked_reason": null,
"error_code": null,
"error_message": null,
"credits_charged": 1,
"extraction_schema_version": "2026-08-28.1",
"extractor_version": "document:2026-09-05.1",
"ai_prompt_version": "document-ai:2026-09-05.1",
"normalization_version": "base:2026-10-21.3",
"quality": {
"status": "normal",
"degraded": false,
"reasons": [],
"ai": {
"status": "completed",
"model": "gpt-5.4-mini-2026-03-17",
"reasoning_effort": "low",
"service_tier": "default",
"max_output_tokens": 8192,
"input_mode": "attachment",
"text_purpose": null,
"detail": "high",
"preflight_unavailable": false,
"low_detail_document_text_cross_checked": null,
"document_text_truncated": false,
"email_text_truncated": false,
"deterministic_supplementation": false,
"no_usable_text": false,
"conflict_fields": [],
"rejected_fields": [],
"recovered_fields": []
},
"pdf": {
"page_count": 1,
"page_count_known": true,
"native_text_available": true,
"native_text_truncated": false,
"ocr_attempted_pages": 0,
"ocr_successful_pages": 0,
"ocr_page_limit_reached": false,
"ocr_page_limit": 200,
"pages_beyond_ocr_limit": 0,
"failed_pages": [],
"skipped_pages": [],
"empty_pages": [],
"blank_pages": [],
"conflict_pages": [],
"unreliable_pages": [],
"limited_pages": []
},
"email": null,
"structured": null
},
"started_at": "2026-05-10T01:00:04.000000Z",
"completed_at": "2026-05-10T01:00:18.000000Z",
"created_at": "2026-05-10T01:00:03.000000Z"
},
"created_at": "2026-05-10T01:00:00.000000Z",
"updated_at": "2026-05-10T01:00:18.000000Z"
}
],
"links": {
"first": "https://www.exdata.app/api/v1/documents?page=1",
"last": "https://www.exdata.app/api/v1/documents?page=4",
"prev": null,
"next": "https://www.exdata.app/api/v1/documents?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 4,
"per_page": 25,
"to": 25,
"total": 82
}
}
/api/v1/documents
Upload document
Upload a file with multipart form data. The response is a document resource you can poll until processing is complete. Omitting extraction_profile selects the generic extraction, which uses one live credit per started 10-page band. de.energy.supply-invoice currently uses 3 credits per band, so 1–10 pages use 3 credits and 11–20 use 6. The final billable total is the credit_cost recorded for the run multiplied by the number of started bands and is kept when a usable result or retrievable extracted fields remain. thumbnail mode only creates a JPEG thumbnail with zero credit usage. The optional batch lane is available to eligible accounts and uses lower-priority capacity for delay-tolerant workloads.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
file | Form data | Yes | File, max 500 MB | Supported document file. Lower format-specific processing limits apply; see the supported-file capability matrix. |
text | Form data | No | String or null, max 256 KiB | Additional source text or business context, such as an accompanying email or customer note. It is treated as document evidence, not extraction instructions, and may contain up to 262,144 UTF-8 bytes. Validation mode rejects this field because it evaluates only the uploaded PDF or XML. |
custom_types[] | Form data | No | Array of strings | Up to 50 optional type extensions, each up to 100 characters. Standard types are invoice, credit-note, reminder, salary-statement, bank-statement, contract, balance-sheet, tax-assessment-note, timesheet, letter, and other. Add custom values only when your integration needs extra document categories. Validation mode does not use or persist these extensions. |
requester | Form data | No | String or null | Integration name, user reference, workflow ID, or source system, up to 255 characters. For de.energy.supply-invoice, it remains upload metadata and does not affect profile extraction. |
locale | Form data | No | String or null | Document locale: en, de, fr, es, it, pl, nl, pt, or ro. The German energy profile requires de. |
processing_mode | Form data | No | extraction, analysis, validation, or thumbnail | Defaults to extraction. Analysis mode does not run AI or structured extraction and returns the thumbnail, previews, page count, extracted text, and e-invoice detection. Validation mode accepts PDF or XML and returns only the structured-invoice assessment without AI, general extraction, persisted previews, a thumbnail, or retained document text. Both use the generic page-band credit calculation. Thumbnail mode skips OCR, AI, structured extraction, persisted previews, extraction runs, and credit usage. |
processing_lane | Form data | No | standard or batch | Defaults to standard. Batch is available to eligible accounts. It uses constrained lower-priority capacity, may take longer to start, and preserves the same result fields, statuses, polling behavior, and lifecycle webhooks. If batch is unavailable for the account, the upload returns 422 with code=validation_failed and a processing_lane validation error. |
pages | Form data | No | String, max 255 | Analysis mode only, for PDF and single-page file types. Comma-separated first, last, all, or 1-based page numbers, for example first,last. Only the requested pages are processed, returned, and charged; the thumbnail still uses the first page. Other processing modes and Word, TIFF, email, or video uploads return 422. |
allow_ai_processing | Form data | No | Boolean | Multipart clients may send true, false, 1, or 0; letter case does not matter for the words. This optional extraction-mode flag disables AI only when false and does not disable OCR, previews, non-AI structured extraction, or page-band credit calculation. Thumbnail, analysis, and validation mode take precedence over either value. |
extraction_profile | Form data | No | generic or de.energy.supply-invoice | Omit for generic extraction. Validation mode accepts only the generic profile. Any active account can explicitly select de.energy.supply-invoice; it accepts PDF uploads and requires locale=de, extraction mode, and AI enabled. Another document locale returns 422. New uploads use profile version 2.0.1 and schema version 2.0.0, and the current rate is 3 credits per started 10-page band. The selected profile determines the version, processing settings, and applicable per-band rate recorded for the run. Upload, polling, result, review, and export operations use the document endpoints. Read the canonical payload from GET /api/v1/documents/{document}/extractions or download its canonical_json and invoice_lines_csv exports after acceptance. |
Omitting processing_mode is the same processing choice as extraction. With extraction mode, allow_ai_processing controls AI only. With analysis, validation, and thumbnail mode, exdata reports ai_processing: false regardless of that field.
Choose analysis when you need the thumbnail, previews, page count, extracted text, and e-invoice detection but not extraction. Structured extraction does not run in analysis mode, so analysis documents complete with an empty extractions array. It uses the same stages, webhooks, and page-band credit calculation as extraction, and returns considerably faster because AI processing is not performed.
Choose validation when a PDF or XML file needs a structured-invoice assessment without general extraction. The asynchronous run performs detection, the applicable XML validation, and required PDF/XML linkage. For valid embedded invoice XML, bounded rendered OCR is required for visible linkage; a searchable or hidden native text layer does not establish a passing result. Multiple recognized invoice XML attachments produce an indeterminate multiple_embedded_invoice_xml_attachments result instead of selecting one. Invalid and unsupported input completes with an explicit assessment; a technical validator dependency failure remains retryable and, after a terminal failure, is available as validation_status: "unavailable". A completed assessment is published only after the final page-band total is covered, so a page-band funding block exposes no completed structured assessment. Read the result from latest_extraction_run.quality.structured. The run creates no extraction fields, persisted previews, thumbnail, or retained document text.
With analysis you can also choose which pages of a PDF to process using pages, for example first, last, first,last, all, or 1,3,5. Pages you do not request are not processed: they are not OCRed and contribute no text to the result, and the page band is charged on the pages you asked for. The thumbnail and the first-page preview are always generated. A smaller selection returns sooner and costs less. Page numbers beyond the document are ignored; a selection whose pages all lie beyond the document processes the first page. When the page count cannot be read, last resolves within the documented visual PDF processing limit. The field is rejected with 422 in other modes and for file types without selectable pages, such as Word, TIFF, email, and video; single-page file types collapse every selection to their one page.
A live extraction keeps its final page-band total when a usable result or retrievable extracted fields remain. A terminal processing error that leaves neither, including a preprocessing failure, returns all applied credits and reports latest_extraction_run.credits_charged: 0. Test-mode and thumbnail-only uploads have zero net live credit usage. Reprocessing after an already charged result also reports zero; reprocessing after a fully returned failure receives a new page-band charge when it first leaves a usable result or retrievable extracted fields.
| Status | Response | Description |
|---|---|---|
201 | Document resource | The document was accepted. A live extraction normally applies the recorded credit_cost to its first band. Because analysis determines the page count later, polling or document.blocked can still report page_band_insufficient_credits; that outcome returns the initial credits. Thumbnail mode is accepted without an extraction run or credit check. |
402 | Insufficient credits error | Extraction, analysis, or validation mode. The document was created, then blocked because available credits do not cover the first band at the applicable rate. No credit was charged. Thumbnail mode is not blocked by the credit balance. |
403 | Error envelope | The token does not have documents:write, or the account is suspended. |
409 | Error envelope | The idempotency key is still processing, was reused with a different upload payload, or identifies an interrupted attempt that created a document. The last case returns conflict_reason=document_created with a cleanup-only document.id; delete it before deciding whether to submit a new upload. |
422 | Validation error | The file or form field failed validation. |
429 | Error envelope | Upload rate limit exceeded, including sandbox daily upload limits for test-mode tokens. |
503 | Error envelope | The upload is temporarily unavailable. Wait for Retry-After, then retry with the same Idempotency-Key, file, and form fields. |
curl -sS -X POST "https://www.exdata.app/api/v1/documents" \
-H "Authorization: Bearer $EXDATA_API_TOKEN" \
-H "Idempotency-Key: invoice-2026-1048" \
-F "file=@./invoice-re-2026-1048.pdf" \
-F "locale=en" \
-F "custom_types[]=invoice" \
-F "requester=accounts-payable"
import fs from "node:fs";
const form = new FormData();
form.set("file", new Blob([fs.readFileSync("./invoice-re-2026-1048.pdf")]), "invoice-re-2026-1048.pdf");
form.set("locale", "en");
form.append("custom_types[]", "invoice");
form.set("requester", "accounts-payable");
const response = await fetch("https://www.exdata.app/api/v1/documents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EXDATA_API_TOKEN}`,
"Idempotency-Key": "invoice-2026-1048",
},
body: form,
});
console.log(await response.json());
import os
import requests
with open("./invoice-re-2026-1048.pdf", "rb") as file:
response = requests.post(
"https://www.exdata.app/api/v1/documents",
headers={
"Authorization": f"Bearer {os.environ['EXDATA_API_TOKEN']}",
"Idempotency-Key": "invoice-2026-1048",
},
files={"file": ("invoice-re-2026-1048.pdf", file, "application/pdf")},
data={
"locale": "en",
"custom_types[]": "invoice",
"requester": "accounts-payable",
},
timeout=60,
)
print(response.json())
<?php
$client = new GuzzleHttp\Client(['base_uri' => 'https://www.exdata.app/api/v1']);
$response = $client->post('/documents', [
'headers' => [
'Authorization' => 'Bearer '.getenv('EXDATA_API_TOKEN'),
'Idempotency-Key' => 'invoice-2026-1048',
],
'multipart' => [
['name' => 'file', 'contents' => fopen('./invoice-re-2026-1048.pdf', 'r'), 'filename' => 'invoice-re-2026-1048.pdf'],
['name' => 'locale', 'contents' => 'en'],
['name' => 'custom_types[]', 'contents' => 'invoice'],
['name' => 'requester', 'contents' => 'accounts-payable'],
],
]);
print_r(json_decode((string) $response->getBody(), true));
curl -sS -X POST "https://www.exdata.app/api/v1/documents" \
-H "Authorization: Bearer $EXDATA_API_TOKEN" \
-H "Idempotency-Key: video-thumbnail-2026-1048" \
-F "file=@./inspection-video.mp4" \
-F "processing_mode=thumbnail"
{
"data": {
"id": 123,
"mode": "live",
"processing_mode": "extraction",
"processing_lane": "standard",
"status": "pending",
"processing_stage": "queued",
"processing_error": null,
"blocked_reason": null,
"filename": "invoice-re-2026-1048.pdf",
"file_format": "pdf",
"file_size": 240123,
"additional_text": null,
"additional_text_plain": null,
"custom_types": ["invoice"],
"requester": "accounts-payable",
"locale": "en",
"number_of_pages": null,
"origin": "api",
"ai_processing": true,
"is_e_invoice": false,
"thumbnail": null,
"previews": null,
"extractions": null,
"latest_extraction_run": {
"id": 456,
"mode": "live",
"processing_lane": "standard",
"source": "api",
"status": "pending",
"blocked_reason": null,
"error_code": null,
"error_message": null,
"credits_charged": 1,
"extraction_schema_version": "2026-08-28.1",
"extractor_version": "document:2026-09-05.1",
"ai_prompt_version": "document-ai:2026-09-05.1",
"normalization_version": "base:2026-10-21.3",
"quality": null,
"started_at": null,
"completed_at": null,
"created_at": "2026-05-10T01:00:03.000000Z"
},
"created_at": "2026-05-10T01:00:00.000000Z",
"updated_at": "2026-05-10T01:00:03.000000Z"
}
}
{
"data": {
"id": 124,
"mode": "live",
"processing_mode": "thumbnail",
"processing_lane": "standard",
"status": "pending",
"processing_stage": "queued",
"processing_error": null,
"filename": "inspection-video.mp4",
"file_format": "mp4",
"origin": "api",
"ai_processing": false,
"thumbnail": null,
"previews": null,
"extractions": null,
"latest_extraction_run": null,
"created_at": "2026-05-10T01:00:00.000000Z",
"updated_at": "2026-05-10T01:00:00.000000Z"
}
}
/api/v1/documents/{document}
Retrieve document
Fetch the current document status, processing mode, file metadata, and completed results. A completed thumbnail-only document contains the thumbnail URL, reports ai_processing: false, and has no persisted previews, extraction fields, or extraction run.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
document | Path | Yes | Integer | Document ID returned by create or list. |
| Status | Response | Description |
|---|---|---|
200 | Document resource | Current document state. Completed documents can include previews and extraction fields. |
403 | Error envelope | The token cannot access this document or lacks documents:read. |
404 | Error envelope | No document exists for this account and ID. |
curl -sS "https://www.exdata.app/api/v1/documents/123" \
-H "Authorization: Bearer $EXDATA_API_TOKEN"
{
"data": {
"id": 123,
"mode": "live",
"processing_mode": "extraction",
"processing_lane": "standard",
"status": "completed",
"processing_stage": "completed",
"processing_error": null,
"blocked_reason": null,
"filename": "invoice-re-2026-1048.pdf",
"file_format": "pdf",
"file_size": 240123,
"number_of_pages": 1,
"origin": "api",
"ai_processing": true,
"is_e_invoice": false,
"thumbnail": "https://www.exdata.app/api/v1/documents/123/thumbnail",
"previews": [
{
"id": 987,
"filename": "page-1.png",
"file_format": "png",
"file_size": 94812,
"preview": "https://www.exdata.app/api/v1/previews/987"
}
],
"extractions": {
"document_number": {
"value": "RE-2026-1048",
"candidates": ["RE-2026-1048"]
},
"gross_amount": {
"value": "1079.50",
"candidates": ["Total due EUR 1,079.50"]
}
},
"latest_extraction_run": {
"id": 456,
"mode": "live",
"processing_lane": "standard",
"source": "api",
"status": "completed",
"blocked_reason": null,
"error_code": null,
"error_message": null,
"credits_charged": 1,
"extraction_schema_version": "2026-08-28.1",
"extractor_version": "document:2026-09-05.1",
"ai_prompt_version": "document-ai:2026-09-05.1",
"normalization_version": "base:2026-10-21.3",
"quality": {
"status": "normal",
"degraded": false,
"reasons": [],
"ai": {
"status": "completed",
"model": "gpt-5.4-mini-2026-03-17",
"reasoning_effort": "low",
"service_tier": "default",
"max_output_tokens": 8192,
"input_mode": "attachment",
"text_purpose": null,
"detail": "high",
"preflight_unavailable": false,
"low_detail_document_text_cross_checked": null,
"document_text_truncated": false,
"email_text_truncated": false,
"deterministic_supplementation": false,
"no_usable_text": false,
"conflict_fields": [],
"rejected_fields": [],
"recovered_fields": []
},
"pdf": {
"page_count": 1,
"page_count_known": true,
"native_text_available": true,
"native_text_truncated": false,
"ocr_attempted_pages": 0,
"ocr_successful_pages": 0,
"ocr_page_limit_reached": false,
"ocr_page_limit": 200,
"pages_beyond_ocr_limit": 0,
"failed_pages": [],
"skipped_pages": [],
"empty_pages": [],
"blank_pages": [],
"conflict_pages": [],
"unreliable_pages": [],
"limited_pages": []
},
"email": null,
"structured": null
},
"started_at": "2026-05-10T01:00:04.000000Z",
"completed_at": "2026-05-10T01:00:18.000000Z",
"created_at": "2026-05-10T01:00:03.000000Z"
},
"created_at": "2026-05-10T01:00:00.000000Z",
"updated_at": "2026-05-10T01:00:18.000000Z"
}
}
{
"data": {
"id": 124,
"mode": "live",
"processing_mode": "thumbnail",
"processing_lane": "standard",
"status": "completed",
"processing_stage": "completed",
"processing_error": null,
"filename": "inspection-video.mp4",
"file_format": "mp4",
"origin": "api",
"ai_processing": false,
"thumbnail": "https://www.exdata.app/api/v1/documents/124/thumbnail",
"previews": [],
"extractions": [],
"latest_extraction_run": null,
"created_at": "2026-05-10T01:00:00.000000Z",
"updated_at": "2026-05-10T01:00:08.000000Z"
}
}
/api/v1/documents/{document}
Delete document
Makes the document resource and stored extracted values unavailable to the account. The service attempts to remove the ordinary source file, thumbnail, and previews, but the response is not an object-storage deletion receipt.
A separately retained quality-evidence sample may remain until its documented retention expiry. This exception is uncommon and does not change the document's account visibility after deletion.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
document | Path | Yes | Integer | Document ID to delete. |
| Status | Response | Description |
|---|---|---|
204 No Content | No body | The document and stored extracted values are no longer available. Ordinary source, thumbnail, and preview cleanup was attempted; this response does not attest to object-storage deletion. A separately retained quality-evidence sample follows its documented retention expiry. |
403 | Error envelope | The token cannot delete this document or lacks documents:write. |
404 | Error envelope | No document exists for this account and ID. |
409 | Error envelope | The current live extraction is still active. Retry after the document reaches a terminal status. |
curl -sS -X DELETE "https://www.exdata.app/api/v1/documents/123" \
-H "Authorization: Bearer $EXDATA_API_TOKEN" \
-i
/api/v1/documents/{document}/thumbnail
Thumbnail
Downloads the generated JPEG thumbnail for a document when one is available. For a video upload, including a thumbnail-only upload, the thumbnail is a representative frame extracted from the video and returned through this same endpoint.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
document | Path | Yes | Integer | Document ID. |
| Status | Response | Description |
|---|---|---|
200 | JPEG binary (image/jpeg) | Generated document thumbnail or, for a video, its representative frame. |
403 | Error envelope | The token cannot access this document or lacks documents:read. |
404 | Error envelope | No document or thumbnail exists for this account and ID. |
curl -L "https://www.exdata.app/api/v1/documents/123/thumbnail" \
-H "Authorization: Bearer $EXDATA_API_TOKEN" \
-o document-123-thumbnail.jpg
/api/v1/documents/{document}/previews
List previews
Returns preview metadata for generated page or file previews. Every supported e-invoice receives one human-readable HTML invoice visualization in CII, UBL, and ZUGFeRD 1.0 COMFORT syntax, so the same invoice layout is returned regardless of the source format. Locally validated Extension XRechnung 3.0 invoices additionally keep their source-ordered recursive invoice-line view. For exceptionally large line sets or repeatable review sections, the affected content is shown as a clearly labelled bounded prefix so browser rendering remains safe; the original XML remains unchanged. This HTML file is the canonical preview; the accompanying JPEG is only a representative thumbnail and visual fallback. Remaining safe XML uses a pretty-printed XML fallback. The visualization is labelled in the selected document locale. Use the returned preview ID with the preview download endpoint. Thumbnail mode never creates persisted previews, so a completed thumbnail-only document returns {"data":[]}.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
document | Path | Yes | Integer | Document ID. |
| Status | Response | Description |
|---|---|---|
200 | Preview list | data contains preview objects for this document, or an empty array for thumbnail mode. |
403 | Error envelope | The token cannot access this document or lacks documents:read. |
404 | Error envelope | No document exists for this account and ID. |
curl -sS "https://www.exdata.app/api/v1/documents/123/previews" \
-H "Authorization: Bearer $EXDATA_API_TOKEN"
{
"data": [
{
"id": 987,
"filename": "page-1.png",
"file_format": "png",
"file_size": 94812,
"preview": "https://www.exdata.app/api/v1/previews/987"
},
{
"id": 988,
"filename": "page-2.png",
"file_format": "png",
"file_size": 88204,
"preview": "https://www.exdata.app/api/v1/previews/988"
}
]
}
For a completed thumbnail-only document, this endpoint returns {"data":[]}. Download the JPEG from /documents/{document}/thumbnail instead.
/api/v1/previews/{preview}
Download preview
Downloads the binary preview file. Preview IDs are returned by the document previews endpoint and are account-scoped.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
preview | Path | Yes | Integer | Preview ID returned by /documents/{document}/previews. |
| Status | Response | Description |
|---|---|---|
200 | Binary stream | Preview file. The content type depends on the generated preview. |
403 | Error envelope | The token cannot access this preview or lacks documents:read. |
404 | Error envelope | No preview exists for this account and ID. |
curl -L "https://www.exdata.app/api/v1/previews/987" \
-H "Authorization: Bearer $EXDATA_API_TOKEN" \
-o document-123-page-1.png
/api/v1/documents/{document}/extractions
Read extractions
Returns extracted fields keyed by field name. Read this endpoint after the document reaches a terminal completed, error, or blocked status, or after receiving a document.completed or document.failed webhook. A finalized specialized profile result is returned under sector_extraction; an error document can therefore expose a canonical failed result, while a blocked document has none. There is no separate profile-result endpoint. Its common envelope is profile-neutral, while payload, validation, review, evidence, and registered projections are supplied by the selected profile. German energy full-invoice profile versions 2.0.0 and 2.0.1 return machine_payload and effective_payload as {"packet": {...}, "invoices": [...]}. A single invoice still appears at invoices[0], independently issued invoices remain separate items, and a source-verified non-invoice has invoices=[] with an unsupported outcome. The result advertises canonical JSON and invoice-line CSV exports under sector_extraction.exports. Structured extraction does not run in thumbnail, analysis, or validation mode, so those documents return {"data":[]} after completion.
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
document | Path | Yes | Integer | Terminal document ID. A finalized specialized result can be selected for delivery from a completed or error document. |
include | Query | No | evidence | Includes profile evidence plus sorted evidence_pages[] descriptors for cited pages from the exact extraction run. Omit for the compact response. |
| Status | Response | Description |
|---|---|---|
200 | Extraction object or empty array | data is an object keyed by normalized field name, or an empty array for thumbnail, analysis, or validation mode. sector_extraction is present when a finalized specialized profile result is selected for delivery, including a canonical failed result when no usable earlier result exists; its ETag is returned in the response header. |
403 | Error envelope | The token cannot access this document or lacks documents:read. |
404 | Error envelope | No document exists for this account and ID. |
curl -sS "https://www.exdata.app/api/v1/documents/123/extractions" \
-H "Authorization: Bearer $EXDATA_API_TOKEN"
const response = await fetch("https://www.exdata.app/api/v1/documents/123/extractions", {
headers: { Authorization: `Bearer ${process.env.EXDATA_API_TOKEN}` },
});
const extractions = await response.json();
console.log(extractions.data);
import os
import requests
response = requests.get(
"https://www.exdata.app/api/v1/documents/123/extractions",
headers={"Authorization": f"Bearer {os.environ['EXDATA_API_TOKEN']}"},
timeout=30,
)
print(response.json()["data"])
<?php
$client = new GuzzleHttp\Client(['base_uri' => 'https://www.exdata.app/api/v1']);
$response = $client->get('/documents/123/extractions', [
'headers' => ['Authorization' => 'Bearer '.getenv('EXDATA_API_TOKEN')],
]);
print_r(json_decode((string) $response->getBody(), true)['data']);
{
"data": {
"type": {
"value": "invoice",
"candidates": ["Invoice"]
},
"document_number": {
"value": "RE-2026-1048",
"candidates": ["RE-2026-1048"]
},
"issue_date": {
"value": "2026-05-10",
"candidates": ["10 May 2026"]
},
"payment_due_date": {
"value": "2026-06-09",
"candidates": ["Due 09/06/2026"]
},
"sender_name": {
"value": "Meyer Supply GmbH",
"candidates": ["Meyer Supply GmbH"]
},
"recipient_name": {
"value": "Northwind Operations Ltd.",
"candidates": ["Northwind Operations Ltd."]
},
"currency": {
"value": "EUR",
"candidates": ["EUR"]
},
"net_amount": {
"value": "1079.50",
"candidates": ["Net amount EUR 1,079.50"]
},
"gross_amount": {
"value": "1079.50",
"candidates": ["Amount due EUR 1,079.50"]
},
"tax_breakdowns": {
"value": [
{
"taxable_amount": "1079.50",
"tax_amount": "0.00",
"tax_rate": "0.00",
"taxability": "taxable",
"tax_collection_mechanism": "reverse_charge",
"tax_exemption_reason": "Intra-EU B2B reverse charge"
}
],
"candidates": ["Reverse charge applies under Article 196 VAT Directive"]
},
"payment_reference": {
"value": "RE-2026-1048",
"candidates": ["Payment reference RE-2026-1048"]
}
}
}
/api/v1/documents/{document}/extraction-runs/{extractionRun}/pages/{physicalPage}/preview
Download extraction evidence page preview
Downloads one protected page artifact cited by a specialized profile result. Use the preview_url returned in sector_extraction.evidence_pages[] instead of constructing a URL from the latest document state. The document, immutable extraction run, and physical page must match exactly; this endpoint never falls back to a page from another run.
| Parameter | Location | Required | Description |
|---|---|---|---|
document | Path | Yes | Account-scoped document ID. |
extractionRun | Path | Yes | Exact extraction run ID from the profile result. |
physicalPage | Path | Yes | One-based cited physical page. |
| Status | Response | Description |
|---|---|---|
200 | Inline binary artifact | Returns the safe stored content type with Cache-Control: private, no-store. |
403 | Error envelope | The token cannot access the account document or lacks documents:read. |
404 | Error envelope | The document, exact run/page relation, or safe persisted artifact is unavailable. Missing and expired artifacts fail closed. |
429 | Error envelope | Read rate limit exceeded. |
curl -L "https://www.exdata.app/api/v1/documents/123/extraction-runs/456/pages/1/preview" \
-H "Authorization: Bearer $EXDATA_API_TOKEN" \
-o evidence-page-1.jpg
/api/v1/documents/{document}/exports/{projection}
Download document profile export
Downloads a projection only when the selected profile registers one. Read the stable URL, projection contract version, and current availability from sector_extraction.exports. German energy full-invoice profile versions 2.0.0 and 2.0.1 register canonical_json at canonical.json for the complete packet and every invoice, and invoice_lines_csv at invoice-lines.csv for all invoice charge lines with leading invoice identity and page-range columns. Both become available only after the effective result and handoff are accepted.
| Status | Response | Description |
|---|---|---|
200 | Projection-defined attachment | Returned after the extraction is finalized and the selected projection's readiness requirements pass. Includes result ETag, X-Projection-Version, and private no-store caching. |
403 | Error envelope | The token cannot access the document or lacks documents:read. |
404 | Error envelope | The document, effective profile result, or requested registered projection is unavailable. |
409 | Error envelope | profile_export_not_ready with current outcome and handoff details. |
429 | Error envelope | Read rate limit exceeded. |
/api/v1/documents/exports/{projection}
Download deterministic batch profile export
Combines 1–100 distinct documents into one batch-capable projection when the selected profile registers one. Repeat document_ids[] for each ID. The request is account-scoped and all-or-nothing: every document must exist in the caller's account, every extraction run must be completed and finalized, every effective result and handoff must be accepted, and every result must expose the same batch projection implementation and version. German energy full-invoice profile versions 2.0.0 and 2.0.1 support batch invoice-lines.csv; rows are ordered by document ID, then preserve invoice source order and each invoice's charge-line order.
| Status | Response | Description |
|---|---|---|
200 | Projection-defined attachment | Rows are rendered in ascending document-ID order regardless of request order. Includes a composite ETag, shared X-Projection-Version, and private no-store caching. |
403 | Error envelope | The token lacks documents:read. |
404 | Error envelope | At least one ID is unknown or outside the account. No partial output or ownership detail is returned. |
409 | Error envelope | profile_export_not_ready for missing, unfinished, or unaccepted results, or profile_export_incompatible when no one shared batch projection can render the complete set. |
422 | Validation error | The request does not contain 1–100 distinct positive integer document_ids[] values. |
429 | Error envelope | Read rate limit exceeded. |
curl -L "https://www.exdata.app/api/v1/documents/exports/invoice-lines.csv?document_ids[]=456&document_ids[]=123" \
-H "Authorization: Bearer $EXDATA_API_TOKEN" \
-o invoice-lines.csv
/api/v1/documents/{document}/extractions/reviews
Review a profile result
Records one append-only review decision and revalidates the effective profile result returned under sector_extraction. For German energy full-invoice profile versions 2.0.0 and 2.0.1, confirmation and correction require the target invoice_ref; complete-packet rejection must omit it. API tokens require extractions:review. Send both Idempotency-Key and the latest extraction ETag as If-Match. A review-only token receives audit metadata after the write; add documents:read to receive field values and the full profile payload.
Every action may include review_duration_seconds, an integer from 1 through 86,400 containing measured active human-review time spent reaching that individual decision. This caller-supplied value is workflow telemetry, not the age of the queue item or wall-clock time between extraction and review. Omit it or send null when active time was not measured; exdata then marks duration coverage incomplete and never assumes the missing time was zero.
| Action | Required JSON fields | Description |
|---|---|---|
confirm | invoice_ref, entity_ref, field | Confirms the current non-empty value in one German energy invoice. |
correct | invoice_ref, entity_ref, field, corrected_value, evidence_reference | Corrects one reviewable field in the selected German energy invoice. Most corrections are non-empty strings; billing_period uses {"start","end"}, while service_address and billing_address use address objects. |
reject | reason | Rejects the complete result. A rejected result is terminal; German energy rejection must omit invoice_ref. |
evidence_reference uses component_ref, physical_page, optional printed_page/label, text, a profile-defined source, optional match, and optional bbox. It is optional for confirm, which otherwise retains the current citation, and required for correct. The selected profile validates the source against the run and, for German energy, verifies that the component and page belong to the selected invoice; the profile currently accepts native_text, embedded_ocr, tesseract_ocr, and combined. Evidence must support the complete reviewed value. Identifier evidence needs a type-compatible label inside the evidence text; supplier and customer names need the correct party role; service and billing addresses need the correct address role. Every populated structured address component must occur in its corrected raw value and the evidence, and an explicit country code must be visible as the country name or uppercase code. Technical identifiers are normalized and format-checked for both actions. MaLo and legacy metering-point values can only be reviewed on a supply point, MeLo on a metering location, meter numbers on meter assignments, and operator_code on the operator that owns it.
exdata verifies that the cited component, physical page, and selected text source belong to the selected invoice in the recorded page manifest. It stores manually supplied evidence text with reviewer_transcribed=true, but does not compare that transcription with OCR or native extracted text. This permits correction of OCR errors, so grant extractions:review only to trusted reviewers who visually check the cited page. A review does not clear an ambiguous supply-point association; affected relationship identifiers remain in review in the canonical payload until a new extraction resolves it.
| Status | Response | Description |
|---|---|---|
201 | Decision and updated profile result | Returns the new ETag. Review-only tokens receive metadata; tokens with documents:read receive the full result. An identical retry also returns Idempotent-Replayed: true. |
403 | Error envelope | The token lacks extractions:review, is outside the account, or the user role cannot review. |
404 | Error envelope | No document or specialized profile result exists for this account and ID. |
409 | Error envelope | extraction_version_conflict for a stale ETag, or idempotency_conflict when the key was reused with another payload. |
422 | Validation error | The action, target field, corrected value, evidence reference, or headers are invalid; rejected results and accepted handoffs are terminal and also reject later decisions. |
429 | Error envelope | Write rate limit exceeded. |
When a decision makes both effective_outcome and handoff_status accepted, the accepted handoff is terminal. A later confirmation, correction, or rejection returns a validation error and cannot mutate the accepted result snapshot.
curl -sS -X POST "https://www.exdata.app/api/v1/documents/123/extractions/reviews" \
-H "Authorization: Bearer $EXDATA_REVIEW_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: correct-customer-number-123" \
-H 'If-Match: "extraction-result-789-v0"' \
--data '{
"action": "correct",
"invoice_ref": "invoice_5f2db86e322d058b2bfd",
"entity_ref": "relationship_1",
"field": "customer_number",
"corrected_value": "KUNDE-10",
"reason": "Confirmed against the printed account block.",
"review_duration_seconds": 95,
"evidence_reference": {
"component_ref": "component_8a1f74f0f1d8c2ab",
"physical_page": 1,
"printed_page": "1",
"label": "Kundennummer",
"text": "Kundennummer KUNDE-10",
"source": "native_text",
"match": "manual"
}
}'
{
"data": {
"decision": {
"id": 901,
"action": "correct",
"invoice_ref": "invoice_5f2db86e322d058b2bfd",
"entity_ref": "relationship_1",
"field": "customer_number",
"review_duration_seconds": 95,
"previous_value": "KUNDE-1O",
"corrected_value": "KUNDE-10",
"expected_lock_version": 0,
"resulting_lock_version": 1
},
"sector_extraction": {
"id": 789,
"machine_outcome": "needs_review",
"effective_outcome": "accepted",
"handoff_status": "accepted",
"lock_version": 1,
"etag": "\"extraction-result-789-v1\""
}
}
}
Response objects
The tables below define the fields used by endpoint responses. The extraction field reference lists every normalized extraction key separately.
For XML documents, is_e_invoice=true means a supported structured e-invoice was accepted after validation; recognizing invoice-like XML alone does not set the flag. Standard XRechnung 3.0 and the exact Extension profile in supported UBL and CII syntax use their matching pinned KoSIT scenarios on the original XML and report xml_profile_conformance. The exact CVD profile in CII syntax uses its pinned KoSIT scenario but reports structural_and_business; UBL CVD is not claimed as supported. The exact ZUGFeRD 1.0 COMFORT profile with TypeCode 380 or 84 is validated against its checksum-pinned XSD, Schematron constraints, and arithmetic rules and reports structural_and_business. The published tax TypeCodes VAT, ZF_INSURANCE_TAX (insurance tax), and AAJ (second-hand-parts tax) are supported. Accepted document-level values map into the v1 fields. Recursive line hierarchy, embedded attachment metadata, and third-party payment rows from the UBL Extension path feed the generated HTML visualization but are not returned as v1 extraction fields; exceptionally large line sets or repeatable review sections are shown as clearly labelled source-order prefixes. Multiple ambiguous payee accounts remain validation-only and leave the singular iban and bic fields null. ZUGFeRD 1.0 BASIC and EXTENDED, other document or tax codes, invalid invoices, and unknown or unsupported profiles fail closed with unsupported_structured_invoice in extraction mode.
Document object
| Field | Type | When present | Description |
|---|---|---|---|
id | Integer | Always | Document ID. |
mode | live or test | Always | Whether the document was created by a live or test-mode token. |
processing_mode | extraction, analysis, validation, or thumbnail | Always | Work selected at upload. extraction is the default; analysis runs the lifecycle without AI extraction; validation returns only the structured-invoice assessment; thumbnail generates only the JPEG thumbnail. |
processing_lane | standard or batch | Always | Processing priority selected at upload. Batch is available to eligible accounts and is returned when selected for the upload. It can take longer to start but does not change the result contract. If batch is unavailable for the account, the upload returns 422 with code=validation_failed and a processing_lane validation error. |
pages | String or null | Always | Normalized page selection stored for this upload, exactly as requested, such as first,last or all. null when the upload did not send a selection; selections only exist in analysis mode. |
status | pending, completed, error, or blocked | Always | Current document processing status. |
processing_stage | String or null | Always | Current stage such as queued, thumbnailing, extracting, or completed. waiting_for_capacity means the document is accepted while your account is already at its concurrent standard-lane limit; it starts automatically as your earlier documents finish. Thumbnail mode can report this too because thumbnail rendering uses standard processing capacity. |
processing_error | String or null | Always | Human-readable processing failure message when status is error. |
blocked_reason | String or null | Always | Machine-readable reason when status is blocked, for example insufficient_credits. |
scanner_status | clean, rejected, or null | Always | Upload scanner decision when available. |
scanner_provider | String or null | Always | Label identifying the scanner result source when available. |
scanner_message | String or null | Always | Scanner explanation when available. |
scanned_at | Date-time or null | Always | When file scanning completed. |
processing_started_at | Date-time or null | Always | When processing work started. |
processed_at | Date-time or null | Always | When processing reached a terminal state. |
filename | String | Always | Original uploaded filename. |
file_format | String | Always | Detected or stored file extension, for example pdf. |
file_size | Integer | Always | File size in bytes. |
additional_text | String or null | Always | Optional upload context supplied through the text form field. |
additional_text_plain | String or null | Always | Plain-text version of additional_text. |
custom_types | Array of strings or null | Always | Type extensions supplied at upload time. |
requester | String or null | Always | Caller reference supplied at upload time. |
locale | String or null | Always | Locale hint supplied at upload time. |
number_of_pages | Integer or null | Always | Detected page count when available. |
extracted_text | String or null | Always | Document text exactly as extraction produced it. Page boundaries are form feed characters, and bracketed diagnostic and structure markers such as [PAGE OCR WARNING: OCR RETURNED NO TEXT] record how the text was produced. |
extracted_text_plain | String or null | Always | The same text normalized for reading and indexing: whitespace collapsed, diagnostic and email structure markers removed, page boundaries kept as form feed characters. Prefer this field for full-text search, and split on the form feed for per-page text. |
origin | api, app, mcp, x402, or null | Always | Where the document was created. |
ai_processing | Boolean | Always | Whether AI extraction is enabled. Always false for thumbnail, analysis, and validation mode. In extraction mode, false does not disable OCR, previews, non-AI structured extraction, or charging. |
extraction_profile | generic or de.energy.supply-invoice | Always | Effective extraction profile recorded at upload. |
is_e_invoice | Boolean | Always | Whether a supported structured e-invoice was accepted after validation. Recognition alone does not set this flag. |
thumbnail | URL or null | Always | Download URL for the generated JPEG thumbnail. Video thumbnails use a representative decoded frame. |
previews | Array or null | Always | Preview objects for completed extraction-mode documents when previews are loaded. Thumbnail mode creates none. |
extractions | Object, empty array, or null | Always | Extraction fields for completed extraction-mode documents when extractions are loaded. Completed thumbnail-only, analysis, and validation documents return an empty array. |
latest_extraction_run | Extraction run or null | Always | Latest extraction attempt metadata. Always null for thumbnail mode. |
sector_extraction_summary | Object or null | Always | Compact current specialized profile result with review and handoff states, ETag, the extractions URL, and export descriptors. Null until a profile result exists. |
created_at | Date-time | Always | Document creation timestamp. |
updated_at | Date-time | Always | Last document update timestamp. |
Extraction run object
| Field | Type | Description |
|---|---|---|
id | Integer | Extraction run ID. |
mode | live or test | Billing/reporting mode for the run. |
processing_lane | standard or batch | Processing priority used by this extraction attempt. |
source | api, app, mcp, x402, or null | Where the run was started. |
status | pending, processing, completed, blocked, or error | Run status. |
blocked_reason | String or null | Machine-readable block reason. |
error_code | String or null | Machine-readable failure code. In extraction mode, unsupported_structured_invoice means XML was deterministically rejected because it exceeded safety limits, was unsafe or malformed, violated structured-invoice validation rules, or used an unsupported profile. Inspect quality.structured.validation_status, assessment, and validation_errors. Technical validator unavailability remains retryable and is never classified as an invalid invoice. |
error_message | String or null | Failure message for diagnosing the run. |
credits_charged | Integer or null | Current net billable total. A live run applies one profile-rate band, calculates usage as credit_cost × started 10-page bands, and keeps the total when a usable result or retrievable extracted fields remain. Terminal errors that leave neither, and page-band blocks caused by unavailable credits, report zero after credits are returned. Test and other non-credit runs report zero. Null means no credit usage was recorded. |
extraction_schema_version | String or null | Extraction schema version used by this run. |
extractor_version | String or null | Extractor version recorded for this run. |
ai_prompt_version | String or null | Processing instruction version recorded for this run. |
normalization_version | String or null | Normalization version used after extraction. |
extraction_profile, profile_version | String | Profile key and contract version used for this run. |
ai_model | String or null | Processing model recorded for this run. |
ai_reasoning_effort | String or null | Reasoning setting recorded for this run. |
strategies_used | Array or null | Profile-specific extraction strategies recorded for reproducibility. |
provenance_summary | Object or null | Winning-value method summary for non-null extraction fields. It reports classification, methods_used, and per-method field_counts. It is null when no automatic winning-field provenance snapshot was persisted, including legacy, in-progress, analysis, validation, and reviewed-profile runs; this is separate from strategies_used. |
billing_mode | String | Billing treatment recorded for the run. This is informational run metadata, not an upload parameter. |
credit_cost | Integer | Credits per started 10-page band recorded for this run. For example, a rate of 3 and two bands yields credits_charged: 6 when the run retains a usable result or retrievable extracted fields. |
credit_cost_source | String | Indicates how the recorded per-band rate was determined. This is returned run metadata, not an upload parameter; use it when your integration needs to interpret the rate rather than infer it from the profile name. |
quality | Object or null | Processing-path quality signals and AI execution settings. This can be null when no AI, PDF, email-attachment, or structured-invoice quality metadata applies. For structured invoices, use quality.structured.assessment as the objective validation and linkage result; do not derive an e-invoice traffic light from the general quality.status. When degraded=true, inspect reasons and the applicable component details before automating critical values. |
started_at | Date-time or null | When extraction processing started. |
completed_at | Date-time or null | When extraction processing completed. |
created_at | Date-time or null | When the run record was created. |
The quality object is deliberately separate from the run status. A run can complete successfully while using a text fallback, partial deterministic supplementation, truncated evidence, incomplete OCR coverage, or a degraded email attachment path. High-detail visual evidence from PDFs and supported JPEG, PNG, and WebP source images is the primary AI attachment path, including a PDF selected from an EML or MSG container. Low detail is used only as a context-limit fallback and is cross-checked for detected conflicts against independently extracted document or OCR text when that text is available. A cross-check is credited only when the fallback pass contains document text without supplemental email evidence; a combined document-and-email pass remains conservatively unverified because field-level provenance cannot be isolated. A missing value in one pass does not confirm the other pass. An attachment+text mode means separate attachment and text evidence attempts contributed to the result; exdata does not send the same attachment content twice in one request. A text fallback may also repair partially valid attachment evidence. A complete, untruncated, document-only pass over clean high-detail PDF processing can remain normal only when every repaired field is deterministically corroborated by document text and no unrelated text field contributes. This strict normal recovery is currently limited to a country-format- and checksum-valid IBAN that differs by exactly one character from a checksum-only rejection and appears as the sole distinct valid IBAN in the document text; quality.ai.recovered_fields reports it. Other rejected fields remain in quality.ai.rejected_fields and keep the result degraded. For email uploads, quality.email records bounded parsing, ignored decoration, unsupported or failed attachments, evidence truncation, and whether multiple document candidates were present. quality.structured records syntax/profile support, structural and arithmetic validation, whether machine-readable values were accepted, and field-name-only linkage or conflicts against a paired PDF. Unsupported standalone e-invoice XML fails processing instead of completing with empty invoice fields. For hybrid PDFs and email containers, rejected structured data is not used silently; another readable source may complete the run with degraded quality and explicit conflict metadata. A single extraction result uses one selected financial attachment and does not merge separate invoices. Treat quality.status=degraded as a review signal for money, tax, payment, identity, and date fields. quality.status=normal means that no unresolved processing degradation remains after bounded recovery; it is not a confidence score or a guarantee that every extracted value is correct.
For an XML-validity indicator, use quality.structured.xml_validation: its assessment, validation_status, findings, and reason codes describe only the XML checks. PDF/text conflicts and unavailable OCR do not change this result. The enclosing quality.structured.assessment and authority still account for source linkage and whether structured values may be used. A passing XML result does not confirm extracted metadata or the visible PDF. Older snapshots without xml_validation have no separately recorded XML result.
New structured-quality snapshots have contract_version: 1. detection_status distinguishes recognized, a completed not_detected result, and inconclusive. validation_status distinguishes accepted validation, warnings, completed invalidity, unsupported profiles, technical unavailability, and input that could not be evaluated safely. validation_scope says whether the claim is XML profile conformance, deterministic structural and business validation, or none. Standard and Extension XRechnung report XML profile conformance in supported UBL and CII syntax; CVD CII and ZUGFeRD 1.0 COMFORT report structural and business validation without claiming complete XML profile conformance. assessment combines validation with required PDF/XML linkage into pass, pass_with_warnings, fail, indeterminate, or not_applicable. Persisted validator metadata identifies the engine, engine version, configuration, standard, and scenario used for that run. Findings contain bounded rule identifiers and severity counts only, never message text or document contents. A valid XML result can still have assessment: "fail" when its visible PDF conflicts. This contract makes no statement about legal validity or complete PDF/A/container conformance, and older snapshots that omit these fields remain unknown rather than not_detected.
Profile-result summary object
This profile-neutral summary is returned under sector_extraction_summary. The German energy profile is the currently available specialized example.
| Field | Type | Description |
|---|---|---|
id, extraction_run_id | Integer | Profile result and source run identifiers. |
profile | String | Canonical profile key. |
profile_version | String | Profile contract version. |
schema_version | String | Canonical payload schema version. |
machine_outcome | String | Immutable extraction/validation outcome. |
effective_outcome | String | Current outcome after review. |
handoff_status | String | Current downstream handoff state. |
review_reasons | Array | Blocking canonical review reason codes. |
handoff_review_reasons | Array | Additional downstream handoff blockers. |
warnings | Array | Non-blocking source or extraction warnings. |
lock_version, etag | Integer and string | Optimistic concurrency state used for review decisions. |
extractions_url | URL | Document-extractions endpoint containing the complete profile result. |
exports | Object | Profile-registered export descriptors. German energy full-invoice profile versions 2.0.0 and 2.0.1 expose canonical_json and invoice_lines_csv, each with a stable url, contract version, and available flag. |
Profile result object
The complete profile-neutral envelope is returned under sector_extraction. profile and schema_version define the nested payload contract.
| Field | Type | Description |
|---|---|---|
id, document_id, extraction_run_id | Integer | Result and source record identifiers. |
profile, profile_version, schema_version | String | Versioned canonical contract. |
machine_outcome | String | accepted, needs_review, unsupported, or failed. |
effective_outcome | String | Machine outcome updated by audited review; can also be rejected. |
handoff_status | String | Downstream handoff acceptance state. |
review_reasons, handoff_review_reasons | Array | Canonical and downstream blocking reason codes. |
warnings | Array | Non-blocking warnings. |
lock_version, etag | Integer and string | Optimistic concurrency state used by review requests. |
acceptance_source | machine, human_review, or null | How the effective result became accepted. |
reviewed_at | Date-time or null | Time of the latest recorded review decision. |
machine_payload | Profile-defined object or null | Immutable normalized payload produced by the extraction run. It is null on a canonical failed result when no finalized usable profile payload exists, including after a provisional result is replaced during failed finalization. German energy full-invoice profile versions 2.0.0 and 2.0.1 otherwise return {packet,invoices}; each invoice's data contains party, relationship, supply-point, tariff, operator, meter, consumption, charge, tax, settlement, and payment entities. Embedded properties named evidence are omitted unless requested explicitly. |
effective_payload | Profile-defined object or null | Current payload after append-only corrections and confirmations. For German energy it retains the same {packet,invoices} shape, and a field decision changes only the invoice named by invoice_ref. It is null when the canonical failed result has no finalized usable machine payload. |
validation_summary | Profile-defined object | Versioned diagnostic validation snapshot. After review, use top-level outcomes/reasons and, for German energy, each effective_payload.invoices[] outcome plus invoices[].data.field_quality as the authoritative current state; machine-time counts can remain unchanged. |
source_components | Array | Profile-defined component and page groups used for provenance. |
evidence | Array | Flattened page evidence; included only with include=evidence. |
evidence_pages | Array | Sorted cited physical pages with available and a protected run-bound preview_url; included only with include=evidence. Unavailable artifacts use a null URL and never fall back to another run. |
review_decisions | Array | Immutable decision history in application order. |
exports | Object | Profile-registered downstream projections with profile-defined readiness. German energy full-invoice profile versions 2.0.0 and 2.0.1 return descriptors for canonical JSON and invoice-line CSV. |
created_at, updated_at | Date-time or null | Result creation and last-update timestamps. |
Profile evidence object
| Field | Type | Description |
|---|---|---|
entity_ref, field, field_status | String | Profile-defined entity and field supported by the evidence. |
component_ref | String | Source component containing the evidence. |
physical_page | Integer | One-based physical PDF page. |
printed_page | String or null | Optional page label printed by the supplier. |
label, text | String or null / String | Optional visible field label and required supporting span. |
source | String | Native text, embedded OCR, Tesseract OCR, or combined provenance. |
match | String | Exact, normalized, or manual match. |
bbox | Object or null | Optional page coordinates. |
Profile review decision object
| Field | Type | Description |
|---|---|---|
id | Integer | Review decision ID. |
action | confirm, correct, or reject | Immutable decision action. |
entity_ref, field | String or null | Stable field target for confirm/correct. |
reason | String or null | Reviewer explanation, required for rejection. |
previous_value, corrected_value | Any JSON value | Audited values before and after a correction. |
evidence_reference | Evidence or null | Evidence retained with the decision. Correction evidence has match=manual and reviewer_transcribed=true. |
review_duration_seconds | Integer or null | Caller-supplied workflow telemetry containing measured active human-review time for this decision. Null means unmeasured coverage, not zero; it is not wall-clock elapsed time. |
reviewer_user_id, account_api_token_id | Integer or null | App user or account token that recorded the decision. |
previous_effective_outcome, resulting_effective_outcome | String | Outcome transition caused by this decision. |
previous_handoff_status, resulting_handoff_status | String | Projection transition caused by this decision. |
expected_lock_version, resulting_lock_version | Integer | Concurrency state before and after the decision. |
idempotency_key_prefix | String | Non-secret prefix retained for retry diagnostics. |
created_at | Date-time or null | When the decision was recorded. |
Preview object
| Field | Type | Description |
|---|---|---|
id | Integer | Preview ID used by the preview download endpoint. |
filename | String | Generated preview filename. |
file_format | String | Preview file extension, for example png. |
file_size | Integer | Preview file size in bytes. |
preview | URL | Download URL for the preview binary. |
Extraction field object
| Field | Type | Description |
|---|---|---|
value | String, array, object, or null | Normalized value for system mapping. Amounts are dot-decimal strings, dates use YYYY-MM-DD, currency uses ISO 4217, and tax_breakdowns uses an array of tax rows. |
candidates | Array or null | Raw or derived candidate values considered for the field. Use these in review UIs and troubleshooting workflows; they do not identify the origin of the selected value. |
provenance | Object or null | Persisted origin of the selected value, with method, source, derived_from, and transforms. Null for legacy rows, null values, or all effective extraction fields projected from a profile result after any human-review decision, including fields not individually reviewed, because that projection no longer represents untouched automatic output. Candidate provenance is not included. |
Error envelope
| Field | Type | Description |
|---|---|---|
message | String | Human-readable error message. |
code | String | Machine-readable error code such as validation_failed or insufficient_credits. |
request_id | String | Request identifier for diagnosing a request and correlating related events. |
errors | Object | Validation field errors. Present on 422 responses. |
retry_after | Integer or null | Retry delay in seconds. Present on some rate-limit responses. |
available_credits | Integer | Available prepaid credits for self-service accounts or remaining committed units for commercial accounts. Present on insufficient-credit upload responses. |
required_credits | Integer | Billable units required for the upload. Present on insufficient-credit upload responses. |
document | Document object | Blocked document created before the credit check failed. Present on insufficient-credit upload responses. |
account_daily_document_limit | Integer | Sandbox account daily upload limit. Present on test-mode limit responses. |
user_daily_document_limit | Integer | Sandbox user daily upload limit. Present on test-mode limit responses. |
token_daily_document_limit | Integer | Sandbox token daily upload limit. Present on test-mode limit responses. |
expected_lock_version | Integer | Lock version submitted with a stale review decision. Present on extraction_version_conflict. |
current_lock_version | Integer | Current profile-result version after a concurrency conflict. |
conflict_reason | String | payload_mismatch when a review idempotency key was reused with another decision. |
document_ids | Array | Batch documents that have no finalized profile result or are not accepted for handoff. Present on some profile_export_not_ready responses. |