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:
| Parameter | Type | Description |
|---|---|---|
userId | string | Filter to requests assigned to a specific user |
status | string | Filter 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:
| Parameter | Type | Description |
|---|---|---|
userId | string | Count 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:
| Parameter | Type | Description |
|---|---|---|
userId | string | Filter 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
- Approvals — the dashboard counterpart and conceptual model
- Audit log — approval decisions are logged here
- API overview — the two API surfaces