레퍼런스HTTP APISessions

Durably accept a session and its first input

POST
/v1/sessions

Creates a session for a catalog profile and repository and queues message as turn 1. Whether repository_id is sent follows the profile's repository_mode (GET /v1/profiles): where the catalog judges the pair, none refuses it, optional takes either, and required needs it. A session on the agent's own GitHub settings (the agent has a settings row) or on a workspace-scoped profile is judged by the workspace ceiling and the agent's list instead, and may start without one whatever the mode. A repository named here is checked against the agent's GitHub settings list where those apply and against the workspace ceiling where the profile uses it, else 403 GITHUB_REFUSED. A session created without one starts in an empty workspace, has no default push target (POST /v1/sessions/{id}/push-requests must name an attached repository, else it answers 409 PUSH_SOURCE_MISSING) and its detail's repository_id is null. The 201 means the input is stored, not that it ran: follow the turn with GET /v1/sessions/{id}/turns/{turn_id} or the event stream. model picks one of the models the profile offers (its model or models in the catalog); omitted, the model of the release's card runs when agent_release_id is sent, else the profile's default. The session's detail reports it as model. The model stays until the session's profile is changed (POST /v1/sessions/{id}/profile), which runs the new profile's default. A session waits in queued while no execution slot is free; it is never refused for that. agent_release_id pins the session to that release of an agent, so a key bound to the agent may create it (it reads the agent's active_release_id from GET /v1/agents);profile_id may then be omitted for the release's runtime profile, and sent it must be that profile (409 RELEASE_PROFILE_MISMATCH). Omitted, an agent with GitHub settings starts without a repository and attaches what it needs, and an agent with none uses the release's repository_id. A release whose profile is no longer in the catalog, or changed since, answers 409 PROFILE_DIGEST_MISMATCH. A profile the operator blocked, or a workspace profile over a blocked one, answers 409 PROFILE_BLOCKED, with a release or without. A private agent (GET /v1/agents/{id}/usage) is used only by the members of its teams, the workspace's owners and admins, its admins and the members of its managing team; for anyone else its release answers 404, the very answer of a release that does not exist, before the integrity, profile and state refusals below and whatever else the caller lacks (a key without the scope, or bound to another agent, or a Slack channel past its ceiling, included). An agent that is paused or archived answers 409 AGENT_NOT_RUNNABLE with details.reason_code agent_inactive, and a release no activation ever approved answers it with release_unapproved; an older release that was approved still starts while the agent is active, and a retry of a session made earlier gets its first answer. Every credential the release names must still be bound to its agent; otherwise 409 CREDENTIAL_UNBOUND, whose details carry the guidance to show the requester and the agent's admins to ask (SessionCredentialUnboundDetails), never the missing names. The caller limits of the agent (GET /v1/agents/{id}/caller-limits) that reach the caller are merged (the workspace's, those of the teams the caller is in, and the Slack channel's) and fixed on the session as caller_limit; the first turn runs under the creator's limit, so a start is checked on all four dimensions the way an input is: a repository outside the merged repositories, a credential the merged credentials_deny names, an MCP server outside mcp_servers, or a tool the release loads that tools_deny names answers 403 FORBIDDEN with details {reason_code: caller_limited, dimension: repository | credential | mcp_server | tool} and no session. The write merges the limits again under lock, and its result wins over a rule changed since. The receipt settles with turn 1: succeeded when it completes, failed when it fails or is interrupted or cancelled, unknown when its outcome is unknown.

Scope: sessions:write (API key or cookie session). Idempotency-Key: required.

Authorization

headerAuthorizationBearer <token>

For servers and scripts: an API key sent as an HTTP bearer token, Bearer <key>. It holds exactly the scopes it was issued with; a key issued before scopes existed holds none. It never needs the CSRF header, and it cannot manage agents, members, teams or API keys. Issue one in the web console under Settings → API keys; each operation names the scope it needs.

Scope: sessions:write

Header Parameters

Idempotency-Key*string

Required, 1–255 characters; a UUID per user intent works. A retry with the same key and the same body returns the original response and runs the command once; the same key with a different body answers 409 IDEMPOTENCY_CONFLICT. A missing or invalid key answers 400 BAD_REQUEST, after the body is read.

Length1 <= length <= 255
X-Requested-With?"werft-web"

Required with a cookie session: the literal werft-web.

Value in"werft-web"

Request Body

application/json
  1. body
profile_id?string

The catalog profile the session runs on. Required without agent_release_id; with it, omitted means the release's profile, and sent it must be that profile.

Length1 <= length
title?string

The session's name. Omitted, it is derived from message: whitespace and line breaks collapsed to one space, the ends trimmed, and text longer than 80 characters cut to 79 and an ellipsis (…). Rename it later with PATCH /v1/sessions/{id}.

Lengthlength <= 1024
repository_id?string

The catalog repository the session works on. Where the catalog judges the pair, required when the profile's repository_mode is required, refused when it is none, either when it is optional; a session on the agent's GitHub settings or a workspace-scoped profile is judged by the workspace ceiling and the agent's list instead. Omitted, a session of an agent with GitHub settings starts without a repository and attaches what it needs, a session of an agent with none works on the release's repository_id when agent_release_id is sent, else starts in an empty workspace with no repository of its own, pushing only to those in attached_repository_ids.

Length1 <= length
attached_repository_ids?array<>

Catalog repositories besides the session's own that it may fetch, push to and open pull requests on. Each one has to be listed in the agent release's GitHub settings (github.repositories), allowed for the profile, and other than the session's repository; otherwise 403 GITHUB_REFUSED with repository_not_allowed, as is any list sent for a session without an agent release, or for a profile whose repository_mode is none when the agent has no GitHub settings. A session started without a repository on an optional profile works on these alone, and names one in each push request. Omitted, the same as []: the session starts with none besides its own, and its agent attaches others the release lists with the built-in MCP tool attach_repository when it needs them.

extensions?

Turns extension bundles on or off for this session, by the bundle ids the workspace registered. Only a bundle its agent or workspace left optional can be named; a required one is always on and a forbidden one cannot be turned on, else UNSUPPORTED_CAPABILITY with details.reason_code naming why (required_removed, required_replaced, forbidden_revived, not_optional, bundle_not_found, bundle_not_approved, skill_name_collision, feature_unsupported, scripts_need_default_mode) and details.bundle the bundle name. An optional bundle not named in enable stays off. The resolved list is fixed at create and returned as extensions.

message*string

At most 32768 bytes of UTF-8

Length1 <= length
agent_release_id?string
Length1 <= length <= 128
model?string

One of the models the profile offers (its model or one of its models in the catalog). Omitted, the model of the agent release's card when agent_release_id is sent, else the profile's default model. Kept until the session's profile is changed, after which the new profile's default model runs.

Length1 <= length
permission_wait?

How long the session's permission and question requests wait for an answer, in seconds: park_after_seconds before the platform parks the turn and frees its worker (0 parks at once), expire_after_seconds from the request until it stops taking answers and is denied. Each can only shorten the profile's value (GET /v1/profiles, permission_wait); a longer one is 400 PERMISSION_WAIT_ABOVE_PROFILE. Omitted, the profile's values apply. A request takes the values in force when it is raised.

Response Body

The session and its first turn are stored; receipt_id tracks the acceptance.

application/json
  1. response
session_id*string
Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
turn_id*string
Length1 <= length
receipt_id*string
Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
receipt_status*string
Value in"accepted""succeeded""failed""unknown"
status*string
Value in"queued""running""needs_input""idle""failed""stopped"
extensions?array<>

The extension bundles the new session runs with. Absent when the session runs with none.

Example Request
curl -X POST 'https://example.com/v1/sessions' \  -H 'Idempotency-Key: string' \  -H 'Content-Type: application/json' \  -d '{  "profile_id": "claude-coding-real",  "repository_id": "sample-app",  "message": "Add a health check endpoint and a test for it."}'
Example Response
{  "session_id": "0f8b6a52-3d1e-4c7a-9b2f-5e6d7c8a9b01",  "turn_id": "1",  "receipt_id": "c4a1e9d2-7b3f-4e8a-a6c5-1d2e3f4a5b6c",  "receipt_status": "accepted",  "status": "queued"}