Skip to content

Field types

Each entry in a schema’s fields list describes one value to extract. Every field needs a name and a type. The other keys are optional, apart from a few that some types require, and they narrow down what counts as a valid value:

{ "name": "rent", "type": "money", "currency": "EUR", "scale": 2, "description": "Monthly rent excluding service costs" }
type Example value in the response Required keys Optional type keys
string "INV-004211" prefix, pattern
bool true
int 12 range
decimal "1250.50" scale range, evidence_role
money { "amount": "1250.00", "currency": "EUR" } scale currency, range, evidence_role
date "2027-03-01" range
datetime "2027-03-01T09:30:00+01:00" range
duration "P12M" basis, range
enum "terminated" values

Decimals and money amounts come back as strings, because a JSON number would be read as floating point by most parsers and could lose precision. Integers are 64-bit.

Key Applies to Type Default
name all string required
type all string required
description all string none
nullable all boolean false
array all boolean false
applicable_when all expression always applicable
locales all string[] defaults.locales
scale decimal, money integer 0 to 28 required
currency money ISO 4217 code defaults.currency, else "EUR"
evidence_role decimal, money string none
basis duration string "calendar"
values enum string[] required
range int, decimal, money, date, datetime, duration object none
prefix string string none
pattern string regex none

Any other key is rejected with E-SCHEMA-UNKNOWN-KEY.

Starts with an ASCII letter or _, followed by letters, digits or _. Case-sensitive and unique within the schema. Reserved: and, or, not, when, round, is_null, is_present, true, false, null, date, dur.

{ "name": "startDate", "type": "date" }

One of the types above.

A hint for the model about which value you mean. It matters most when a document contains several values of the same type, such as an invoice with a subtotal, a VAT amount and a total.

{ "name": "total", "type": "money", "scale": 2, "description": "Amount payable including VAT" }

Set this when a document may say outright that there is no value (“N/A”, “none”). Without it, Tickmarks can’t tell “the lease says there’s no deposit” from “the lease doesn’t mention a deposit”: both give status no_evidence. With it, the first case gets explicit_null.

{ "name": "deposit", "type": "money", "scale": 2, "nullable": true }

A document reading “Deposit: none” gives deposit status explicit_null.

Extract every occurrence as a list instead of one value, such as all the tenants named on a lease. Array fields can’t appear in relations or in applicable_when (E-ARRAY-RELATION).

{ "name": "tenants", "type": "string", "array": true }

Response: "tenants": ["Alice Johnson", "Bram de Vries"] in data, and per-element evidence in fields.

The field only applies when the condition holds. When it’s false, the field gets status not_applicable and value null, so a lease that isn’t terminated doesn’t report a missing termination date. The condition is an expression that may not mention the field itself, use when, or form a cycle with other conditions.

{ "name": "status", "type": "enum", "values": ["active", "terminated"] },
{ "name": "terminationDate", "type": "date", "applicable_when": "status == 'terminated'" }

Overrides defaults.locales for this field. Use it when one field is written differently from the rest, such as an American-style date in a Dutch document.

{ "name": "signedOn", "type": "date", "locales": ["en-US"] }

Required for decimal and money. The maximum number of digits after the decimal point, 0 to 28. Trailing zeros don’t count: with scale 1, 1.50 fits and 1.55 doesn’t. Tickmarks drops a value that doesn’t fit rather than rounding it, because silently rounding a figure from a document would change it. If you want rounding, write it in a relation with round.

{ "name": "vatRate", "type": "decimal", "scale": 2 }

The field’s currency. An amount with no currency marker in the text is read in this currency, and an amount with a different marker ($ on a euro field) is dropped. Tickmarks never converts between currencies.

{ "name": "fee", "type": "money", "currency": "USD", "scale": 2 }

Which printed amount on the document the field is. Tickmarks uses it to avoid mistakes such as deriving a total from an amount that is only a subtotal. One of: subtotal, taxable_subtotal, non_taxable_subtotal, tax_amount, total, surcharge, discount_amount, withholding_amount.

{ "name": "vat", "type": "money", "scale": 2, "evidence_role": "tax_amount" }

How a duration is measured. A month is 28 to 31 days, so “1 month” and “30 days” are different kinds of length, and durations of different bases can’t be combined or compared.

basis Holds Typical use
calendar months and days Contract terms: “1 year and 2 weeks”
months whole months Terms always given in months
days whole days Notice periods: “30 days”
exact an exact length of time Time spans between datetime fields: “2 hours”
{ "name": "noticePeriod", "type": "duration", "basis": "days" }

See calendar and money rules for how each basis measures the distance between two dates.

Required for enum. The allowed labels, each a distinct, non-empty string. Relations compare an enum with a label: status == 'terminated'.

{ "name": "status", "type": "enum", "values": ["active", "terminated", "draft"] }

Inclusive bounds: give min, max or both. Integer bounds are JSON numbers, and every other type uses a string in the field’s format. Candidates outside the range are dropped, and if none remain the field’s status is normalization_failed.

{ "name": "units", "type": "int", "range": { "min": 1, "max": 10 } }
{ "name": "rent", "type": "money", "scale": 2, "range": { "min": "0" } }
{ "name": "signedOn", "type": "date", "range": { "min": "2000-01-01" } }
{ "name": "noticePeriod", "type": "duration", "basis": "days", "range": { "max": "P90D" } }

A duration bound must fit the field’s basis: "P1M" is not a valid bound for a days field. min greater than max is E-RANGE.

Restrict a string to a known format, such as an invoice number. prefix is a literal, case-sensitive start. pattern is a regular expression that must match the whole value, so ^ and $ are optional. If you set both, a candidate must pass both, and each one rejected is reported as a W-PATTERN warning.

{ "name": "invoiceNumber", "type": "string", "prefix": "INV-", "pattern": "INV-[0-9]{6}" }
Candidate Result
INV-004211 accepted
inv-004211 rejected: prefix is case-sensitive
INV-4211 rejected: pattern needs six digits

Before matching, strings are normalized to Unicode NFC, trimmed, and runs of whitespace are collapsed to one space. Look-around and backreferences aren’t supported. Write [0-9] rather than \d, since \d also matches digits from other scripts.

A pattern only filters: it rejects candidates in the wrong format, but if the document contains two valid invoice numbers, it can’t tell you which one you meant. Use description for that.