Skip to main content

Overview

Extract is called through the OpenAI-compatible POST /v1/chat/completions endpoint. Set model to extract (latest) or extract-1 (pinned version), put the text in the last user message, and describe the fields you want with the standard response_format JSON schema. The model runs Named Entity Recognition over the text. Unlike standard NER, you define the entity types in plain English - each property name becomes an entity type and its description is the search criteria - and each match comes back with a confidence score and surrounding context. Because the endpoint is OpenAI-compatible, you can point an unmodified OpenAI or OpenRouter SDK at ScaleDown by changing only the base URL and API key.

Request

string
required
"extract" (always the latest version) or "extract-1" (pinned to version 1). Both currently behave identically.
array
required
Standard OpenAI chat messages. The last user message is the text to extract from. Any system messages are joined and used as a global instruction - rules that apply across all entity types, such as deduplication logic or ranking constraints.
object
required
Defines what to extract. Must be { "type": "json_schema", "json_schema": { "name": "...", "schema": {...} } }. Each property of schema.properties is an entity type:
  • A scalar property (string, number, integer, boolean) - its description says what to look for. Each distinct value found in the text is returned as its own entity (up to 30 per type)
  • A nested object (type: "object" with properties) - a structured sub-schema
  • An array of objects (type: "array" with object items) - a repeated structure, such as line items
  • A classification key - a property with an enum, or a oneOf of const branches, routed to the classifier (see Classification keys)
Per-property threshold (0-1) and top_n annotations override the global values for that entity type only. $ref and $defs are not supported; inline your schema. Alternatively, send a flat top-level entities object instead of response_format (see below).
number
default:0
Non-standard extension, passed with the SDK’s extra_body. Global confidence threshold (0-1). Extracted entities whose confidence is below it are dropped. 0 (default) keeps everything. Can be overridden per entity type. Does not apply to nested/array schemas or classification keys.
number
default:0
Non-standard extension, passed with extra_body. Global limit on how many entities to return per entity type, keeping the most confident. 0 (default) returns all matches. Results are still returned in order of appearance in the text. Can be overridden per entity type.
number
default:500
Non-standard extension, passed with extra_body. Number of characters of surrounding text to include on each side of a matched span in the context response field. Set to 0 to omit context entirely.
string
Non-standard extension, passed with extra_body. A base64-encoded file to extract from. Supported formats: JPEG, PNG, TIFF, single-page PDF, multi-page PDF. The text is read from the file and used as input; if the user message also has text, the document text is appended after it.
string
Non-standard extension, passed with extra_body. MIME type of the document (e.g. "image/jpeg", "application/pdf"). Required when document is provided.
object
Optional flat alternative to response_format, matching a batch item body. A mapping of entity type name to a description string, or to an object with description, threshold and top_n, a nested object, an array of objects, or a classification config {"labels": [{"name": ..., "rubric": ...}]}. When present it takes precedence over response_format. Top-level text and instruction likewise take precedence over the user and system messages.

Response

A standard chat.completion. The extraction result is a JSON string in choices[0].message.content - parse it to read the fields below.
string
Echoes the model you sent (extract or extract-1).
object
prompt_tokens is the input tokens billed for the request, completion_tokens the model’s output tokens, and total_tokens their sum.
string
A JSON string with these fields:

Error responses

Authentication

Include your API key in every request using the x-api-key header. Authorization: Bearer <your-api-key> is also accepted, so an OpenAI SDK only needs its api_key set.

Examples

The Python and TypeScript examples assume this client:

Basic extraction

The parsed content is:

Extracting from an image or PDF

Pass a base64-encoded photo, scan, or PDF through extra_body and the text is read before extraction, so receipts, invoices, IDs, and scanned forms work without a separate preprocessing step. A message is still required; use a short placeholder user message. The raw extracted text is returned as ocr_text.
For a PDF, set document_mime_type to "application/pdf"; single-page and multi-page PDFs are both supported.

Structured (nested) extraction

Use a nested object, or an array of objects for repeated structures such as line items. The full result is returned in structured_result, keyed by property name.

Classification keys

Some of the fields you want are not really extraction targets - they are decisions. “Is this account past due?” or “Is this an electric or gas meter?” have no verbatim span to pull; they require judging the text against a fixed set of options. The extraction model is tuned to copy spans out of the source, so it is a poor fit for these, and mixing them into an extraction call drags down accuracy for every key. To handle these, mark the key as a classification key by constraining it to a fixed list of labels. Any key constrained this way is split out of the extraction call and routed to our classification model instead - a Constrained Decoding Model (CDM) purpose-built for categorical and boolean decisions, using constrained decoding over your labels with calibrated confidence scores. The rest of the request extracts as normal, and classified values are merged back into the same response so nothing changes on your side. Each label has a name and, optionally, a rubric describing when that label applies. Rubrics meaningfully improve accuracy - prefer them over bare strings for anything non-obvious. An optional per-key description adds context about the decision. To mark a key as a classification key in a response_format schema, constrain the property to a fixed set of choices in one of two ways:
  • enum - the values are the labels; an optional description becomes the shared rubric.
  • oneOf of const branches - each branch’s description is that label’s rubric (per-label rubrics).
Alternatively, with the flat entities body, send {"is_delinquent": {"labels": [{"name": "yes", "rubric": "balance is overdue"}, ...]}}. The classified value is returned as the entity text, and confidence is the calibrated probability of the chosen label. The label itself is usually not a verbatim span in the source (it was inferred), so start/end and context instead point at the evidence the model used for the decision - here, "90 days past due" for is_delinquent, not the word “delinquent”. If no relevant evidence is found, start/end fall back to 0 and context is empty. In a structured (nested) request, classified values also appear in structured_result under their key.
A classification key still counts as part of your single extract call - one request in, one response out, billed as one extract call. Each classification key needs at least 2 and at most 20 labels; outside that range the request returns 422.
To locate the evidence span, each classification key runs a brief follow-up pass that pulls the verbatim phrase backing the decision. This is transparent - the span and context are populated for you - but it does add to the token usage reported for the call. If you don’t need spans for a boolean/categorical field, you can ignore start/end/context on those entities.

Multiple matches, with limits

A scalar property returns each distinct value found in the text, one entity per value, in order of first appearance, up to 30 per entity type. A value that occurs several times is returned once (at its first occurrence). You do not need to ask for a list.
Python
This returns three person entities (Alice Johnson, Bob Martinez, Carol Nguyen) and two city entities (Paris, London). Every entity has a confidence. Use threshold and top_n to filter on it. Set them for the whole request through extra_body, and override them per property with extra keywords on that property:
Python
Here person returns only its single most confident match, and every entity below 0.5 confidence is dropped. Per-property values override the request-level ones.

Writing good entity labels

The entity name and description are both used as part of the model’s search criteria - wording them well is the biggest lever you have on extraction quality. Use lowercase or Title Case. The model was trained with lowercase labels. Keeping your entity names lowercase (e.g. person, company) or Title Case (e.g. Person, Company) produces better results than ALL_CAPS or other conventions. Be specific with names, and test synonyms. The entity name itself influences what the model looks for. person and full name will find slightly different things. If results are missing or noisy, try rephrasing the name - person name, individual, or full name may all behave differently on your data. Labels can be descriptive phrases, not just single words. Instead of city, use capital city and population center. The extra context helps the model distinguish between entity types that might otherwise overlap. Descriptions can be full instructions. Rather than "Name of the person", write "Find the first and last name of the person mentioned in the text". Instruction-style descriptions consistently outperform short noun phrases on complex or ambiguous entities. Avoid mixing overlapping granularities in the same call. If you include both location and city, the model has to decide which label to assign to a city - and will often split results unpredictably between them. Pick one level of granularity per concept. Examples:

Notes

  • GET /v1/models lists extract and extract-1. The bare name always points at the latest version; use extract-1 to pin.
  • Streaming is not supported; the full result is returned in one response.
  • Scalar properties return at most 30 distinct values each. Nested and array definitions are bounded at 200 items.
  • Results are grouped by entity type and returned in order of appearance in the text. top_n selects the most confident entities but does not reorder them.
  • Array properties are returned in structured_result as a plain list (strings or objects), not a list of lists. If a request contains any array or nested-object property, its scalar properties return a single value each; send them in a separate request (or declare them as arrays) to get every match.
  • threshold and top_n apply to extracted scalar entities only. A classification key always returns a single chosen label.
  • The context field is always derived from the original text input - it is not generated by the model.
  • Character offsets (start, end) refer to byte positions in the original text string.
  • There is no fixed limit on the number of entity types you can define in a single request.
  • Classification keys (any property constrained to a fixed set of choices) are routed to the classification model rather than extracted. Use them for boolean or categorical fields - keeping them out of the extraction path improves accuracy for both.