Approvals

Developer·3 minutes to read

Verified against docs/superpowers/specs/2026-09-15-p4-a-typed-http-api-openapi-webhooks.md §4.3, §4.4, §5.2, §6; re-verified against the code at close-out.

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 (F-APR-1 to F-APR-7 — 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 admin the key is bound to — 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_ORIGIN/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_ORIGIN}/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, or the POST carries no Idempotency-Key.Answer with one of the two values.
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.