Extract

Send a JSON Schema and get a typed object back, with null for every field the text does not carry.

POST /v1/decision-machine-1/extract fills a JSON Schema from one text. You send text and schema. The model reads the text once and returns data in the shape of your schema. The shape supports strings, numbers, integers, booleans, enums, string arrays and nested objects. An array-of-objects property is accepted and returned empty for now. A field the text does not carry comes back as null. Nothing is generated: every value comes from a span in the text.

When to use it

  • You need several fields from one document in one call: an invoice, a résumé, an order confirmation.
  • You already have a JSON Schema and want it filled.
  • Use Answer instead when you need one field plus its character offsets.
  • Use Entities instead when you want every occurrence of a type, not one value per field.
  • Use Verify after extraction to check a critical value against the source text.

Three example decisions

  • Pull the invoice number, the total and the payment terms from a billing email.
  • Turn a shipping confirmation into {tracking_number, carrier, delivery_date}.
  • Read a job application and fill {candidate_name, years_experience, remote_ok}.

Request

FieldTypeRequiredMeaningLimits
textstringone of text / textsThe text to read1 to 20,000 characters
textsstring[]one of text / textsA batch of texts1 to 32 items, each 1 to 20,000 characters
schemaobjectyesA JSON Schema that describes the outputMust be type: "object" with a properties map

text and texts are mutually exclusive. Send one of them, never both.

Write a description on every property. The description is what the model searches for. "total_due" alone works; "total amount due" works better.

curl -X POST https://api.milliseconds.ai/v1/decision-machine-1/extract \
-H "content-type: application/json" \
-d '{
"text": "INVOICE #4471\nBilled to: Acme Corp\nCurrency: USD\nPaid: yes\nTotal due: $2,676.00\nPayment terms: Net 30",
"schema": {
"type": "object",
"title": "invoice",
"properties": {
"invoice_number": { "type": "string", "description": "invoice number" },
"customer": { "type": "string", "description": "the company billed" },
"total_due": { "type": "number", "description": "total amount due" },
"net_terms_days": { "type": "integer", "description": "payment terms in days" },
"paid": { "type": "boolean", "description": "is the invoice paid" },
"currency": { "enum": ["USD", "EUR", "GBP"], "description": "currency code" }
}
}
}'

No API key is required during the launch period.

Response

This capture ran the same schema with tags and vendor added. The call took 0.72 s.

{
"data": {
"invoice_number": "4471",
"customer": "Acme Corp",
"total_due": 2676,
"net_terms_days": 30,
"paid": true,
"currency": "USD",
"tags": ["Design work", "Hosting"],
"vendor": { "name": "Acme Corp", "city": null }
}
}
  • data — one object in the shape of your schema. Only data comes back. There is no probability on this endpoint.
  • Scalar values carry the text’s own span, coerced to your type.
  • A missing value is null. The key stays in place.
  • A batch over texts wraps the results: {"results":[{"data":{...}}, ...]}.

Reading the numbers

Extract returns no probability, so read the values themselves.

The scalars above are all correct: total_due parsed $2,676.00 into 2676, net_terms_days truncated Net 30 to 30, and paid read yes as true.

One field is wrong, and the failure is typical. vendor.name copied the billing name, because the text has no vendor block. The model fills a described field with the closest span it finds; it does not refuse.

Treat the result as a draft. Some fields move money or write to a record. Send those values and the source text to Verify. Check matches before you commit the value.

Batching

Send texts instead of text to run the same schema over up to 32 texts. Each text costs one inference call.

curl -X POST https://api.milliseconds.ai/v1/decision-machine-1/extract \
-H "content-type: application/json" \
-d '{
"texts": ["Total due: $2,676.00", "Total due: $10.00"],
"schema": {
"type": "object",
"properties": {
"total_due": { "type": "number", "description": "total amount due" }
}
}
}'
{"results":[{"data":{"total_due":2676}},{"data":{"total_due":10}}]}

Results keep the input order. Batching covers the shape on every capability.

Schema support matrix

ConstructSupportedBehaviour
{"type":"string"}YesThe raw span
No type at allYesTreated as a string
{"type":"number"}YesNon-numeric characters are stripped: $2,676.00 becomes 2676. Unparseable becomes null.
{"type":"integer"}YesSame, then truncated
{"type":"boolean"}Yestrue, yes, y and 1 become true
{"enum":[...]}YesThe model picks one member. enum wins over type.
{"type":"array","items":{"type":"string"}}YesAn array of strings
Array of another scalarPartlyReturns strings, not numbers
Nested objectYesReturned in its nested shape
Array of objectsAccepted, returned as an empty array for nowThe property stays in data and holds []
{"type":["string","null"]}YesThe first non-null type wins
{"type":"null"}NoThe key is dropped from data
oneOf, anyOf, allOf, $refNoRead as a plain string, so the value is usually null
Array of arraysNoResolves to an array of strings
requiredParsed, not enforcedA required field can still come back null
title, descriptionReaddescription (else title, else the field name) tells the model what to look for. A root title names the structure.

This call used prices as an array of number, day as a string, meta as {"type":"null"} and weird as an anyOf:

{"data":{"prices":["30","20","10"],"day":"Tuesday","weird":null}}

The prices came back as strings, and weird came back null. The key meta is absent from data.

Limits and gotchas

  • text takes up to 20,000 characters. texts takes up to 32 items, each up to 20,000 characters.
  • Send text or texts, never both. Sending neither also fails, with the same message: body: provide text or texts, not both.
  • A root schema that is not an object with properties returns 400:
{"error":{"code":"invalid_schema","message":"schema must be an object with properties"}}
  • The common mistake is a bare field name with no description. Describe the field in the words the document uses.
  • The second mistake is trusting required. Handle null in your own code.
  • Array order is not stable between calls. Compare arrays by value, not by position.
  • Keep schemas small. Root scalar fields run in groups of four, because one large structure makes fields compete for the same span.
  • Text over 2,000 characters is chunked, and the records from each chunk merge. Long text and chunking covers the rules.
  • Extract runs on the multilingual model, so it reads non-English text. Languages gives the guidance.
  • Observed latency: 0.72 s for a nine-property schema, 1.03 s for two texts.

Next