Extract, then verify

Read a field out of the text, check it against the source, and queue the disagreements for a person.

Extract returns a filled object and nothing else. The response carries no probability, so a wrong value looks exactly like a right one.

Verify closes that gap. It re-reads the source text for one field, compares your value against what it finds, and returns the spans it read. Run it before you write an extracted value into a database, a ledger, or a payment.

The loop

1

Extract the record

Send your JSON Schema to /v1/decision-machine-1/extract. You get a typed object back. Missing values are null.

2

Verify the fields that carry risk

Send each high-risk field and its extracted value to /v1/decision-machine-1/verify. One call per field.

3

Route on the result

Write the value when matches is true and probability is high. Queue the record for review otherwise. Attach found to the queue item.

Verify does not correct a value. It reports agreement or disagreement. Your code decides what happens next.

Step 1: extract

This invoice text drives the whole page.

INVOICE #4471
Billed to: Acme Corp
Invoice date: 3 September 2026
Currency: USD
Paid: yes
Total due: $2,676.00
Payment terms: Net 30

A four-field schema returns this:

{"data":{"invoice_number":"4471","customer":"Acme Corp","total_due":2676,"currency":"USD"}}

Two of these fields move money: invoice_number and total_due. Verify both.

Step 2: verify

curl -s -X POST https://api.milliseconds.ai/v1/decision-machine-1/verify \
-H "content-type: application/json" \
-d '{
"text": "INVOICE #4471\nBilled to: Acme Corp\nInvoice date: 3 September 2026\nCurrency: USD\nPaid: yes\nTotal due: $2,676.00\nPayment terms: Net 30",
"field": {"name": "total_due", "description": "Total amount due on the invoice"},
"value": 2676
}'

Step 3: read the answer

Real responses against the invoice text above:

FieldValue sentResponse
total_due2676{"matches":true,"probability":0.998,"found":["$2,676.00"]}
total_due2500{"matches":false,"probability":0,"found":["$2,676.00"]}
invoice_number"4471"{"matches":true,"probability":0.638,"found":["4471"]}
invoice_number"4417"{"matches":false,"probability":0,"found":["4471"]}

Three fields carry the decision.

  • matches is true when probability >= 0.5.
  • probability is the confidence of the span that matched. It is 0 when no span matches.
  • found lists every span verify read for that field, match or not.

A correct value can still score low. The right invoice number scored 0.638 above.

found is the field that makes a review queue useful. A human sees ["4471"] next to your value 4417 and fixes the record in seconds.

The matching rule

Verify compares canonical forms, and containment counts only from 3 characters up, so short values need exact text. Verify states the full rule.

Routing bands

Set one threshold per field, not one per document. Raise the bar with the blast radius.

ResultAction
matches is true, probability at or above the field thresholdWrite the value
matches is true, probability below the field thresholdWrite a draft and flag it
matches is falseQueue for review with found
The extracted value is nullQueue for review

Start at 0.9 for payment amounts and account numbers. Start at 0.7 for references, dates and counterparties. These two numbers are a starting point, not a measured result. The helper above uses one threshold of 0.7; raise it per field for the values that move money.

The thresholds page explains how to pick these numbers from your own data.

Verify only checks fields you ask about. One verify call covers one field. List every field that carries risk, and check each one.

Latency cost

A verify call takes about 0.5 s. An extract call over a short document takes about 0.7 s. Three verify calls in sequence add about 1.5 s. Run them concurrently to keep the wait near one call.

Both calls read the same text. Cost scales with the length of the text plus the schema and the field you send, at $0.04 per million input tokens and $0 per output token. See Pricing. Send the smallest span that contains the field. A 2,000-character page beats a 20,000-character bundle.

Next