Start a conversation.
const url = 'https://shiftagent.example.com/conversations';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"user_id":"usr_01hzx8jane001","external_id":"acme:conversation:ticket-4521","title":"Invoice questions","metadata":{"host_ref":"ticket-4521"}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://shiftagent.example.com/conversations \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "user_id": "usr_01hzx8jane001", "external_id": "acme:conversation:ticket-4521", "title": "Invoice questions", "metadata": { "host_ref": "ticket-4521" } }'Creates a conversation for a user, resolving and snapshotting its
context at creation: the user’s role plus the tenant’s repository and
the role’s effective skills, recorded in
context {role_id, repository_id, skill_ids} so history is
self-explaining.
- The user’s assigned role resolves the context; a user with no role
responds
422role-required. - The repository is the tenant’s (inherited by every conversation and message — there are no per-conversation overrides).
runtimeselects the agent runtime (agent_type, open enum) and placement:pooled(default) orwarmwithidle_ttl_seconds(sliding idle timeout, default 300, capped by tenant settings).filler {enabled}overrides the tenant’s filler-agent default for this conversation.on_capacitychooses the capacity strategy for the initial message (rejectdefault;holdqueues withqueuedplatform notices).- A per-conversation storage bucket is auto-attached.
external_id optionally records the host system’s conversation ID
(unique per tenant). If taken, responds 409 external-id-conflict
with conflicting_resource_id pointing at the existing conversation
— the guarded-explicit style (getConversationByExternalId to
check, POST to create), complementing the deterministic
upsertConversationByExternalId upsert. For host-mapped
conversations prefer the upsert.
Response shape depends on initial_message: without it, 201
with the conversation JSON; with it, 200 streaming
application/x-ndjson — the conversation id rides in the
X-Shiftagent-Conversation-Id response header plus a leading
{"object":"platform.event","type":"conversation"} line, then the
assistant reply streams as the native agent event stream (see
createMessage for the full protocol).
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Header Parameters
Section titled “Header Parameters ”Optional idempotency key (any unique string, e.g. a UUID; max 255 chars). Responses are cached 24h per (key principal, operation, key); replays return the original status and body with Idempotency-Replayed: true. Reusing a key with a different payload responds 409 idempotency-key-conflict.
Request Body required
Section titled “Request Body required ”Body for createConversation.
object
Owning user.
Optional host mapping (unique per tenant). If taken, responds 409 external-id-conflict with conflicting_resource_id pointing at the existing conversation. For host-mapped conversations prefer upsertConversationByExternalId.
Display title.
Narrowing within the resolved context skills (the user’s role resolves the context; a user with no role → 422 role-required).
Runtime knobs at conversation creation.
object
Agent runtime (open enum — e.g. claude-agent-sdk, codex, deepagent). Defaults from tenant settings. Immutable afterwards.
Placement mode.
Sliding idle timeout for warm mode. Capped by tenant.settings.max_idle_ttl_seconds (422 beyond the cap). Defaults to 300 when mode is warm.
Conversation-level filler override.
object
Whether the low-latency filler agent runs for this scope.
Capacity strategy for the initial message: reject → 429 capacity-exhausted + Retry-After; hold → queue with queued platform notices (bounded by the deployment’s max hold time).
Free-form string key–value map for host/adapter bookkeeping (e.g. a host-side reference ID). Max 50 keys; values max 500 chars. Replaced wholesale when provided in updates.
object
When present, the response is the NDJSON agent event stream (200) instead of the conversation JSON (201); the conversation id rides in the X-Shiftagent-Conversation-Id header and a leading platform notice line.
object
The user’s message text.
Optional typed blocks (extensibility).
Typed content block — the extensibility seam for richer runs. Known types: text, tool_call, tool_result; unknown types must be ignored by clients.
object
Block type (open enum).
Text content (for text blocks).
Per-message skill narrowing — keeps the agent context lean. Must be within the conversation’s effective skills.
Plaintext, non-secret run parameters, visible to the agent verbatim. Never place secret material here — use secrets.
object
Write-only alias → value map, scoped to this message’s run (ephemeral — resolved before conversation-, user-, and tenant-scoped secrets). Never echoed anywhere; the agent sees only {{secret:ALIAS}} placeholders resolved by the egress proxy.
object
Per-message filler override (most specific wins).
object
Whether the low-latency filler agent runs for this scope.
reject → 429 capacity-exhausted + Retry-After when no sandbox is available; hold → the stream first emits queued platform notices until one frees (bounded by the deployment’s max hold time).
Free-form string key–value map for host/adapter bookkeeping (e.g. a host-side reference ID). Max 50 keys; values max 500 chars. Replaced wholesale when provided in updates.
object
Examples
Start a conversation (no initial message)
{ "user_id": "usr_01hzx8jane001", "external_id": "acme:conversation:ticket-4521", "title": "Invoice questions", "metadata": { "host_ref": "ticket-4521" }}Warm runtime + initial message (streams)
{ "user_id": "usr_01hzx8jane001", "runtime": { "agent_type": "claude-agent-sdk", "mode": "warm", "idle_ttl_seconds": 300 }, "filler": { "enabled": true }, "on_capacity": "hold", "initial_message": { "content": "Summarize today's open jobs." }}Responses
Section titled “ Responses ”Streaming response (only when initial_message is present): the native agent event stream. The created conversation’s id rides in the X-Shiftagent-Conversation-Id header and a leading platform notice line; the protocol is identical to createMessage.
One NDJSON stream line. The stream is the platform’s native agent
event stream — raw Claude Agent SDK events passed through verbatim
(vocabulary versioned by the X-Shiftagent-Stream-Format response
header, currently claude-sdk/1) — interleaved with platform lines:
- Agent events carry a top-level
type—system,stream_event,assistant,user, and the terminalresult. {"event":"ping"}— keepalive.{"object":"platform.event","type":"queued","position":n, "retry_hint_seconds":n}— capacity hold notice.{"object":"platform.event","type":"conversation", "conversation":{…}}— leading line on create/upsert-with-initial_messagestreams, carrying the conversation.{"object":"platform.event","type":"error","problem":{…}}— terminal platform abort carrying an RFC 9457 problem (e.g.missing-secret).
Clients MUST ignore unknown line shapes (new event types are
additive). Exactly one terminal line (result or platform error)
ends every complete stream; a stream that closes without one is
truncated — reconcile via listMessages.
object
Example generated
{}Conversation created (no initial message).
An agent conversation owned by a user within a tenant. Snapshots its resolution context at creation and carries live runtime state.
object
Host system’s conversation ID (namespaced by the adapter). Unique per tenant; natural key for upsertConversationByExternalId — the continue-vs-create decision. Null for conversations created via plain createConversation without one.
Display title; auto-derivable from the first message.
Archived conversations keep readable history but reject message writes with 409 conversation-archived.
Resolution snapshot taken at conversation creation (user → role → repository → skills) — makes history self-explaining even after roles or repositories change.
object
The role the conversation was resolved under.
The effective repository at creation.
The effective skills at creation.
Optional narrowing within context.skill_ids; null means no narrowing.
Runtime placement state of a conversation. agent_type selects the agent runtime behind the platform’s runtime abstraction — each type runs in its own isolated sandbox.
object
Agent runtime — open enum so new runtimes are non-breaking. Known values: claude-agent-sdk, codex, deepagent. Defaults from tenant settings. Immutable after creation.
pooled — each message claims a warm-pool sandbox and releases it; warm — the conversation keeps its sandbox alive between messages under a sliding idle timeout (idle_ttl_seconds, reset on every message). The most recent message’s settings govern what happens next.
Sliding idle timeout of the warm sandbox; null for pooled conversations.
warm — a sandbox is currently held for this conversation (fast next message); cold — none held (pooled, warm-before-first- message, or the idle timer lapsed and the sandbox was recycled — the next message cold-starts and resumes the same session).
When the warm hold lapses (moves forward on every message); null when sandbox_state is cold.
Filler-agent enablement. Cascade: tenant settings → conversation → message; most specific wins. Filler output is part of the reply stream and the persisted message — indistinguishable from agent output by design.
object
Whether the low-latency filler agent runs for this scope.
S3-style storage bucket attached to a user or conversation. Platform-assigned automatically at creation; host-owned buckets can be linked via update (provider: "external").
object
platform — bucket provisioned and owned by the deployment; external — host-linked BYO bucket.
S3-style URI of the bucket root (e.g. s3://bucket/prefix).
Persisted message count (all roles).
Timestamp of the newest message; null when empty.
Free-form string key–value map for host/adapter bookkeeping (e.g. a host-side reference ID). Max 50 keys; values max 500 chars. Replaced wholesale when provided in updates.
object
RFC 3339 / ISO 8601 timestamp, UTC.
RFC 3339 / ISO 8601 timestamp, UTC.
Examples
Context snapshot resolved at creation
{ "object": "conversation", "id": "con_01hzx8conv001", "external_id": "acme:conversation:ticket-4521", "tenant_id": "tnt_01hzx8acme001", "user_id": "usr_01hzx8jane001", "title": "Invoice questions", "status": "active", "context": { "role_id": "rol_01hzx8csr001", "repository_id": "rep_01hzx8fieldops", "skill_ids": [ "skl_01hzx8dispatch", "skl_01hzx8invoice" ] }, "selected_skill_ids": null, "runtime": { "agent_type": "claude-agent-sdk", "mode": "pooled", "idle_ttl_seconds": null, "sandbox_state": "cold", "expires_at": null }, "filler": null, "storage": { "provider": "platform", "bucket_uri": "s3://shiftagent-tenant-acme/con_01hzx8conv001" }, "message_count": 0, "last_message_at": null, "metadata": { "host_ref": "ticket-4521" }, "created_at": "2026-07-02T10:00:00Z", "updated_at": "2026-07-02T10:00:00Z"}Missing or invalid credentials — no bearer token, an unknown/revoked sk_int_ key, or an expired platform JWT.
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Missing or invalid bearer token
{ "type": "https://shiftagent.example.com/problems/insufficient-scope", "title": "Unauthorized", "status": 401, "detail": "Provide a valid sk_int_ service key or platform JWT.", "request_id": "req_01hzx8auth001"}Forbidden — tenant-suspended (writes to a suspended tenant) or insufficient-scope (key/token lacks the scope or a platform JWT reaches beyond its user).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Suspended tenant rejects conversation writes
{ "type": "https://shiftagent.example.com/problems/tenant-suspended", "title": "Tenant suspended", "status": 403, "detail": "Tenant tnt_01hzx8acme001 is suspended; conversation writes are rejected.", "request_id": "req_01hzx8sus001"}Not found — the resource does not exist, was deprovisioned, or lies outside the integration key’s subtree (indistinguishable by design).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Unknown resource
{ "type": "https://shiftagent.example.com/problems/not-found", "title": "Not found", "status": 404, "detail": "No tenant with external_id acme:tenant:999999.", "request_id": "req_01hzx8nf001"}Conflict — name-conflict / external-id-conflict (unique name or external ID taken; conflicting_resource_id names the holder — fetch it and continue), resource-in-use (guarded delete refused), cross-tenant (referenced resource belongs to another tenant), conversation-archived (write to an archived conversation), or idempotency-key-conflict (same key, different payload).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Named sub-resource already exists — recoverable
{ "type": "https://shiftagent.example.com/problems/name-conflict", "title": "Name conflict", "status": 409, "detail": "A role named \"csr\" already exists in this tenant.", "conflicting_resource_id": "rol_01hzx8csr001", "request_id": "req_01hzx8conf01"}Guarded delete refused
{ "type": "https://shiftagent.example.com/problems/resource-in-use", "title": "Resource in use", "status": 409, "detail": "Repository is attached to 1 tenant and pinned by 2 roles.", "conflicting_resource_id": "tnt_01hzx8acme001", "request_id": "req_01hzx8used01"}Unprocessable — validation-error (schema/semantic validation failed; errors[] lists JSON-pointer details), role-required (the user has no role assigned, so no conversation context can resolve), or missing-secret (a referenced alias is vaulted at no scope).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Field-level validation failure
{ "type": "https://shiftagent.example.com/problems/validation-error", "title": "Validation error", "status": 422, "detail": "One or more fields failed validation.", "errors": [ { "pointer": "/skill_access/skill_ids/0", "message": "skl_01hzx8unknown does not belong to the tenant's repository." } ], "request_id": "req_01hzx8val001"}User has no role at conversation creation
{ "type": "https://shiftagent.example.com/problems/role-required", "title": "Role required", "status": 422, "detail": "User usr_01hzx8jane001 has no role assigned; assign one before starting conversations.", "request_id": "req_01hzx8role01"}Too many requests — capacity-exhausted (no sandbox available, or the maximum hold time elapsed under on_capacity=hold) or rate-limited. Honor Retry-After.
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Sandbox pool exhausted (on_capacity=reject)
{ "type": "https://shiftagent.example.com/problems/capacity-exhausted", "title": "Capacity exhausted", "status": 429, "detail": "No sandbox available; retry after the indicated delay or use on_capacity=hold.", "request_id": "req_01hzx8cap001"}Headers
Section titled “Headers ”Seconds to wait before retrying.