Extract

Extract

Turn documents into structured JSON with per-field citations, in one call.

View as Markdown

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.
  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 and extract against its file id.

$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"] }
> }
> }
> }'

The TypeScript and Python samples are plain HTTP so they run with nothing installed. The same call is one line on either SDK 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

1{
2 "object": "extract_run",
3 "id": "exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
4 "status": "processed",
5 "expiresAt": "2026-07-26T15:57:41.907Z",
6 "statusUrl": "/v1/runs/exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
7 "files": [
8 { "id": "b88ea8f9-20d4-4704-b379-ddee5a23c678", "name": "w9.pdf", "status": "processed" }
9 ],
10 "file": { "id": "b88ea8f9-20d4-4704-b379-ddee5a23c678", "name": "w9.pdf", "status": "processed" },
11 "output": {
12 "value": {
13 "business_name": null,
14 "tax_classification": "Individual/sole proprietor or single-member LLC"
15 },
16 "citations": {
17 "tax_classification": [
18 {
19 "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678",
20 "page": 0,
21 "bbox": { "x": 0.086, "y": 0.379, "width": 0.261, "height": 0.016 },
22 "text": "Individual/sole proprietor or single-member LLC",
23 "confidence": 5
24 }
25 ]
26 },
27 "documents": [
28 {
29 "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678",
30 "name": "w9.pdf",
31 "status": "done",
32 "value": {
33 "business_name": null,
34 "tax_classification": "Individual/sole proprietor or single-member LLC"
35 },
36 "citations": {
37 "tax_classification": [
38 {
39 "fileId": "b88ea8f9-20d4-4704-b379-ddee5a23c678",
40 "page": 0,
41 "bbox": { "x": 0.086, "y": 0.379, "width": 0.261, "height": 0.016 },
42 "text": "Individual/sole proprietor or single-member LLC",
43 "confidence": 5
44 }
45 ]
46 }
47 }
48 ]
49 }
50}

Key fields

FieldWhat it is
objectAlways extract_run.
idThe run id (exr_…). Use it with GET /v1/runs/:id.
statusqueued, processing, processed, failed, cancelled, expired, or needs_input.
expiresAtWhen the run and its files are purged. Controlled by ttl.
statusUrlPath 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.valueThe extracted data for a single-document run — an alias of documents[0].value.
output.citationsMap of field path (tax_classification, line_items[0].amount) to the evidence behind it. Each entry carries fileId, the matched text, and confidence (05, higher is stronger grounding); documents also carry page (0-based) and bbox, a box normalized to the page as {x, y, width, height} in 01 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.

FieldTypeWhat it does
file{url, name?, processing?} or {id}One document. Exactly one of file or files is required.
files[]array of the same unionUp to 100 documents in one run.
schemaobjectThe output shape. Exactly one of schema or action is required. Must follow the extraction schema dialect.
actionstringA saved action — its act_ id, its installed id, or its slug. Inline fields on the request are merged over the action’s saved configuration.
instructionsstringFree-text guidance applied on top of the schema (“amounts are in EUR”, “ignore the cover letter”).
citationsbooleanSet false to skip grounding and return values only. Omit it to keep whatever the saved action specifies (grounding is on by default).
unitper_document | across_documents | rows_per_documentOne result per document (default), one result over the whole set, or a row array per document.
modelstringPin a specific model instead of the default for the capability.
judgebooleanAsk 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.
metadataobjectYour 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.
ttlinteger seconds, 1–604800How 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
omittedHolds up to 60 seconds.
1120Holds up to that many seconds.
0Returns immediately with 202.

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

1{
2 "object": "extract_run",
3 "id": "exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
4 "status": "processing",
5 "statusUrl": "/v1/runs/exr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
6}

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.

$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:

1{
2 "object": "extract_run",
3 "id": "exr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
4 "status": "processed",
5 "config": {
6 "schema": {
7 "type": "object",
8 "properties": {
9 "form_type": { "type": ["string", "null"], "description": "Form identifier (e.g., W-9)" },
10 "form_revision_date": { "type": ["string", "null"], "description": "Form revision date (e.g., March 2024)" },
11 "catalog_number": { "type": ["string", "null"], "description": "IRS catalog number for the form (e.g., 10231X)" },
12 "entity_name": { "type": ["string", "null"], "description": "Name of entity or individual (Line 1)" },
13 "tax_classification": { "type": ["string", "null"], "description": "Federal tax classification selected (Line 3a)" }
14 }
15 }
16 },
17 "output": {
18 "value": {
19 "form_type": "W-9",
20 "form_revision_date": "March 2024",
21 "catalog_number": "10231X",
22 "entity_name": null,
23 "tax_classification": null
24 },
25 "citations": {
26 "form_type": [
27 {
28 "fileId": "a0375090-2f78-4fc5-a016-cb29dc43f8ea",
29 "page": 0,
30 "bbox": { "x": 0.091, "y": 0.037, "width": 0.065, "height": 0.036 },
31 "text": "Form W-9",
32 "confidence": 5
33 }
34 ]
35 }
36 }
37}

That response is trimmed — the real call on this document inferred 29 fields. The inferred schema is plain JSON Schema in the extraction dialect: 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 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.

$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 covers the catalog, the merge rules, and what the saved ramp buys you; the same actions are editable in the app under Actions.

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.

$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:

1{
2 "object": "extract_batch",
3 "count": 3,
4 "runs": [
5 { "id": "exr_01KYDQEEK5FQT6CWENZZKEA2Z5", "statusUrl": "/v1/runs/exr_01KYDQEEK5FQT6CWENZZKEA2Z5", "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" } },
6 { "id": "exr_01KYDQEEK5PMS75WBRJ962NA53", "statusUrl": "/v1/runs/exr_01KYDQEEK5PMS75WBRJ962NA53", "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" } },
7 { "id": "exr_01KYDQEEK5413ASVYK21GJ375J", "statusUrl": "/v1/runs/exr_01KYDQEEK5413ASVYK21GJ375J", "file": { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" } }
8 ]
9}

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.

FieldTypeRules
actionstringRequired. A saved extract action, by id or slug. A batch has no inline arm.
files[]array of file refs1–100 entries, the same {url, name?, processing?} or {id} union as a single run.
metadataobjectApplied to every run in the batch — the handle you filter on later.
webhook{url} or {id}Delivered per run, not once per batch.
ttlinteger secondsApplied 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 action passed as action is also a 400.

Track a batch

The batch costs one rate-limit token, and shared metadata is how you find its runs again:

$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. For a per-run result, GET /v1/runs/:id stays authoritative, and a webhook removes the polling entirely.

Next steps