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

  1. Agent (during a credential vend or sensitive operation) hits a policy that requires approval. Bulwark returns 202 approval_required with an approval_id.
  2. Agent calls POST /api/v1/ciba/requests with the action + user_id + reason.
  3. Bulwark notifies the user.
  4. Agent polls GET /api/v1/ciba/requests/{id}/poll.
  5. User opens their dashboard or notification, sees the request via GET /api/v1/ciba/pending, hits approve or deny.
  6. Agent's poll returns with status: approved or denied. 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.