Agent Session Endpoints

Agent sessions represent an agent acting on behalf of a user or autonomously. Sessions scope credentials, attach a Biscuit capability token, and enforce policy on every credential vend.

All endpoints below are under /api/v1/agent/sessions (note the agent/ segment). Earlier drafts of these docs used /api/v1/sessions/*; that prefix does not exist.


Authentication

| Header | Description | |--------|-------------| | Authorization | Bearer <agent-api-key> — Required on every endpoint. The agent's API key authenticates the request and identifies the acting agent. | | X-Bulwark-Tenant | Tenant UUID. | | X-Bulwark-Token | Biscuit token from the create-session response. Required on credential / proxy endpoints; the token carries the session's capability rights. |


Create Session

POST /api/v1/agent/sessions

Creates a new agent session and returns a Biscuit capability token. The Biscuit token must be sent on subsequent credential / proxy requests.

Body

{
  "task_description": "Reconcile invoices for Q2",
  "ttl_seconds": 900,
  "max_uses": 50,
  "rights": [
    { "service": "stripe", "operation": "charges:list" }
  ],
  "device": {
    "device_id": "macbook-air-m2-001",
    "hostname": "ron.local",
    "platform": "darwin",
    "arch": "arm64",
    "labels": { "team": "engineering" }
  }
}

| Field | Required | Description | |-------|----------|-------------| | task_description | No | Human-readable purpose. Surfaced in audit and CIBA approval prompts. | | ttl_seconds | No | Session lifetime. Defaults to 900 (15 min). | | max_uses | No | Credential vend cap for this session. Defaults to a service-level setting. | | rights | No | Biscuit capability rights to attenuate the session with. Empty = full agent capabilities. | | device | No | Device attestation context. If supplied and the device is suspended, the session is rejected with 403. |

Response 201

{
  "session": {
    "id": "01j...",
    "agent_id": "01j...",
    "tenant_id": "01j...",
    "status": "active",
    "task_description": "Reconcile invoices for Q2",
    "expires_at": "2026-05-07T00:15:00Z",
    "max_uses": 50,
    "current_uses": 0,
    "created_at": "2026-05-07T00:00:00Z"
  },
  "biscuit_token": "<base64-biscuit>"
}

The biscuit_token is shown ONCE. Store it client-side and send it as X-Bulwark-Token on subsequent calls within this session.


Request Credentials (Vending)

POST /api/v1/agent/sessions/{id}/credentials

Vend specific credential fields from a registered vault service. Bulwark evaluates the credential vending policy. If denied, returns 403. If approval is required, returns 202 with an approval reference (CIBA flow); the agent should then poll the CIBA endpoint and retry once approved.

Headers

X-Bulwark-Token is required (the Biscuit from create-session).

Body

{
  "service_name": "stripe",
  "fields": ["api_key"],
  "approval_id": "01j..."
}

| Field | Required | Description | |-------|----------|-------------| | service_name | Yes | Service registered in the vault. | | fields | Yes | Specific credential fields to return (e.g. ["api_key"], ["username", "password"], ["totp_code"]). Only the requested fields are returned. | | approval_id | No | If a previous request returned 202 with an approval ID, pass it here on retry after the user approved. |

Response 200 (vended)

{
  "fields": {
    "api_key": "sk_live_..."
  },
  "grant_id": "01j...",
  "use_count": 1,
  "max_uses": 50
}

Response 202 (approval required)

{
  "approval_required": true,
  "approval_id": "01j...",
  "poll_url": "/api/v1/ciba/requests/01j.../poll"
}

Errors

| Status | Cause | |--------|-------| | 403 | Session not active, session not owned by this agent, policy denied access, or biscuit denied operation. | | 404 | Service not found in vault, or connector not registered. | | 429 | Session's max_uses exhausted. |


Proxy Service Call

POST /api/v1/agent/sessions/{id}/proxy

Make an outbound HTTP call to a registered service through Bulwark, which injects the credential at the gateway layer. Useful when you don't want plaintext credentials touching the agent at all.

Headers

X-Bulwark-Token is required.

Body

{
  "service_name": "stripe",
  "method": "GET",
  "path": "/v1/charges?limit=10",
  "body": null,
  "operations": ["charges:list"]
}

| Field | Required | Description | |-------|----------|-------------| | service_name | Yes | Service registered in the vault. | | method | Yes | HTTP method to use against the service. | | path | Yes | Path on the service (Bulwark prepends the service's base_url). | | body | No | Request body for POST/PUT/PATCH. | | operations | Yes | Operation labels for policy decisions (e.g. ["charges:list"]). Must be a subset of the service's available_operations AND the session's biscuit rights. |

Response

The provider's response is forwarded as-is. Bulwark adds X-Bulwark-Vended-Grant: <grant_id> to the response headers for audit correlation.


Attenuate Session

POST /api/v1/agent/sessions/{id}/attenuate

Tighten the session's biscuit token by appending additional restrictions (TTL shorten, scope narrow, single-operation lock). Returns a new biscuit token that the agent must use going forward; the old token still works until it expires, but any restriction added is permanent on the new token.

Body

{
  "rights": [
    { "service": "stripe", "operation": "charges:list" }
  ],
  "ttl_seconds": 300
}

Response 200

{
  "biscuit_token": "<new-base64-biscuit>"
}

Complete Session

POST /api/v1/agent/sessions/{id}/complete

Marks the session completed. Future credential vends and proxy calls return 403. Use when the agent's task is done and you want to release any remaining capacity.

Response 200

{ "status": "completed" }

Typical flow

  1. Agent (with API key) calls POST /agent/sessions with task description and optional rights/device. Receives session + Biscuit token.
  2. Agent calls POST /agent/sessions/{id}/credentials (with X-Bulwark-Token) to fetch credential fields, OR calls POST /agent/sessions/{id}/proxy to call the service through Bulwark.
  3. If 202 approval_required, agent polls GET /api/v1/ciba/requests/{approval_id}/poll until status changes, then retries the credential request with approval_id.
  4. When the task is done, agent calls POST /agent/sessions/{id}/complete.