API context for agents

The whole CloudRaker API contract on one page, written for a coding agent to fetch as Markdown.
View as Markdown

Point a coding agent at https://docs.cloudraker.com/developers/agents.md. The page gives it all it needs to write a working integration. It covers auth, the request and error envelopes, all six verbs, and the run lifecycle. It also covers multi-step agent runs, the schema dialect, and the limits.

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

Every page on this site has a .md twin. https://docs.cloudraker.com/llms.txt indexes them all. Start from this page.

Base URL and auth

Base URL: https://api.cloudraker.com. Every request carries an organization API key as a bearer token:

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

One key belongs to one organization. The key identifies the tenant, so you never pass a tenant, account, or workspace id. Create keys in the app under Admin → API keys. The value is shown once.

The six verbs

Each verb is a single POST that creates a run. Each is synchronous by default and accepts ?wait= (0–120 seconds, 60 default). Minimal bodies:

1// POST /v1/extract — document -> JSON matching your schema; add "citations": true for evidence
2{ "file": { "url": "https://example.com/invoice.pdf" }, "citations": true,
3 "schema": { "type": "object", "properties": { "total": { "type": ["number", "null"] } } } }
4
5// POST /v1/extract — no schema: infer one, then extract with it (exploration only)
6{ "file": { "url": "https://example.com/invoice.pdf" }, "hints": "It's an invoice; capture the header fields" }
7
8// POST /v1/extract/batch — one saved config over many files; always 202
9{ "action": "invoice-header", "files": [{ "url": "" }, { "url": "" }] }
10
11// POST /v1/parse — document -> markdown + structured JSON, no schema
12{ "file": { "url": "https://example.com/contract.pdf" } }
13
14// POST /v1/redact — destructive PII removal; returns a NEW file
15{ "file": { "id": "<fileId>" }, "categories": ["ssn", "ein"], "mode": "targeted" }
16
17// POST /v1/fill — fill a form PDF; exactly one of "values" (deterministic, no model)
18// or "files" (drafted from sources); "template" optional when "action" carries one
19{ "template": { "id": "<templateId>" }, "files": [{ "id": "<fileId>" }], "review": "none", "output": "flattened" }
20{ "action": "<fill config id or slug>", "values": { "<field name>": "Acme Co." } }
21
22// POST /v1/sign — e-signature envelope; always 202, status needs_input
23{ "file": { "id": "<fileId>" }, "signers": [{ "name": "Jane Doe", "email": "[email protected]" }] }
24
25// POST /v1/pipeline — several capabilities over one file set, in parallel (not chained)
26{ "files": [{ "url": "" }],
27 "steps": [{ "extract": { "schema": { "type": "object", "properties": {} } } }, { "redact": { "categories": ["ssn"] } }] }

Shared optional fields on every verb: metadata (your own key/values, ≤10 KB, echoed back and filterable), webhook ({url} or {id}), ttl (seconds, default 86400, max 604800), and the idempotency-key request header.

Citations are off by default everywhere. To get them, send "citations": true on POST /v1/extract, on POST /v1/extract/batch, or on an extract step in a pipeline. On a saved config, the grounding install setting does the same and also defaults to false.

extract, redact, fill, and sign also accept action: "<id or slug>" instead of inline config. Inline fields win over the saved ones. Save a config with POST /v1/{extract|redact|fill}/configs {name, config}. The flat POST /v1/actions {capability, name, config} is a deprecated alias. Discover configurable shapes with GET /v1/actions/catalog.

File references

Every file input takes one of two shapes, never a multipart upload:

1{ "url": "https://…", "name": "invoice.pdf", "processing": "auto" } // fetched server-side
2{ "id": "a04d6597-4e34-4a99-94ea-964c289a4c68" } // already registered or produced

processing: auto (default), ocr, simple, transcribe, transcribe_diarize. Single-file verbs take file. Multi-file verbs take files[] (up to 100). A request with both is a 400.

Register a persistent file with POST /v1/files. Send {url, name?} — the source must serve a Content-Length. Or send {name, mimeType} to get an uploadUrl valid for 15 minutes. PUT the bytes to it with the identical Content-Type. Poll GET /v1/files/:id until status: "ready". Files a run creates inline expire with the run. Files you register do not.

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

Id prefixes: exr_ extract, par_ parse, rdr_ redact, flr_ fill, sgr_ sign, plr_ pipeline. Treat ids as opaque strings.

Statuses: queued, processing, processed, failed, cancelled, expired, needs_input. output exists only at processed. needs_input means a signature envelope is open; read it through envelopeUrl.

A synchronous call that does not finish within ?wait= returns 202 with the run handle, never a timeout error. Poll statusUrl or use a webhook. Sign runs are exempt from the TTL purge while the envelope is open. Every other run and its inline files are purged at expiresAt.

GET /v1/runs takes object, status (the six values above minus queued), limit (1–50, default 20), cursor, and up to three metadata.<key>=<value> pairs. It returns {object:"list", data:[…handles…], has_more, cursor}. The list is eventually consistent. GET /v1/runs/:id is authoritative.

Agents and agent runs

This surface is separate from the verbs and from /v1/runs. An agent is a saved, versioned, multi-step automation (steps + saved configs + sign-off gates). An agent run (agr_…) is one execution of an agent, and it can wait days on a person.

GET /v1/agents agents you can run
GET /v1/agents/:id one agent: tasks[], actions[], version
POST /v1/agent-runs start one; ?wait= 0–120, default 60
GET /v1/agent-runs/:id read it; ?wait= 0–120, default 0
POST /v1/agent-runs/:id/approvals/:approvalId {decision: "approve"|"reject", note?, params?, files?}
POST /v1/agent-runs/:id/tasks/:taskId/complete {note?, files?} — send {} if neither
1// POST /v1/agent-runs — files 1–200, metadata ≤10 KB, no ttl
2{ "agent": "<agentId>", "files": [{ "url": "https://example.com/intake.pdf" }],
3 "metadata": { "caseId": "42" }, "webhook": { "url": "https://example.com/hooks" } }

Statuses: queued, processing, waiting, paused, completed, failed, cancelled, expired. A completed run with unfinished work also carries incomplete: true. paused (paused.reason) is resumable, not a failure. The run carries tasks[] (executor: "agent"|"human", status: pending|ready|in_progress|completed|skipped), approvals[] (kind: "before"|"output", with the proposed params and files), waiting: {approvals, tasks, summary}, progress.tasks, result, output.files[] (signed ~1 h), error, metadata, expiresAt (~7 days).

Rules that differ from capability runs:

  • Poll a queued agent run. A read starts it once its files are prepared. A webhook alone leaves it queued.
  • No list, no cancel, no delete. Agent runs never appear in GET /v1/runs. Keep the agr_ id, or tag runs with metadata.
  • No ttl. Files passed by URL become persistent files. expiresAt (~7 days) is the deadline. Past it, an unfinished run parks permanently.
  • Only executor: "human" steps are completable (422 otherwise). An unmet dependsOn or an already-closed step is 409. A rejection requires note. A second decision on the same approval is 409.
  • Webhook events: agent_run.waiting, agent_run.approval_requested (minimal; re-read the run for params), agent_run.task_ready, agent_run.completed, agent_run.failed (also carries cancelled/expired; trust data.status). Deliveries use the same signature and JWKS as every other delivery. processingId carries the agr_ id.

Extract output

1{
2 "object": "extract_run", "id": "exr_…", "status": "processed",
3 "config": { "schema": { /* the schema actually applied — inline, saved, or inferred */ } },
4 "output": {
5 "value": { "total": 1889.45, "po_number": null }, // alias of documents[0].value
6 "citations": { // only when the run was grounded; field path -> evidence
7 "total": [{ "fileId": "", "page": 0, "bbox": { "x": 0.1, "y": 0.2, "width": 0.1, "height": 0.02 },
8 "text": "CAD 1,889.45", "confidence": 5 }],
9 "po_number": [{ "fileId": "", "notFound": true }] // declared absent; value is null, never a guess
10 },
11 "documents": [{ "fileId": "", "name": "invoice.pdf", "status": "done", "value": { /* … */ }, "citations": { /* same shape, per document */ } }]
12 }
13}

page is 0-based. bbox is normalized 01 with a top-left origin. confidence is 05. Audio grounds with a timecode instead of page/bbox. A notFound entry carries only fileId: no page, bbox, timecode, text, or confidence. A field the document does not contain comes back null. Declare every property nullable for this reason.

Grounding is guaranteed-or-flagged. When citations are on, every filled field returns at least one citation or an explicit notFound declaration. A deterministic second model pass cites or declares absent anything the first pass left uncited. When the run was not grounded, the citations key is absent, never an empty object. citationsOmitted: true on output means the result exceeded the size budget, so citations were dropped. It appears at output level only, never per document.

Extraction schema dialect

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

  1. The root must be {"type": "object"}.
  2. Nesting may not exceed 5 levels.
  3. No $defs, $ref, oneOf, anyOf, allOf, const, or pattern. These are the only rejected keywords, and only in keyword positions (a property named pattern is valid). Constrain values with enum. Describe formats in description.
  4. The serialized schema must stay under 64 KB.

Make every primitive nullable: {"type": ["string", "null"]}. This is recommended, never enforced. A non-nullable field pressures the extractor to invent a value instead of a null. This change has the highest impact on extraction quality. With citations on, an absent value is also marked notFound, not left ambiguous.

Add a description per property. It is the strongest accuracy lever. unit controls cardinality: per_document (default), across_documents, rows_per_document.

Errors

Every /v1 failure is the same envelope, with x-request-id also on the response headers:

1{ "code": "invalid_request", "message": "file: provide exactly one of `file` or `files`",
2 "retryable": false, "requestId": "req_…", "docUrl": "https://docs.cloudraker.com/developers/errors#invalid_request" }

Branch on code, never on message. 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_not_found, webhook_endpoint_disabled (422); rate_limited (429); internal_error, file_unreadable, file_upload_failed (5xx). Retry only where retryable is true.

Rate limits

The limit is at least 67 requests per minute per organization, shared across every /v1 endpoint. This is a guaranteed floor enforced per edge location, so a distributed caller can sustain more. Over the limit, you get 429, code: "rate_limited", Retry-After: 60. Nothing starts, and nothing is billed. Respect Retry-After, then back off exponentially with jitter. One batch call and one long-poll each cost one request. Both beat looping for this reason.

Webhooks

Send webhook: {"url": "https://…"} on any verb, or register an endpoint with POST /v1/webhooks and reference it as webhook: {"id": …}. Deliveries are signed JWTs. Verify them against GET /v1/webhooks/jwks.json (public, unauthenticated, never rate limited). Deliveries do not include run metadata. Re-fetch the run by id.

Rules that save you a debugging session

  • No /v1 run data appears in the CloudRaker app until you POST /v1/runs/:id/keep. This is deliberate: ephemeral by default.
  • Schema inference (hints, no schema) picks its own field names and can differ between runs. Use it once to discover the shape, then pin config.schema or save it as an action.
  • hints with a schema or an action is a 400. Use instructions instead.
  • POST /v1/extract/batch has a strict body: it rejects schema and hints, not ignores them. It always returns 202. It accepts citations.
  • Pipeline steps run in parallel over the same files. A step never consumes 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 ask again.
  • Reuse fileIds instead of URLs. The API does not fetch or parse the document again.

Full reference

  • OpenAPI: https://docs.cloudraker.com/openapi.json — the full gateway spec, with POST /v1/extract/batch and GET /v1/runs. It is re-exported when the surface changes. Where the two disagree, this page is right.
  • Markdown index for agents: https://docs.cloudraker.com/llms.txt
  • MCP server: https://mcp.cloudraker.com — see MCP server
  • Human-readable start: Agent quickstart, Quickstart