This is the API the SDKs and CLI talk to. You will rarely call it by hand — the TypeScript SDK wraps every endpoint with a typed client — but the request and response format is the same whichever way you reach it. There are three groups of endpoints: policy (pull the rules your agent enforces), receipts (push signed Action Receipts to the outbox), and approvals (route an action to a human and poll for their decision).
Base URL and authentication
The base URL is your control-plane endpoint. There is no single fixed host, so every example below uses https://<your-endpoint> as a placeholder. Every request carries two headers:
Authorization: Bearer <API_KEY>— your team’s API key. Set it once at startup; see Authentication for where the key comes from and how to configure the SDK client.Content-Type: application/json— request and response bodies are JSON.
A minimal authenticated request looks like this:
curl https://<your-endpoint>/v1/teams/team_19/policy \ -H "Authorization: Bearer $HESO_API_KEY" \ -H "Content-Type: application/json"
In TypeScript, call configure(apiKey, endpoint) once before any cloud call rather than assembling requests by hand — every method then inherits the key and base URL. See the TypeScript SDK reference.
Policy
Fetch the policy bundle a team currently enforces. The bundle is the ordered set of rules plus a version string you can pin against. Floors and parsing are checked when the policy is loaded — see Pinned floors — so any bundle you pull has already passed those checks.
GET /v1/teams/{teamId}/policyREST
Return the active PolicyBundle for a team.
GET /v1/teams/{teamId}/policyParameters
- teamIdstringrequired
- Path parameter. The team whose policy you want.
ReturnsPolicyBundle
A PolicyBundle with the fields below.
Example
curl https://<your-endpoint>/v1/teams/team_19/policy \ -H "Authorization: Bearer $HESO_API_KEY"
The response shape:
| Field | Type | Description |
|---|---|---|
version | string | The bundle version. Pin against it to detect a policy change. |
rules | PolicyRule[] | The ordered rule list, first-match-wins. See Policy files for the rule shape. |
fetchedAt | string | ISO timestamp of when the bundle was fetched. |
Receipts
Push signed receipts to the outbox. The server re-verifies every receipt before accepting it. It recomputes the BLAKE3 action_hash, checks the Ed25519 signatures, and re-derives the trust level — the same verify orderthe browser and SDKs run. A receipt that fails is rejected with a reason; acceptance never depends on the client’s word.
POST /v1/receiptsREST
Submit a single Action Receipt to the outbox.
POST /v1/receiptsParameters
- bodyActionReceiptrequired
- The full signed receipt. See the receipt schema for every field.
ReturnsOutboxPushResult
An OutboxPushResult with the fields below.
Example
curl https://<your-endpoint>/v1/receipts \ -H "Authorization: Bearer $HESO_API_KEY" \ -H "Content-Type: application/json" \ -d @receipt.json
The response shape:
| Field | Type | Description |
|---|---|---|
receiptId | string | The server-assigned id for the stored receipt. |
accepted | boolean | Whether the receipt passed server-side re-verification and was stored. |
rejectionReason | string? | Present only when accepted is false — why the receipt was refused. |
POST /v1/receipts/batchREST
Submit many receipts in one request.
POST /v1/receipts/batchParameters
- receiptsActionReceipt[]required
- An array of signed receipts, wrapped in a { receipts } object.
ReturnsOutboxPushResult[]
An array of OutboxPushResult, one per submitted receipt, in the same order.
Example
curl https://<your-endpoint>/v1/receipts/batch \ -H "Authorization: Bearer $HESO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "receipts": [ /* ActionReceipt, ActionReceipt, … */ ] }'
Each receipt is re-verified independently, so a batch can come back with some entries accepted and others rejected. Match each result to its receipt by position.
An accepted receipt means the server re-ran the math and the signatures hold — it confirms the operator (and, at L1, an approver) authorized the action under a known policy. The server holds no signing key; it only re-checks what is already on the receipt. It records what was authorized, not whether the action succeeded downstream.
Approvals
When policy routes an action to a human, open an approval, poll it until the approver decides, then carry their decision back into the receipt. The approver signs with their own device-held key — the cloud holds no signing key. See Human approval for the full flow.
POST /v1/approvalsREST
Open an approval request for a receipt and route it to a human.
POST /v1/approvalsParameters
- receiptActionReceiptrequired
- The receipt whose action needs a human decision.
- routingHintstring
- Optional hint for who should be asked (e.g. a queue or on-call label).
ReturnsOpenApprovalResult
An OpenApprovalResult with the fields below.
Example
curl https://<your-endpoint>/v1/approvals \ -H "Authorization: Bearer $HESO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "receipt": { /* ActionReceipt */ }, "routingHint": "finance-oncall" }'
The response shape:
| Field | Type | Description |
|---|---|---|
approvalId | string | The id you poll to learn the outcome. |
token | string? | An optional one-time token for the approver to act on. |
expiresAt | string | ISO timestamp after which the request can no longer be answered. |
GET /v1/approvals/{approvalId}REST
Poll the current state of an approval.
GET /v1/approvals/{approvalId}Parameters
- approvalIdstringrequired
- Path parameter. The id returned when the approval was opened.
ReturnsPollApprovalResult
A PollApprovalResult with the fields below.
Example
curl https://<your-endpoint>/v1/approvals/apr_7c2 \ -H "Authorization: Bearer $HESO_API_KEY"
The response shape:
| Field | Type | Description |
|---|---|---|
outcome | ApprovalOutcome | The current state — pending, or a settled decision. |
approval | Approval? | The settled approval record, present once the approver has decided. |
The TypeScript SDK’s waitForApproval wraps this poll loop for you, with a configurable interval and timeout — reach for it instead of writing your own loop.
POST /v1/approvals/{approvalId}/tokenREST
Submit the approver's signed token to settle an approval.
POST /v1/approvals/{approvalId}/tokenParameters
- approvalIdstringrequired
- Path parameter. The id of the open approval.
- tokenstringrequired
- The approver's token, carried in the request body.
ReturnsApproval
The settled Approval record.
Example
curl https://<your-endpoint>/v1/approvals/apr_7c2/token \ -H "Authorization: Bearer $HESO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "token": "3a9f…04af" }'
Errors and limits
A non-2xx response carries a JSON error body. The TypeScript client surfaces this as a thrown Error whose message includes the HTTP status, the statusText, and the response body, so you do not have to parse it by hand.
{ "error": "policy_not_found", "message": "No policy bundle for team team_19"}
The status codes you will see:
| Status | Meaning |
|---|---|
400 | Malformed request — bad JSON, or a body that fails schema validation. |
401 | Missing or invalid API key. |
404 | No such team, receipt, or approval. |
422 | A receipt was well-formed but failed server-side re-verification. |
429 | Rate limited — the request rate for your plan was exceeded. Wait for the Retry-After header, then retry (see Rate limits). |
503 | Server briefly at capacity — retry after the short Retry-After delay. |
Each plan has a sustained request rate (with a short burst allowance) and a monthly receipt quota. The exact numbers depend on your plan and can change, so rather than list them here we surface them where they stay accurate: your dashboard shows the current rate limit and usage for your team. Over the rate, the API returns 429 with a Retry-After header — see Rate limits for the headers and how to back off.
