# AI Approvel > Human approval for AI agents. One REST call asks a named human on WhatsApp or SMS to approve or reject an action; the agent polls (or receives a signed webhook) for the decision and only then runs the risky step. Every state change is written to an Ed25519-signed, hash-chained audit trail that anyone can verify offline. Key facts (exact; the full reference is at /docs and /llms-full.txt): - Base URL: `https://.supabase.co/functions/v1/approvals` (the Supabase project ref of your AI Approvel deployment plus the `approvals` function; `https://.functions.supabase.co/approvals` is equivalent). - Auth: `Authorization: Bearer ` on every request. Keys look like `apk_live_<8 hex>_` (the `apk_live_<8 hex>` part is a non-secret identifier); they are created and rotated in the dashboard under Settings → API key and the full key is shown once. Missing, malformed, unknown or rotated key → `401 unauthorized`. - Bodies are JSON; ids are UUIDs; timestamps are RFC 3339 UTC; errors are always `{ "error": { "code", "message", "details" } }` — branch on `code`. - Endpoints: `POST /` create (202 → `{ id, status, expires_at, replayed }`), `GET /?limit=50` list newest first (`{ count, approvals: [...] }`; rows have `id, status, action, amount, currency, channel_used, created_at, expires_at`), `GET /{id}` status + decision, `GET /{id}/audit` signed audit trail. Only `POST /` is rate-limited. - Create body (strict — unknown fields are a 400): `approver_id` (required, uuid), `action` (required, 1–500 chars, what the approver reads), `amount` (number ≥ 0), `currency` (3 letters, upper-cased), `context` (any JSON, kept in the audit trail; not in the webhook), `params_hash` (1–128 chars, binds the approval to the exact parameters), `callback_url` (https, public host), `locale` (`en` | `ar`), `timeout_seconds` (integer > 0, default 3600, max 86400), `metadata` (any JSON, kept in the audit trail). There is no `expires_in_seconds`. - Idempotency: send an `Idempotency-Key` header (unique per project); a repeat returns the original approval with `202` and `replayed: true`, nobody is messaged twice and the replay is not charged to the quota. - Statuses: `pending → delivered → decided`, side exits `expired` (no reply within `timeout_seconds`; a late reply is refused) and `failed` (could not deliver). `decided`, `expired`, `failed` are terminal. `decision` is `null` until decided, then `{ outcome: "approved" | "rejected", decided_via, decided_by, decided_at }` with `decided_via` ∈ `whatsapp_button | whatsapp_text | sms_reply | web_link | console | api` and `decided_by` the approver's E.164 number. `channel_used` ∈ `whatsapp | sms | console`. `GET /{id}` also returns `params_hash`, `locale`, `expires_at`, `created_at`, `updated_at`. - Polling every 2.5 s is fine. `GET /?status=&q=&cursor=&limit=` filters server-side (`status` is a lifecycle status or `approved` / `rejected`; `q` is a substring of `action`), returns `{ count, approvals, next_cursor, has_more }` with a `decision` object on each row, and pages by passing `next_cursor` back as `cursor` until `has_more` is false (invalid cursor → 400). - Webhook `approval.decided`: POST to `callback_url`, body `{ id, status, outcome, decided_via, decided_at, action, params_hash, approver_id, amount, currency }` in that order (additive changes only), headers `X-Approv-Event: approval.decided`, `X-Approv-Timestamp` (Unix seconds) and `X-Approv-Signature` = lowercase hex HMAC-SHA256 over `${timestamp}.${rawBody}` keyed with your project's `whsec_…` signing secret (Settings → Webhooks, or `GET /account/webhook-secret`; rotate with `POST /account/webhook-secret/rotate` — the old secret stops verifying immediately). Verify over the raw bytes, compare in constant time, reject if |now − timestamp| > 300 s, respond 2xx within 10 s; non-2xx/redirect/timeout is retried from the queue (about a minute apart, up to 5 attempts) and every attempt is an audit event `webhook.delivered` / `webhook.failed`. - Audit trail: `{ approval_request_id, algorithm, public_key, verification, events }`; each event has `seq` (from 0), `event_type` (`approval.requested`, `message.queued`, `message.sent`, `message.failed`, `approval.decided`, `approval.expired`, `webhook.delivered`, `webhook.failed`), `payload`, `prev_hash` (64 zeros for seq 0), `hash`, `signature`, `public_key_id`, `created_at`. `hash = sha256( prev_hash ␟ event_type ␟ canonical_json(payload) ␟ created_at )` — the four fields joined with U+241F (`algorithm.field_separator`; the `|` in `algorithm.hash_formula` is notation), keys sorted recursively, no whitespace, lowercase hex. `signature` = base64 Ed25519 over the UTF-8 bytes of the hex hash string; public key `spki-der-base64` with a `key_id`. Verifier: /verify-audit.mjs. - Teams, scoped keys and webhook endpoints (Phase C, dashboard session routes, marked `x-approv-status: "phase-c"` in /openapi.json): roles `owner` / `admin` / `member` with `/account/members` and `/account/invites/:token`; up to 20 named API keys per workspace with scopes `approvals:write` / `approvals:read` and optional expiry via `/account/keys` (`403 insufficient_scope` outside the scope); up to 10 webhook endpoints with their own `whsec_…` secrets and a delivery log with replay via `/webhooks/endpoints` and `/webhooks/deliveries`. - Error codes: `validation_error` 400 (`details` = `{ formErrors, fieldErrors }` or `{ approver_id }`), `unauthorized` 401, `quota_exceeded` 402 (`details` = `{ plan, limit, used, period }`; upgrade, retrying won't help), `not_found` 404, `rate_limited` 429 (honour `Retry-After`, seconds), `internal_error` 500. Default rate limit 60 creates/min per project; monthly approval quota by plan (Free 100, Growth 5,000, Scale unlimited). ## Docs - [API reference](https://ai-approvel.com/docs): every endpoint with request/response pairs, status lifecycle, webhook and audit verification. - [OpenAPI 3.1 spec](https://ai-approvel.com/openapi.json): machine-readable description of all paths, schemas and the `approval.decided` webhook. - [Recipes](https://ai-approvel.com/docs#recipes): `requireApproval({ action, amount })` helper in TypeScript and Python, plus tool guards for the OpenAI Agents SDK, LangGraph, Vercel AI SDK and CrewAI. - [Offline audit verifier](https://ai-approvel.com/verify-audit.mjs): dependency-free Node script, `node verify-audit.mjs trail.json`, documented at https://ai-approvel.com/docs#verify-offline. - [Full reference as text](https://ai-approvel.com/llms-full.txt): the docs page flattened to Markdown for ingestion. ## Product - [Pricing](https://ai-approvel.com/pricing): Free, Growth and Scale plans and what counts against the monthly quota. - [Changelog](https://ai-approvel.com/changelog): product and API changes. - [Security](https://ai-approvel.com/security): how keys, webhooks and the audit trail are protected; compliance roadmap. ## Optional - [Home](https://ai-approvel.com/): product overview. - [Contact](https://ai-approvel.com/contact) - [Terms](https://ai-approvel.com/terms) - [Privacy](https://ai-approvel.com/privacy)