---
title: "Extract Invoice and Receipt Fields with One API Call"
description: "Extract invoice number, dates, totals and tax from invoice text or receipt photos with a typed TypeScript SDK."
canonical: "https://system-one.dev/en/use-cases/invoice-receipt-extraction"
language: "en"
---

# Extract Invoice and Receipt Fields with One API Call

**Key points**\n\n1. Define the fields you need (invoice number, due date, total, tax) and Clairic returns a typed value for each, from invoice text or up to 4 receipt photos.
2. `referenceDate` fills in dates written without a year, and Clairic can work out values such as a due date from the issue date and payment terms.
3. Fields that aren't on the document, like a discount that isn't there, come back as null instead of a made-up number.

To pull fields from an invoice or receipt, call Clairic's `extract()` with the invoice text or receipt photos plus your field definitions. It returns typed values for the invoice number, dates, totals, tax and anything else you define.

![Invoice text and receipt photo turned into typed fields by Clairic](TODO-IMG: Cover. A mock invoice (text) and a mock receipt photo → field cards (invoice_number, total, tax…). Designed.)

## Fields to extract

| Field | Type / factory | Notes |
|---|---|---|
| invoice_number / order_number | text / `textField()` | Document number, can carry a span |
| issue date, due_date | date / `dateField()` | Calendar date |
| purchase_time | datetime / `dateTimeField()` | Date and time |
| total, subtotal, tax, tip, unit_price | number / `numberField({ decimals: 2 })` | `decimals` is 0–6 |
| quantity | number / `numberField({ integer: true })` | Whole number |
| contacts / line item names | list / `listField({ items: 'text' })` | `items` is required: text, number or date |
| days_to_pay | estimate / `estimateField({ min, max })` | Returns median, p10, p90 and more |

## Text invoice example (INV-2291)

This is the invoice example from the [Clairic SDK docs](https://docs.system-one.dev/en/docs/sdk/clairic). Install the SDK:

```bash
npm install @system-one-ai/core@0.8.0 @system-one-ai/adapter-system-one@0.7.0 @system-one-ai/transport-fetch@0.8.0
```

Save as `extract-invoice.mts` and set `SYSTEM_ONE_API_KEY` in `.env`:

```ts
import {
  createSystemOne, defineFields, textField, numberField, dateField, listField, estimateField,
} from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
if (!apiKey) throw new Error('Set SYSTEM_ONE_API_KEY.');

const client = createSystemOne({
  adapter: systemOneAdapter,
  transport: createFetchTransport(),
  apiKey,
  timeoutMs: 30_000,
  maxRetries: 0,
});

const fields = defineFields({
  invoice_number: textField({ instructions: 'The invoice id printed on the document.' }),
  due_date: dateField({ instructions: 'Issue date plus the net payment days.' }),
  total: numberField({ decimals: 2, instructions: 'The total amount due.' }),
  contacts: listField({ items: 'text' }),
  days_to_pay: estimateField({ min: 0, max: 120, step: 1 }),
});

const result = await client.extract({
  model: 'clairic',
  state: [
    'INVOICE INV-2291',
    'From: Northwind Supply',
    'To: Contoso Ltd',
    'Issued: 2026-09-02, net 30',
    'Total due: 1080.00 including shipping',
    'Contact: Dana Ruiz, Priya Shah',
  ].join('\n'),
  fields,
  referenceDate: '2026-10-19',
  options: { includeQuantiles: false },
});

const invoice: string | null = result.answers.invoice_number.value;
console.log({
  model: result.model,
  invoice,
  dueDate: result.answers.due_date.value,
  total: result.answers.total.value,
  contacts: result.answers.contacts.value,
  daysToPay: result.answers.days_to_pay.median,
  truncated: result.truncated,
  usage: result.usage,
});
```

Run it with `node --env-file=.env extract-invoice.mts`. Every run creates a new billable request.

`contacts` is a list, so its value is an array of names. `days_to_pay` is an estimate field, so read its `median`; `p10` and `p90` come back too.

![Clairic output for invoice INV-2291 with invoice number, due date and total](TODO-IMG: Real terminal run of extract-invoice.mts, API key masked as sk-****.)

[Try the invoice demo](https://system-one.dev/en/demo/invoice-extraction) *(CTA button)*

## Skewed, blurry receipt photos

In an example from the official blog, Clairic reads a receipt photo taken at an angle with hard-to-read text and pulls the order number, purchase time, quantity, unit price, subtotal, tax and tip. The receipt shows no discount, so the discount field returns null.

Here's the photo version, saved as `extract-receipt.mts`. You can send up to 4 images per request:

```ts
import { readFile } from 'node:fs/promises';
import {
  createSystemOne, defineFields, textField, numberField, dateTimeField,
  type ImageInput,
} from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
const imagePath = process.argv[2];
if (!apiKey || !imagePath) throw new Error('Set SYSTEM_ONE_API_KEY and supply a JPEG path.');

const images: readonly ImageInput[] = [{
  mediaType: 'image/jpeg',
  base64: (await readFile(imagePath)).toString('base64'),
}];

const client = createSystemOne({
  adapter: systemOneAdapter,
  transport: createFetchTransport(),
  apiKey,
  timeoutMs: 30_000,
  maxRetries: 0,
});

const fields = defineFields({
  order_number: textField({ instructions: 'The order or receipt number.' }),
  purchase_time: dateTimeField({ instructions: 'Date and time of purchase.' }),
  quantity: numberField({ integer: true }),
  unit_price: numberField({ decimals: 2 }),
  subtotal: numberField({ decimals: 2 }),
  tax: numberField({ decimals: 2 }),
  tip: numberField({ decimals: 2 }),
  discount: numberField({ decimals: 2, instructions: 'Discount amount, only if the receipt shows one.' }),
});

const result = await client.extract({ model: 'clairic', state: '', images, fields });

for (const [name, answer] of Object.entries(result.answers)) {
  console.log(name, answer.value, answer.confidence, answer.pNull);
}
```

When the receipt has no discount, `discount` comes back null. Before production, test with your own photos and set thresholds on `confidence` and `pNull`.

![Skewed receipt photo and Clairic fields, with discount returned as null](TODO-IMG: Left: mock skewed, blurry receipt photo with no discount line. Right: real run output of extract-receipt.mts showing discount null; API key masked.)

## referenceDate and due dates

`referenceDate` (`reference_date` over HTTP) sets the year for dates written without one, such as a receipt that only prints the day and month.

Due dates can be computed. In the invoice example, the `due_date` field's instructions say "Issue date plus the net payment days.", so Clairic works out the due date from 2026-09-02 and net 30.

## Missing fields return null

When `pNull` is above the null threshold (0.5 by default), the value is null, and `guess` may still hold something. Adjust the threshold for the whole request with `options.nullThreshold`, or per field.

The [invoice extraction demo](https://system-one.dev/en/demo/invoice-extraction) shows this: when the total due says "to be confirmed", the amount comes back null and the invoice number is still extracted.

## Pricing in one line

Clairic is billed once per request based on input size, not per field; see the [Clairic model page](https://system-one.dev/en/models/clairic). New accounts get 100 free credits on sign-up.

## Try the demo

Try the same invoice task in the [invoice extraction demo](https://system-one.dev/en/demo/invoice-extraction) (sign-in required). To call the API directly, this is the HTTP body for `POST https://system-one.dev/v1/systemone`:

```json
{
  "model": "clairic",
  "state": "INVOICE INV-2291\nIssued: 2026-09-02, net 30\nTotal due: 1080.00",
  "reference_date": "2026-10-19",
  "fields": {
    "invoice_number": { "type": "text" },
    "total": { "type": "number", "decimals": 2 }
  }
}
```

See the API docs for authentication, idempotency and headers. Retrying the same operation with the same key doesn't create a second charge while the replay record remains.

![Clairic invoice extraction demo on system-one.dev](TODO-IMG: Real screenshot of /en/demo/invoice-extraction, fields and result area.)

[Get an API key](https://system-one.dev/en/signup) *(CTA button)*

## Related reading

- [OCR vs AI field extraction](https://system-one.dev/en/blog/clairic-vs-ocr)
- [Screenshot to JSON for AI agents](https://system-one.dev/en/use-cases/screenshot-to-json)
- [Clairic SDK docs](https://docs.system-one.dev/en/docs/sdk/clairic)
- [Clairic model page](https://system-one.dev/en/models/clairic)

[Get an API key](https://system-one.dev/en/signup) *(CTA button, end of page)*

Last verified: 2026-10-22

## Frequently asked questions

### Can Clairic read a skewed or blurry receipt photo?

It can extract fields from photos taken at an angle or with hard-to-read text; the official example pulls order number, purchase time, quantity, unit price, subtotal, tax and tip from one. Test with your own photos and check confidence and pNull.

### Which inputs can I send?

Text, JSON, and up to 4 images (PNG, JPEG or WebP) embedded as base64.

### What if a field isn't on the invoice?

It comes back as null when pNull is above the null threshold, so a missing discount stays null rather than being filled in.

### How is it billed?

Once per request, based on input size: each 4KB block costs 2 credits, no matter how many fields you extract. See the Clairic model page.

### What does referenceDate do?

It sets the year for dates written without one, for example on a receipt that only shows day and month.
