Approvals

Developer·3 minutes to read

What it does

When a bot wants to do something the policy does not release on its own, it raises an approval and waits: these endpoints list what is pending, read one, and answer it (the approval card's fixed resource field, the deterministic policy engine, taint, saved rules, the automatic reviewer, human-only writes, and signed approvals).

Authentication

An organisation API key. GET /approvals and GET /approvals/{id} need scope read or wider; POST /approvals/{id}/decide needs write or wider. The decision is recorded as the person the key acts as — a script is that person's instrument, and nothing on this surface lets a bot answer its own approval. See Authentication and keys.

Request

GET /approvals

<!-- generated:begin GET /approvals --> <!-- generated:end GET /approvals -->

GET /approvals/{id}

<!-- generated:begin GET /approvals/{id} --> <!-- generated:end GET /approvals/{id} -->

POST /approvals/{id}/decide

<!-- generated:begin POST /approvals/{id}/decide --> <!-- generated:end POST /approvals/{id}/decide -->

GET /approvals takes state=pending. A decision is approved or denied and nothing else: the app's third answer, the standing always allow, is refused at the schema here, because a saved rule is policy and policy is written in the app where a person can see what they are signing up to.

curl -sS "$BOTSEON_API_URL/api/v1/approvals?state=pending&limit=20" \
  -H "authorization: Bearer $BOTSEON_API_KEY" \
  -H "accept: application/json"
const res = await fetch(
  `${process.env.BOTSEON_API_URL}/api/v1/approvals?state=pending&limit=20`,
  {
    headers: {
      authorization: `Bearer ${process.env.BOTSEON_API_KEY}`,
      accept: 'application/json',
    },
  },
);
if (!res.ok) throw new Error(`approvals: ${res.status}`);
const page = (await res.json()) as {
  data: Array<{ id: string; runId: string; toolName: string; resource: string | null }>;
  nextCursor: string | null;
};
for (const approval of page.data) console.log(approval.toolName, approval.resource ?? '—');

Response

{
  "id": "4a5b6c7d-8e9f-4a0b-8c1d-2e3f4a5b6c7d",
  "runId": "7c1e2d3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
  "toolName": "send_email",
  "resource": "mail:ops@example.test",
  "tags": ["external_send"],
  "reason": null,
  "inputRedacted": { "to": "ops@example.test", "subject": "Weekly digest" },
  "decision": null,
  "reviewVerdict": null,
  "expiresAt": "2026-09-18T09:00:00.000Z",
  "createdAt": "2026-09-18T08:00:00.000Z"
}

tags are the permission tags the policy engine matched on — external_send is one of the eleven, and one of the set that needs an approval unless a rule says otherwise. resource is a fixed field the model does not author, which is what makes it safe to read in a script. inputRedacted is the app's own redacted projection of the tool input; this API adds no unredacted reader. reviewVerdict is the automatic reviewer's reading — allow, block or unclear — and it is an input to the decision, never the authority for it.

Errors

StatusCodeWhenWhat to do
401unauthenticatedThe credential is missing, refused, or below the scope the route needs.Use a write key to decide.
400invalid_requestThe decision is not approved or denied. The Idempotency-Key header is optional on this route, and honoured when sent.Answer with one of the two values.
403forbiddenThe key's person can read the run (a shared conversation) but may not decide its approvals.Decide as the conversation's owner.
404not_foundNo such approval in this organisation, or it belongs to a run this person cannot see.List the pending ones first.
409conflictThe approval has expired, or it has already been decided.Read it again; a decided approval keeps its answer.
429rate_limitedA bucket is full.Wait for Retry-After.

Rate limits

Two one-minute request buckets, 300 per key and 1 000 per organisation. A full bucket answers rate_limited with Retry-After: 60; wait that many seconds and retry. Failed authentication is counted separately, against the caller's address, so a working key's allowance is never spent by somebody else's bad credential. See Envelopes, errors, idempotency and paging.

Last verified against build c0f77aa.