Overview
Extract is called through the OpenAI-compatiblePOST /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) - itsdescriptionsays 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"withproperties) - a structured sub-schema - An array of objects (
type: "array"with objectitems) - a repeated structure, such as line items - A classification key - a property with an
enum, or aoneOfofconstbranches, routed to the classifier (see Classification keys)
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 standardchat.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 thex-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
content is:
Extracting from an image or PDF
Pass a base64-encoded photo, scan, or PDF throughextra_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.
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 instructured_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, arubric 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 optionaldescriptionbecomes the shared rubric.oneOfofconstbranches - each branch’sdescriptionis that label’s rubric (per-label rubrics).
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.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
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
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/modelslistsextractandextract-1. The bare name always points at the latest version; useextract-1to 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_nselects the most confident entities but does not reorder them. - Array properties are returned in
structured_resultas 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. thresholdandtop_napply to extracted scalar entities only. A classification key always returns a single chosen label.- The
contextfield is always derived from the originaltextinput - it is not generated by the model. - Character offsets (
start,end) refer to byte positions in the originaltextstring. - 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.
