---
name: cloudraker-api
description: >-
  Use when writing or debugging code against the CloudRaker paperwork API
  (api.cloudraker.com, the /v1 endpoints) — extracting structured data from
  documents, parsing, redacting PII, filling form PDFs, collecting e-signatures,
  or running pipelines. Triggers on CLOUDRAKER_API_KEY, api.cloudraker.com,
  @cloudraker/api, the `cloudraker` Python package, or run ids like exr_/par_/
  rdr_/flr_/sgr_/plr_.
---

# CloudRaker API

One REST API over `https://api.cloudraker.com` that turns documents into data
and documents into other documents. Six verbs, one run lifecycle.

**Fetch the current contract before writing code** — this file is a snapshot:

```bash
curl -s https://docs.cloudraker.com/developers/agents.md
```

Full OpenAPI: `https://docs.cloudraker.com/openapi.json` — the full gateway
spec, batch and run listing included, re-exported whenever the surface changes;
where it and this file ever disagree, this file is right. Any docs page as
Markdown: append `.md` to its URL. Docs index: `https://docs.cloudraker.com/llms.txt`.

## Auth

```
Authorization: Bearer $CLOUDRAKER_API_KEY
Content-Type: application/json
```

The key belongs to exactly one organization and *is* the tenant — never pass a
tenant, account, or workspace id. Read the key from the environment; never
commit it. Staging is `https://api.staging.raker.one`, development is
`https://api.dev.raker.one`, and keys never cross environments.

## The six verbs

All are `POST`, all create a run, all are synchronous by default with
`?wait=` (0–120 s, default 60). Minimal bodies:

```jsonc
// extract: document -> JSON matching your schema, with per-field citations
POST /v1/extract
{ "file": { "url": "https://example.com/invoice.pdf" },
  "schema": { "type": "object", "properties": { "total": { "type": ["number", "null"] } } } }

// extract, exploration mode: omit schema, add hints -> a schema is inferred and applied
POST /v1/extract
{ "file": { "url": "…" }, "hints": "It's an invoice; capture the header fields" }

// extract at volume: one saved action over many files; always 202
POST /v1/extract/batch
{ "action": "invoice-header", "files": [{ "url": "…" }, { "url": "…" }] }

// parse: document -> markdown + structured JSON, no schema
POST /v1/parse
{ "file": { "url": "…" } }

// redact: destructive PII removal, returns a NEW file
POST /v1/redact
{ "file": { "id": "<fileId>" }, "categories": ["ssn", "ein"], "mode": "targeted" }

// fill: fill a form PDF from source documents
POST /v1/fill
{ "template": { "id": "<templateId>" }, "files": [{ "id": "<fileId>" }],
  "review": "none", "output": "flattened" }

// sign: e-signature envelope; always 202, status needs_input
POST /v1/sign
{ "file": { "id": "<fileId>" }, "signers": [{ "name": "Jane Doe", "email": "jane@example.com" }] }

// pipeline: several capabilities over one file set, in PARALLEL (never chained)
POST /v1/pipeline
{ "files": [{ "url": "…" }],
  "steps": [{ "extract": { "schema": {} } }, { "redact": { "categories": ["ssn"] } }] }
```

Optional on every verb: `metadata` (your ids, ≤10 KB, echoed and filterable),
`webhook` ({url} or {id}), `ttl` (seconds, default 86400, max 604800), and the
`idempotency-key` header. `extract`/`redact`/`fill`/`sign` also accept
`action: "<id or slug>"` in place of inline config; inline fields override the
saved ones.

## Files

A file reference is always one of two shapes — there is no multipart upload:

```jsonc
{ "url": "https://…", "name": "invoice.pdf", "processing": "auto" }
{ "id": "a04d6597-4e34-4a99-94ea-964c289a4c68" }
```

`processing`: `auto` | `ocr` | `simple` | `transcribe` | `transcribe_diarize`.
Single-file verbs take `file`, multi-file verbs take `files[]` (≤100); sending
both is a 400.

Persist a file with `POST /v1/files`: `{url, name?}` (the source must serve a
`Content-Length`) or `{name, mimeType}` → `uploadUrl` (15 min) that you `PUT`
the bytes to with the *identical* `Content-Type`, then poll `GET /v1/files/:id`
until `status: "ready"`. Reuse file ids instead of re-sending URLs: no re-fetch,
no re-parse, lower cost.

## Runs

```
GET    /v1/runs                   list; filters below
GET    /v1/runs/:id               status + result; accepts ?wait=
POST   /v1/runs/:id/cancel        stop in-flight work
DELETE /v1/runs/:id               purge now (204, idempotent)
POST   /v1/runs/:id/keep          persist into a space; clears the TTL
GET    /v1/runs/:id/output/:name  302 to a signed download URL
```

Prefixes: `exr_` extract, `par_` parse, `rdr_` redact, `flr_` fill, `sgr_` sign,
`plr_` pipeline. Ids are opaque — never parse them.

Statuses: `queued`, `processing`, `processed`, `failed`, `cancelled`, `expired`,
`needs_input`. `output` exists only at `processed`. `needs_input` = a human is
required (fill review via `tasks[]`, or an open signature envelope via
`envelopeUrl`).

`GET /v1/runs` accepts `object`, `status` (all but `queued`), `limit` (1–50,
default 20), `cursor`, and up to three `metadata.<key>=<value>` pairs. It returns
`{object:"list", data, has_more, cursor}` and is **eventually consistent** —
`GET /v1/runs/:id` is authoritative.

Runs and their inline files are purged at `expiresAt`. Sign runs are exempt while
the envelope is open. Nothing a run touches is visible in the CloudRaker app
until you `POST /v1/runs/:id/keep`.

## Extract output shape

```jsonc
{ "config": { "schema": { /* the schema actually applied */ } },
  "output": {
    "value": { "total": 1889.45 },
    "citations": { "total": [ { "fileId": "…", "page": 0,
      "bbox": { "x": 0.1, "y": 0.2, "width": 0.1, "height": 0.02 },
      "text": "CAD 1,889.45", "confidence": 5 } ] },
    "documents": [ { "fileId": "…", "status": "done", "value": {}, "citations": {} } ] } }
```

`page` is 0-based, `bbox` is normalized 0–1 top-left origin, `confidence` is 0–5,
missing fields are `null`.

## Schema dialect (extract)

Plain JSON Schema with four constraints, validated before the run starts
(`400 invalid_schema` names the offending path):

1. Root must be `{"type": "object"}`.
2. Max nesting depth 5.
3. Rejected keywords — exactly these: `$defs`, `$ref`, `oneOf`, `anyOf`,
   `allOf`, `const`, `pattern`. Keyword positions only; a property *named*
   `pattern` is fine. Use `enum` for value constraints, `description` for
   formats.
4. Serialized schema under 64 KB.

Nullable primitives (`{"type": ["string", "null"]}`) are **recommended, never
enforced** — a non-nullable field pressures the extractor into inventing a
value instead of returning `null`. Highest-impact quality change there is.

A `description` per property is the strongest accuracy lever. `unit` controls
cardinality: `per_document` (default) | `across_documents` | `rows_per_document`.

## Errors

```json
{ "code": "invalid_request", "message": "…", "retryable": false,
  "requestId": "req_…", "docUrl": "https://docs.cloudraker.com/developers/errors#invalid_request" }
```

Branch on `code`, never on `message`. `requestId` is also the `x-request-id`
header. Retry only when `retryable` is true. Common codes: `unauthorized`,
`invalid_token` (401); `invalid_request`, `invalid_schema`, `action_unknown`,
`file_ineligible`, `run_too_large`, `schema_too_large` (400); `not_found` (404);
`file_not_ready`, `output_not_ready`, `already_kept`,
`pipeline_running` (409); `run_expired` (410);
`file_fetch_failed`, `webhook_endpoint_*` (422); `rate_limited` (429);
`internal_error`, `file_unreadable`, `file_upload_failed` (5xx).

## Rate limits

At least **67 requests/minute per organization**, shared across all of `/v1` — a
guaranteed floor enforced per edge location, so a distributed caller may sustain
more. Over it: `429`, `code: "rate_limited"`, `Retry-After: 60`; nothing started,
nothing billed. Sleep for `Retry-After`, then exponential backoff with jitter.
One batch call and one long-poll each cost a single request — both beat looping.

## Traps that cost real debugging time

- A slow run returns `202` with the run handle, never a timeout error. Poll
  `statusUrl` or use a webhook.
- Inference picks its own field names and can differ run to run. Use it once,
  then pin `config.schema` or save it as an action.
- `hints` alongside `schema` or `action` is a 400 — use `instructions` instead.
- `POST /v1/extract/batch` has a strict body (`schema`/`hints` are rejected,
  not ignored) and never runs synchronously.
- Pipeline steps run in parallel over the *original* files; a step never reads
  another step's output.
- A produced file is registered a moment after `processed` — until then
  `output.file.url` and `/output/:name` return `409 output_not_ready`
  (retryable, never `404`). Wait a moment and retry.
- Presigned PUT `Content-Type` must exactly equal the registered `mimeType`.
- URL registration needs the source to serve a `Content-Length`; HTML pages and
  gzip endpoints often don't — upload those instead.

## MCP

- API: `https://mcp.cloudraker.com` (bearer = the same API key, or OAuth).
- Docs: `https://docs.cloudraker.com/_mcp/server`.
