Document types
Define the fields you want read, the checks they must pass, and publish versions.
On this page
A document type says what you want read off a document and how sure ReceViz has to be. It has a key (such as payment_receipt), a name, and a definition: the fields, the rules the fields must satisfy together, and a few settings. Extraction always runs against a published version of it, never against a draft.
Start from a template#
Three built-in templates carry ReceViz's tuned rules for receipts and invoices. Create a document type from one in the console, or with template in POST /v1/document-types, then rename, drop, add or tighten fields as your own.
| Template | Reads | Checks |
|---|---|---|
payment_receiptPayment Terminal Receipt | amount, currency, transaction_date, transaction_time, receipt_number, rrn, auth_code, terminal_id, payment_method, card_scheme, card_last_four, approved, merchant, merchant_address | Field patterns; the date not in the future and at most 400 days old |
retail_receiptRetail Receipt | merchant, merchant_address, merchant_phone, merchant_category, date, time, receipt_number, subtotal, tax, total, currency, trn, payment_method, card_scheme, card_last_four, and items (sku, description, type, quantity, unit_price, amount) | subtotal + tax = total; the date not in the future and at most 730 days old |
invoiceInvoice | invoice_number, invoice_date, due_date, supplier_name, supplier_trn, customer_name, subtotal, tax, total, currency | subtotal + tax = total; the due date on or after the invoice date |
merchant_category is inferred from the merchant's name and what was bought, not read: its source is inferred. GET /v1/document-types/{key} returns any document type's exact definition.
Fields#
A field is an object in fields. Only key is required; everything else has a default.
keystringrequiredThe machine name, and the field's name in
data: a lowercase letter, then up to 63 of a-z, 0-9 and _. Unique among its siblings.labelstringWhat a person sees. Defaults to the key, humanized.
typestringstring(the default),integer,decimal,currency,date,datetime,boolean,enum,arrayorobject. Money and decimals come back as JSON numbers, dates asYYYY-MM-DD, date-times asYYYY-MM-DDTHH:MM:SS.requiredbooleanA required field that is not found makes the result need review (
missing_required). The value is stillnull, never invented.descriptionstringUp to 500 characters. It appears in the JSON Schema of the document type, and the verifier reads it.
aliasesarray of stringsLabels the value may be printed under, such as
TOTAL,GRAND TOTAL,AMOUNT DUE. Up to 25, each up to 60 characters.exclude_aliasesarray of stringsLabels that mean something else and must not be taken for this field, such as
SUBTOTALfor a total.semanticstringRead the field with ReceViz's tuned rules for that kind of fact (see below). The semantic must fit the field's type.
positionstringany(the default),toporbottom: a hint that nudges rows in that part of the page.validationobjectChecks the value must pass. See Validation.
enumarrayFor
enumfields, the values it may take: plain strings, or{"value", "label", "aliases"}where the aliases are what may be printed (VISA,MASTERCARDforcard). Up to 60 values.confidence_thresholdnumber, 0 to 1Below it, the field's status is
needs_review. The default is 0.7. See Confidence and verification.verificationstringWhen VERIFIED mode re-reads the field:
never,when_uncertain(the default) oralways.fallbackstringWhat happens when no value is found.
null(the default) leaves it null.defaultusesdefault, with statusdefaultand confidence 0.verifiermarks the fieldverification_recommendedoutside VERIFIED mode.errormarks itfield_unreadable, which makes the result need review even when the field is optional.defaultanyThe value for
fallback: "default". It must fit the field's type.fieldsarrayFor
objectfields: the sub-fields. Rules reach them with dots,totals.tax.items, max_itemsobject, integerFor
arrayfields: the definition of one item (an object with its own fields, and no key), and the most items to return, 1 to 500 (default 100). An array is read as a table of line items: give it theline_itemssemantic, or item fields for a description and an amount (by theline_item_descriptionandline_item_amountsemantics, or keys such asdescriptionandamount). Other lists are not read, and a required one carries the issuearray_needs_verification. Arrays of arrays are not supported.
A definition holds at most 120 fields counting nested ones, and objects and arrays nest at most three levels deep.
How a field is found#
For each field, in order, ReceViz:
- gathers candidates: the tuned rule for its
semantic, if it has one; matches on its aliases; and, for a field with no semantic, matches on its own label and key, values that could only be its type, and tokens that fully match its validation pattern; - merges candidates that agree on the value, so their evidence adds up;
- chooses the best value that can become the field's type; the others are kept as alternatives, and the runner-up sets how ambiguous the field is;
- records the value, the printed text, its position, its source and its confidence.
A label is found the way a person finds it: the value is what follows the label on the same printed row, or, when the row holds only the label, the start of the row under it. An array of line items is read as a table.
Semantics#
| Semantic | The fact it reads | Field types |
|---|---|---|
total_amount | Total amount | currency, decimal |
subtotal | Subtotal, the amount before tax | currency, decimal |
tax_amount | Tax or VAT amount | currency, decimal |
currency | Currency code | string, enum |
document_date | Document or transaction date | date, datetime, string |
document_time | Transaction time | string |
document_number | Receipt or invoice number | string |
trace_number | Trace number (STAN) | string |
rrn | Retrieval reference number | string |
auth_code | Authorisation code | string |
terminal_id | Terminal ID | string |
merchant_id | Merchant ID | string |
batch_number | Batch number | string |
card_scheme | Card scheme | string, enum |
card_last_four | Last four digits of the card | string |
payment_method | How it was paid | enum, string |
approved | Approved or declined | boolean |
merchant_name | Merchant or supplier name | string |
merchant_address | Merchant or supplier address | string |
merchant_phone | Merchant or supplier phone | string |
merchant_category | Kind of business, inferred rather than read | string, enum |
tax_registration_number | Tax registration number (TRN, VAT number) | string |
email | Email address | string |
phone | Phone number | string |
due_date | Due date | date, string |
line_items | Line items | array |
line_item_description | Line item description | string |
line_item_quantity | Line item quantity | decimal, integer |
line_item_unit_price | Line item unit price | currency, decimal |
line_item_amount | Line item amount | currency, decimal |
line_item_sku | Line item SKU or product code | string |
line_item_type | Line item type: product, fee or discount | enum, string |
Validation#
validation checks a value once it is read, in STANDARD and VERIFIED modes. A value that fails a check keeps its value, gets the status invalid with an issue saying why, and its confidence is capped at 0.35.
patternregular expressionThe value must match it in full (
pattern_mismatch). Up to 200 characters; nested quantifiers such as(a+)+are refused because they can take forever to match. On a number or a date, the pattern is checked against the value's canonical text, such as150.00or2026-09-26.min, maxnumberFor numbers and money (
below_minimum,above_maximum).min_length, max_lengthintegerFor strings (
too_short,too_long).not_futurebooleanFor dates: not after the reference date (
date_in_future). The reference date is today (UTC), oroptions.reference_date.max_age_daysintegerFor dates: at most this many days before the reference date (
date_too_old).
Rules#
rules check fields against each other, in STANDARD and VERIFIED modes. A failed rule never changes a value: it adds a rule_failed issue to the fields involved, sets them to needs_review, and appears in validation.rules with its message. Each rule may carry a message of your own. Up to 30 rules.
sum{"type": "sum", "fields": ["subtotal", "tax"], "equals": "total", "tolerance": 0.02}. All numeric fields; at least two parts.allow_extras(default true) skips the rule when the document shows other charges or discounts that explain a gap.compare{"type": "compare", "left": "due_date", "op": ">=", "right": "invoice_date"}. Two numeric or two date fields;opis one of< <= == >= > !=.required_one_of{"type": "required_one_of", "fields": ["receipt_number", "rrn"]}. At least one of two or more fields must be found.tax_rate{"type": "tax_rate", "base": "subtotal", "tax": "tax", "rates": [5], "tolerance": 0.02}. The tax must be one of the rates (percent, above 0 and at most 50) of the base.
A rule is skipped when a value it needs is missing. Paths use dots for nested fields. Whenever a definition reads a total, ReceViz also checks the document's own arithmetic (base plus tax equals the total) without being asked; see the checks that run first.
Settings#
date_orderstringDMY(the default) orMDY: how to read a date such as 03/04/2026. A request can override it withoptions.date_order.default_currencystringA three-letter currency code to assume when a document prints none: a field with the
currencysemantic that was not found takes it, marked as a default rather than read (statusdefault, confidence 0). The application's setting and a request'soptions.default_currencycome first.capture.nounstringTop level, beside
settings: what the SDK scanners call this document in their guidance (“Point at an invoice”), up to 40 letters. Leave it out and ReceViz works it out from the key, then the name:payment_receiptis a receipt,bill_of_ladinga bill of lading,emirates_idan ID card, anything unknown a document.GET /v1/document-typesreturns the noun in use ascapture.noun.
A full example#
An invoice type with nested objects, line items, an enum with a default, and four rules. It passes the definition validator as it stands.
{
"fields": [
{
"key": "invoice_number",
"label": "Invoice number",
"type": "string",
"required": true,
"semantic": "document_number",
"aliases": [
"INVOICE NO",
"INV #",
"BILL NO"
],
"validation": {
"pattern": "^[A-Z0-9-]{3,20}$"
},
"confidence_threshold": 0.85
},
{
"key": "invoice_date",
"label": "Invoice date",
"type": "date",
"required": true,
"semantic": "document_date",
"validation": {
"not_future": true,
"max_age_days": 365
}
},
{
"key": "due_date",
"label": "Due date",
"type": "date",
"semantic": "due_date"
},
{
"key": "supplier",
"label": "Supplier",
"type": "object",
"required": true,
"fields": [
{
"key": "name",
"label": "Name",
"type": "string",
"semantic": "merchant_name"
},
{
"key": "trn",
"label": "TRN",
"type": "string",
"semantic": "tax_registration_number",
"validation": {
"pattern": "^[0-9]{15}$"
}
}
]
},
{
"key": "po_number",
"label": "Purchase order",
"type": "string",
"aliases": [
"PO NO",
"P.O. NUMBER"
],
"verification": "never"
},
{
"key": "payment_terms",
"label": "Payment terms",
"type": "enum",
"enum": [
{
"value": "net_30",
"label": "Net 30",
"aliases": [
"NET 30",
"30 DAYS"
]
},
{
"value": "on_receipt",
"label": "Due on receipt",
"aliases": [
"DUE ON RECEIPT",
"IMMEDIATE"
]
}
],
"fallback": "default",
"default": "net_30"
},
{
"key": "totals",
"label": "Totals",
"type": "object",
"fields": [
{
"key": "subtotal",
"label": "Subtotal",
"type": "currency",
"semantic": "subtotal"
},
{
"key": "tax",
"label": "VAT",
"type": "currency",
"semantic": "tax_amount",
"exclude_aliases": [
"TAX INVOICE"
]
},
{
"key": "total",
"label": "Total",
"type": "currency",
"semantic": "total_amount",
"validation": {
"min": 0
},
"verification": "always"
}
]
},
{
"key": "lines",
"label": "Line items",
"type": "array",
"semantic": "line_items",
"max_items": 200,
"items": {
"type": "object",
"fields": [
{
"key": "description",
"type": "string",
"semantic": "line_item_description"
},
{
"key": "quantity",
"type": "decimal",
"semantic": "line_item_quantity"
},
{
"key": "amount",
"type": "currency",
"semantic": "line_item_amount"
}
]
}
}
],
"rules": [
{
"type": "sum",
"fields": [
"totals.subtotal",
"totals.tax"
],
"equals": "totals.total",
"tolerance": 0.02
},
{
"type": "tax_rate",
"base": "totals.subtotal",
"tax": "totals.tax",
"rates": [
5
]
},
{
"type": "compare",
"left": "due_date",
"op": ">=",
"right": "invoice_date",
"message": "The due date is before the invoice date."
},
{
"type": "required_one_of",
"fields": [
"invoice_number",
"po_number"
]
}
],
"settings": {
"date_order": "DMY"
}
}The validator is strict about structure and forgiving about wording: it returns every problem at once, each with a path, so an invalid definition answers 400 invalid_definition with details.problems such as {"path": "fields[2].type", "message": "…"}.
The shape of data#
Every published version carries the JSON Schema of data for that definition: what your code reads. Every field is present, and null when it was not found. This is the schema of the payment_receipt template:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"amount": {
"type": [
"number",
"null"
]
},
"currency": {
"type": [
"string",
"null"
]
},
"transaction_date": {
"type": [
"string",
"null"
],
"format": "date"
},
"transaction_time": {
"type": [
"string",
"null"
]
},
"receipt_number": {
"type": [
"string",
"null"
]
},
"rrn": {
"type": [
"string",
"null"
]
},
"auth_code": {
"type": [
"string",
"null"
]
},
"terminal_id": {
"type": [
"string",
"null"
]
},
"payment_method": {
"type": [
"string",
"null"
],
"enum": [
"card",
"cash",
"bank_transfer",
"cheque",
"wallet",
"other",
null
]
},
"card_scheme": {
"type": [
"string",
"null"
],
"enum": [
"visa",
"mastercard",
"amex",
"maestro",
"discover",
"diners",
"jcb",
"unionpay",
"mada",
"rupay",
null
]
},
"card_last_four": {
"type": [
"string",
"null"
]
},
"approved": {
"type": [
"boolean",
"null"
]
},
"merchant": {
"type": [
"string",
"null"
]
},
"merchant_address": {
"type": [
"string",
"null"
]
}
},
"required": [
"amount",
"currency",
"transaction_date",
"transaction_time",
"receipt_number",
"rrn",
"auth_code",
"terminal_id",
"payment_method",
"card_scheme",
"card_last_four",
"approved",
"merchant",
"merchant_address"
],
"additionalProperties": false
}Read it from GET /v1/document-types/{key} (schema.json_schema), GET /v1/document-types/{key}/versions/{version} or GET /v1/schemas/{schema_id}. All three need schemas.read.
Drafts, versions and breaking changes#
Editing a document type writes its draft. Publishing validates the draft, compares it with the latest version, and adds a new version that never changes afterwards. Nothing you save is published until someone publishes it; in the console, drafts can be edited by developers and published by admins and owners.
| Breaking | Not breaking |
|---|---|
| A field removed | An optional field added |
| A field's type changed | A required field made optional |
| An optional field made required, or a new required field | Labels, descriptions, aliases, position or semantic changed |
| An enum value removed | An enum value added |
| An array's item type changed | Validation, thresholds, verification, fallback or default changed |
| Rules or settings changed |
A new required field is breaking on purpose: documents that used to come back complete would start to need review. Publishing a breaking draft has to be confirmed deliberately; without that, it answers 409 breaking_change with the breaking changes in details.changes. A draft that is the same as the latest version answers 409 nothing_changed.
Which version a request uses#
An extraction uses, in this order:
- the
schema_versionsent with the request; - the version the application is pinned to for that document type;
- the latest published version.
Pin a version per document type on an application in the console to keep an integration on a schema while you publish new ones. Every result says which it used, as schema_version and schema_id.
Document types and applications#
In the console, an application can be given the list of document types it may use, one of them as its default. Then:
- a request for any other type answers
403 document_type_not_assigned; - a request without
document_typeuses the default, or the only assigned type; otherwise it answers400 document_type_required.
An application with no list may use every active document type of the organization, and every request must name one. Using one of your own document types, rather than one created from a template, needs the schemas.execute capability. Archiving a document type stops it being used for new extractions.
Describe a document in words#
In the console you can describe a document in a sentence or two, for example: “Extract the receipt number, transaction date, total amount and last four digits of the card. Receipt number may appear as RRN, REF NO, Receipt No or Reference.” ReceViz answers with a proposed definition, built by a language model when your organization's data policy allows one, or otherwise from a vocabulary of the facts documents carry. Either way the proposal is run through the definition validator, and says which builder made it.
A proposal, never a change
Nothing is saved or published by describing a document. You review the proposal, edit it, save it as a draft, and publish it as a separate, deliberate step. Your description is treated only as a description; the builder sees no documents.
An organization can ask for up to 30 proposals an hour.
Create a document type through the API#
POST /v1/document-types creates one from your server. It needs a secret key with schemas.write (an opt-in capability), and a custom definition also needs schemas.execute.
keystringrequired2 to 64 characters of a-z, 0-9 and _, starting with a letter.
409 key_takenif the organization already has it.namestringUp to 120 characters.
descriptionstringUp to 1000 characters.
templatestringpayment_receipt,retail_receiptorinvoice: start from a built-in.definitionobjectYour own definition, as above.
publishbooleanPublish version 1 at once. The default is true.
RECEVIZ_API="https://accounthouse-backend-793493499887.me-central1.run.app/api/receviz/v1"
# From a built-in template, published as version 1.
curl "$RECEVIZ_API/document-types" \
-H "Authorization: Bearer $RECEVIZ_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"key": "shop_receipt", "name": "Shop receipt", "template": "retail_receipt"}'The answer is 201 with the document type and its current schema. If the calling application has a list of document types, the new one is added to it. An organization has at most 50 active document types unless ReceViz set another limit.