Reference Tables
Reference tables map human-readable values to system codes. Each table is a list of key-value pairs where key = output code and value = label. For example, a country reference table might map "United States" to US, "Germany" to DE, and "United Kingdom" to GB. During extraction, a 3-step lookup cascade runs automatically against the table to normalize extracted values to your canonical codes:
Lookup cascade
| Parameter | Type | Description |
|---|---|---|
| Step 1 | normalization | Exact match after lowercasing, stripping punctuation and legal suffixes (Ltd, GmbH, Inc, LLC and similar), then a retry with parenthetical text removed. Payment terms also match on day count, so "Net 30" finds "30 days". Free, instant. Confidence: 0.95. |
| Step 2 | fuzzy | Token-overlap matching for name variations (e.g., "Northwind Energy UK Ltd" vs "Northwind Energy Trading"). Accepts a Jaccard score at or above the threshold (default 0.5), strong containment, or a multi-word key fully contained in the value. Free. Confidence: match score × 0.85. |
| Step 3 | AI fallback | AI maps value to code when fuzzy matching fails. Last resort. Confidence: the model's rating × 0.75. |
To set up a reference table, upload a CSV or manually enter key-value pairs where the key is the code you want in your output and the value is the human-readable label found in documents. During extraction, the system tries each step in order — most values resolve instantly at Step 1, so keeping your labels clean and consistent dramatically improves both speed and accuracy.
Reference tables are used in two Job phases. In Phase 1, the lookup cascade runs as part of the resolve step, mapping extracted labels to codes without any AI calls (Step 1 and Step 2). In Phase 3, the cascade runs again on values produced by Phase 2's AI extraction, normalizing free-text AI output to your canonical codes. This two-pass approach ensures maximum code coverage across the entire Job.
For example, consider a "Contract Type" field with a reference table mapping codes to labels: std_master = "Master Agreement", std_service = "Service Agreement", std_nda = "Non-Disclosure Agreement". When the AI extracts "Frame Agreement" from a document, the Phase 3 lookup cascade normalizes it: Step 1 finds no exact match, Step 2 fuzzy matching shares only the token "agreement" with "Master Agreement", a Jaccard score of 0.33, below the default 0.5 threshold, so Step 3 AI fallback can map it to std_master at a lower confidence. Adding "Frame Agreement" as a synonym pointing to std_master would promote this to a Step 1 match (0.95 confidence) in future runs.
For best results, include common variations and abbreviations as separate value entries all pointing to the same key. For example, if your code is US, add values for "United States", "USA", "U.S.A.", and "United States of America". The more variations you cover, the more values resolve at Step 1 (highest confidence) without falling through to fuzzy or AI matching.
key,value
std_master,Master Agreement
std_master,Frame Agreement
std_service,Service Agreement
std_service,Service Contract
std_nda,Non-Disclosure Agreement
std_nda,NDA
std_nda,Confidentiality Agreement- Open the field in the schema editor and choose Reference table.
- Upload the CSV, or enter the key-value pairs manually.
- Run a Test Extraction on a few documents and check that extracted labels resolve to the expected codes.
- After production runs, review values flagged
lookup_failedand add them as new value rows pointing to the right key.
Reference fields also support multi-hop resolution chains. Each step names a reference, a join_key column to look the current value up in, and an output_key column whose value is passed to the next step. This is useful when your data needs to traverse multiple mapping layers, such as resolving a local product code to a regional code and then to a global SKU. Chain steps are case-insensitive exact lookups, not the fuzzy cascade, and the chain stops with no value as soon as one step misses. The reference versions used are pinned when the run starts, so a run is not affected by edits made while it executes.
lookup[name]:exact(col, val) and :where(col, val) still work and return up to 100 rows per call, while whole-table operations such as :fuzzy() and :all() raise a "keyed-access only" error. See the data-policy rules reference in the API docs for details.Inline tables and linked reference datasets
A field's lookup table can live in two places. An inline table is stored on the field itself and is frozen into the run snapshot when a run is created, so re-running that run reproduces exactly the same lookups. A linked table points at a reference dataset by ID and is re-read from the dataset on every execution, so a re-run picks up rows you added since. Linked datasets suit lists that change often, such as supplier or product masters, and let you correct a mapping and re-run resolution without re-extracting documents. Data Policy lookup rules use the same linked datasets through their reference_table_id setting.
Reference datasets can be created from the app or through the public API with POST /v1/reference-data, which takes a name and a non-empty data array of row objects. The column list is derived from the keys of the first row. To use a dataset as a field lookup table, give every row a key column (the output code) and a value column (the label to match). Rows without a key are skipped, and a row without a value uses its key as the label. Extra columns are kept and can be used by a row filter.
curl -X POST https://api.talonic.com/v1/reference-data \
-H "Authorization: Bearer $TALONIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Contract types",
"data": [
{ "key": "std_master", "value": "Master Agreement" },
{ "key": "std_master", "value": "Frame Agreement" },
{ "key": "std_service", "value": "Service Agreement" },
{ "key": "std_nda", "value": "Non-Disclosure Agreement" }
]
}'
# Response
{
"id": "5f0c2a9e-3b1d-4c7a-9e2f-8d6b1a0c4e77",
"name": "Contract types",
"source_type": "api",
"row_count": 4,
"columns": [
{ "name": "key", "type": "string" },
{ "name": "value", "type": "string" }
],
"created_at": "2026-10-06T09:12:00.000Z",
"links": {
"self": "/v1/reference-data/5f0c2a9e-3b1d-4c7a-9e2f-8d6b1a0c4e77",
"rows": "/v1/reference-data/5f0c2a9e-3b1d-4c7a-9e2f-8d6b1a0c4e77/rows"
}
}To check what a dataset holds, GET /v1/reference-data/{id}/rows returns rows in upload order, 100 per page by default and at most 500 per page via limit, and GET /v1/reference-data/{id}/csv downloads the whole dataset as a CSV file. There is no row-level edit endpoint: to change a dataset through the API, create a new one and point the field at it, or edit it in the app. Deleting a dataset with DELETE /v1/reference-data/{id} removes it for every field and policy that links to it, so check those first.
Two field settings control how strict a lookup is. A minimum lookup confidence rejects matches whose raw score falls below it; the gate compares the raw match score rather than the stored confidence, so a strict setting such as 0.85 still lets a strong fuzzy match through. A row filter narrows a large table per document before the cascade runs: it names a column in the dataset and a value, either a literal such as DE or a placeholder like {field.country} read from the same document. If the placeholder resolves to an empty value, the filtered table is empty and the lookup returns nothing, which is safer than picking a code from the wrong country.