Skip to content

Agent Sessions API ​

Start, list and delete agent sessions, follow their event log, answer approvals and questions, attach files, change a session's budget, and poll background runs. The Responses calls that use a session are on the Agents API page; the concepts are in the Agent runtime guide.

All endpoints live under /api/client/v1 and are authenticated with a cpeer_ API token. A token sees only its own project.

Authorization: Bearer cpeer_…

Conventions ​

  • Field names are snake_case throughout this API (runtime_status, max_cost_usd, required_action). Tool args and free-form answers are never re-keyed.
  • {id} / {conversationId} is the session id — the bare conversation id, i.e. the part of resp_{id} after the prefix.
  • Errors use the envelope { "error": { "type", "code", "message", … } } with extra snake_case fields where they help (pending_action_ids, spent_usd, max_cost_usd, action_status). See the error index.
  • A session that belongs to another project, or does not exist, is 404 session_not_found — an id never confirms that something exists elsewhere.

Endpoint map ​

Endpoint
POST /agents/sessionsStart a session without a message
GET /agents/sessionsList sessions
GET /agents/sessions/{id}Get a session — runtime state, pending requests, budget
PATCH /agents/sessions/{id}Change the budget; continues a session paused on it
DELETE /agents/sessions/{id}Delete a session
GET /agents/sessions/{id}/eventsRead the event log
GET /agents/sessions/{id}/events/streamFollow the event log (SSE)
POST /agents/sessions/{id}/actions/{actionId}Answer an approval or a question
POST /agents/sessions/{id}/filesUpload a file
GET /agents/sessions/{id}/filesList a session's files
GET /agents/sessions/{id}/files/{fileId}/contentRead a file
DELETE /agents/sessions/{id}/files/{fileId}Delete a file
GET /agents/runs/{runId}Poll a background run
POST /agents/runs/{runId}/cancelCancel a background run

Session objects ​

Session ​

json
{
  "id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "object": "agent.session",
  "agent": "support-bot",
  "title": "Refund #4821",
  "created_at": "2026-10-11T09:00:00.000Z",
  "updated_at": "2026-10-11T09:14:03.000Z",
  "metadata": { "ticket": "4821" },
  "runtime_status": "requires_action",
  "stop_reason": "requires_action",
  "pending_actions": [
    {
      "id": "act_0123456789abcdef01234567",
      "kind": "tool_approval",
      "tool_name": "sandbox_exec",
      "args": { "command": "rm -rf build" },
      "status": "pending",
      "expires_at": "2026-10-12T09:14:03.000Z",
      "requested_at": "2026-10-11T09:14:03.000Z"
    }
  ],
  "last_event_seq": 41,
  "budget": { "max_cost_usd": 2, "on_reach": "pause" },
  "spent_usd": 0.4213
}
FieldType
idstringThe session id. Pass it as session_id.
agentstringThe agent key.
titlestring | nullUp to 200 characters.
metadataobjectThe string pairs given when the session was created. {} for a session not created through this API.
runtime_statusenumidle, running, requires_action, budget_paused.
stop_reasonenum | nullend_turn, requires_action, limit, cancelled, error, budget_reached. null before the first turn.
pending_actionsarrayRequests still waiting for an answer — see pending action.
last_event_seqintegerThe newest event number; resume events/stream?after= from it.
budgetobject | null{ max_cost_usd, on_reach }.
spent_usdnumberPriced spend so far, in USD.

A list row is a session without pending_actions and with message_count instead of a transcript; its spent_usd is null for a session that last ran before the running total was kept.

Pending action ​

Field
idact_… — pass it to the resolve call.
kindtool_approval, user_question or client_tool.
tool_name, argstool_approval and client_tool only. Never on a question.
questions, allow_free_textuser_question only. questions[]: { question, header?, multi_select?, placeholder?, required?, options?: [{ label, value?, description? }] }.
timeout_secondsclient_tool only.
statuspending, resolved, expired, cancelled.
expires_at, requested_atISO timestamps. expires_at is absent when the agent waits forever.

Start a session ​

http
POST /api/client/v1/agents/sessions

Creates an empty session. Nothing runs and nothing is spent. The session belongs to the user that owns the token and is marked as coming from the API.

Body fieldType
agentstring, requiredThe key of an active agent in the token's project.
titlestringAt most 200 characters. Default New conversation.
metadataobjectUp to 16 string pairs, keys 1–64 characters, values up to 512.
budgetobject | null{ max_cost_usd (> 0), on_reach?: "pause" | "stop" }. null = no budget, overriding the agent's default; absent = the agent's default.
runtime_contextobject{ headers?, metadata?, … } entered once for the whole session and merged under every turn's own. This request's HTTP headers are not kept.
bash
curl -X POST "$CONSOLE_URL/api/client/v1/agents/sessions" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "agent": "support-bot", "title": "Refund #4821", "metadata": { "ticket": "4821" }, "budget": { "max_cost_usd": 2 } }'
typescript
const session = await client.agents.sessions.create({
  agent: 'support-bot',
  title: 'Refund #4821',
  metadata: { ticket: '4821' },
  budget: { max_cost_usd: 2 },
});

201 with the session. Errors: 400 invalid_request (agent missing, title too long), 400 agent_not_active, 400 invalid_metadata, 400 invalid_budget, 403 budget_override_forbidden, 404 agent_not_found.

List sessions ​

http
GET /api/client/v1/agents/sessions?agent=support-bot&status=requires_action&limit=20&after=<cursor>
Query
agentOnly this agent's sessions.
statusidle, running, requires_action, budget_paused. idle includes a session that has not run yet.
limit1–100, default 20.
afterThe previous page's next_cursor.

200: { "object": "list", "data": [ …session rows… ], "has_more": true, "next_cursor": "…" }, newest created first. The cursor marks the last row you read, so sessions created while you page never shift a row into or out of a page you already have. Errors: 400 invalid_limit, 400 invalid_status, 400 invalid_cursor.

typescript
for await (const session of client.agents.sessions.list({ status: 'requires_action' })) {
  console.log(session.id, session.agent);
}

Get a session ​

http
GET /api/client/v1/agents/sessions/{id}

200 with the session, including pending_actions and the newest last_event_seq read from the log. 404 session_not_found. SDK: client.agents.sessions.get(id).

Update a session budget ​

http
PATCH /api/client/v1/agents/sessions/{id}
Body fieldType
budgetobject | null, requiredThe new { max_cost_usd, on_reach? }, or null to remove it. A new amount must be above what the session has already spent.
resumebooleanDefault true. When the session is budget_paused, continue it as a background run. false = change the budget only.
runtime_contextobjectContext for the continuation.

Responses

StatusBodyWhen
200The sessionBudget changed; nothing to continue (the session was not paused, resume is false, the turn had already finished when it crossed the budget, or the budget stops instead of pausing).
202An agent runThe session was budget_paused and its turn carries on as a background run.

Errors: 400 invalid_budget (missing or malformed budget, or not above what was spent), 400 agent_background_disabled (a continuation needs background runs allowed; nothing is changed), 403 budget_override_forbidden, 404 session_not_found, 409 agent_run_conflict. SDK: client.agents.sessions.update(id, body) — tell the two result shapes apart with result.object (agent.session or agent.run). See Session budgets.

Delete a session ​

http
DELETE /api/client/v1/agents/sessions/{id}

200: { "id": "…", "object": "agent.session", "deleted": true }. Removes the session with its files (object, markdown and record), its event log and any open approvals. 404 session_not_found; 409 agent_run_conflict while a run is queued or running on it. SDK: client.agents.sessions.delete(id).

Events ​

List events ​

http
GET /api/client/v1/agents/sessions/{id}/events?after=40&types=agent.message,session.status_idle&limit=100
QueryDefault
after0Only events with a higher seq.
typesallComma-separated event types.
limit100At most 1000.

200:

json
{
  "object": "list",
  "events": [
    {
      "seq": 41,
      "type": "session.status_idle",
      "data": { "stop_reason": "end_turn" },
      "run_id": "run_3f9c1a2b4d5e6f708192a3b4",
      "created_at": "2026-10-01T12:00:03.120Z"
    }
  ],
  "has_more": false,
  "last_seq": 41
}

Oldest first. run_id is present only for events written by a run. The full list of event types and their data is in Session events. SDK: client.agents.sessions.events.list(id, { after, types, limit }).

Stream events ​

http
GET /api/client/v1/agents/sessions/{id}/events/stream?after=41&types=…
Accept: text/event-stream
Last-Event-ID: 41

Server-Sent Events. Replays the stored events after after (or after Last-Event-ID), then follows the session live. Each stored event is a frame with id: <seq>, event: <type>, and the event as JSON in data:. Live agent.message.delta frames have no id and are not stored. A : ping comment is sent every 15 seconds; the stream ends after an hour. SDK: client.agents.sessions.events.stream(id, { after, signal }) (reconnects for you).

Answer an action ​

http
POST /api/client/v1/agents/sessions/{id}/actions/{actionId}
Content-Type: application/json

Body — one of:

KindBody
tool_approval{ "decision": "allow" | "deny", "comment"?, "approved_args"?: { … }, "remember_for_session"?: true }
user_question{ "answers": { "<question text>": "value" | ["v1", "v2"] } } or { "cancelled": true }
client_tool{ "output": <any JSON, ≤ 64 KB> } or { "error": "<text>" }

(approvedArgs and rememberForSession — the camelCase spellings early drafts used — are still accepted, but write the snake_case ones.)

Optional in every body: runtime_context, and "background": true (or the X-Cognipeer-Background: true header) to continue as a run.

Responses

StatusBody
200The continuation, in the Responses shape: a finished response (completed or incomplete), or requires_action again. While other requests of the same pause are still open, status: "requires_action" with an empty output and only those in required_action.actions.
202An agent run — with background: true.

Errors: 400 invalid_resolution, 400 agent_background_disabled, 403 action_forbidden, 404 action_not_found, 404 session_not_found, 409 action_not_pending (with error.action_status), 409 action_cancelled, 409 agent_run_conflict, 410 action_expired.

typescript
const next = await client.agents.sessions.actions.resolve(sessionId, actionId, { decision: 'allow' });

See Approvals & questions.

Files ​

Upload a file ​

http
POST /api/client/v1/agents/sessions/{id}/files?name=contract.pdf
Content-Type: application/pdf

<raw bytes>

Either the raw bytes as the body (MIME type as Content-Type, name in ?name=), or JSON { "name": "notes.txt", "content_type": "text/plain", "data": "<base64 or data URL>" } for a small file. 201 with the file:

json
{
  "id": "6710a3f2c1d4e5f60718293a",
  "object": "agent.session.file",
  "session_id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "name": "contract.pdf",
  "mime_type": "application/pdf",
  "size": 183422,
  "conversion_status": "succeeded",
  "bucket_key": "uploads",
  "path": "agent-sessions/support-bot/6f1c0a2b9d3e4f5a6b7c8d9e/contract-1727780400000-ab12cd34.pdf",
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-31T12:00:00.000Z",
  "content_url": "/api/client/v1/agents/sessions/6f1c0a2b9d3e4f5a6b7c8d9e/files/6710a3f2c1d4e5f60718293a/content"
}

conversion_status: succeeded, pending, failed, skipped (conversion_error says why). Errors: 400 empty_file, 400 invalid_request, 403 attachments_disabled, 404 session_not_found, 409 too_many_files, 409 storage_unavailable, 413 file_too_large, 415 unsupported_file_type, 429 quota_exceeded. SDK: client.agents.sessions.files.upload(id, bytes, { name, mimeType }).

List and delete ​

GET …/files → { "object": "list", "data": [ …files… ] }. DELETE …/files/{fileId} → { "id", "object": "agent.session.file", "deleted": true } (404 file_not_found). SDK: files.list(id), files.delete(id, fileId).

Read a file ​

http
GET /api/client/v1/agents/sessions/{id}/files/{fileId}/content?variant=original&disposition=inline
Range: bytes=0-1023
Query
variantoriginal (default) or markdown — the converted text (409 markdown_unavailable when there is none).
dispositioninline or attachment, where it is safe. SVG, HTML and similar types are always a download.

Returns the bytes with the file's own Content-Type, a sanitised Content-Disposition, Cache-Control: private, max-age=60, X-Content-Type-Options: nosniff and Accept-Ranges: bytes. One Range is answered 206 with Content-Range; an unsatisfiable range is 416. 404 file_not_found for a file of another session. SDK: files.content(id, fileId, { variant }), files.contentStream(…).

See Session files for what the agent does with them.

Background runs ​

http
GET  /api/client/v1/agents/runs/{runId}
POST /api/client/v1/agents/runs/{runId}/cancel

Agent run ​

json
{
  "id": "run_65f1c0a2b9d3e4f5a6b7c8d9",
  "object": "agent.run",
  "status": "succeeded",
  "stop_reason": "end_turn",
  "agent": "support-bot",
  "conversation_id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "result": { "id": "resp_65f1c0a2b9d3e4f5a6b7c8d9", "object": "response", "status": "completed", "…": "…" },
  "error": null,
  "created_at": 1760000000,
  "started_at": 1760000001,
  "completed_at": 1760000042,
  "cancel_requested_at": null,
  "max_duration_ms": 1800000,
  "callback": { "url": "https://example.com/hook", "status": "delivered", "attempts": 1, "signed": true }
}

status: queued, running, requires_action, succeeded, failed, canceled. Timestamps are epoch seconds. result is set once succeeded, and while requires_action; error ({ type, message }) once failed or canceled. Cancel answers 200 with the run, 404 agent_run_not_found, or 409 agent_run_already_terminal. SDK: client.agents.runs.get / cancel / wait. See Background runs.

Responses fields that use a session ​

The calls themselves are documented on the Agents API. The fields that matter here:

Field
session_id / conversation / previous_response_idContinue a session. Give one, or several that agree.
budgetA budget for the session this request starts; ignored when it continues one.
background, callback_url, callback_secretRun in the background.
input[].content[] with input_file / input_imageAttach files to the session this turn runs on.
runtime_contextDownstream headers and prompt variables for this turn.

Error index ​

StatusCodeWhere
400invalid_requestMalformed body or query; session_id and conversation name different sessions.
400session_conflictsession_id/conversation and previous_response_id name different sessions.
400invalid_metadata, invalid_budget, invalid_limit, invalid_status, invalid_cursorSessions.
400agent_not_active, agent_background_disabledStarting a session; continuing in the background.
400invalid_resolutionAn answer that is not valid for the action; the request stays open.
400attachment_not_found, attachment_unreachable, empty_fileFiles.
400invalid_callback, idempotency_key_sync_not_supported, idempotency_key_too_longBackground calls.
402budget_reachedA message to a session whose budget is used up (spent_usd, max_cost_usd).
403attachments_disabledFiles off for the agent (or input_file / input_image sent to one).
403budget_override_forbiddenThe agent's session budget binds API clients.
403action_forbiddenNot an allowed approver.
404session_not_found, agent_not_found, action_not_found, file_not_found, agent_run_not_foundThe thing does not exist in the token's project.
409agent_run_conflictA run is already active on the conversation.
409pending_actionA new message while the session waits for an answer (pending_action_ids).
409action_not_pending, action_cancelledThe request was already answered, or cancelled (action_status).
409too_many_files, storage_unavailable, markdown_unavailableFiles.
409idempotency_key_conflict, agent_run_already_terminalBackground calls.
410action_expiredThe request ran out of time.
413file_too_largeA file over the agent's limit.
415unsupported_file_typeA file type the agent does not accept.
429spend_limit_exceededThe tenant's daily or monthly spend limit is used up.
429agent_run_concurrency_limit, quota_exceededToo many background runs; file quotas.
504timeoutA synchronous call passed its ceiling (side_effects_possible: true).

SDK mapping ​

Endpoint@cognipeer/console-sdk (3.1.0+)
POST /agents/sessionsclient.agents.sessions.create()
GET /agents/sessionsclient.agents.sessions.list() — awaitable page, or for await over every session
GET /agents/sessions/{id}client.agents.sessions.get()
PATCH /agents/sessions/{id}client.agents.sessions.update()
DELETE /agents/sessions/{id}client.agents.sessions.delete()
…/eventsclient.agents.sessions.events.list()
…/events/streamclient.agents.sessions.events.stream()
…/actions/{actionId}client.agents.sessions.actions.resolve(); actions.list() reads pending_actions
…/filesclient.agents.sessions.files.upload() / list() / content() / contentStream() / delete()
/agents/runs/{runId}client.agents.runs.get() / cancel() / wait()

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub