> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.tenfive.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Endpoint reference

> Complete request and response reference for every Agent Harness v1 API endpoint.

# Endpoint Reference

Complete reference for the Agent Harness v1 API. All request/response shapes are documented here. For the run state machine and event consumption patterns, see [run-lifecycle.md](run-lifecycle.md).

***

## Base URL and versioning

* API base path: `/v1`
* OpenAPI schema: `GET /openapi.json` (no auth)
* Health checks: `/health/live`, `/health/ready`, `/health/deps` (no auth)

All protected endpoints require `Authorization: Bearer <key_id>:<secret>` and return JSON unless otherwise noted.

***

## Run lifecycle endpoints

### POST /v1/runs — Create run

Create a new run. The run is enqueued for background execution and returns immediately with `status: "queued"`.

Idempotent: the same `idempotency-key` header returns the existing run without creating a duplicate.

| Item    | Value                                                                           |
| ------- | ------------------------------------------------------------------------------- |
| Method  | POST                                                                            |
| Path    | `/v1/runs`                                                                      |
| Auth    | Required                                                                        |
| Headers | `idempotency-key` (required), `Authorization`, `Content-Type: application/json` |

**Request body:**

| Field              | Type      |   Required  | Description                                                                                                                               |
| ------------------ | --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `input`            | object    |     Yes     | Initial context for agent execution. Max 256 KB combined with `metadata`.                                                                 |
| `metadata`         | object    |     Yes     | Arbitrary metadata attached to the run. Max 256 KB combined with `input`.                                                                 |
| `workspace_id`     | string    |      No     | Customer-provided organizational label for billing attribution and filtering.                                                             |
| `subject_id`       | string    |      No     | Customer-provided user/actor label for billing attribution and filtering.                                                                 |
| `attachment_refs`  | string\[] |      No     | References to attachments (by reference, not inline).                                                                                     |
| `sensitivity_tags` | array     | Conditional | Required when `input` or `metadata` contains sensitive keys (password, secret, token, ssn, credit, apiKey, etc). See below.               |
| `routing_hint`     | string    |      No     | `skip_planning` (first step → worker) or `require_planning` (first step → planner). When omitted, the server uses a heuristic classifier. |
| `run_class`        | string    |      No     | `default`, `short`, or `long`. Controls execution resource allocation. Default: `default`.                                                |
| `streaming`        | boolean   |      No     | Hint for streaming preference. Default: server decides.                                                                                   |
| `plan`             | object    |      No     | Pre-defined v2 execution plan. Must be a valid v2 plan with at least one step. Most clients should omit this.                             |

**Sensitivity tag schema** (each element of `sensitivity_tags`):

| Field                 | Type    | Required | Description                                                                 |
| --------------------- | ------- | :------: | --------------------------------------------------------------------------- |
| `path`                | string  |    Yes   | JSONPath starting with `$.input` or `$.metadata` (e.g. `$.input.api_token`) |
| `classification`      | string  |    Yes   | One of: `pii`, `financial`, `health`, `credential`, `proprietary`           |
| `redaction`           | string  |    No    | `mask` (default) or `drop`                                                  |
| `retention_hint_days` | integer |    No    | Advisory retention hint (does not override platform policy)                 |

**Minimal example:**

```json  theme={null}
{
  "input": { "user_query": "Summarize Q4 sales data" },
  "metadata": {}
}
```

**Full example with attribution and routing:**

```json  theme={null}
{
  "input": { "user_query": "Analyze the attached document" },
  "metadata": { "source": "web-ui" },
  "workspace_id": "ws_engineering",
  "subject_id": "user_42",
  "routing_hint": "skip_planning",
  "sensitivity_tags": []
}
```

**Example with sensitivity tags:**

```json  theme={null}
{
  "input": { "user_query": "Review this API key", "api_token": "sk-xxx" },
  "metadata": {},
  "sensitivity_tags": [
    { "path": "$.input.api_token", "classification": "credential" }
  ]
}
```

**Responses:**

| Status | Meaning                                    |
| :----: | ------------------------------------------ |
|   201  | Run created                                |
|   200  | Idempotent replay (same `idempotency-key`) |
|   400  | Validation error — see reason codes below  |
|   401  | Unauthorized                               |
|   403  | Forbidden                                  |
|   409  | Conflict                                   |

**400 reason codes:**

| Reason code                                    | Meaning                                                    |
| ---------------------------------------------- | ---------------------------------------------------------- |
| `IDEMPOTENCY_KEY_REQUIRED`                     | Missing `idempotency-key` header                           |
| `INPUT_PAYLOAD_INVALID`                        | Malformed request body                                     |
| `INPUT_PAYLOAD_TOO_LARGE`                      | Combined `input` + `metadata` exceeds 256 KB               |
| `INPUT_SENSITIVITY_TAG_REQUIRED`               | Sensitive keys detected but no `sensitivity_tags` provided |
| `INPUT_SENSITIVITY_TAG_INVALID_PATH`           | Tag path does not start with `$.input` or `$.metadata`     |
| `INPUT_SENSITIVITY_TAG_INVALID_CLASSIFICATION` | Unsupported classification value                           |
| `INPUT_SENSITIVITY_TAG_DUPLICATE_PATH`         | Duplicate path in `sensitivity_tags`                       |
| `INPUT_PLAN_INVALID`                           | Supplied `plan` is not a valid v2 plan with steps          |

**Response body (201/200):**

```json  theme={null}
{
  "id": "run_xxx",
  "workspace_id": "ws_engineering",
  "subject_id": "user_42",
  "status": "queued",
  "run_class": "default",
  "metadata": {
    "created_at": "2026-03-25T14:30:00.000Z",
    "updated_at": "2026-03-25T14:30:00.000Z"
  },
  "event_payload": { "redacted": true, "value": null },
  "replayed": false,
  "request_id": "uuid"
}
```

**Response field notes:**

| Field           | Notes                                                          |
| --------------- | -------------------------------------------------------------- |
| `id`            | Unique run identifier. Use this for all subsequent operations. |
| `status`        | Always `queued` on initial creation.                           |
| `workspace_id`  | Echoed from request body. `null` if not provided.              |
| `subject_id`    | Echoed from request body. `null` if not provided.              |
| `run_class`     | Resolved run class (`default`, `short`, or `long`).            |
| `event_payload` | Always `null` at creation time — no output yet.                |
| `replayed`      | `true` on idempotent replay (HTTP 200).                        |
| `request_id`    | Correlation ID for this request.                               |

***

### GET /v1/runs/{id} — Get run

Retrieve current run details. Use to poll for status changes.

| Item   | Value           |
| ------ | --------------- |
| Method | GET             |
| Path   | `/v1/runs/{id}` |
| Auth   | Required        |

**Responses:** 200, 401, 403, 404 (`RUN_NOT_FOUND`)

**Response body (200):**

```json  theme={null}
{
  "id": "run_xxx",
  "workspace_id": "ws_engineering",
  "subject_id": "user_42",
  "status": "running",
  "run_class": "default",
  "metadata": {
    "created_at": "2026-03-25T14:30:00.000Z",
    "updated_at": "2026-03-25T14:30:05.000Z"
  },
  "event_payload": { "redacted": true, "value": null },
  "request_id": "uuid"
}
```

The `status` field reflects the current lifecycle state. Poll until `status` is `succeeded`, `failed`, or `cancelled`. For richer progress, use the event endpoints.

***

### POST /v1/runs/{id}/cancel — Cancel run

Request cancellation. Best-effort — the run may complete before the cancel is processed.

| Item   | Value                  |
| ------ | ---------------------- |
| Method | POST                   |
| Path   | `/v1/runs/{id}/cancel` |
| Auth   | Required               |

**Responses:** 200 (cancelled), 401, 403, 404, 409 (invalid state transition)

***

### POST /v1/runs/{id}/retry — Retry run

Retry a failed run. Only valid when `status` is `failed`.

| Item   | Value                 |
| ------ | --------------------- |
| Method | POST                  |
| Path   | `/v1/runs/{id}/retry` |
| Auth   | Required              |

**Responses:** 200 (retried), 401, 403, 404, 409 (invalid state transition)

***

### POST /v1/runs/{id}/resume — Resume run

Resume a stalled or paused run. Only valid when `status` is `stalled`.

| Item   | Value                  |
| ------ | ---------------------- |
| Method | POST                   |
| Path   | `/v1/runs/{id}/resume` |
| Auth   | Required               |

**Responses:** 200 (resumed), 401, 403, 404, 409 (invalid state transition)

***

### POST /v1/runs/{id}/signal — Send signal

Send a signal to a run that is awaiting input. Used for human-in-the-loop approval flows and injecting data mid-run.

| Item         | Value                  |
| ------------ | ---------------------- |
| Method       | POST                   |
| Path         | `/v1/runs/{id}/signal` |
| Auth         | Required               |
| Content-Type | `application/json`     |

**Request body:**

| Field             | Type   | Required | Description                                       |
| ----------------- | ------ | :------: | ------------------------------------------------- |
| `action`          | string |    Yes   | `approve`, `reject`, or `submit_input`            |
| `payload`         | any    |    No    | Arbitrary data payload (used with `submit_input`) |
| `idempotency_key` | string |    No    | Idempotency key for this signal (per-run scope)   |

**Actions:**

| Action         | Effect                                   |
| -------------- | ---------------------------------------- |
| `approve`      | Resume the run — enqueues the next step  |
| `reject`       | Fail the run — transitions to `failed`   |
| `submit_input` | Inject `payload` data and resume the run |

**Example — approve:**

```json  theme={null}
{ "action": "approve" }
```

**Example — submit input:**

```json  theme={null}
{
  "action": "submit_input",
  "payload": { "user_choice": "option_a", "notes": "Proceed with plan B" }
}
```

**Responses:**

| Status | Meaning                                                   |
| :----: | --------------------------------------------------------- |
|   200  | Signal accepted                                           |
|   400  | Invalid signal body (`SIGNAL_PAYLOAD_INVALID`)            |
|   401  | Unauthorized                                              |
|   403  | Forbidden                                                 |
|   404  | Run not found                                             |
|   409  | Run not in awaiting\_input state                          |
|   500  | Signal service unavailable (`SIGNAL_SERVICE_UNAVAILABLE`) |

**Response body (200):**

```json  theme={null}
{ "ok": true, "request_id": "uuid" }
```

***

## Event endpoints

### GET /v1/runs/{id}/events — List run events (polling)

Cursor-based pagination for run events. Use `next_cursor` from the response as the `cursor` query parameter to get subsequent events.

| Item   | Value                                                   |
| ------ | ------------------------------------------------------- |
| Method | GET                                                     |
| Path   | `/v1/runs/{id}/events`                                  |
| Auth   | Required                                                |
| Query  | `cursor` (optional, integer), `limit` (optional, 1–200) |

**Responses:** 200, 401, 403, 404

**Response body (200):**

```json  theme={null}
{
  "events": [
    {
      "seq": 1,
      "type": "run.created",
      "timestamp": "2026-03-25T14:30:00.000Z",
      "payload": { "redacted": true, "value": {} }
    },
    {
      "seq": 2,
      "type": "run.worker.started",
      "timestamp": "2026-03-25T14:30:01.000Z",
      "payload": {
        "redacted": false,
        "value": {
          "request_id": "uuid",
          "from_status": "queued",
          "to_status": "running",
          "reason_code": null
        }
      }
    }
  ],
  "next_cursor": 2,
  "request_id": "uuid"
}
```

Pass `next_cursor` as `cursor` on the next request to receive only newer events.

***

### GET /v1/runs/{id}/events/stream — Stream run events (SSE)

Server-Sent Events stream for real-time run progress.

| Item                  | Value                                                  |
| --------------------- | ------------------------------------------------------ |
| Method                | GET                                                    |
| Path                  | `/v1/runs/{id}/events/stream`                          |
| Auth                  | Required (`Authorization` header)                      |
| Response Content-Type | `text/event-stream`                                    |
| Query                 | `cursor` (optional — resume from this sequence number) |

**SSE message format:**

```
event: run_event
data: {"seq":3,"type":"step.progress","timestamp":"...","payload":{...}}

event: run_event
data: {"seq":4,"type":"step.done","timestamp":"...","payload":{...}}
```

Each `data` line contains one event object with the same shape as the polling endpoint. The stream closes after idle timeout or client disconnect. Reconnect with `cursor` parameter set to the last received `seq` to resume.

**Responses:** 200 (stream established), 401, 403, 404

**Important**: The standard browser `EventSource` API does not support custom headers. Use a library like `@microsoft/fetch-event-source` or `eventsource` (Node.js) to send the `Authorization` header.

***

## Event object shape

Every event — from both polling and SSE endpoints — has this structure:

| Field       | Type    | Description                                                                      |
| ----------- | ------- | -------------------------------------------------------------------------------- |
| `seq`       | integer | Monotonically increasing sequence number. Use as `cursor` for pagination/resume. |
| `type`      | string  | Event type identifier (see below).                                               |
| `timestamp` | string  | ISO 8601 timestamp.                                                              |
| `payload`   | object  | Contains `redacted` (boolean) and `value` (event-specific fields).               |

`payload.redacted` is `true` when client-supplied fields (`input`, `metadata`, `attachment_refs`, `sensitivity_tags`) have been stripped from `value` based on sensitivity tags.

***

## Event types

> **Complete events reference:** See the [Events reference](/guides/events) for detailed payload schemas and example payloads for every event type.

### Lifecycle events

Status transition events for the run.

**`run.created`** — Run accepted and enqueued.

| Payload field      | Type      | Notes                                          |
| ------------------ | --------- | ---------------------------------------------- |
| `request_id`       | string    | Correlation ID                                 |
| `input`            | object    | Client-supplied input (redacted by default)    |
| `metadata`         | object    | Client-supplied metadata (redacted by default) |
| `attachment_refs`  | string\[] | (redacted by default)                          |
| `sensitivity_tags` | array     | (redacted by default)                          |

**`run.worker.started`** — Worker picked up the run; status → `running`.

| Payload field | Type         | Notes                           |
| ------------- | ------------ | ------------------------------- |
| `request_id`  | string       | Correlation ID                  |
| `from_status` | string       | Previous status (e.g. `queued`) |
| `to_status`   | string       | New status (`running`)          |
| `reason_code` | string\|null | Transition reason               |

**`run.worker.succeeded`** — Run completed successfully. Same shape as `run.worker.started`.

**`run.worker.failed`** — Run failed. Same shape as `run.worker.started`, with `reason_code` indicating failure cause.

**`run.worker.stalled`** — Worker heartbeat lost or timeout. Run may be resumed. Same shape.

**`run.worker.retry_scheduled`** — Failed run re-enqueued for retry. Same shape.

**`run.cancelled`** — Run cancelled by client request.

| Payload field | Type   | Notes           |
| ------------- | ------ | --------------- |
| `request_id`  | string | Correlation ID  |
| `from_status` | string | Previous status |
| `to_status`   | string | `cancelled`     |

**`run.resumed`** — Stalled or paused run resumed. Same shape as `run.cancelled`.

***

### Step events

Content produced during execution. Most clients focus on these.

**`step.progress`** — Incremental update during step execution (streaming content).

| Payload field   | Type              | Notes                                                   |
| --------------- | ----------------- | ------------------------------------------------------- |
| `task_id`       | string            | ID of the executing task                                |
| `kind`          | string            | `content_delta`, `tool_call_start`, or `tool_call_done` |
| `content_delta` | string\|undefined | Text chunk (when `kind` is `content_delta`)             |
| `tool_call_id`  | string\|undefined | Tool call ID (tool-related kinds)                       |
| `tool_name`     | string\|undefined | Tool name (tool-related kinds)                          |
| `request_id`    | string            | Correlation ID                                          |

**`step.done`** — Step completed with full response.

| Payload field | Type              | Notes                                                                                                                   |
| ------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `task_id`     | string            | ID of the completed task                                                                                                |
| `content`     | string            | **Full assistant response text for this step**                                                                          |
| `outcome`     | string            | `succeeded`, `retry_step`, or `fail_run`. `retry_step`: orchestrator will retry; `fail_run`: run transitions to failed. |
| `reason_code` | string\|undefined | Failure reason if applicable                                                                                            |
| `request_id`  | string            | Correlation ID                                                                                                          |

***

### Tool events

**`run.tool.invoked`** — A tool was called during step execution.

| Payload field         | Type         | Notes                                                |
| --------------------- | ------------ | ---------------------------------------------------- |
| `tool_call_id`        | string       | Unique invocation ID                                 |
| `tool_name`           | string       | Name of the tool called                              |
| `tool_outcome`        | string       | `succeeded`, `failed`, `timeout`, or `policy_denied` |
| `tool_input_summary`  | object       | Bounded summary (see below)                          |
| `tool_output_summary` | object       | Bounded summary (see below)                          |
| `policy_reason_code`  | string\|null | Reason if denied by policy                           |
| `duration_ms`         | number       | Execution time in milliseconds                       |

**Tool summary object** (`tool_input_summary`, `tool_output_summary`):

| Field            | Type         | Description                                                                        |
| ---------------- | ------------ | ---------------------------------------------------------------------------------- |
| `schema_version` | string       | Summary schema version                                                             |
| `preview`        | string\|null | First 240 characters of content                                                    |
| `highlights`     | array        | Up to 10 key items, each: `{ key: string, value: string, redacted: boolean }`      |
| `stats`          | object       | `{ fields_total, fields_redacted, bytes_before_redaction, bytes_after_redaction }` |
| `truncated`      | boolean      | Whether the summary was truncated                                                  |

***

### Coordination events

**`run.coordination.decision`** — Orchestrator decided what happens next.

| Payload field    | Type   | Notes                                               |
| ---------------- | ------ | --------------------------------------------------- |
| `request_id`     | string | Correlation ID                                      |
| `decision_type`  | string | `continue`, `replan`, `stop`, or `await_input`      |
| `reason_code`    | string | Machine-readable decision reason                    |
| `role`           | string | Role for next step: `planner`, `worker`, or `judge` |
| `judge_decision` | object | `{ decision, reason }` — judge's evaluation         |

**`run.awaiting_input`** — Run paused, waiting for external signal.

| Payload field | Type              | Notes                                                       |
| ------------- | ----------------- | ----------------------------------------------------------- |
| `request_id`  | string            | Correlation ID                                              |
| `reason_code` | string            | Why input is needed                                         |
| `input_kind`  | string\|undefined | Expected signal type: `approval`, `rejection`, or `payload` |

When you receive this event, use `POST /v1/runs/{id}/signal` to send `approve`, `reject`, or `submit_input`. If no signal is sent within the server-configured timeout, the run escalates to `failed` or `replan` (server-configured).

**`run.signal_applied`** — External signal processed.

| Payload field | Type   | Notes                 |
| ------------- | ------ | --------------------- |
| `request_id`  | string | Correlation ID        |
| `action`      | string | `approve` or `reject` |

**`run.input_received`** — External input submitted and accepted.

| Payload field | Type   | Notes          |
| ------------- | ------ | -------------- |
| `request_id`  | string | Correlation ID |
| `action`      | string | `submit_input` |

***

## Run status values

| Status      | Terminal? | Meaning                                          |
| ----------- | :-------: | ------------------------------------------------ |
| `queued`    |     No    | Enqueued, waiting for worker pickup              |
| `running`   |     No    | Actively executing                               |
| `stalled`   |     No    | Worker heartbeat lost or timeout; may be resumed |
| `succeeded` |    Yes    | Completed successfully                           |
| `failed`    |    Yes    | Completed with failure                           |
| `cancelled` |    Yes    | Cancelled by client request                      |

***

## Memory (read-only)

Customer-scoped read-only memory for audit and debugging. Scoped by `customer_id` from the authenticated API key.

| Method | Path                    | Description                                                                                 |
| ------ | ----------------------- | ------------------------------------------------------------------------------------------- |
| GET    | `/v1/memory/export`     | Export memory as markdown tree: `{ files: [{ path, content }] }`                            |
| GET    | `/v1/memory/items`      | Paginated list: `{ items, limit, offset }`                                                  |
| GET    | `/v1/memory/items/{id}` | Single memory item (snake\_case fields; embeddings omitted unless `include_embedding=true`) |

**Common query parameters:**

| Parameter           | Type    | Description                                 |
| ------------------- | ------- | ------------------------------------------- |
| `workspace_id`      | string  | Filter by workspace                         |
| `subject_id`        | string  | Filter by subject                           |
| `status`            | string  | Filter by item status                       |
| `include_harness`   | boolean | Include harness-internal items              |
| `include_embedding` | boolean | Include embedding vectors (items/{id} only) |

All memory responses include `Cache-Control: no-store`.

***

## Health endpoints

Unauthenticated health probes for connectivity and operational verification.

| Endpoint            | Purpose                                             | Healthy response           |
| ------------------- | --------------------------------------------------- | -------------------------- |
| `GET /health/live`  | Liveness — is the process running?                  | 200                        |
| `GET /health/ready` | Readiness — can the service handle requests?        | 200                        |
| `GET /health/deps`  | Dependency health — are database and queue healthy? | 200 with dependency status |

***

## Error envelope

All non-stream error responses use this JSON envelope:

```json  theme={null}
{
  "error": "bad_request",
  "reason_code": "INPUT_PAYLOAD_INVALID",
  "request_id": "uuid"
}
```

| Field         | Type   | Description                                                                                        |
| ------------- | ------ | -------------------------------------------------------------------------------------------------- |
| `error`       | string | Error class: `bad_request`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `internal_error` |
| `reason_code` | string | Machine-readable code for programmatic handling                                                    |
| `request_id`  | string | Correlation ID for support                                                                         |

***

## Complete reason code reference

### Auth (401/403)

See [authentication.md](authentication.md#auth-reason-codes) for the full list.

### Validation (400)

| Reason code                                    | Context                                             |
| ---------------------------------------------- | --------------------------------------------------- |
| `IDEMPOTENCY_KEY_REQUIRED`                     | `POST /v1/runs` — missing header                    |
| `INPUT_PAYLOAD_INVALID`                        | `POST /v1/runs` — malformed body                    |
| `INPUT_PAYLOAD_TOO_LARGE`                      | `POST /v1/runs` — exceeds 256 KB                    |
| `INPUT_SENSITIVITY_TAG_REQUIRED`               | `POST /v1/runs` — sensitive keys without tags       |
| `INPUT_SENSITIVITY_TAG_INVALID_PATH`           | `POST /v1/runs` — bad tag path                      |
| `INPUT_SENSITIVITY_TAG_INVALID_CLASSIFICATION` | `POST /v1/runs` — bad classification                |
| `INPUT_SENSITIVITY_TAG_DUPLICATE_PATH`         | `POST /v1/runs` — duplicate tag path                |
| `INPUT_PLAN_INVALID`                           | `POST /v1/runs` — invalid v2 plan                   |
| `SIGNAL_PAYLOAD_INVALID`                       | `POST /v1/runs/{id}/signal` — malformed signal body |
| `USAGE_QUERY_PARAMS_INVALID`                   | `GET /v1/usage` — invalid query parameters          |

### Resource (404/409)

| Reason code          | Context                                                 |
| -------------------- | ------------------------------------------------------- |
| `RUN_NOT_FOUND`      | Run does not exist or is not accessible                 |
| State transition 409 | Run is not in a valid state for the requested operation |

### Internal (500)

| Reason code                  | Context                      |
| ---------------------------- | ---------------------------- |
| `SIGNAL_SERVICE_UNAVAILABLE` | Signal service not available |


Built with [Mintlify](https://mintlify.com).