Send a message and stream the agent's reply.
const url = 'https://shiftagent.example.com/conversations/example/messages?stream=true';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"content":"Summarize today\'s open jobs."}'};
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/example/messages?stream=true' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "content": "Summarize today'\''s open jobs." }'Appends a user message and runs the agent. Default response is a
streaming application/x-ndjson body (200): one
ConversationEvent per line. Pass ?stream=false for a blocking
201 with the completed assistant Message JSON instead.
NDJSON protocol
seqis monotonic from0with no gaps — detect truncation by a gap or a missing terminal event.- Exactly one terminal event ends every complete stream:
message_end(carries the full persisted assistant message) orerror(carries an RFC 9457 problem object). - Ordering: optional
queuedevents (capacity hold) →message_start→content_delta* (filler deltas flaggeddata.filler: true) → optionalapproval_required…resumed→message_end. - If the connection drops mid-stream, the run continues server-side;
reconcile via
listMessages(the assistant message lands in history regardless).
{"object":"conversation.event","type":"queued","conversation_id":"con_01hzx8conv001","message_id":null,"seq":0,"data":{"position":2,"retry_hint_seconds":15},"created_at":"2026-07-02T10:00:00Z"}
{"object":"conversation.event","type":"message_start","conversation_id":"con_01hzx8conv001","message_id":"msg_01hzx8asst001","seq":1,"data":{"role":"assistant"},"created_at":"2026-07-02T10:00:01Z"}
{"object":"conversation.event","type":"content_delta","conversation_id":"con_01hzx8conv001","message_id":"msg_01hzx8asst001","seq":2,"data":{"text":"One moment while I pull that up — ","filler":true},"created_at":"2026-07-02T10:00:01Z"}
{"object":"conversation.event","type":"content_delta","conversation_id":"con_01hzx8conv001","message_id":"msg_01hzx8asst001","seq":3,"data":{"text":"You have three open jobs today."},"created_at":"2026-07-02T10:00:03Z"}
{"object":"conversation.event","type":"message_end","conversation_id":"con_01hzx8conv001","message_id":"msg_01hzx8asst001","seq":4,"data":{"message":{"object":"message","id":"msg_01hzx8asst001","...":"..."}},"created_at":"2026-07-02T10:00:04Z"}
Per-message knobs (most specific wins in every cascade)
repository_id— one-shot repository override for this run.skill_ids— narrow to specific skills so the agent context stays lean (must be within the conversation’s effective skills).env— plaintext, non-secret run parameters. Never place secret material here — the runtime sees these values verbatim.secrets— write-onlyalias → valuemap, vaulted on arrival and scoped to the conversation. Values never appear in any response; the agent sees only{{secret:ALIAS}}placeholders, resolved by the egress proxy at the network boundary.filler {enabled}— override the filler cascade for this message.on_capacity—reject(default):429capacity-exhausted+Retry-Afterwhen no sandbox is available;hold: the stream first emitsqueuedevents, bounded by the deployment’s max hold time.
HITL
When the agent raises an approval, the stream emits
approval_required (full Approval object, incl. requested_items)
and the message parks in awaiting_approval — sticky sandboxes stay
warm until the approval’s expires_at; pooled runs checkpoint and
re-hydrate. The stream stays open awaiting resolution: on a valid
signed approveApproval it emits resumed and continues to
message_end; deny/expiry ends with a problem-typed error event
and status: "failed". Disconnected clients reconcile via
listMessages / listApprovals.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Internal conversation ID.
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.
Query Parameters
Section titled “Query Parameters ”true (default) streams NDJSON events; false blocks until the run completes and returns the assistant message as JSON.
Request Body required
Section titled “Request Body required ”Body for createMessage (and initial_message on createConversation).
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).
One-shot repository override for this run only (top of the resolution cascade).
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. Vaulted at the boundary, conversation-scoped, 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 events 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
Plain message
{ "content": "Summarize today's open jobs."}Repo override + skill narrowing + env + secrets
{ "content": "Reconcile yesterday's invoices against the CRM.", "repository_id": "rep_01hzx8dispatchtools", "skill_ids": [ "skl_01hzx8invoice" ], "env": { "REGION": "us-east", "REPORT_DATE": "2026-07-01" }, "secrets": { "CRM_API_KEY": "example-value-vaulted-never-echoed" }, "filler": { "enabled": false }, "on_capacity": "hold"}Responses
Section titled “ Responses ”NDJSON event stream (default). Each line is one
ConversationEvent; the stream terminates with message_end or
error. See the operation description for the full protocol and
an end-to-end transcript.
The assistant message has opened; deltas follow.
object
Event discriminator.
The assistant message this event belongs to; null on queued events emitted before the run starts.
Monotonic per-response counter — gap ⇒ truncation.
RFC 3339 / ISO 8601 timestamp, UTC.
object
Present only on streams initiated by createConversation with initial_message — the just-created conversation.
object
Display title; auto-derivable from the first message.
Archived conversations keep readable history but reject message writes with 409 conversation-archived.
Conversation-level repository override in the cascade.
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; sticky — a dedicated sandbox is leased for sticky_ttl_seconds (refreshed per message).
Lease TTL; null for pooled conversations.
warm — no dedicated sandbox held (pooled, or sticky before first message); active — sticky lease held; expired — sticky lease lapsed (next message re-acquires, subject to capacity).
Sticky lease expiry; null for pooled conversations.
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.
A chunk of assistant output. data.filler: true marks low-latency filler output (render or suppress; not part of the persisted message).
object
Event discriminator.
The assistant message this event belongs to; null on queued events emitted before the run starts.
Monotonic per-response counter — gap ⇒ truncation.
RFC 3339 / ISO 8601 timestamp, UTC.
object
The text chunk.
True when produced by the filler agent.
Emitted while the request is held for capacity (on_capacity=hold), before message_start. May repeat as the queue drains; bounded by the deployment’s max hold time, after which the stream terminates with a capacity-exhausted error event.
object
Event discriminator.
The assistant message this event belongs to; null on queued events emitted before the run starts.
Monotonic per-response counter — gap ⇒ truncation.
RFC 3339 / ISO 8601 timestamp, UTC.
object
Position in the hold queue (1 = next).
Estimated wait until a sandbox frees.
The agent raised a HITL gate; the message parks in awaiting_approval. data is the full Approval object. Resolve via approveApproval / denyApproval.
object
Event discriminator.
The assistant message this event belongs to; null on queued events emitted before the run starts.
Monotonic per-response counter — gap ⇒ truncation.
RFC 3339 / ISO 8601 timestamp, UTC.
A human-in-the-loop gate raised by the agent mid-run. Resolution requires a signed assertion minted with a per-tenant approver key (never the sk_int_ service key).
object
The assistant message parked on this approval.
expired — expires_at passed unresolved; the parked run ended failed.
Agent-stated explanation of why approval is needed.
What the agent needs (actions and/or secrets).
One thing the agent needs to proceed.
object
action — permission to perform a described action; secret — a credential to be supplied under alias via the approve body’s secrets map.
Agent-stated need, human-readable.
For secret items — the alias to vault the value under.
Resolution deadline; sticky sandboxes stay warm until then.
Approver-key identity that resolved it (e.g. approver_key:apk_…); null while pending.
Resolution time; null while pending.
RFC 3339 / ISO 8601 timestamp, UTC.
RFC 3339 / ISO 8601 timestamp, UTC.
A pending approval was granted; the parked run continues toward message_end.
object
Event discriminator.
The assistant message this event belongs to; null on queued events emitted before the run starts.
Monotonic per-response counter — gap ⇒ truncation.
RFC 3339 / ISO 8601 timestamp, UTC.
object
Denials terminate with an error event instead.
Terminal success event — data.message is the full persisted assistant message (also available via listMessages).
object
Event discriminator.
The assistant message this event belongs to; null on queued events emitted before the run starts.
Monotonic per-response counter — gap ⇒ truncation.
RFC 3339 / ISO 8601 timestamp, UTC.
object
A persisted conversation message. Write-only request fields (secrets) are never present; env is echoed as sent (non-secret by contract).
object
Author role.
Full text content. Assistant content references secrets only by alias ({{secret:ALIAS}}) — never by value.
Typed content blocks.
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 repository override used for this run.
Per-message skill narrowing used for this run.
Plaintext run parameters as sent (never secret material by contract).
awaiting_approval — parked on a pending HITL approval; failed — the run errored, was denied, or the approval expired.
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.
Terminal failure event — data is an RFC 9457 problem object (same registry as HTTP errors). The message ends failed.
object
Event discriminator.
The assistant message this event belongs to; null on queued events emitted before the run starts.
Monotonic per-response counter — gap ⇒ truncation.
RFC 3339 / ISO 8601 timestamp, UTC.
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
Stream line: assistant message opened
{ "object": "conversation.event", "type": "message_start", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "seq": 1, "data": { "role": "assistant" }, "created_at": "2026-07-02T10:00:01Z"}Stream line: filler-flagged delta (render or suppress)
{ "object": "conversation.event", "type": "content_delta", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "seq": 2, "data": { "text": "One moment while I pull that up — ", "filler": true }, "created_at": "2026-07-02T10:00:01Z"}Stream line: real reply text
{ "object": "conversation.event", "type": "content_delta", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "seq": 3, "data": { "text": "You have three open jobs today." }, "created_at": "2026-07-02T10:00:03Z"}Stream line: held for capacity (on_capacity=hold)
{ "object": "conversation.event", "type": "queued", "conversation_id": "con_01hzx8conv001", "message_id": null, "seq": 0, "data": { "position": 2, "retry_hint_seconds": 15 }, "created_at": "2026-07-02T10:00:00Z"}Stream line: HITL gate raised, run parked
{ "object": "conversation.event", "type": "approval_required", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "seq": 4, "data": { "object": "approval", "id": "apr_01hzx8appr001", "tenant_id": "tnt_01hzx8acme001", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "status": "pending", "reason": "The CRM lookup requires a credential that is not on file for this conversation.", "requested_items": [ { "kind": "secret", "description": "API key for the CRM system", "alias": "CRM_API_KEY" } ], "expires_at": "2026-07-02T10:15:00Z", "resolved_by": null, "resolved_at": null, "created_at": "2026-07-02T10:00:05Z", "updated_at": "2026-07-02T10:00:05Z" }, "created_at": "2026-07-02T10:00:05Z"}Stream line: approval granted, run continues
{ "object": "conversation.event", "type": "resumed", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "seq": 5, "data": { "approval_id": "apr_01hzx8appr001", "decision": "approved" }, "created_at": "2026-07-02T10:02:10Z"}Stream line: terminal event with the persisted message
{ "object": "conversation.event", "type": "message_end", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "seq": 6, "data": { "message": { "object": "message", "id": "msg_01hzx8asst001", "conversation_id": "con_01hzx8conv001", "role": "assistant", "content": "Reconciled 14 invoices against the CRM using {{secret:CRM_API_KEY}}.", "parts": [ { "type": "text", "text": "Reconciled 14 invoices against the CRM using {{secret:CRM_API_KEY}}." } ], "repository_id": null, "skill_ids": null, "env": null, "status": "completed", "usage": { "input_tokens": 2048, "output_tokens": 96 }, "created_at": "2026-07-02T10:02:20Z" } }, "created_at": "2026-07-02T10:02:20Z"}Stream line: terminal problem-typed error
{ "object": "conversation.event", "type": "error", "conversation_id": "con_01hzx8conv001", "message_id": "msg_01hzx8asst001", "seq": 5, "data": { "type": "https://shiftagent.example.com/problems/approval-expired", "title": "Approval expired", "status": 409, "detail": "Approval apr_01hzx8appr001 expired before it was resolved; the run was aborted.", "request_id": "req_01hzx8err001" }, "created_at": "2026-07-02T10:15:01Z"}Completed assistant message (only with ?stream=false). The user message and this reply both land in history.
A persisted conversation message. Write-only request fields (secrets) are never present; env is echoed as sent (non-secret by contract).
object
Author role.
Full text content. Assistant content references secrets only by alias ({{secret:ALIAS}}) — never by value.
Typed content blocks.
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 repository override used for this run.
Per-message skill narrowing used for this run.
Plaintext run parameters as sent (never secret material by contract).
awaiting_approval — parked on a pending HITL approval; failed — the run errored, was denied, or the approval expired.
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.
Example
{ "object": "message", "role": "user", "status": "completed"}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), insufficient-scope (key/token lacks the scope or a platform JWT reaches beyond its user), or approval-signature-invalid (approval assertion failed verification).
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"}Approval assertion failed verification
{ "type": "https://shiftagent.example.com/problems/approval-signature-invalid", "title": "Approval signature invalid", "status": 403, "detail": "Signature did not verify against approver key apk_01hzx8host001.", "request_id": "req_01hzx8sig001"}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), approval-expired (approval already resolved or expired), 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) or role-required (user has multiple roles and no role_id was given).
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 effective repository." } ], "request_id": "req_01hzx8val001"}Ambiguous role at conversation creation
{ "type": "https://shiftagent.example.com/problems/role-required", "title": "Role required", "status": 422, "detail": "User usr_01hzx8jane001 holds 2 roles; pass role_id explicitly.", "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.