Approvals gate the money path. An agent requests promotion to paper trading; a human approves or denies. See Approval workflow for the full flow.
List approvals
GET /api/approvals?run_id=run_abc&state=pending
Authorization: Bearer <token>
| Query param | Type | Description |
|---|
run_id | string | Optional. Filter to approvals for a specific run. |
state | string | Optional. Filter by pending, approved, or denied. |
Response
{
"approvals": [
{
"id": "apr_def",
"run_id": "run_abc",
"scope": "paper",
"state": "pending",
"requested_by": { "actor_kind": "api_key", "key_id": "kid_123" },
"decided_by": null,
"tenant_id": "tnt_abc",
"created_at": "2025-01-15T12:00:00Z",
"decided_at": null
}
]
}
Requires the approvals:read scope.
Decide an approval
POST /api/approvals/{id}/decide
Authorization: Bearer <clerk-jwt>
Content-Type: application/json
{ "decision": "approve" }
| Field | Type | Required | Description |
|---|
decision | enum | yes | approve or deny. |
Deciding an approval is human-only. The endpoint requires a Clerk session JWT; an API key (agent) is rejected with 403. The approvals:decide scope is only ever granted to human principals.
Response (approved)
{
"id": "apr_def",
"run_id": "run_abc",
"scope": "paper",
"state": "approved",
"requested_by": { "actor_kind": "api_key", "key_id": "kid_123" },
"decided_by": { "actor_kind": "human", "user_id": "user_josh" },
"decided_at": "2025-01-15T12:05:00Z"
}
Response (denied)
{
"id": "apr_def",
"run_id": "run_abc",
"scope": "paper",
"state": "denied",
"decided_by": { "actor_kind": "human", "user_id": "user_josh" },
"decided_at": "2025-01-15T12:05:00Z"
}
Trace events
Deciding an approval emits an approval_decided trace event, which is streamed over SSE and persisted in the audit trail. See Trace events.
Errors
| Status | When |
|---|---|---|
| 403 | Caller is an API key (agent), not a human session. |
| 404 | Approval does not exist or belongs to another tenant. |
| 409 | Approval is not in the pending state (already decided). |