Skip to main content

Data Extraction API

The extract endpoint pulls fields you name out of unstructured text — invoices, emails, resumes, support tickets, contracts — and returns them as typed JSON. There is no schema to train and no template to maintain: describe the fields in the request and the endpoint fills in the ones the document actually states.

Nearly every value is grounded: the model has to quote the span of the text a value came from, and the server verifies that quote really occurs in the document. Anything it cannot point at is reported missing instead of guessed. The one exception is boolean fields, whose true/false answers are judgments about the text rather than spans in it and so are not span-verified — see Tips. Values are also coerced to the type you declare, so total comes back as 1299.0 rather than "$1,299.00", and dates come back as YYYY-MM-DD.

Where the sentiment and tone endpoints answer "what kind of text is this?", extract answers "what is in it?". For a value that is nowhere stated in the document and has to be inferred or condensed, use summarize instead — see Tips.

Try it out

Sample Code

curl -X POST https://api.sapling.ai/api/v1/extract \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "text":"Invoice INV-1042 for Acme Corp, total $1,299.00, due March 5, 2026.", "fields": ["invoice_number", {"name": "total", "type": "number", "description": "Amount due", "required": true}, {"name": "due_date", "type": "date"}, "purchase_order"], "context": "A vendor invoice"}'

Sample Response

{
"data": {
"invoice_number": "INV-1042",
"total": 1299.0,
"due_date": "2026-03-05",
"purchase_order": null
},
"fields": [
{"name": "invoice_number", "type": "string", "value": "INV-1042",
"evidence": "Invoice INV-1042", "found": true},
{"name": "total", "type": "number", "value": 1299.0,
"evidence": "total $1,299.00", "found": true},
{"name": "due_date", "type": "date", "value": "2026-03-05",
"evidence": "due March 5, 2026", "found": true},
{"name": "purchase_order", "type": "string", "value": null,
"evidence": "", "found": false}
],
"missing": ["purchase_order"]
}

A document that yields nothing is still a successful 200: data is all null, every found is false, and missing lists all of your field names.

Batch Requests

To run the same extraction over many short documents — a folder of receipts, a queue of form submissions — in one request, send texts (a list of 1–10 strings) instead of text. The same fields and context apply to every item, and the combined length of all items may be up to 10,000 characters (the same cap as a single text). Exactly one of text and texts must be provided.

curl -X POST https://api.sapling.ai/api/v1/extract \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "texts": ["Invoice INV-1042 for Acme Corp.", "Invoice INV-2077 for Globex."], "fields": ["invoice_number", "customer_name"]}'

The batch response is {"results": [...]} with one entry per input, in input order; each entry has exactly the single-response shape above (data, fields, missing):

{
"results": [
{
"data": {"invoice_number": "INV-1042", "customer_name": "Acme Corp"},
"fields": [
{"name": "invoice_number", "type": "string", "value": "INV-1042",
"evidence": "Invoice INV-1042", "found": true},
{"name": "customer_name", "type": "string", "value": "Acme Corp",
"evidence": "Acme Corp", "found": true}
],
"missing": []
},
{
"data": {"invoice_number": "INV-2077", "customer_name": "Globex"},
"fields": [
{"name": "invoice_number", "type": "string", "value": "INV-2077",
"evidence": "Invoice INV-2077", "found": true},
{"name": "customer_name", "type": "string", "value": "Globex",
"evidence": "Globex", "found": true}
],
"missing": []
}
]
}

Items are billed individually (a batch of N costs the same as N single requests), and each item is cached the same way as a single request — batches and single calls share the cache. If any item fails to extract, the whole request returns a 502 and nothing is billed; the items that did succeed are already cached, so a retry only re-runs the failures.

Request Parameters

POST to https://api.sapling.ai/api/v1/extract

key: String
32-character API key. Can also be supplied via the Authorization header as a bearer token; if both are provided, the key parameter takes precedence.

text: String
The document to extract from, up to 10,000 characters (measured on the raw input, before any HTML is stripped). Plain text or HTML — tags are stripped server-side. Text that is empty after stripping returns a 400. The text is treated as untrusted data: instructions inside it are ignored and only its content is extracted from. Provide exactly one of text and texts.

texts: List[String]
Batch form (see Batch Requests): 1–10 documents to extract from in one request, each treated exactly like text above, with a combined length of up to 10,000 characters. An item that is empty after stripping returns a 400 naming its index. The response becomes {"results": [...]}, one entry per item in input order.

fields: List[String | Object]
The fields to look for: between 1 and 20 entries. Each entry is either a string (the field name) or an object with the keys below; any other key returns a 400. Names are at most 50 characters, non-empty, and case-insensitively unique across the list.

  • name String — the field name, echoed back as the key in data.
  • type String, optional, defaults to string — one of string, number, integer, boolean, date or list. Unknown types return a 400.
  • description String, optional — up to 200 characters saying what this field means; the cheapest way to fix a field that is being read wrongly.
  • required Boolean, optional, defaults to false — a hint that the field is expected to be present. It never forces a value: a required field that the text does not state is still reported in missing rather than guessed.

context: String, optional
Up to 500 characters describing what the document is and how to read ambiguous fields, for example "A vendor invoice; total means the amount due including tax". Guidance only — it can never add a field outside fields.

Response Parameters

data: Object
A convenience {field name: value} map. Every requested field is present, with null where the document did not state it, so you can index it without checking for missing keys.

fields: List[Object]
One entry per requested field, in the order you requested them. Each has:

  • name: the field name as submitted.
  • type: the type the value was coerced to (string when you did not declare one).
  • value: the extracted value, or null when not found.
  • evidence: the verbatim span of text the value was taken from, or "" when not found. Useful for highlighting the source in your own UI and for spot-checking results.
  • found: whether a value was extracted.

missing: List[String]
The names of the fields the document did not yield, in request order. Empty when everything was found.

Types

typeValue in the response
stringThe extracted text.
numberA float. Currency symbols, thousands separators and trailing units are stripped ("$1,299.00"1299.0).
integerA whole number; a value with a fractional part is reported missing rather than rounded.
booleantrue or false.
dateAlways YYYY-MM-DD. "March 5, 2026", "5 March 2026" and ISO timestamps all normalize; ambiguous numeric forms like 03/04/2026 are deliberately not guessed.
listA list of strings, deduplicated, at most 20 items.

A value that will not convert to the declared type is reported missing rather than returned in the wrong type, so you never have to type-check data yourself.

Errors

StatusMeaning
400Validation error — missing or oversized text, text empty after stripping, both or neither of text/texts provided, an invalid or over-length texts batch, context over 500 characters, or invalid fields (empty, more than 20, a duplicate or oversized name, an unknown type, or an unrecognized key inside a field object). The body is {"msg": "..."}.
401 / 403Missing or invalid API key.
429Rate limit exceeded or key over capacity. Each call draws at least 100 tokens from the burst bucket.
502{"msg": "Unexpected error extracting from text."} — the extraction model failed; safe to retry. Not billed and not cached.

Tips

  • Describe the ambiguous fields. A bare name works for invoice_number, but a one-line description is what separates total (amount due, including tax) from subtotal, or start_date from signed_date. This is usually the first thing to reach for when a field comes back wrong.
  • Declare types. {"name": "amount", "type": "number"} gets you a float you can sum, and "type": "date" gets you an ISO date you can sort — no parsing on your side, and values that do not convert are reported missing rather than returned in a shape your code did not expect.
  • Use list for repeated items. Line items, attendees, skills, ticket tags — one list field beats item_1, item_2, item_3, and each item is grounded in the text individually.
  • Extraction is not inference. Values must be stated in the document; the grounding check discards anything the model cannot quote. boolean fields are the exception (a true/false answer such as {"name": "is_urgent", "type": "boolean"} is a judgment about the text rather than a span in it). For a value that has to be inferred or condensed rather than quoted, use summarize.
  • Treat missing as normal. Documents vary, and an all-null result is a successful response, not an error. Branch on found (or on missing) rather than assuming every field will be filled, and use required to mark the fields whose absence should raise a flag in your own workflow.
  • Show the evidence. When a person reviews extractions, rendering evidence next to each value makes verification a glance instead of a re-read of the whole document.
  • Results are cached on Sapling's side for about three days keyed on the text, fields and context, so re-running an identical request is fast — but the same document against a different field set is separate work.