> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ndi.nace.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Extract

> Pull JSON-schema-shaped structured data out of a document

Extract pulls specific fields out of a document as structured JSON. You describe the data you need with a JSON schema — field names and, crucially, `description`s guide the extraction — and NDI returns values matching it, handling parsing, long documents, and repeating data (arrays) internally.

Long documents are processed in content batches well below the model's context window: scalar fields take the first value the document states; top-level array fields (line items, transactions) are extracted batch-by-batch and concatenated, so long lists don't get truncated.

| Endpoint              | Behavior                                                                |
| --------------------- | ----------------------------------------------------------------------- |
| `POST /extract`       | Starts the job and waits for the result (bounded by `timeout_seconds`). |
| `POST /extract_async` | Starts the job and returns `202 {"job_id": …}` immediately.             |

## Schema rules

NDI accepts a restricted subset of JSON Schema:

* The root must be `{"type": "object", "properties": {…}}`.
* Allowed keywords: `type`, `properties`, `items`, `required`, `description`, `enum`, `title`.
* Allowed types: `object`, `array`, `string`, `number`, `integer`, `boolean`, `null`.
* Nesting is limited to 5 levels; at most 100 properties total.
* `$ref`, `$defs`, `anyOf`/`oneOf`/`allOf`/`not`, formats, and defaults are **not** supported — encode such constraints in the field `description` instead.

Schemas outside the subset are rejected with a `422` before the job starts. Fields the document does not contain come back as `null` (scalars) or `[]` (arrays) — a `required` field missing from the document does not fail the job.

Extract never returns partial data silently: a document that exceeds the extraction capacity (\~1,300 standard pages) fails with an `extract_content_exceeds_limit` error rather than truncating — split such files and extract the parts separately.

## Request

<ParamField query="timeout_seconds" type="number" default="120">
  **Sync endpoint only.** Max seconds to wait, between 1 and 300.
</ParamField>

<ParamField body="file" type="object" required>
  The document to extract from. Same shape as everywhere else — `source_url`
  (+ `file_name` for `s3://`/`https://`). See [Files & sources](/concepts/files).
  Supported extensions are the same as [parse](/api-reference/parse), plus `.md`/`.mdx`.
</ParamField>

<ParamField body="schema" type="object" required>
  The JSON schema describing what to extract (see the rules above). Write each
  field's `description` as if instructing a careful analyst — it directly
  drives accuracy.
</ParamField>

<ParamField body="markdown_url" type="string">
  `markdown_url` from a **prior parse of the same file** (an `s3://` URL
  returned by NDI). When present, extract skips its internal parse — faster
  and cheaper if you already parsed the document. Ignored for spreadsheets.
</ParamField>

<ParamField body="idempotency_key" type="string">
  Up to 128 characters. See [Idempotency](/concepts/jobs#idempotency).
</ParamField>

## Result

<ResponseField name="result_type" type="&#x22;extract&#x22;">
  Discriminator — always `"extract"`.
</ResponseField>

<ResponseField name="status" type="&#x22;success&#x22; | &#x22;failed&#x22;">
  Outcome of the extraction run; `error` carries detail on failure.
</ResponseField>

<ResponseField name="data" type="object | null">
  The extracted values, shaped exactly like your schema. Scalars the document
  does not contain are `null`; arrays with no matches are `[]`.
</ResponseField>

<ResponseField name="json_url" type="string | null">
  The same content as `data`, persisted as a JSON artifact (time-limited
  download URL).
</ResponseField>

<ResponseField name="page_count" type="integer">
  Document pages processed (`0` for spreadsheets).
</ResponseField>

<ResponseField name="sheet_count" type="integer">
  Spreadsheet sheets processed (`0` for documents).
</ResponseField>

## Examples

<CodeGroup>
  ```bash Invoice fields + line items theme={"dark"}
  curl -s "$NDI_BASE/extract" \
    -H "X-API-Key: $NDI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file": { "source_url": "ndi://file/7f4d2a10-3b7e-4c25-9f61-8f0f4f0a1b2c" },
      "schema": {
        "type": "object",
        "properties": {
          "invoice_number": { "type": "string", "description": "The invoice identifier, e.g. INV-2026-0042." },
          "total_amount": { "type": "number", "description": "Grand total due, without currency symbols." },
          "currency": { "type": "string", "description": "ISO 4217 currency code of the total." },
          "line_items": {
            "type": "array",
            "description": "Every billed line item on the invoice.",
            "items": {
              "type": "object",
              "properties": {
                "description": { "type": "string" },
                "quantity": { "type": "number" },
                "amount": { "type": "number", "description": "Line total, without currency symbols." }
              },
              "required": ["description", "amount"]
            }
          }
        },
        "required": ["invoice_number", "total_amount"]
      }
    }'
  ```

  ```bash Reuse a prior parse (async) theme={"dark"}
  curl -s "$NDI_BASE/extract_async" \
    -H "X-API-Key: $NDI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file": {
        "source_url": "https://example.com/annual-report.pdf",
        "file_name": "annual-report.pdf"
      },
      "markdown_url": "s3://…/jobs/…/parse/annual-report/document.md",
      "schema": {
        "type": "object",
        "properties": {
          "company_name": { "type": "string", "description": "Legal name of the reporting entity." },
          "fiscal_year": { "type": "integer", "description": "Fiscal year the report covers." },
          "revenue": { "type": "number", "description": "Total revenue for the fiscal year in USD." }
        }
      }
    }'
  ```
</CodeGroup>

```json Result theme={"dark"}
{
  "job_id": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b",
  "action": "extract",
  "status": "succeeded",
  "result": {
    "result_type": "extract",
    "status": "success",
    "error": null,
    "data": {
      "invoice_number": "INV-2026-0042",
      "total_amount": 18500.0,
      "currency": "USD",
      "line_items": [
        { "description": "Q4 maintenance services", "quantity": 1, "amount": 17000.0 },
        { "description": "Emergency call-out fee", "quantity": 2, "amount": 1500.0 }
      ]
    },
    "json_url": "https://…(time-limited)…/invoice_extraction.json",
    "page_count": 3,
    "sheet_count": 0
  },
  "error": null,
  "created_at": "2026-07-14T10:20:05Z",
  "started_at": "2026-07-14T10:20:06Z",
  "completed_at": "2026-07-14T10:20:41Z"
}
```
