documentation
06 api reference

Approvals API

The approvals API lives under /api/approvals and gives you programmatic access to the approval gate system — list pending requests, read details, approve or reject, and pull history for audit.

Authentication

Viewer role or above for reads, the default decider role (usually Admin) for submitting decisions. See Approvals for the full conceptual model.

Endpoints

GET /api/approvals

List approval requests.

Query parameters:

ParameterTypeDescription
userIdstringFilter to requests assigned to a specific user
statusstringFilter by status (Pending, Approved, Rejected, Expired)

Response: an array of approval request objects. Each object includes id, requesting agent, session id, action summary, target, requested timestamp, expiration timestamp, and current status.

GET /api/approvals/pending/count

Get a count of pending approval requests. Useful when rendering a badge in your own UI without fetching the full list.

Query parameters:

ParameterTypeDescription
userIdstringCount pending requests assigned to a specific user

Response:

{
  "count": 3
}

GET /api/approvals/{id}

Get a single approval request by id.

Response: the full request with:

  • Request summary and target
  • The agent’s reasoning (what they want to do and why)
  • The exact tool call to be executed if approved
  • Context from the session (recent messages, prior decisions)
  • Current status and any prior decisions if this request has been touched

PUT /api/approvals/{id}/decide

Submit an approve or reject decision.

Request body:

{
  "decision": "Approve",
  "reason": "Recipient is on the approved outreach list",
  "deciderId": "user_admin_123"
}

Decision values: Approve or Reject. The reason is optional for Approve but strongly recommended for Reject — agents read the reason and use it to adjust their behaviour on the next turn.

Response: the updated approval object, with status changed to Approved or Rejected. The requesting agent will pick up the decision on its next turn and execute (on approve) or adjust (on reject).

GET /api/approvals/history

Get a chronological history of approval decisions — who approved what, when, and with what reason. This is effectively the approval-specific slice of the audit log, useful when you want to pull approval history without scanning the full audit trail.

Query parameters:

ParameterTypeDescription
userIdstringFilter to decisions made by a specific user

Response: an array of historical approval objects with decision, reason, decider, timestamp, and the original request details.

Example — approve a pending request

# List pending requests
curl "https://your-exolvra.example/api/approvals?status=Pending" \
  -H "Authorization: Bearer exou_..."

# Read one in detail
curl https://your-exolvra.example/api/approvals/appr_xyz \
  -H "Authorization: Bearer exou_..."

# Approve it
curl -X PUT https://your-exolvra.example/api/approvals/appr_xyz/decide \
  -H "Authorization: Bearer exou_..." \
  -H "Content-Type: application/json" \
  -d '{
    "decision": "Approve",
    "reason": "Reviewed the generated email, looks fine",
    "deciderId": "user_admin_123"
  }'

Example — auto-approve a specific action pattern

Sometimes you want to script an auto-approver for a narrow case — for example, auto-approve any send_email where the recipient is on an internal allowlist. The pattern is a cron script that polls pending, filters by action pattern, and calls PUT /{id}/decide for matching requests.

#!/bin/bash
# Pull pending email approvals
curl "https://your-exolvra.example/api/approvals?status=Pending" \
  -H "Authorization: Bearer $EXOLVRA_KEY" \
  | jq '.[] | select(.action == "send_email" and (.target | test("@internal.example$")))' \
  | while read -r request; do
      id=$(echo "$request" | jq -r '.id')
      curl -X PUT "https://your-exolvra.example/api/approvals/$id/decide" \
        -H "Authorization: Bearer $EXOLVRA_KEY" \
        -H "Content-Type: application/json" \
        -d '{"decision":"Approve","reason":"Auto-approved: internal recipient","deciderId":"auto-approver"}'
    done

Run it every minute from cron. Use the approval gate’s rule configuration as the fallback — only requests that don’t match your auto-approver rule get surfaced to humans.

Common pitfalls

Approving without reading the request. The whole point of the gate is human review. Scripting blanket approvals defeats the purpose — use an auto-approver only when you can narrowly characterise which requests are safe.

Not providing a reason on rejection. Agents learn from rejection reasons. A reason-less rejection teaches nothing and the agent may repeat the same mistake next time. Be specific.

Forgetting about expiration. Unattended requests expire after their TTL (default 24 hours) and count as rejections. If you’re scripting an approver, make sure it runs often enough to catch requests before they expire.

Where to go next