Skip to content

Documents (OCR and UBL)

Send a purchase invoice or a receipt as a PDF, JPEG or PNG. AccountCloud reads it and gives you what is on it as JSON, in the line of EN 16931, and as UBL. A document is processed in one of two ways:

processing What happens Ready
automatic (default) Read by OCR. Expected within 2 minutes.
verified Read by OCR, then checked and corrected by a processor, who approves it only when its UBL passes the validation. Within 24 working hours.

OCR is not error-free. Check what you use, especially with automatic: the warnings and the confidence help you decide which documents a person should look at.

Operation Endpoint Scope
ocr.extract POST /v1/documents with processing: automatic ocr
ubl.verified POST /v1/documents with processing: verified ubl
ocr.read GET /v1/documents, GET /v1/documents/{id}, DELETE /v1/documents/{id} ocr
ubl.generate GET /v1/documents/{id}/ubl ubl
ocr.feedback POST /v1/documents/{id}/feedback ocr
ubl.validate POST /v1/ubl/validate ubl

A key needs the scope of the product, or the operation itself (see scopes). A key that sends verified documents and reads them back needs both, for example ubl and ocr.

Only sending a document costs money. Reading, listing and deleting documents, downloading the UBL, validating a UBL file and reporting a wrong result are free. See what it costs. All six operations are in preview: their status in GET /v1/pricing is preview.

As JSON, with the file in base64:

Terminal window
curl https://api.accountcloud.io/v1/documents \
-H "Authorization: Bearer ac_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2e0a-4b7d-4e59-9a51-0c3f8f2d7b14" \
-d '{
"file": { "name": "invoice-2026-118.pdf", "content_base64": "JVBERi0xLjcK…" },
"processing": "automatic",
"output": ["json", "ubl"],
"own_party": {
"name": "Example Bakery B.V.",
"vat_number": "NL000000000B01",
"country_code": "NL",
"street": "Bakkerstraat",
"house_number": "1",
"postal_code": "1000 AA",
"city": "Amsterdam"
},
"reference": "inbox-48213",
"metadata": { "administration": "8842" }
}'

Or as multipart, with the same fields as form fields:

Terminal window
curl https://api.accountcloud.io/v1/documents \
-H "Authorization: Bearer ac_test_…" \
-H "Idempotency-Key: 6f1c2e0a-4b7d-4e59-9a51-0c3f8f2d7b14" \
-F "processing=automatic" \
-F "output[]=json" -F "output[]=ubl" \
-F "own_party[name]=Example Bakery B.V." \
-F "own_party[vat_number]=NL000000000B01" \
-F "reference=inbox-48213" \
-F "metadata[administration]=8842"
Field What it is
file Required. In JSON an object with name (at most 255 characters) and content_base64, where a prefix such as data:application/pdf;base64, is allowed. In multipart a file field. At most 10 MB. The name only counts for test mode and display: the type is decided from the content.
processing automatic (default) or verified.
type A hint: purchase_invoice, receipt or auto (default). The OCR does not use it; with verified the processor sees it.
direction purchase (default) or sales. On a purchase document own_party is the buyer, on a sales document the seller.
output ["json"] (default) or ["json", "ubl"]. With ubl the UBL is made and validated as soon as the document is completed; otherwise on the first download.
country_hint A hint, ISO 3166-1 alpha-2. The OCR does not use it; with verified the processor sees it.
own_party The party you process the document for: name, vat_number, registration_number, country_code, street, house_number, postal_code and city, all optional. It helps the OCR tell the supplier and the customer apart, and becomes the buyer (with sales, the seller) in the UBL.
accepted_currencies Up to 20 ISO 4217 codes. With verified, a document in another currency is rejected with currency_not_accepted.
reference Your own id for the document, at most 255 characters. The list can be filtered on it.
metadata Up to 20 keys of 1 to 40 letters, digits, _, ., : or -, with text values of at most 500 characters. Comes back unchanged on the document and in every webhook.

The answer is 202 Accepted, with the new document and its URL in Location:

HTTP/1.1 202 Accepted
Location: /v1/documents/doc_01jbx6y4q9t2m8rz1v3k5n7p0a
{
"id": "doc_01jbx6y4q9t2m8rz1v3k5n7p0a",
"object": "document",
"status": "extracting",
"processing": "automatic",
"environment": "test",
"reference": "inbox-48213",
"metadata": { "administration": "8842" },
"file": { "name": "invoice-2026-118.pdf", "mime_type": "application/pdf", "size_bytes": 182311, "pages": 2, "sha256": "9f2c4e7a…" },
"duplicate_of": null,
"expected_ready_at": "2026-11-04T09:16:12Z",
"created_at": "2026-11-04T09:14:12Z",
"extracted_at": null,
"completed_at": null,
"failure": null,
"result": null,
"confidence": null,
"warnings": [],
"verification": null,
"rejection": null,
"ubl": { "available": false, "href": "/v1/documents/doc_01jbx6y4q9t2m8rz1v3k5n7p0a/ubl", "valid": null, "profile": "peppol-bis-billing-3.0", "errors": [] },
"charge": { "operation": "ocr.extract", "status": "none", "amount": "0.0000", "currency": "EUR", "live_amount": "0.0350" },
"content_available": true,
"content_purged_at": null,
"retention_until": "2026-11-05T09:14:12Z"
}

This is a test document. With a live key a document starts as queued, its price is reserved (charge.status: reserved), and retention_until is set once the document has its outcome. expected_ready_at is the time of sending plus two minutes for automatic, and the deadline for verified.

The file is checked before anything is stored, with a test key too. A request that fails a check costs nothing.

The same file again. Within the same account, environment, entity and processing, the same file gives 200 with the earlier document instead of a new one. Its duplicate_of is then set, to that document’s own id. The answer costs nothing; the charge in it is still that of the earlier document. It is not a duplicate when the earlier document failed, was deleted, or its content is gone after the retention period, and neither is a file you sent as automatic before and now send as verified. To have the same file read again, delete the earlier document first. The SHA-256 of the file is in file.sha256.

Entities. When you send documents for several administrations or customers of your own, send the AccountCloud-Entity header with your id for it (1 to 255 visible characters). Duplicates are then found per entity, the list can be narrowed to it, and charges are recorded per entity.

Retrying a request. Send an Idempotency-Key with every POST. A retry with the same key and the same request gets the first answer again, for 24 hours, with Idempotency-Replayed: true, and is never charged twice. The same key with a different request (with multipart: a different file, too) gets 409 idempotency_key_reused. Only a 2xx answer is kept, so a retry after an error is processed anew. See idempotency.

Status What it means
queued Accepted, and waiting to be read.
extracting Being read. When the OCR is busy or down, it is tried again after 2, 5, 15, 30 and 60 minutes, and the status stays extracting.
awaiting_review Only verified: read, and waiting for a processor.
in_review Only verified: a processor has it open. It goes back to awaiting_review when they let it go.
completed Done. With verified: approved by a processor.
failed Only automatic: the document could not be read. With failure.code.
rejected Only verified: a processor rejected it. With rejection.

completed, failed and rejected are final. The failure.code of a failed document:

Code Why
extraction_failed The OCR failed in a way that trying again does not fix.
document_unreadable The OCR could not read the file.
provider_unavailable The OCR was still unavailable after the last retry.

A failed document costs nothing, and you can send the file again. A verified document never fails: when the OCR fails, it still goes to a processor, who fills it in by hand, and it gets the warning extraction_failed.

Add a webhook endpoint in the portal (see webhooks) and pick the document.* types:

Type When
document.extracted Only verified: the document was read and waits for a processor. With expected_ready_at.
document.completed The document is done. With verified: a processor approved it.
document.failed Only automatic: the document could not be read. With failure.
document.rejected Only verified: a processor rejected it. With rejection.

The payload is thin: ids, the status, your reference and metadata, and the charge. Never the contents of the document. Fetch the result with GET /v1/documents/{id}.

{
"type": "document.completed",
"timestamp": "2026-11-04T09:14:16Z",
"data": {
"id": "doc_01jbx6y4q9t2m8rz1v3k5n7p0a",
"status": "completed",
"processing": "automatic",
"reference": "inbox-48213",
"metadata": { "administration": "8842" },
"charge": { "operation": "ocr.extract", "amount": "0.0350", "currency": "EUR" }
}
}

charge is what the document costs at that moment; for a test document it also has live_amount. A document’s events go to the endpoints of its environment: those of a test document to your test endpoints.

GET /v1/documents/{id} is free, but counts towards your rate limits. expected_ready_at says when to expect the outcome.

Terminal window
curl https://api.accountcloud.io/v1/documents/doc_01jbx6y4q9t2m8rz1v3k5n7p0a \
-H "Authorization: Bearer ac_test_…"
{
"id": "doc_01jbx6y4q9t2m8rz1v3k5n7p0a",
"object": "document",
"status": "completed",
"processing": "automatic",
"environment": "test",
"reference": "inbox-48213",
"metadata": { "administration": "8842" },
"file": { "name": "invoice-2026-118.pdf", "mime_type": "application/pdf", "size_bytes": 182311, "pages": 2, "sha256": "9f2c4e7a…" },
"duplicate_of": null,
"expected_ready_at": "2026-11-04T09:16:12Z",
"created_at": "2026-11-04T09:14:12Z",
"extracted_at": "2026-11-04T09:14:15Z",
"completed_at": "2026-11-04T09:14:15Z",
"failure": null,
"result": {
"document_type": "invoice",
"direction": "purchase",
"invoice_number": "TEST-2026-0001",
"issue_date": "2026-11-02",
"due_date": "2026-12-02",
"currency": "EUR",
"order_reference": "PO-TEST-1",
"customer_reference": null,
"supplier": {
"name": "Testleverancier B.V.",
"vat_number": "NL000000000B02",
"registration_number": "00000002",
"iban": "NL91ABNA0417164300",
"bic": null,
"email": "[email protected]",
"phone": null,
"website": null,
"address": { "street": "Leveranciersweg", "house_number": "2", "postal_code": "1000 AB", "city": "Amsterdam", "country_code": "NL" }
},
"customer": { "name": "Testafnemer B.V.", "vat_number": "NL000000000B01", "matches_own_party": false },
"lines": [
{ "description": "Advieswerk oktober", "quantity": "1", "unit_price": "100.00", "vat_rate": "21.00", "amount_excl_vat": "100.00", "verified": false },
{ "description": "Vakboeken", "quantity": "2", "unit_price": "25.00", "vat_rate": "9.00", "amount_excl_vat": "50.00", "verified": false }
],
"vat_breakdown": [
{ "rate": "21.00", "taxable_amount": "100.00", "vat_amount": "21.00" },
{ "rate": "9.00", "taxable_amount": "50.00", "vat_amount": "4.50" }
],
"totals": { "amount_excl_vat": "150.00", "vat_amount": "25.50", "amount_incl_vat": "175.50" }
},
"confidence": {
"document": 0.95,
"fields": { "invoice_number": 0.99, "issue_date": 0.99, "supplier.name": 0.98, "supplier.vat_number": 0.99, "totals.amount_incl_vat": 0.99, "totals.vat_amount": null }
},
"warnings": [],
"verification": null,
"rejection": null,
"ubl": { "available": true, "href": "/v1/documents/doc_01jbx6y4q9t2m8rz1v3k5n7p0a/ubl", "valid": true, "profile": "peppol-bis-billing-3.0", "errors": [] },
"charge": { "operation": "ocr.extract", "status": "none", "amount": "0.0000", "currency": "EUR", "live_amount": "0.0350" },
"content_available": true,
"content_purged_at": null,
"retention_until": "2026-11-05T09:14:12Z"
}
Field What it is
status, processing, environment See statuses. environment is live or test.
file name, mime_type (from the content), size_bytes, pages and sha256.
duplicate_of Only set in the answer to a duplicate POST.
expected_ready_at automatic: the time of sending plus two minutes. verified: the deadline.
created_at, extracted_at, completed_at When the document was sent, read and completed.
failure Only with failed: the code.
result What was read; see below. null until the document is read, with verified until a processor approved it, and after the retention period.
confidence See confidence.
warnings See warnings.
verification Only verified: whether a processor checked the document (verified, verified_at), and which fields they did not check (unverified_fields).
rejection Only with rejected: the reason and a message.
ubl available: whether the document is completed and its content still there; href: the download; valid and errors: the outcome of the validation, once the UBL was made; profile. See the UBL.
charge See what it costs.
content_available, content_purged_at, retention_until See retention.

Amounts are decimal strings in the currency of the document, with two decimals ("45.00"); rates too ("21.00"). Any value can be null when it was not found.

Field What it is
document_type What the document was read as: invoice, proforma, receipt, credit_note, quote, order, packing_slip, contract, bank_statement, reminder or other.
direction purchase or sales, as you sent it.
invoice_number, issue_date, due_date Dates in ISO 8601 (2026-11-02).
currency ISO 4217.
order_reference, customer_reference The order number and the customer number on the document.
supplier name, vat_number, registration_number, iban, bic, email, phone, website, and the address (street, house_number, postal_code, city, country_code).
customer name, vat_number, and matches_own_party: whether the customer on the document is the own_party you sent.
lines description, quantity, unit_price, vat_rate, amount_excl_vat, and verified, which is always false: no processor checks the lines.
vat_breakdown Per VAT rate: rate, taxable_amount and vat_amount.
totals amount_excl_vat, vat_amount and amount_incl_vat.

The field names are also the paths you use when you report a wrong result.

confidence.fields has, per path of the result, how sure the OCR was of the text that value came from, from 0 to 1. A value that was calculated or normalised rather than read from one place has null. confidence.document is a rough score for the whole document: it mainly says whether the amounts add up and the main fields were found on the page.

Neither number is calibrated: 0.95 does not mean that 95 out of 100 such values are right. Use them to sort, for example to pick the documents a person looks at first, not as a probability. With verified, confidence is that of the OCR, before the processor checked it.

Warning What it means
totals_do_not_reconcile The subtotal plus the VAT is not the total, the lines do not add up to the subtotal, or a VAT amount does not fit its rate and base.
vat_breakdown_derived A taxable amount per rate was not on the document, and was calculated from its VAT amount.
roles_swapped Supplier and customer look swapped: the document labels them the other way round, or on a sales document your own_party came out as the customer.
multiple_documents_suspected The file seems to hold more than one invoice. A suspicion only: this cannot be detected reliably.
text_truncated The document has more lines of text than were read. Check the lines and the totals.
extraction_failed Only verified: the OCR failed, so the processor filled the document in by hand.

GET /v1/documents/{id}?include=boxes adds boxes: where the values stand on the page, for example to draw them over the PDF.

{
"boxes": {
"header": [
{ "key": "invoice_number", "value": "2026-000481", "confidence": 0.991, "page": 0, "vertices": [[0.61, 0.12], [0.74, 0.12], [0.74, 0.14], [0.61, 0.14]] }
],
"lines": [
{ "key": "title", "line_index": 0, "value": "Mobiele telefonie oktober", "confidence": 0.987, "page": 0, "vertices": [[0.08, 0.41], [0.36, 0.41], [0.36, 0.43], [0.08, 0.43]] }
]
}
}
  • page starts at 0. vertices are the four corners, as [x, y] from 0 to 1 of the width and height of the page, from the top left.
  • key names the value as the OCR found it: in header keys such as invoice_number, document_date and total_amount, and keys starting with supplier_, customer_, tax_ and payment_; in lines keys such as title, quantity, amount_each and vat_percentage, with the position of the line in line_index. These keys are not the paths of result. A value can have more than one box, when it is on the page more than once.
  • boxes is empty for a test document and after the retention period.
Terminal window
curl "https://api.accountcloud.io/v1/documents?status=completed&limit=50" \
-H "Authorization: Bearer ac_test_…"
{
"object": "list",
"data": [
{
"id": "doc_01jbx6y4q9t2m8rz1v3k5n7p0a",
"object": "document",
"status": "completed",
"processing": "automatic",
"reference": "inbox-48213",
"metadata": { "administration": "8842" },
"file": { "name": "invoice-2026-118.pdf", "mime_type": "application/pdf", "size_bytes": 182311, "pages": 2, "sha256": "9f2c4e7a…" },
"failure": null,
"rejection": null,
"charge": { "operation": "ocr.extract", "status": "none", "amount": "0.0000", "currency": "EUR", "live_amount": "0.0350" },
"expected_ready_at": "2026-11-04T09:16:12Z",
"created_at": "2026-11-04T09:14:12Z",
"completed_at": "2026-11-04T09:14:15Z",
"content_available": true
}
],
"has_more": false,
"next_cursor": null
}

Newest first, without the result. Pass next_cursor as cursor for the next page. With an AccountCloud-Entity header, only that entity’s documents.

Parameter What it does
status Only documents with this status.
processing automatic or verified.
reference Only documents with this reference.
created_after, created_before ISO 8601. From and including created_after, up to created_before.
limit 1 to 100; 25 by default.
cursor The next_cursor of the previous page.
Terminal window
curl https://api.accountcloud.io/v1/documents/doc_01jbx6y4q9t2m8rz1v3k5n7p0a/ubl \
-H "Authorization: Bearer ac_test_…" \
-o doc_01jbx6y4q9t2m8rz1v3k5n7p0a.xml

The answer is UBL 2.1 according to Peppol BIS Billing 3.0, as application/xml. Every UBL we make is validated; the outcome is on the document, in ubl.valid and ubl.errors. A UBL that does not pass can still be downloaded.

  • Before the document is completed: 409 document_not_ready. The same code when no UBL could be made from the result at all; ubl.errors on the document then says why.
  • A rejected document has no UBL: 409 document_rejected.
  • After the retention period: 410 content_purged.

With automatic, the UBL is made from the result: when the document completes if you asked for ubl in output, otherwise on the first download.

  • On a purchase document the supplier is the seller and your own_party the buyer; on a sales document your own_party is the seller and the customer the buyer. Without own_party the buyer only has the name and VAT number on the document, so the UBL usually does not pass the validation. Send own_party with its address when you need a valid UBL.
  • The lines of the result go into the UBL when they add up, per VAT rate and in total, to the VAT breakdown. Otherwise the UBL has one line per VAT rate.
  • A 0% rate becomes “zero rated”, or “reverse charge” when the document says so. Intra-community supplies and exports are not assumed from the text of a document.
  • A credit note becomes a UBL CreditNote, with positive amounts.
  • The rates, amounts and totals are those of the result; nothing is recalculated, except where a breakdown is missing.

With verified, the UBL is made from the values the processor checked, with one line per VAT rate, and the processor can only approve it when it passes the validation.

POST /v1/ubl/validate checks a UBL 2.1 Invoice or CreditNote of yours against the UBL schema, EN 16931 and Peppol BIS Billing 3.0. Send it as JSON with base64, or as multipart, at most 10 MB:

Terminal window
curl https://api.accountcloud.io/v1/ubl/validate \
-H "Authorization: Bearer ac_test_…" \
{
"valid": false,
"profile": "peppol-bis-billing-3.0",
"complete": true,
"engine": "saxon",
"engine_error": null,
"artifacts": { "ubl": "2.1", "peppol_bis": "3.0.20", "cen_schematron": "1.3.15" },
"errors": [
{
"rule": "BR-CO-15",
"level": "fatal",
"location": "/Invoice",
"message": "Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110)."
}
]
}
  • An invalid file is still a 200, with valid: false. valid is true when no finding is fatal.
  • errors lists every finding, fatal and warning: the rule (such as BR-CO-15 or PEPPOL-EN16931-R001; UBL-XSD for the schema; UBL-XML for a file that is not well-formed XML, contains a DOCTYPE, or is not an Invoice or CreditNote), its level, the location as an XPath, and the text of the rule.
  • complete is true when the full official rule sets ran (engine: saxon). When it is false, only the schema and our own subset of the business rules ran (engine: php-subset), and valid means “no rule we check is broken”, not “passes the official validator”.
  • artifacts has the versions of UBL, Peppol BIS Billing and the EN 16931 schematron we validate against.
  • When the validator itself is down, the answer is 503 service_unavailable.

As JSON, send {"file": {"content_base64": "…"}}; a name is optional.

With processing: verified, a processor of our team checks and corrects what the OCR read, next to the document. They can only approve it when all of this holds:

  • the VAT breakdown adds up to the total, to the cent, and there is at least one VAT line;
  • the supplier has a name, an IBAN passes its check digits, and a VAT number has the format of its country (it is not checked at VIES);
  • there is an invoice number and a valid invoice date, and the due date is not before it;
  • the currency is an ISO 4217 code, and one of your accepted_currencies if you sent them;
  • the UBL made from it passes the UBL schema, EN 16931 and Peppol BIS Billing 3.0.

The result of an approved document has the values the processor checked: the supplier, the header, the VAT breakdown and the totals. Not checked:

  • the lines. verification.unverified_fields is ["lines"], every line keeps verified: false, and the UBL has one line per VAT rate instead of the lines of the invoice;
  • the customer, which stays as the OCR read it. In the UBL of a purchase document, the buyer is your own_party, completed with what the OCR read.

expected_ready_at is the deadline: 24 working hours after you sent the document. Working hours run on Monday to Friday, Amsterdam time, and not on holidays: a document sent on Friday at 15:00 is due on Monday at 15:00, one sent on Saturday on Tuesday at 00:00.

An account has a daily maximum of verified documents, 100 unless agreed otherwise. Above it, a document gets the deadline of the next working day, and so on per block of that size, so expected_ready_at shows the real expectation. At five times the maximum on one working day, a new verified document gets 429 verified_daily_limit, with daily_max.

A processor can reject a document, with one of these reasons in rejection.reason, and their note or a standard text in rejection.message:

Reason What it means
not_readable The document could not be read.
not_an_invoice The document is not an invoice or a receipt.
multiple_documents The file holds more than one document.
invoice_requirements The document does not meet the requirements of an invoice.
duplicate This invoice was processed before: the same number from the same supplier.
currency_not_accepted The currency is not one of your accepted_currencies. Only when you sent them.

A rejected document has no UBL and no result. What a rejection costs is under what it costs.

We keep the content of a document (the file, the result and the UBL) for a limited time. retention_until says until when.

  • Live documents: until the retention period of your account after the outcome (completed, failed or rejected). The period is 7 days unless an owner or admin sets 0 to 30 days in the portal. A change applies to documents that get their outcome after it. At 0 days the content is deleted right after the outcome, so there is hardly time to fetch the result.
  • Test documents: 24 hours after you sent them.

After that the document is still there, with content_available: false, content_purged_at, and no result, confidence or file name. The UBL then answers 410 content_purged. The status, the times, the charge, your reference and metadata, and the file’s type, size, pages and SHA-256 stay.

Delete a document to remove its content at once:

Terminal window
curl -X DELETE https://api.accountcloud.io/v1/documents/doc_01jbx6y4q9t2m8rz1v3k5n7p0a \
-H "Authorization: Bearer ac_test_…"
{ "id": "doc_01jbx6y4q9t2m8rz1v3k5n7p0a", "object": "document", "deleted": true }

After that, every call on the document answers 404 document_not_found. We keep its metadata (hash, pages, cost, times and outcome) for our statistics. A verified document that is not done yet is taken off the processors’ queue; it costs the OCR price when it was read already, otherwise nothing.

A live document is charged once, at its outcome:

Outcome automatic verified
Refused at intake (4xx), or a duplicate Free Free
completed The OCR price The verified price
failed Free —
rejected as a duplicate — Free
rejected for another reason, after the OCR read it — The OCR price
rejected, and the OCR could not read it — Free
Deleted before the outcome Free The OCR price when the OCR read it, otherwise free

The start prices are €0.0350 for the OCR (ocr.extract) and €0.3500 for verified (ubl.verified), excluding VAT. The current ones are in GET /v1/pricing. A document pays the price that applied when you sent it.

  • When you send a live document, its full price is reserved on your credit. When the credit does not cover it, the answer is 402 insufficient_credits.
  • The 202 itself has AccountCloud-Charged: 0.0000: the price is taken at the outcome, and you find it in charge on the document and in its webhook.
  • charge.status is reserved while the document is processed, captured once it is charged, and released when its outcome costs nothing (operation is then null). A test document has none, an amount of 0.0000, and in live_amount what it would cost live.

When the OCR read a document wrong, tell us which fields:

Terminal window
curl https://api.accountcloud.io/v1/documents/doc_01jbx6y4q9t2m8rz1v3k5n7p0a/feedback \
-H "Authorization: Bearer ac_test_…" \
-H "Content-Type: application/json" \
-d '{
"result": "incorrect",
"fields": [
{ "field": "totals.vat_amount", "expected": "21.00" },
{ "field": "supplier.vat_number", "expected": "NL000000000B02" }
],
"comment": "The VAT amount was taken from the wrong line.",
"allow_training": true
}'
{
"id": "fbk_01jbx6y4q9t2m8rz1v3k5n7p0b",
"object": "feedback",
"document_id": "doc_01jbx6y4q9t2m8rz1v3k5n7p0a",
"training": "accepted"
}
Field What it is
allow_training Required. true lets us add the document to the set we test and improve the OCR with. false keeps only the report, without the document.
fields Up to 50 fields that were wrong. field is a path of the result, such as totals.vat_amount, supplier.address.city, or a line or VAT rate by its index: lines.1.amount_excl_vat, vat_breakdown.0.rate. expected is the value you expected, at most 500 characters. Another path gives 422 unknown_field.
comment At most 2,000 characters.
result Optional. Always incorrect.
file Only needed after the retention period: the same file again, as content_base64.

The answer is 202, and training says what we did:

  • accepted: the document joins the training set. We keep a copy of the file, what the OCR read and your report, for at most 12 months.
  • stats_only: we keep only the report. That is the answer with allow_training: false, for every test document, and when your account has switched off sharing for training.

In the portal, an owner or admin can switch off sharing documents for training for the whole account: a report with allow_training: true is then kept as a report only, and the answer says stats_only. There they can also ask to delete every training case of the account.

After the retention period the original is gone, so a report needs the file again, in file.content_base64. Its SHA-256 must be the file.sha256 of the document, or the answer is 422 file_hash_mismatch. Without the file it is 409 feedback_window_closed. A deleted document cannot be reported on.

Reporting is free. An account can send 100 reports per UTC day unless agreed otherwise; after that it gets 429 feedback_limit_reached until 00:00 UTC. A report does not give back the price of the document by itself.

With a test key, nothing is read and nothing is charged. A test document goes through the same checks at intake, statuses and webhooks as a live one, and gets a fixed result that depends on its file name: receipt.*, unbalanced.pdf, slow.pdf and fail.pdf for automatic, reject.pdf and duplicate.pdf for verified. A verified test document is approved or rejected after a minute, without a processor. The full list is in test mode.