> 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.

# Agent runs

An [agent](/capabilities/agents) 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

```bash
curl https://api.cloudraker.com/v1/agents \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY"
```

```jsonc
{
  "object": "list",
  "data": [
    {
      "object": "agent",
      "id": "01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
      "name": "Client intake",
      "version": 7,
      "tasks": [
        { "id": "task-1", "title": "Extract the intake fields", "executor": "agent", "dependsOn": [] },
        { "id": "task-2", "title": "Countersign the engagement letter", "executor": "human", "dependsOn": ["task-1"] }
      ],
      "actions": [{ "name": "Sign document", "approval": "before" }]
      // …description, updatedAt
    }
  ]
}
```

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

```bash
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" }
  }'
```

| Field      | Type                                                  | What it does                                                                                                                                     |
| ---------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `agent`    | string                                                | **Required.** The agent id from `GET /v1/agents`.                                                                                                |
| `files[]`  | array of `{url, name?, processing?}` or `{id}`, 1–200 | **Required.** `{url}` registers the file and fetches it; `{id}` reuses one from [`POST /v1/files`](/developers/files) and is never parsed twice. |
| `metadata` | object                                                | Your 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](/developers/webhooks#agent-runs).                                                             |

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.

| Outcome                    | Response                                     |
| -------------------------- | -------------------------------------------- |
| Finished inside the window | `200` with the full run                      |
| Files still being prepared | `202` with `status: "queued"`                |
| Still working at the cap   | `202` with `{object, id, status, statusUrl}` |
| Blocked on a person        | `202` right away, `status: "waiting"`        |

```json
{
  "object": "agent_run",
  "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
  "status": "waiting",
  "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
}
```

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.

### Request

POST [https://api.cloudraker.com/v1/agent-runs](https://api.cloudraker.com/v1/agent-runs)

```curl
curl -X POST https://api.cloudraker.com/v1/agent-runs \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "agent": "agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
  "files": [
    {
      "url": "https://documents.example.com/case123/intake_form.pdf"
    }
  ]
}'
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.agents.createAgentRun({
        agent: "agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
        files: [
            {
                url: "https://documents.example.com/case123/intake_form.pdf",
            },
        ],
    });
}
main();

```

```python
from cloudraker import CloudRaker
from cloudraker.agents import V1CreateAgentRunBodyFilesItemName

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.agents.create_agent_run(
    agent="agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
    files=[
        V1CreateAgentRunBodyFilesItemName(
            url="https://documents.example.com/case123/intake_form.pdf",
        )
    ],
)

```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/v1/agent-runs"

	payload := strings.NewReader("{\n  \"agent\": \"agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH\",\n  \"files\": [\n    {\n      \"url\": \"https://documents.example.com/case123/intake_form.pdf\"\n    }\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/v1/agent-runs")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"agent\": \"agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH\",\n  \"files\": [\n    {\n      \"url\": \"https://documents.example.com/case123/intake_form.pdf\"\n    }\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.cloudraker.com/v1/agent-runs")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"agent\": \"agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH\",\n  \"files\": [\n    {\n      \"url\": \"https://documents.example.com/case123/intake_form.pdf\"\n    }\n  ]\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.cloudraker.com/v1/agent-runs', [
  'body' => '{
  "agent": "agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
  "files": [
    {
      "url": "https://documents.example.com/case123/intake_form.pdf"
    }
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/v1/agent-runs");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"agent\": \"agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH\",\n  \"files\": [\n    {\n      \"url\": \"https://documents.example.com/case123/intake_form.pdf\"\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "agent": "agr_01JQ8ZKMRT4V6WXYZ0ABCDEFGH",
  "files": [["url": "https://documents.example.com/case123/intake_form.pdf"]]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/v1/agent-runs")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

## Poll the run

```bash
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.

```jsonc
{
  "object": "agent_run",
  "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
  "agent": { "id": "01JQ8ZKMRT4V6WXYZ0ABCDEFGH", "name": "Client intake", "version": 7 },
  "status": "waiting",
  "waiting": { "approvals": 1, "tasks": 0, "summary": "Waiting on a sign-off for Sign document." },
  "progress": { "tasks": { "total": 2, "completed": 1 } },
  "tasks": [
    {
      "id": "task-1",
      "title": "Extract the intake fields",
      "executor": "agent",
      "status": "completed",
      "summary": "Read 14 fields from intake.pdf.",
      "completedAt": "2026-07-29T09:41:02.117Z"
    },
    {
      "id": "task-2",
      "title": "Countersign the engagement letter",
      "executor": "human",
      "status": "pending",
      "summary": null,
      "completedAt": null
    }
  ],
  "approvals": [
    {
      "id": "7c1e9f42-3b0d-4a5e-8f61-2d9c4b7ae013",
      "kind": "before",
      "action": "Sign document",
      "requestedAt": "2026-07-29T09:41:05.902Z",
      "rationale": "Ready to send the engagement letter for signature.",
      "files": [{ "id": "a04d6597-4e34-4a99-94ea-964c289a4c68", "name": "engagement-letter.pdf" }],
      "params": { "signers": [{ "name": "Jane Doe", "email": "jane@example.com" }] }
    }
  ],
  "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
  "metadata": { "caseId": "42" },
  "createdAt": "2026-07-29T09:40:58.004Z",
  "expiresAt": "2026-08-05T09:40:58.004Z"
}
```

| Field            | What it is                                                                                                                                                    |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`         | One of `queued`, `processing`, `waiting`, `paused`, `completed`, `failed`, `cancelled`, `expired`. See the [status table](/capabilities/agents#run-statuses). |
| `waiting`        | Present while `status` is `waiting` — how many approvals and human steps are outstanding, plus a one-line `summary`.                                          |
| `paused`         | Present 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}`.                                                                                                                                         |
| `result`         | The 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`.                                                                                                     |
| `incomplete`     | `true` on a `completed` run that still had outstanding work.                                                                                                  |
| `expiresAt`      | The run's deadline, about seven days out. An unfinished run parks itself for good past it.                                                                    |

### Request

GET [https://api.cloudraker.com/v1/agent-runs/\{id}](https://api.cloudraker.com/v1/agent-runs/\{id})

```curl
curl https://api.cloudraker.com/v1/agent-runs/id \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json"
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.agents.getAgentRun("id", {});
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.agents.get_agent_run(
    id="id",
)

```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/v1/agent-runs/id"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("GET", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/v1/agent-runs/id")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.cloudraker.com/v1/agent-runs/id")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.cloudraker.com/v1/agent-runs/id', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/v1/agent-runs/id");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/v1/agent-runs/id")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

## Decide an approval

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

```bash
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" }'
```

```json
{
  "object": "agent_run_approval",
  "id": "7c1e9f42-3b0d-4a5e-8f61-2d9c4b7ae013",
  "kind": "before",
  "action": "Sign document",
  "status": "approved",
  "requestedAt": "2026-07-29T09:41:05.902Z",
  "decidedAt": "2026-07-29T09:42:11.508Z",
  "note": null,
  "run": {
    "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
    "status": "waiting",
    "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
  }
}
```

`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.

| Field      | What it does                                                                                                                   |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `decision` | **Required.** `approve` or `reject`.                                                                                           |
| `note`     | ≤ 4000 characters. **Required on `reject`** — a rejection without one is a `400`.                                              |
| `params`   | Replaces the inputs the agent proposed, for a `before` gate.                                                                   |
| `files`    | Replaces the files the step works on — up to 200 file ids, from [`POST /v1/files`](/developers/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.

```bash
# 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": "ada@example.com" }] } }'
```

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

### Request

POST [https://api.cloudraker.com/v1/agent-runs/\{id}/approvals/\{approvalId}](https://api.cloudraker.com/v1/agent-runs/\{id}/approvals/\{approvalId})

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

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.agents.decideAgentRunApproval("id", "approvalId", {
        decision: "approve",
    });
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.agents.decide_agent_run_approval(
    id="id",
    approval_id="approvalId",
    decision="approve",
)

```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/v1/agent-runs/id/approvals/approvalId"

	payload := strings.NewReader("{\n  \"decision\": \"approve\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/v1/agent-runs/id/approvals/approvalId")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"decision\": \"approve\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.cloudraker.com/v1/agent-runs/id/approvals/approvalId")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"decision\": \"approve\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.cloudraker.com/v1/agent-runs/id/approvals/approvalId', [
  'body' => '{
  "decision": "approve"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/v1/agent-runs/id/approvals/approvalId");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"decision\": \"approve\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["decision": "approve"] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/v1/agent-runs/id/approvals/approvalId")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

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

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

```json
{
  "object": "agent_run_task",
  "id": "task-2",
  "title": "Countersign the engagement letter",
  "executor": "human",
  "status": "completed",
  "summary": "Client signed; scan attached.",
  "completedAt": "2026-07-29T09:44:37.221Z",
  "run": {
    "id": "agr_01KYD1J8QW2RN4T6VXZ0ABCDEF",
    "status": "completed",
    "statusUrl": "/v1/agent-runs/agr_01KYD1J8QW2RN4T6VXZ0ABCDEF"
  }
}
```

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`.

| Situation                             | Response                                  |
| ------------------------------------- | ----------------------------------------- |
| The step's `executor` is `agent`      | `422` — only human steps are completable. |
| Its `dependsOn` steps aren't finished | `409`.                                    |
| It is already done                    | `409`.                                    |

### Request

POST [https://api.cloudraker.com/v1/agent-runs/\{id}/tasks/\{taskId}/complete](https://api.cloudraker.com/v1/agent-runs/\{id}/tasks/\{taskId}/complete)

```curl
curl -X POST https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{}'
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.agents.completeAgentRunTask("id", "taskId", {});
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.agents.complete_agent_run_task(
    id="id",
    task_id="taskId",
)

```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/v1/agent-runs/id/tasks/taskId/complete")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

## End to end

The whole loop, with a webhook doing the waiting:

#### Start the run

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

#### 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.

#### 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[]`.

#### Answer it

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

#### A human step opens

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

#### 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](/developers/runs) doesn't apply here: an agent run's inputs are persistent files and it has no `ttl`.

## Next steps

#### [Agents](/capabilities/agents)

What an agent is: the step ledger, the approval gates, the statuses.

#### [Webhooks](/developers/webhooks#agent-runs)

The five `agent_run.*` events and how to verify a delivery.

#### [Files](/developers/files)

Register a document once and pass it to a run by id.

#### [Errors](/developers/errors)

The error envelope and which codes are worth retrying.