$AUDIT_KEY, $APPROVER_KEY, $OPS_KEY and $ADMIN_KEY for credentials of that role.
Approvals
GET /v1/approvals?status=&run_id= (auditor+)
status: pending, approved, denied, consumed, expired. Only argument names are stored. required_roles (roles or
groups that may decide; empty means any approver), quorum, decisions, on_timeout and escalated come from the manifest
and policy (approvals). A decided request also has break_glass when overridden that way.
POST /v1/approvals/{id}/approve and /deny (approver+)
{"note":"…"}. Returns the updated record (with quorum, status stays pending until enough distinct approvers
agree). Errors: 404 unknown; 403 you are not among the required roles or groups, the request was made by your own run, or you already voted; 409 no longer
pending (approval … is approved, not pending). The caller’s roles and groups are taken from the credential.
POST /v1/approvals/{id}/break-glass (admin)
reason needs at least 10 characters (400 otherwise) and evidence a ticket or
incident reference. The record becomes approved with break_glass: {by, reason, evidence, ms} and the console shows a break-glass badge on it.
Delegations
GET /v1/delegations (auditor+), POST /v1/delegations (approver+), DELETE /v1/delegations/{id} (the delegator or an admin).
Body of the POST: {"to":"bob","hours":24,"reason":"on leave","groups":["finance"]}; from defaults to you and only an admin may
set another. → 201 with the delegation.
reversi-server serves these routes. The same delegations are visible in the console (Approvals page) and reversi delegate.
Evidence
GET /v1/runs/{id}/bundle and GET /v1/ledger/bundle (auditor+)
A signed evidence bundle. 404 unless the server has REVERSI_SIGNING_KEY.
GET /v1/evidence/key (auditor+)
GET /v1/ledger/verify (auditor+)
{"ok":true,"records":3,"head":"e3c2c625…"} or {"ok":false,"seq":7,"error":"…"}.
Status and kill switch
GET /v1/status (auditor+)
POST /v1/halt (admin)
reason is required. Optional tool, actor, tenant and environment (globs) narrow the scope; with none, every new call is
blocked. halted in the answer is the global halt message (null for a scoped halt). Rollbacks still work. The caller’s name is
appended to the reason.
GET /v1/halts (auditor+) and POST /v1/resume (admin)
GET → {"halts":[…]}. POST /v1/resume with {"id":"halt_…"} lifts one; with no body it lifts all → {"halted":null,"lifted":1}.
Legal holds, retention, anchors
See retention and evidence.
Manifest and policy registry
These are the routesreversi apply --server uses. They need a server started on a registry folder (and a policy versions
folder for the policy routes), else 404 no manifest registry folder is configured on this server. See GitOps.
GET /v1/registry (operator+)
policy is null without a policy versions folder.
POST /v1/registry/manifests
status "draft" needs operator; anything else publishes as approved and needs admin, then reloads the running
configuration. 400 for an invalid manifest (the message lists the problems). Optional note. New versions are signed if the
server has a signing key.
POST /v1/registry/manifests/{tool}/approve and /deprecate (admin)
approve body {"version"?: n, "four_eyes"?: true} promotes a draft (the latest by default) to a new approved version; with
four_eyes the author cannot be the approver. deprecate body {"replaced_by"?: "tool"}. Both → {"tool","version"} and reload.
POST /v1/registry/policy and POST /v1/registry/policy/{version}/activate (admin)
{"text":"<policy YAML>","note":"…","activate":true} validates and stores a new version (activating it unless activate is
false) → 201 with the version record. activate makes an older version current, which is how you roll back → {"active":1}.
If the saved configuration fails to load the answer is 409 saved, but the new configuration did not load: … and the previous
one stays active.
POST /v1/reload (admin)
Re-read manifests and policy now.
error is present, with changed: false, if the new configuration was rejected.
API keys
NeedREVERSI_KEYS to point at a writable file (404 otherwise). Admin only.
GET /v1/keyslists keys byindexwith name, roles, groups, tenant, expiry,disabled,allow_observe,max_permissionsand a 12-characterfingerprint(never the key).POST /v1/keysbody:name,roleorroles, optionalgroups,tenant,max_permissions,expires_in_days. Only the SHA-256 is stored.PATCH /v1/keys/{index}with{"disabled": true}and/or{"expires_at": "<ISO date>" | null}.DELETE /v1/keys/{index}.
Slack callback
POST /integrations/slack/actions approves or denies from the buttons on a Slack notification, verifying Slack’s signature and
a 5-minute timestamp window. reversi-server enables it when REVERSI_SLACK_SIGNING_SECRET and REVERSI_SLACK_USERS (a JSON file mapping Slack user ids to
names, roles and groups) are set, and otherwise answers 404 Slack approvals are not enabled. A mapped user needs the
approver role or higher to decide (authentication).
Operational
GET /healthz → {"ok":true}. GET /readyz → {"ok":true,"ledger_head":"…","halted":null}, 503 if the store cannot be read.
GET /metrics (auditor, or open with REVERSI_METRICS_PUBLIC=1): see monitoring.
