> 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. It returns data shaped like your schema. Add `"citations": true` to ground every field. Each field then points at the page and region it came from, or is declared absent.

## How it works

1. You send a **file** (a URL or a file id you already have) and an **output shape**. The shape is 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 the run finishes in time, you get `200` with the full result. If not, you get `202` with a run id to poll. This is not an error.
4. The run and its files expire on their own (`ttl`, 24 hours by default). Nothing accumulates in your organization.

## Quickstart

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

If you prefer a business document over a blank government form, download the sample invoice. It is one fictional page with a vendor, a bill-to, four line items, and totals. Send it with a [presigned upload](/paperwork/developers/files#presigned-upload), then 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" },
    "citations": true,
    "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" },
    citations: true,
    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"},
        "citations": True,
        "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](/paperwork/developers/sdks) as of 0.3.0: `client.extract({ file, schema })` in TypeScript, `client.extract(file=…, schema=…)` in Python. 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
        }
      ],
      "business_name": [
        { "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678", "notFound": true }
      ]
    },
    "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
            }
          ],
          "business_name": [
            { "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678", "notFound": true }
          ]
        }
      }
    ]
  }
}
```

## Key fields

| Field                     | What it is                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `object`                  | Always `extract_run`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `id`                      | The run id (`exr_…`). Use it with [`GET /v1/runs/:id`](/paperwork/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`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `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`        | **Present only when the run was grounded.** Absent otherwise, never an empty object. Maps each **field key** to the evidence behind it (`tax_classification`; `rows_per_document` rows carry the row index prefix, `[0].amount`). Each entry carries `fileId`, the matched `text`, and `confidence` (`0`–`5`, higher is stronger grounding). Documents also carry `page` (0-based) and `bbox`. The `bbox` is normalized to the page as `{x, y, width, height}` in `0`–`1` with a top-left origin. Audio citations carry no location at all — see [Cite against a recording](#cite-against-a-recording). Keys not applicable to a source are omitted. A `{"fileId": …, "notFound": true}` entry says the value is not in the documents. It carries nothing else, and the field's value is `null`. |
| `output.citationsOmitted` | `true` when the citations were dropped because the result exceeded the size budget. Output-level only. It never appears on a document.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `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`). It is not the run status. It is also not `files[].status`, which tracks the document itself.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |

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

### Guaranteed grounding

With `citations` enabled, every filled field comes back **cited or declared absent**. Nothing is left ambiguous. A field the extractor filled without evidence gets a deterministic second model pass. That pass finds the citation or declares the value not present. A field declared not present is returned as `null` with a `notFound` citation, never a guess.

### Cite against a recording

Extraction over audio grounds each field in the transcript, not in the audio timeline. An audio citation names the `fileId`, the quoted `text`, and a `confidence`. It carries no `page`, no `bbox`, and no timecode.

The transcript supplies the timing. Fetch it, then match the citation against it:

#### Fetch the transcript

`GET /v1/files/:id`, then fetch `urls.json`. See [Audio transcripts](/paperwork/developers/files#audio-transcripts) for the shape.

#### Match the citation text to a segment

Normalize both strings first. The extractor re-punctuates what it quotes, so compare on letters and digits only, in lower case.

#### Seek to that segment

The matching segment's `start` is the position in seconds. Use `words[]` when you need a tighter offset.

Two details make the match reliable. Join every segment into one normalized string, so a quote that crosses a segment boundary still resolves to where it begins. Then, if the whole quote does not match, retry with its first five to eight words — the platform trims long quotes at the tail, rarely at the head.

`POST /v1/extract` never inlines the transcript. Only [`GET /process/:id`](/paperwork/developers/process-api#inlined-content) does, with `include=results,content&format=json`. On the verbs, plan for the extra file fetch.

## 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](/paperwork/capabilities/extract/schema). Can carry optional [field types](/paperwork/capabilities/extract/field-types) that mark a value as money, a date, or a phone number. |
| `action`       | string                                                      | A saved config, by its `act_` id, its installed id, or its slug. Inline fields on the request are merged over the saved configuration.                                                                                                                                                       |
| `instructions` | string                                                      | Free-text guidance applied on top of the schema ("amounts are in EUR", "ignore the cover letter").                                                                                                                                                                                           |
| `citations`    | boolean                                                     | Grounding is **off by default**. Set `true` to get a citation for every field. Omit it to keep the saved config's setting.                                                                                                                                                                   |
| `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.                                                                                                                                                                                               |
| `judge`        | boolean                                                     | Request a second pass that re-scores the extracted values against the evidence they cite. It **needs `citations: true`**: the judge audits citations, so with grounding off it has nothing to read and changes nothing.                                                                      |
| `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](/paperwork/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.

### One result over many documents

`across_documents` extracts each document independently. It then deterministically folds the per-document results into one record. It is not a joint pass over all files at once.

Per field, a real value always beats an empty one. The best-grounded value wins. With `citations` on, that is the value with the strongest citation. On ties, the earliest document in request order wins. With grounding off, every comparison is a tie.

Values are taken whole, together with their citations. Arrays are **not** unioned across documents. A list field comes from exactly one document.

Use it for related but independent documents that each contribute fields to one record. Examples: an application form plus a bank statement, a contract plus its amendment. It is not built for fragments of a single source. For a recording split into parts, concatenate the audio into one file before upload. Extraction then sees one continuous document.

## 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 has not 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 use a `webhook`. 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`. Never block a request thread.

## Schema inference

If you do not have a schema yet, **omit both `schema` and `action`**. CloudRaker infers a schema 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" },
    "citations": true,
    "hints": "This is a tax form; capture its identity"
  }'
```

The finished run carries the schema it used at **`config.schema`**, alongside the `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
        }
      ],
      "entity_name": [{ "fileId": "a0375090-2f78-4fc5-a016-cb29dc43f8ea", "notFound": true }],
      "tax_classification": [{ "fileId": "a0375090-2f78-4fc5-a016-cb29dc43f8ea", "notFound": true }]
    }
  }
}
```

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

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

**Inference is for exploration, not production.** The model picks the fields. Two runs over the same document can return different field names and a different field count. 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 inference 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 must always send `schema` or `action`.

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

## Save it as a config

Passing the same `schema`, `instructions`, and `model` on every call is repetitive. Save that configuration once in the extract config library. Then 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/extract/configs \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Invoice lines",
    "config": { "schema": { "type": "object", "properties": { } }, "unit": "rows_per_document", "grounding": true }
  }'
```

`GET`, `PATCH` and `DELETE /v1/extract/configs/{idOrSlug}` read, change and remove a config. `GET /v1/extract/configs` lists them. The flat `/v1/actions` routes still work as a deprecated alias over the same objects.

Citations are an add-on you enable per request or on the saved config. A saved config uses the setting's internal name, `grounding`. It grounds its runs only when the config says `"grounding": true`, or when the request itself sends `"citations": true`.

The id and the slug are interchangeable wherever a config is referenced. [Saved configs](/paperwork/capabilities/actions) covers the catalog, the merge rules, and the benefits of the saved ramp. The same configurations are editable in the app under [Actions](/workspace/actions/overview).

## Batch

`POST /v1/extract/batch` runs **one saved config over many documents**. It mints a separate run per file. Use it 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 `{id, statusUrl, file}` for an accepted file. A file rejected at admission gets `{file, error: {code, message}}` instead. 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.                         |
| `citations` | boolean            | Set `true` to ground every run in the batch, overriding the saved config. Off by default, like a single run. |
| `metadata`  | object             | Applied to every run in the batch. This is 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 you resend.

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

### Track a batch

The batch costs one [rate-limit](/paperwork/developers/rate-limits) token. 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](/paperwork/developers/runs#list-runs). For a per-run result, `GET /v1/runs/:id` stays authoritative. A `webhook` removes the polling entirely.

## Next steps

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

The five rules your `schema` must satisfy, and the meaning of each rejection.

#### [Field types](/capabilities/extract/field-types)

Mark a field as currency, a date, or a phone number for better reads.

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

Get clean markdown and structured JSON without a schema.

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

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

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

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