> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cloudraker.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cloudraker.com/_mcp/server.

# Extract

`POST /v1/extract` takes one or more documents and a JSON Schema, and returns data shaped like your schema — plus a citation for every field pointing back at the page and region it came from.

## How it works

1. You send a **file** (a URL or a file id you already have) and an **output shape** — either an inline `schema` or a saved [`action`](#save-as-an-action).
2. CloudRaker fetches the bytes, parses the document, and runs extraction against your schema.
3. The call **holds until the run finishes** — up to `?wait=` seconds (60 by default, 120 max). If it finishes in time you get `200` with the full result. If it doesn't, you get `202` with a run id to poll instead of an error.
4. The run and its files expire on their own (`ttl`, 24 hours by default), so nothing accumulates in your organization.

## Quickstart

The sample below uses a blank IRS Form W-9 as a public, stable test document, so the call works with no local files. Replace it with your own URL when you're ready.

Want a business document rather than a blank government form? Download the sample invoice — one fictional page with a vendor, a bill-to, four line items, and totals — then send it with a [presigned upload](/developers/files#presigned-upload) and extract against its file id.

```bash title="curl"
curl -X POST https://api.cloudraker.com/v1/extract \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "name": "w9.pdf" },
    "schema": {
      "type": "object",
      "properties": {
        "business_name": { "type": ["string", "null"] },
        "tax_classification": { "type": ["string", "null"] }
      }
    }
  }'
```

```ts title="TypeScript"
const res = await fetch("https://api.cloudraker.com/v1/extract", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CLOUDRAKER_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    file: { url: "https://www.irs.gov/pub/irs-pdf/fw9.pdf", name: "w9.pdf" },
    schema: {
      type: "object",
      properties: {
        business_name: { type: ["string", "null"] },
        tax_classification: { type: ["string", "null"] },
      },
    },
  }),
});

const run = await res.json();
console.log(run.status, run.output?.value);
```

```python title="Python"
import os, requests

res = requests.post(
    "https://api.cloudraker.com/v1/extract",
    headers={"Authorization": f"Bearer {os.environ['CLOUDRAKER_API_KEY']}"},
    json={
        "file": {"url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "name": "w9.pdf"},
        "schema": {
            "type": "object",
            "properties": {
                "business_name": {"type": ["string", "null"]},
                "tax_classification": {"type": ["string", "null"]},
            },
        },
    },
)

run = res.json()
print(run["status"], run.get("output", {}).get("value"))
```

The TypeScript and Python samples are plain HTTP so they run with nothing installed. The same call is one line on either [SDK](/developers/sdks) as of 0.3.0 — `client.extract({ file, schema })` in TypeScript, `client.extract(file=…, schema=…)` in Python — and that page shows this exact extraction end to end in both languages.

## Example response

```json
{
  "object": "extract_run",
  "id": "exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
  "status": "processed",
  "expiresAt": "2026-07-26T15:57:41.907Z",
  "statusUrl": "/v1/runs/exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
  "files": [
    { "id": "b88ea8f9-20d4-4704-b379-ddee5a23c678", "name": "w9.pdf", "status": "processed" }
  ],
  "file": { "id": "b88ea8f9-20d4-4704-b379-ddee5a23c678", "name": "w9.pdf", "status": "processed" },
  "output": {
    "value": {
      "business_name": null,
      "tax_classification": "Individual/sole proprietor or single-member LLC"
    },
    "citations": {
      "tax_classification": [
        {
          "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678",
          "page": 0,
          "bbox": { "x": 0.086, "y": 0.379, "width": 0.261, "height": 0.016 },
          "text": "Individual/sole proprietor or single-member LLC",
          "confidence": 5
        }
      ]
    },
    "documents": [
      {
        "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678",
        "name": "w9.pdf",
        "status": "done",
        "value": {
          "business_name": null,
          "tax_classification": "Individual/sole proprietor or single-member LLC"
        },
        "citations": {
          "tax_classification": [
            {
              "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678",
              "page": 0,
              "bbox": { "x": 0.086, "y": 0.379, "width": 0.261, "height": 0.016 },
              "text": "Individual/sole proprietor or single-member LLC",
              "confidence": 5
            }
          ]
        }
      }
    ]
  }
}
```

## Key fields

| Field                | What it is                                                                                                                                                                                                                                                                                                                                                                                           |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `object`             | Always `extract_run`.                                                                                                                                                                                                                                                                                                                                                                                |
| `id`                 | The run id (`exr_…`). Use it with [`GET /v1/runs/:id`](/developers/quickstart#track-a-run).                                                                                                                                                                                                                                                                                                          |
| `status`             | `queued`, `processing`, `processed`, `failed`, `cancelled`, `expired`, or `needs_input`.                                                                                                                                                                                                                                                                                                             |
| `expiresAt`          | When the run and its files are purged. Controlled by `ttl`.                                                                                                                                                                                                                                                                                                                                          |
| `statusUrl`          | Path to poll for this run.                                                                                                                                                                                                                                                                                                                                                                           |
| `files[]`            | One entry per input file, with its own `status` and `error`. `file` is an alias for `files[0]` on single-file runs.                                                                                                                                                                                                                                                                                  |
| `output.value`       | The extracted data for a single-document run — an alias of `documents[0].value`.                                                                                                                                                                                                                                                                                                                     |
| `output.citations`   | Map of **field path** (`tax_classification`, `line_items[0].amount`) to the evidence behind it. Each entry carries `fileId`, the matched `text`, and `confidence` (`0`–`5`, higher is stronger grounding); documents also carry `page` (0-based) and `bbox`, a box normalized to the page as `{x, y, width, height}` in `0`–`1` with a top-left origin. Keys not applicable to a source are omitted. |
| `output.documents[]` | Always present. One entry per document, so multi-file runs have a stable shape. Its per-document `status` is the extraction's own (`done`, or `failed` with an `error`) — not the run status, and not `files[].status`, which tracks the document itself.                                                                                                                                            |

`output` appears only once `status` is `processed`.

## Configuration

Every field below is optional unless noted.

| Field          | Type                                                        | What it does                                                                                                                                                                                             |
| -------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `file`         | `{url, name?, processing?}` or `{id}`                       | One document. Exactly one of `file` or `files` is required.                                                                                                                                              |
| `files[]`      | array of the same union                                     | Up to 100 documents in one run.                                                                                                                                                                          |
| `schema`       | object                                                      | The output shape. Exactly one of `schema` or `action` is required. Must follow the [extraction schema dialect](/capabilities/extract/schema).                                                            |
| `action`       | string                                                      | A saved action — its `act_` id, its installed id, or its slug. Inline fields on the request are merged over the action's saved configuration.                                                            |
| `instructions` | string                                                      | Free-text guidance applied on top of the schema ("amounts are in EUR", "ignore the cover letter").                                                                                                       |
| `citations`    | boolean                                                     | Set `false` to skip grounding and return values only. Omit it to keep whatever the saved action specifies (grounding is on by default).                                                                  |
| `unit`         | `per_document` \| `across_documents` \| `rows_per_document` | One result per document (default), one result over the whole set, or a row array per document.                                                                                                           |
| `model`        | string                                                      | Pin a specific model instead of the default for the capability.                                                                                                                                          |
| `judge`        | boolean                                                     | Ask for a second verification pass that re-scores the extracted values. It runs only when a judge model is configured for the action; without one the extraction model's own scores are kept, unaudited. |
| `metadata`     | object                                                      | Your own key/values, echoed back on the run body (not on webhook deliveries). Max 10 KB serialized.                                                                                                      |
| `webhook`      | `{url}` or `{id}`                                           | Where to deliver the terminal event instead of polling. See [Webhooks](/developers/webhooks).                                                                                                            |
| `ttl`          | integer seconds, 1–604800                                   | How long the run and its files live. Default 24 hours, max 7 days.                                                                                                                                       |

`processing` on a file ref picks how the document is read: `auto` (default), `ocr`, `simple`, `transcribe`, or `transcribe_diarize` for audio.

## Sync vs async

The endpoint is **synchronous by default** and degrades instead of failing.

| `?wait=`  | Behavior                        |
| --------- | ------------------------------- |
| omitted   | Holds up to 60 seconds.         |
| `1`–`120` | Holds up to that many seconds.  |
| `0`       | Returns immediately with `202`. |

When the run hasn't finished by the cap, you get `202` with a handle — never a timeout error:

```json
{
  "object": "extract_run",
  "id": "exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
  "status": "processing",
  "statusUrl": "/v1/runs/exr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
}
```

Poll `GET /v1/runs/:id` (which also accepts `?wait=`) or let a `webhook` tell you. Send an `idempotency-key` header to make retries safe: a replay returns the original run and an `idempotent-replay: true` response header.

Large batches and scanned documents are the common causes of a `202`. If you always want the handle, pass `?wait=0` and never block a request thread.

## Schema inference

Don't have a schema yet? **Omit both `schema` and `action`** and CloudRaker infers one from the document, then extracts against it in the same call. Add `hints` (up to 2,000 characters) to steer what it looks for.

```bash
curl -X POST https://api.cloudraker.com/v1/extract \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "name": "w9.pdf" },
    "hints": "This is a tax form; capture its identity"
  }'
```

The finished run carries the schema it used at **`config.schema`**, alongside the usual grounded `output`:

```json
{
  "object": "extract_run",
  "id": "exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
  "status": "processed",
  "config": {
    "schema": {
      "type": "object",
      "properties": {
        "form_type": { "type": ["string", "null"], "description": "Form identifier (e.g., W-9)" },
        "form_revision_date": { "type": ["string", "null"], "description": "Form revision date (e.g., March 2024)" },
        "catalog_number": { "type": ["string", "null"], "description": "IRS catalog number for the form (e.g., 10231X)" },
        "entity_name": { "type": ["string", "null"], "description": "Name of entity or individual (Line 1)" },
        "tax_classification": { "type": ["string", "null"], "description": "Federal tax classification selected (Line 3a)" }
      }
    }
  },
  "output": {
    "value": {
      "form_type": "W-9",
      "form_revision_date": "March 2024",
      "catalog_number": "10231X",
      "entity_name": null,
      "tax_classification": null
    },
    "citations": {
      "form_type": [
        {
          "fileId": "a0375090-2f78-4fc5-a016-cb29dc43f8ea",
          "page": 0,
          "bbox": { "x": 0.091, "y": 0.037, "width": 0.065, "height": 0.036 },
          "text": "Form W-9",
          "confidence": 5
        }
      ]
    }
  }
}
```

That response is trimmed — the real call on this document inferred 29 fields. The inferred schema is plain JSON Schema in the [extraction dialect](/capabilities/extract/schema): every field is nullable, every field carries a `description`. You can send it straight back as `schema` with no editing.

`config.schema` is present whichever way the shape was decided — inferred, sent inline, or loaded from a saved action — so one code path reads the applied shape.

**Inference is for exploration, not production.** The model picks the fields, so two runs over the same document can return different field names and a different number of them (the two runs behind this page produced 32 and 29 fields, with `form_number` in one and `form_type` in the other). Nothing downstream of you can rely on that.

Use it once to discover the shape, then **pin it**: copy `config.schema` into your own request, or save it as an action and call that by name. Production callers should always send `schema` or `action`.

`hints` only applies to inference. Sending it alongside a `schema` or an `action` is a `400 invalid_request` — use [`instructions`](#configuration) to guide an extraction whose shape you already fixed.

## Save as an action

Passing the same `schema`, `instructions`, and `model` on every call gets old. Saving that configuration once as an **action** lets you call `{"file": …, "action": "invoice-lines"}` and keep the request to two fields — inline fields still win when you send them.

```bash
curl -X POST https://api.cloudraker.com/v1/actions \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "capability": "extract",
    "name": "Invoice lines",
    "config": { "schema": { "type": "object", "properties": { } }, "unit": "rows_per_document" }
  }'
```

The id and the slug are interchangeable wherever an action is referenced. [Saved actions](/capabilities/actions) covers the catalog, the merge rules, and what the saved ramp buys you; the same actions are editable in the app under [Actions](/actions/overview).

## Batch

`POST /v1/extract/batch` runs **one saved action over many documents**, minting a separate run per file — the shape you want for a nightly backfill or a queue drain.

```bash
curl -X POST https://api.cloudraker.com/v1/extract/batch \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "w9-identity",
    "files": [
      { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" },
      { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" },
      { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" }
    ],
    "metadata": { "job": "nightly-backfill" }
  }'
```

The response is always **`202`** — batches never run synchronously:

```json
{
  "object": "extract_batch",
  "count": 3,
  "runs": [
    { "id": "exr_01KYDQEEK5FQT6CWENZZKEA2Z5", "statusUrl": "/v1/runs/exr_01KYDQEEK5FQT6CWENZZKEA2Z5", "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" } },
    { "id": "exr_01KYDQEEK5PMS75WBRJ962NA53", "statusUrl": "/v1/runs/exr_01KYDQEEK5PMS75WBRJ962NA53", "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" } },
    { "id": "exr_01KYDQEEK5413ASVYK21GJ375J", "statusUrl": "/v1/runs/exr_01KYDQEEK5413ASVYK21GJ375J", "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" } }
  ]
}
```

There is no batch object and no batch id — `runs[]` is the whole handle. Each entry is either `{id, statusUrl, file}` for an accepted file or `{file, error: {code, message}}` for one rejected at admission, so a bad URL in the list never sinks the rest.

| Field      | Type               | Rules                                                                                |
| ---------- | ------------------ | ------------------------------------------------------------------------------------ |
| `action`   | string             | **Required.** A saved extract action, by id or slug. A batch has no inline arm.      |
| `files[]`  | array of file refs | 1–100 entries, the same `{url, name?, processing?}` or `{id}` union as a single run. |
| `metadata` | object             | Applied to every run in the batch — the handle you filter on later.                  |
| `webhook`  | `{url}` or `{id}`  | Delivered per run, not once per batch.                                               |
| `ttl`      | integer seconds    | Applied to every run.                                                                |

`Idempotency-Key` is **not honoured on a batch** — one key cannot address N runs, so a retried batch fans out a second time. If a batch call fails ambiguously (a `429`, a timeout), list its runs by shared `metadata` before resending.

The body is **strict**: `schema` and `hints` are rejected with `400 invalid_request` ("Unrecognized key") rather than silently ignored. Save the schema as an action first — that is the point of the endpoint. A saved [sign](/capabilities/sign) action passed as `action` is also a `400`.

### Track a batch

The batch costs one [rate-limit](/developers/rate-limits) token, and shared `metadata` is how you find its runs again:

```bash
curl -G "https://api.cloudraker.com/v1/runs" \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  --data-urlencode "object=extract_run" \
  --data-urlencode "metadata.job=nightly-backfill"
```

That returns the three runs and their statuses in one call instead of three polls — see [listing runs](/developers/runs#list-runs). For a per-run result, `GET /v1/runs/:id` stays authoritative, and a `webhook` removes the polling entirely.

## Next steps

#### [Extraction schema dialect](/capabilities/extract/schema)

The five rules your `schema` must satisfy, and what each rejection means.

#### [Parse](/capabilities/parse)

Get clean markdown and structured JSON without defining a schema.

#### [Saved actions](/capabilities/actions)

Save a schema once and reference it by id or slug.

#### [Runs](/developers/runs)

Statuses, TTL, downloading outputs, and keeping a result.