CIBA Endpoints
Client Initiated Backchannel Authentication — human-in-the-loop authorization flows where an agent requests approval from a user out-of-band, the user approves on their phone or admin dashboard, and the agent's pending operation unblocks.
All endpoints below are under
/api/v1/ciba/requests. Earlier drafts of these docs used/api/v1/ciba/authorize,/ciba/approve/{id}, and/ciba/deny/{id}— those routes do not exist.
Authentication
| Header | Description |
|--------|-------------|
| Authorization | Bearer <jwt> — Bulwark JWT. The agent and the responding user both authenticate via JWT. |
| X-Bulwark-Tenant | Tenant UUID. |
Approval and deny endpoints additionally require the JWT subject to match the request's user_id — agents cannot approve their own requests.
Create Authorization Request
POST /api/v1/ciba/requests
Agent requests human approval for an action.
Body
{
"agent_id": "01j...",
"user_id": "01j...",
"action": "credential_access",
"resource": "stripe",
"reason": "Reconcile Q2 invoices — agent requests stripe API key",
"severity": "medium",
"ttl_seconds": 300
}
| Field | Required | Description |
|-------|----------|-------------|
| agent_id | Yes | The agent making the request. |
| user_id | Yes | The user whose approval is being requested. |
| action | Yes | What the agent wants to do (e.g. credential_access, write_data, delete_data). |
| resource | No | Target resource identifier (e.g. service name). |
| reason | No | Human-readable justification surfaced to the user in the approval prompt. |
| severity | No | low / medium / high. Affects notification routing. Defaults to medium. |
| ttl_seconds | No | Request lifetime. Defaults to 300 (5 min). |
Response 201
{
"id": "01j...",
"tenant_id": "01j...",
"agent_id": "01j...",
"user_id": "01j...",
"action": "credential_access",
"resource": "stripe",
"reason": "Reconcile Q2 invoices — agent requests stripe API key",
"severity": "medium",
"status": "pending",
"created_at": "2026-05-07T00:00:00Z",
"expires_at": "2026-05-07T00:05:00Z"
}
After this call, Bulwark notifies the user via the configured channel (push notification, email, webhook). The agent should poll GET /requests/{id}/poll until status changes.
Get Request
GET /api/v1/ciba/requests/{id}
Returns the current state of a request. Useful for a one-shot check; for blocking-until-decided semantics, use the /poll variant below.
Response 200
Same shape as the create-request response. status is one of pending, approved, denied, expired.
Errors
404— request not found.
Long-Poll Request
GET /api/v1/ciba/requests/{id}/poll
Long-polling variant. Blocks for up to 30 seconds waiting for the request's status to leave pending. Returns immediately if it's already non-pending. Returns the current state on timeout (still pending) — call again to keep waiting.
Response 200
Same shape as GET /requests/{id}.
List Pending (User View)
GET /api/v1/ciba/pending
Returns the list of pending authorization requests awaiting the authenticated user's decision. The JWT subject determines whose pending list is returned; you cannot list another user's pending approvals.
Response 200
{
"requests": [
{
"id": "01j...",
"agent_id": "01j...",
"user_id": "01j...",
"action": "credential_access",
"resource": "stripe",
"reason": "Reconcile Q2 invoices",
"severity": "medium",
"status": "pending",
"created_at": "2026-05-07T00:00:00Z",
"expires_at": "2026-05-07T00:05:00Z"
}
]
}
Approve Request
POST /api/v1/ciba/requests/{id}/approve
User approves a pending request. The JWT subject must match the request's user_id and must NOT be an agent session (agents can't approve their own requests).
Response 200
{ "status": "approved" }
Errors
| Status | Cause |
|--------|-------|
| 404 | Request not found. |
| 409 | Request is no longer pending (already approved/denied). |
| 410 | Request has expired. |
| 401 | Authenticated session is an agent, not a user. |
Deny Request
POST /api/v1/ciba/requests/{id}/deny
User denies a pending request. Same auth rules as approve.
Response 200
{ "status": "denied" }
Errors mirror the approve endpoint.
List All Requests (Admin)
GET /api/v1/ciba/requests
Paginated list of every CIBA request in the tenant. Requires admin role.
Query Parameters
| Param | Description |
|-------|-------------|
| status | Filter by status (pending, approved, denied, expired). |
| offset | Pagination offset. |
| limit | Page size. |
Response 200
{
"requests": [/* AuthorizationRequest objects */],
"total": 42,
"offset": 0
}
Typical flow
- Agent (during a credential vend or sensitive operation) hits a policy that requires approval. Bulwark returns
202 approval_requiredwith anapproval_id. - Agent calls
POST /api/v1/ciba/requestswith the action + user_id + reason. - Bulwark notifies the user.
- Agent polls
GET /api/v1/ciba/requests/{id}/poll. - User opens their dashboard or notification, sees the request via
GET /api/v1/ciba/pending, hitsapproveordeny. - Agent's poll returns with
status: approvedordenied. On approved, agent retries the original credential vend with the approval_id.
The approve/deny is a single-shot decision — once recorded, it cannot be reversed via this API.