Seal
API reference

Approvals

Create, read, and decide approval requests — POST /v1/approvals, GET /v1/approvals/:id, and POST /v1/approvals/:id/decision.

POST /v1/approvals

Creates an approval request. The referenced policy is evaluated synchronously: an allow verdict is auto-approved and returned with 200; anything else is created as pending and returned with 201.

Requires the approvals:write scope.

Request body

FieldTypeRequiredNotes
actionstringyes1–200 chars.
payloadobjectyesJSON object evaluated by the policy.
policyKeystringyes1–200 chars.
contextobjectnoReviewer-facing reasoning; not evaluated.
agentstringno1–200 chars.
idempotencyKeystringnoReturns the existing record on replay.
curl -X POST https://api.your-org.com/v1/approvals \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{
    "action": "wire.transfer",
    "policyKey": "payments.high_value",
    "payload": { "to": "acct_999", "amount": 5000 },
    "context": { "reason": "Vendor invoice #4471" }
  }'

ApprovalRequestView

Both endpoints return this shape.

{
  "id": "6f1c…-uuid",
  "status": "pending",
  "action": "wire.transfer",
  "payload": { "to": "acct_999", "amount": 5000 },
  "context": { "reason": "Vendor invoice #4471" },
  "createdAt": "2026-07-05T12:00:00.000Z",
  "expiresAt": "2026-07-05T12:15:00.000Z",
  "feedback": null
}
FieldTypeNotes
idstring (uuid)Approval id.
statusenumpending, approved, rejected, expired, escalated.
actionstringEchoes the request.
payloadobjectThe evaluated payload.
contextobjectPresent only if supplied.
createdAtstring (ISO 8601)Creation time.
expiresAtstring | nullWhen a pending request times out; null once terminal-safe.
feedbackstring | nullReviewer feedback; present once approved or rejected.

Responses

StatusWhen
200policy returned allow; request auto-approved
201request created as pending (or idempotencyKey hit)
402free-tier monthly limit reached
403key lacks approvals:write
422body failed validation

GET /v1/approvals/:id

Reads one approval. Supports long-polling: pass ?wait=<seconds> (1–30) and, if the request is still pending, the server holds the connection until it settles or the budget elapses, then returns the current state.

Requires the approvals:read scope.

curl "https://api.your-org.com/v1/approvals/6f1c…-uuid?wait=30" \
  -H "authorization: Bearer sk_live_..."
StatusWhen
200returns the ApprovalRequestView
403key lacks approvals:read
404no approval with that id in your org

POST /v1/approvals/:id/decision

Records a human decision on a pending approval. Reviewers normally decide in Slack, the reviewer console, or a signed decision link — use this endpoint to decide programmatically, for example from a custom reviewer integration.

Requires the approvals:decide scope.

Request body

FieldTypeRequiredNotes
verdictenumyesapproved or rejected.
feedbackstringno≤ 5000 chars. Stored on the record and included in the webhook.
editedPayloadobjectnoApprove with a modified payload; the approved record carries it.
curl -X POST https://api.your-org.com/v1/approvals/6f1c…-uuid/decision \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "verdict": "approved", "feedback": "Amount and recipient verified." }'

Returns the updated ApprovalRequestView.

Responses

StatusWhen
200decision recorded; returns the ApprovalRequestView
403key lacks approvals:decide
404no approval with that id in your org
409approval is not pending (already decided or expired)
422body failed validation

Prefer the SDK over calling create/read directly — gate.require() handles the create-then-poll loop and surfaces terminal states as typed errors.

On this page