Extract typed fields with Clairic
Call extract({ state, fields }) with Clairic — field types, images, and hosted HTTP body examples.
Clairic is a System One extraction model. It reads shared state — the text or structured context you want values taken from — and a map of typed fields, then returns one typed value per field. It does not generate free-form text and it does not answer Choice, Score, or Noul questions.
Use extract({ state, fields }) on the same SystemOne client you use for evaluate(). Do not pass fields to evaluate(), and do not pass questions to extract().
The public model name is clairic, the same style as jev and clef. Copy that name, or the exact name from GET /v1/models when the catalog lists it. Do not add a prefix to the model name.
Try the same invoice task in the signed-in invoice extraction demo. Model facts and credit rates are on the Clairic model page.
Answer-ready
Q: How do I use the Clairic SDK?
A: Call extract({ state, fields }) with Clairic to pull typed fields from agent state. Use the System One TypeScript SDK or the hosted HTTP API with the Clairic model.
Q: Is Clairic the same as Choice?
A: No. Choice picks among options; Clairic extract returns structured fields you define. Both are part of System One.
Install
Extraction needs core 0.8.0, the hosted adapter 0.7.0, and Fetch transport 0.8.0. All three resolve @system-one-ai/core@^0.8.0.
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.0systemOneAdapter posts extract requests to {baseURL}/systemone. The adapter default is https://system-one.dev/v1, so the hosted path is POST https://system-one.dev/v1/systemone with a fields body. The default extract model is clairic.
Extract an invoice
Save as extract-invoice.mts. Set SYSTEM_ONE_API_KEY to a System One platform key. Every run creates a new billable operation.
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,
});node --env-file=.env extract-invoice.mtscreateSystemOne() returns a SystemOne instance. new SystemOne({ ... }) is the same constructor. defineFields() preserves literal field IDs for ExtractRequest, ExtractResult, and FieldAnswers.
Request
| Field | Rule |
|---|---|
fields | Required. At least one named field. Names match [A-Za-z_][A-Za-z0-9_.-]{0,63}. |
state | String to read. Required and nonempty unless images are sent. Text answers are spans of this string — a span is the start and end character index of that value in state. |
images | Optional. At most four embedded PNG, JPEG, or WebP images (4MB decoded each, 8MB total). Remote URLs are rejected. The full JSON request body must not exceed 13MB (the catalog may set a stricter limit). |
model | Optional. Hosted default is clairic. Per-request model overrides the client. |
referenceDate | Optional ISO date (YYYY-MM-DD) that fills in dates written without a year. The HTTP body uses reference_date. |
options.nullThreshold | Optional (0, 1]. The service default is 0.5. Applied to scalar fields that omit their own threshold. value is null when pNull is above the threshold; guess may still hold a value. |
options.includeQuantiles | Optional. Adds all 99 percentiles to estimate and forecast answers. |
options.returnStateText | Optional. Returns the text that spans index into. |
Core validates field names, per-type properties, and image representation before the request is sent. The full JSON request body must not exceed 13MB (the catalog may set a stricter limit). Adapters that do not set supportsExtract: true reject extract() with UnsupportedFeatureError before I/O.
Field types
Factories are exported from @system-one-ai/core. Inline { type: 'text' } objects are also valid.
| Type | Factory | value | Notes |
|---|---|---|---|
text | textField() | string | null | Optional span: [start, end]. |
number | numberField({ unit, integer, decimals }) | number | null | decimals is 0–6. |
date | dateField() | string | null | Calendar date. |
time | timeField() | string | null | Time of day. |
datetime | dateTimeField() | string | null | Combined date and time. |
duration | durationField({ unit, integer, decimals }) | number | null | Units: seconds, minutes, hours, days, weeks, months, years. |
list | listField({ items }) | readonly array | items is required: text, number, or date. |
estimate | estimateField({ min, max }) | number | string | min and max are required. Optional step, scale (linear | log), unit. |
forecast | forecastField({ min, max }) | readonly array | Same range options as estimate, plus horizon (1–64) and interval. |
Every field may include instructions and, except lists, nullThreshold. Lists may include unit and decimals.
import {
defineFields, dateTimeField, durationField, forecastField, timeField,
type ExtractRequest, type ExtractResult, type FieldAnswers,
} from '@system-one-ai/core';
const more = defineFields({
opens: timeField(),
signed: dateTimeField(),
delay: durationField({ unit: 'days' }),
travelers: forecastField({ min: 500_000, max: 4_000_000, horizon: 1, interval: 'day' }),
});
type MoreRequest = ExtractRequest<typeof more>;
type MoreResult = ExtractResult<typeof more>;
type MoreAnswers = FieldAnswers<typeof more>;Response
Only value is guaranteed on each answer. Scalar types also report guess, confidence, and pNull when the model sends them. Text may include span. Lists include value and optional spans. Estimates and forecasts include mean, median, p10, p90, pBelow, and pAbove; they do not use confidence / pNull. Forecast value is an array with one value per step (horizon steps).
truncated is always a boolean. usage normalizes token counts to inputTokens, outputTokens, totalTokens, and optional stateTokens, imageTokens, schemaTokens, passes. Optional counts that were not returned do not appear on the result.
result.model may differ from the requested ID. Record both.
Images with extract()
Clairic accepts up to four PNG, JPEG, or WebP images on extract(). state may be '' when images are present. ImageInput is exported by core: an embedded base64 data URL or { mediaType: 'image/png', base64: '...' }.
import { readFile } from 'node:fs/promises';
import { createSystemOne, textField, 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 PNG path.');
const images: readonly ImageInput[] = [{
mediaType: 'image/png',
base64: (await readFile(imagePath)).toString('base64'),
}];
const client = createSystemOne({
adapter: systemOneAdapter,
transport: createFetchTransport(),
apiKey,
timeoutMs: 30_000,
maxRetries: 0,
});
const result = await client.extract({
model: 'clairic',
state: '',
images,
fields: { invoice_number: textField() },
});
console.log(result.answers.invoice_number.value);This is extraction, not a decision. Decision images on hosted Clef still use Fetch because systemOneAdapter does not encode evaluate() images.
HTTP equivalent
CamelCase field names in the SDK become snake_case in the actual HTTP request (for example referenceDate → reference_date). The following body is what POST /v1/systemone accepts for the same task:
{
"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 extraction contract for authentication, idempotency (retrying the same operation with the same key does not create a second charge while the replay record remains), and headers. Extraction credits are billed once per request from input size, not multiplied by field count. Check the current credit rate before sending.