> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.milliseconds.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.milliseconds.ai/_mcp/server.

# Verify

`POST /v1/decision-machine-1/verify` takes a field and a value you already have. The model reads the text and pulls what the text says for that field. It then compares that against your value. You get a boolean `matches`, a `probability`, and `found`, the raw spans the model pulled. Use it as the check step after an extraction, an OCR pass, or a form submission. Every request needs an API key. See [Authentication](/authentication).

## When to use it

* You hold a value from another system and must confirm the source document supports it.
* You want a cheap second opinion on one field of an [extract](/capabilities/extract) result before you write it to a database.
* You need the spans behind a disagreement, not only a verdict: `found` shows the competing values.
* Use [extract](/capabilities/extract) instead when you have no value yet and want the model to produce one.
* Use [answer](/capabilities/answer) instead when you want the value plus its character offsets in the text.
* Use [yes-no](/capabilities/yes-no) instead when the claim is a sentence, not a field and value pair.

## Three example decisions

1. An OCR pipeline read invoice number `4471`. Does the invoice text say `4471`?
2. A payment record holds a total of `2676`. Does the invoice support that total?
3. A CRM row says the customer is `Acme Corp`. Does the contract name that customer?

## Request

| Field               | Type             | Required                | Meaning                                          | Limits                                        |
| ------------------- | ---------------- | ----------------------- | ------------------------------------------------ | --------------------------------------------- |
| `text`              | string           | one of `text` / `texts` | The document to read                             | 1 to 20,000 characters                        |
| `texts`             | string\[]        | one of `text` / `texts` | A batch of documents                             | 1 to 32 items, each 1 to 20,000 characters    |
| `field`             | object           | yes                     | The field to look up in the text                 | `{ name, description }`                       |
| `field.name`        | string           | yes                     | The field name, such as `invoice_number`         | minimum 1 character                           |
| `field.description` | string           | no                      | A sentence that tells the model what to look for | no cap                                        |
| `value`             | string or number | yes                     | The value you want to check                      | Numbers are stringified before the comparison |

`text` and `texts` are mutually exclusive. Write `field.description` in English, even for non-English text. See [Languages](/concepts/languages).

**`curl`**

```bash title="curl"
curl -X POST https://api.milliseconds.ai/v1/decision-machine-1/verify \
  -H "content-type: application/json" \
  -H "authorization: Bearer test_sk-..." \
  -d '{
    "text": "INVOICE #4471 - Acme Corp. Issued 2026-03-02. Net 30. Subtotal $2,500.00. Tax $176.00. Total due $2,676.00.",
    "field": {
      "name": "invoice_number",
      "description": "The invoice number printed on the document"
    },
    "value": "4471"
  }'
```

**`Python`**

```python title="Python"
import requests

TEXT = (
    "INVOICE #4471 - Acme Corp. Issued 2026-03-02. Net 30. "
    "Subtotal $2,500.00. Tax $176.00. Total due $2,676.00."
)

response = requests.post(
    "https://api.milliseconds.ai/v1/decision-machine-1/verify",
    headers={"authorization": "Bearer test_sk-..."},
    json={
        "text": TEXT,
        "field": {
            "name": "invoice_number",
            "description": "The invoice number printed on the document",
        },
        "value": "4471",
    },
)

result = response.json()
print(result["matches"], result["probability"], result["found"])
```

**`TypeScript`**

```typescript title="TypeScript"
const text =
  "INVOICE #4471 - Acme Corp. Issued 2026-03-02. Net 30. " +
  "Subtotal $2,500.00. Tax $176.00. Total due $2,676.00.";

const response = await fetch(
  "https://api.milliseconds.ai/v1/decision-machine-1/verify",
  {
    method: "POST",
    headers: { "content-type": "application/json", authorization: "Bearer test_sk-..." },
    body: JSON.stringify({
      text,
      field: {
        name: "invoice_number",
        description: "The invoice number printed on the document",
      },
      value: "4471",
    }),
  },
);

const result = await response.json();
console.log(result.matches, result.probability, result.found);
```

## Response

```json
{"matches":true,"probability":0.689,"found":["4471"]}
```

* `matches` — boolean. It is `true` when `probability` is 0.5 or more.
* `probability` — number from 0 to 1, rounded to 3 decimals. It is the confidence of the span that matched your value. It is exactly `0` when nothing the model found matches.
* `found` — string array. Every span the model pulled for that field, match or not. Use it to debug a disagreement.

Two headers come back on every call: `x-input-chars`, the number of input characters, and `x-input-tokens`, the input tokens billed for this call. The call above returned `x-input-chars: 107` and `x-input-tokens: 27`. See [Pricing](/reference/pricing).

## Reading the numbers

Send the same invoice with `value` set to `4417`, a transposed digit:

```json
{"matches":false,"probability":0,"found":["4471"]}
```

`probability` is `0` and `found` still holds `4471`. That pair is the useful signal. The model did read the field, and the text disagrees with your value. Route this case to a person.

Now ask the same invoice for `shipping_address`, a field it does not carry:

```json
{"matches":false,"probability":0,"found":[]}
```

Both results say `matches: false` with `probability: 0`. Only `found` separates them. An empty `found` means the model saw no such field, so your value is unconfirmed, not contradicted. A populated `found` means the text states something else.

A match on a strong span looks different:

```json
{"matches":true,"probability":0.997,"found":["$2,676.00","$2,500.00","$176.00"]}
```

That is `total_due` against the value `2676`. The model pulled three money spans, and `$2,676.00` matched. `probability` therefore reports that span's confidence. The other two spans are noise from the same field. They do not lower the number.

> **Note**
>
> `probability` measures how sure the model is about the span it read, not how sure it is that you are right. A match at 0.689 and a match at 0.997 are both matches. The low number means the span itself was uncertain, so re-read `found` before you act.

Practical bands for a financial field: act on `matches: true` above 0.9, and confirm between 0.5 and 0.9. Review every `matches: false` that has a non-empty `found`. Raise the bar with the blast radius. See [Thresholds and confidence routing](/concepts/thresholds).

## Batching

Pass `texts` to check one value against up to 32 documents in one call. The response is always `{"results":[...]}`, one entry per text, in input order.

```bash
curl -X POST https://api.milliseconds.ai/v1/decision-machine-1/verify \
  -H "content-type: application/json" \
  -H "authorization: Bearer test_sk-..." \
  -d '{
    "texts": [
      "INVOICE #4471 - Acme Corp. Total due $2,676.00.",
      "INVOICE #8812 - Globex Ltd. Total due $940.00."
    ],
    "field": {
      "name": "invoice_number",
      "description": "The invoice number printed on the document"
    },
    "value": "4471"
  }'
```

```json
{"results":[
  {"matches":true,"probability":0.849,"found":["INVOICE #4471"]},
  {"matches":false,"probability":0,"found":["INVOICE #8812"]}
]}
```

`field` and `value` apply to every text. To check several fields, send one call per field. Each text costs one inference call, so a batch of 32 costs 32 calls. See [Batching](/capabilities/batching).

## Limits and gotchas

**The matching rule.** The API lowercases both sides and removes every non-alphanumeric character. It then accepts an exact match, or containment when your canonical value is **3 or more characters**. So `$2,676.00` matches the value `2676`. A one or two character value must match exactly.

**Containment cuts both ways.** Against the same invoice, the value `471` returns `matches: true` with `found: ["4471"]`, because `471` sits inside `4471`. The value `47` returns `matches: false`, because two characters need an exact match.

> **Warning**
>
> Short numeric values produce false positives. A three-digit code, a quantity, or a year can be contained in a longer number in the text. Compare `found` to your value yourself when the value is short.

**Input limits.** `text` accepts 1 to 20,000 characters. `texts` accepts 1 to 32 items, each 1 to 20,000 characters. Send one of the two, never both.

**Field limits.** `field.name` needs at least 1 character. `field.description` has no cap. A missing `field` returns `{"error":{"code":"invalid_request","message":"field: Invalid input: expected object, received undefined"}}`. A missing `value` returns `{"error":{"code":"invalid_request","message":"value: Invalid input"}}`. Both are 400. See [Errors](/reference/errors).

**Write a real description.** `field.name` alone often works, but a description narrows the search. `"The invoice number printed on the document"` beats `"number"`. See [Writing good statements and labels](/concepts/writing-good-questions).

**Long text.** Past 2,000 characters the text is split into overlapping windows and the spans merge. Cost is linear in characters. See [Long text and chunking](/concepts/long-text).

**Latency.** One verify call measured 0.40 s end to end over a short input on 2026-09-18.

## Images

Send `image` instead of `text` to run this capability over a photograph or a scanned page. `image` is
a `data:image/jpeg;base64,` URL or bare base64 of a JPEG, PNG or WebP, at most 5 MB decoded. `detail`
picks the resolution: `low` is 512 px, `medium` is 768 px and is the default, `high` is 1024 px.

```bash
curl -X POST https://api.milliseconds.ai/v1/decision-machine-1/verify \
  -H "content-type: application/json" \
  -H "authorization: Bearer test_sk-..." \
  -d '{
    "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
    "detail": "low",
    "field": { "name": "total", "description": "The amount due" },
    "value": "21.50"
  }'
```

The response shape does not change: `matches`, `probability` and `found`. `matches` and `found`
behave as on text. On an image the model reads the field and the comparison decides, so `probability`
is `1` when the value matches and `0` when it does not. Threshold on `matches`, not on the number.

`image` works with `text`, never with `texts`. The image bills a fixed number of tokens:
2,000 for `low`, 4,000 for `medium`, 8,000 for `high`. The rest of the body bills as today. See [Images](/concepts/images).

## Next

#### [Extract, then verify](/patterns/extract-then-verify)

Extract a field, verify it against the source, and queue the disagreements.

#### [Invoice extraction with verification](/recipes/invoice-extraction)

A full invoice schema, two verified fields, and a review queue.

#### [Extract](/capabilities/extract)

Produce the value in the first place, typed by your JSON Schema.