Agent runs

Start an agent on a set of files, then answer what it asks — sign-offs and human steps, over the API.

View as Markdown

An agent is a multi-step automation with human sign-off built into it. An agent run (agr_…) is one execution of it, and these six routes are the whole surface:

GET /v1/agents agents you can run
GET /v1/agents/:id one agent: steps, actions, gates
POST /v1/agent-runs start a run
GET /v1/agent-runs/:id where it is, accepts ?wait=
POST /v1/agent-runs/:id/approvals/:approvalId approve or reject a step
POST /v1/agent-runs/:id/tasks/:taskId/complete mark a human step done

Pick an agent

$curl https://api.cloudraker.com/v1/agents \
> -H "Authorization: Bearer $CLOUDRAKER_API_KEY"
1{
2 "object": "list",
3 "data": [
4 {
5 "object": "agent",
6 "id": "01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
7 "name": "Client intake",
8 "version": 7,
9 "tasks": [
10 { "id": "task-1", "title": "Extract the intake fields", "executor": "agent", "dependsOn": [] },
11 { "id": "task-2", "title": "Countersign the engagement letter", "executor": "human", "dependsOn": ["task-1"] }
12 ],
13 "actions": [{ "name": "Sign document", "approval": "before" }]
14 // …description, updatedAt
15 }
16 ]
17}

Read tasks[] and actions[] before you start: they tell you which steps will come back to you as human work, and which actions will stop for a sign-off. GET /v1/agents/:id returns the same shape for one agent.

Start a run

$curl -X POST "https://api.cloudraker.com/v1/agent-runs?wait=60" \
> -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "agent": "01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
> "files": [{ "url": "https://example.com/intake.pdf", "name": "intake.pdf" }],
> "metadata": { "caseId": "42" },
> "webhook": { "url": "https://example.com/hooks/cloudraker" }
> }'
FieldTypeWhat it does
agentstringRequired. The agent id from GET /v1/agents.
files[]array of {url, name?, processing?} or {id}, 1–200Required. {url} registers the file and fetches it; {id} reuses one from POST /v1/files and is never parsed twice.
metadataobjectYour own key/values, echoed back on every read of the run. Max 10 KB. Carry your caseId here and you never need a lookup table.
webhook{url} or {id}Where to deliver this run’s events. See Webhooks.

There is no ttl: files you pass by URL join your reusable corpus and are never purged on a deadline, because a run can wait days on a person.

What comes back

The call holds open up to ?wait= seconds (default 60, max 120, 0 returns immediately) and releases early the moment the run either finishes or blocks on a person.

OutcomeResponse
Finished inside the window200 with the full run
Files still being prepared202 with status: "queued"
Still working at the cap202 with {object, id, status, statusUrl}
Blocked on a person202 right away, status: "waiting"
1{
2 "object": "agent_run",
3 "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
4 "status": "waiting",
5 "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
6}

The 202 is a graceful degrade, never an error — the run id is yours either way. Sending an idempotency-key header makes retries safe: a replay returns the original run with an idempotent-replay: true response header.

A queued run has to be polled. Its files are still being prepared — minutes, for scans or long audio — and reading the run is what picks it up once they’re ready. A run that hasn’t started has nothing to report, so waiting on a webhook alone leaves it queued forever. Once it’s running, events are delivered as they happen.

POST
/v1/agent-runs
1curl -X POST https://api.cloudraker.com/v1/agent-runs \
2 -H "Authorization: Bearer <token>" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "agent": "agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
6 "files": [
7 {
8 "url": "https://documents.example.com/case123/intake_form.pdf"
9 }
10 ]
11}'

Poll the run

$curl "https://api.cloudraker.com/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF?wait=60" \
> -H "Authorization: Bearer $CLOUDRAKER_API_KEY"

?wait= (0–120, default 0 here) holds the request until the run finishes or blocks on a person, so a loop of long polls costs one request per minute instead of one per second.

1{
2 "object": "agent_run",
3 "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
4 "agent": { "id": "01JQ8ZKMRT4V6WXYZ0ABCDEFGH", "name": "Client intake", "version": 7 },
5 "status": "waiting",
6 "waiting": { "approvals": 1, "tasks": 0, "summary": "Waiting on a sign-off for Sign document." },
7 "progress": { "tasks": { "total": 2, "completed": 1 } },
8 "tasks": [
9 {
10 "id": "task-1",
11 "title": "Extract the intake fields",
12 "executor": "agent",
13 "status": "completed",
14 "summary": "Read 14 fields from intake.pdf.",
15 "completedAt": "2026-07-29T09:41:02.117Z"
16 },
17 {
18 "id": "task-2",
19 "title": "Countersign the engagement letter",
20 "executor": "human",
21 "status": "pending",
22 "summary": null,
23 "completedAt": null
24 }
25 ],
26 "approvals": [
27 {
28 "id": "7c1e9f42-3b0d-4a5e-8f61-2d9c4b7ae013",
29 "kind": "before",
30 "action": "Sign document",
31 "requestedAt": "2026-07-29T09:41:05.902Z",
32 "rationale": "Ready to send the engagement letter for signature.",
33 "files": [{ "id": "a04d6597-4e34-4a99-94ea-964c289a4c68", "name": "engagement-letter.pdf" }],
34 "params": { "signers": [{ "name": "Jane Doe", "email": "[email protected]" }] }
35 }
36 ],
37 "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
38 "metadata": { "caseId": "42" },
39 "createdAt": "2026-07-29T09:40:58.004Z",
40 "expiresAt": "2026-08-05T09:40:58.004Z"
41}
FieldWhat it is
statusOne of queued, processing, waiting, paused, completed, failed, cancelled, expired. See the status table.
waitingPresent while status is waiting — how many approvals and human steps are outstanding, plus a one-line summary.
pausedPresent while status is paused, with reason: "model_error" or "system_error". Not a failure.
tasks[]The step ledger: executor, status, and the summary the agent recorded when a step closed.
approvals[]Sign-offs the run is blocked on, oldest first, with the proposed params and files.
progress.tasks{total, completed}.
resultThe agent’s closing summary, on a finished run.
output.files[]Files attached while steps were completed — each url is signed and good for about an hour.
error{code, message}, present whenever status is failed.
incompletetrue on a completed run that still had outstanding work.
expiresAtThe run’s deadline, about seven days out. An unfinished run parks itself for good past it.
GET
/v1/agent-runs/:id
1curl https://api.cloudraker.com/v1/agent-runs/id \
2 -H "Authorization: Bearer <token>" \
3 -H "Content-Type: application/json"

Decide an approval

Read the outstanding ones from approvals[], then answer one by id:

$curl -X POST "https://api.cloudraker.com/v1/agent-runs/agr_01KYD1J8QW…/approvals/7c1e9f42-3b0d-4a5e-8f61-2d9c4b7ae013?wait=60" \
> -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{ "decision": "approve" }'
1{
2 "object": "agent_run_approval",
3 "id": "7c1e9f42-3b0d-4a5e-8f61-2d9c4b7ae013",
4 "kind": "before",
5 "action": "Sign document",
6 "status": "approved",
7 "requestedAt": "2026-07-29T09:41:05.902Z",
8 "decidedAt": "2026-07-29T09:42:11.508Z",
9 "note": null,
10 "run": {
11 "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
12 "status": "waiting",
13 "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
14 }
15}

run.status tells you whether the run moved on, finished, or blocked on the next thing — ?wait= holds the request while it picks the work back up.

FieldWhat it does
decisionRequired. approve or reject.
note≤ 4000 characters. Required on reject — a rejection without one is a 400.
paramsReplaces the inputs the agent proposed, for a before gate.
filesReplaces the files the step works on — up to 200 file ids, from POST /v1/files or the run’s own inputs.

params and files together must stay under 1 MiB. Deciding the same approval twice is a 409: the first answer stands.

$# reject, with the reason
$-d '{ "decision": "reject", "note": "Wrong signer — resend to the CFO." }'
$
$# approve, but fix the inputs first
$-d '{ "decision": "approve", "params": { "signers": [{ "name": "Ada Byron", "email": "[email protected]" }] } }'

An API key has no person behind it, so the run records your organization’s key as the actor rather than a named individual.

POST
/v1/agent-runs/:id/approvals/:approvalId
1curl -X POST https://api.cloudraker.com/v1/agent-runs/id/approvals/approvalId \
2 -H "Authorization: Bearer <token>" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "decision": "approve"
6}'

Complete a human step

The steps that are yours are the ones in tasks[] with executor: "human" and status: "ready". Closing one releases everything that was waiting on it:

$curl -X POST "https://api.cloudraker.com/v1/agent-runs/agr_01KYD1J8QW…/tasks/task-2/complete?wait=60" \
> -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{ "note": "Client signed; scan attached.", "files": ["a04d6597-4e34-4a99-94ea-964c289a4c68"] }'
1{
2 "object": "agent_run_task",
3 "id": "task-2",
4 "title": "Countersign the engagement letter",
5 "executor": "human",
6 "status": "completed",
7 "summary": "Client signed; scan attached.",
8 "completedAt": "2026-07-29T09:44:37.221Z",
9 "run": {
10 "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
11 "status": "completed",
12 "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
13 }
14}

Both body fields are optional — send {} if you have neither. note is ≤ 2000 characters; files is up to 20 ids you registered or that the run already holds, and they show up in the run’s output.files.

SituationResponse
The step’s executor is agent422 — only human steps are completable.
Its dependsOn steps aren’t finished409.
It is already done409.
POST
/v1/agent-runs/:id/tasks/:taskId/complete
1curl -X POST https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete \
2 -H "Authorization: Bearer <token>" \
3 -H "Content-Type: application/json" \
4 -d '{}'

End to end

The whole loop, with a webhook doing the waiting:

1

Start the run

POST /v1/agent-runs with {agent, files: [{url}], metadata, webhook: {url}}202, status: "queued". Keep the agr_ id.

2

Poll once to pick it up

GET /v1/agent-runs/:id?wait=60. This is what starts a queued run once its files are ready. From here events flow.

3

A sign-off is requested

agent_run.approval_requested arrives. It’s deliberately minimal, so re-read the run and take the params and files from approvals[].

4

Answer it

POST /v1/agent-runs/:id/approvals/:approvalId with {"decision": "approve"}. The response’s run.status says what the run did next.

5

A human step opens

agent_run.task_ready arrives with the step’s id and title — the one thing a person has to do.

6

Close it

POST /v1/agent-runs/:id/tasks/:taskId/complete with a note and any files. agent_run.completed follows, carrying result, progress, and output.

What this surface does not have

  • No list. There is no GET /v1/agent-runs — hold the agr_ id from create, or tag runs with metadata you already know.
  • No cancel, no delete. An agent run runs until it finishes, parks, or reaches expiresAt.
  • Not in GET /v1/runs. That list is capability runs only (extract_run, parse_run, …), and the TTL and keep story on Runs doesn’t apply here: an agent run’s inputs are persistent files and it has no ttl.

Next steps