Skip to main content
Use this endpoint to send prompt, control, and result events to an existing session. A request contains 1 to 100 events, and only one event may start a new turn. Control events and immediate custom tool results can be sent while a turn is running or paused for required action. A final long-running custom tool result starts a new turn and requires an idle session. Exceeding a limit — more than 100 events per request, more than 100 text blocks in a content array, or a text value over 20,000 characters — rejects the whole request with 400 validation_error. Nothing is truncated, and the error message names the failing field and limit. See Text content block. A user.message starts a new turn, and a session runs one turn at a time: by default a prompt sent while a turn is running is rejected with 409. Set the request-level delivery field to "when_idle" to queue the prompt instead — see Queue a message while a turn is running.

Send a message

metadata is optional turn context for the agent. It is persisted on the user.message event payload and returned by list/stream APIs, but it is not part of the visible content text your UI renders as the user’s message. Metadata values must be strings, numbers, booleans, or null.

Send a message with attachments

user.message accepts up to 10 turn-scoped file attachments. Attachments are sent as base64 data_url sources. The accepted event, including each source.dataUrl, is retained in session event history.
Supported attachment types are PDF, CSV, TXT, Markdown, DOCX, PNG, JPEG, WebP, GIF, and BMP. The agent can inspect images and documents as part of the turn. PDFs default to document mode; set analysisMode: "visual" on a PDF attachment only when you want page-image analysis. Limits: images, CSV, TXT, Markdown, and DOCX are capped at 25 MB per attachment; PDFs are capped at 50 MB.

Queue a message while a turn is running

By default, sending a prompt-starting event while a turn is already running fails with 409 invalid_state (“A turn is already in progress”), and your application owns the retry. When your caller means “deliver this whenever the agent can take it” — for example a background job posting a completion notification into a session — set the request-level delivery field to "when_idle":
With "when_idle":
  • If the session is idle, the prompt starts a turn immediately — exactly like an immediate send.
  • Queued prompts start oldest-first, one per turn end, and each runs as its own turn.
  • The 202 response echoes the persisted events; data[].status === "queued" tells you the prompt was parked rather than started. The status moves to received when its turn starts, then processed like any other prompt.
  • Queued prompts remain durable until they are delivered once the session is idle again.
A when_idle request must contain only prompt-starting user.message events. Control and result events (user.steer, user.approval_response, and so on) target the currently running turn and are never queued for a later one, so a when_idle request that includes them is rejected with 400 validation_error — send them in a separate request with the default delivery.

Rate-limit counting

The agent’s message limit defaults to 100 accepted inbound user.* events per stable subject (subjectId) per fixed 60-second window. Every event in a request consumes one point from this allowance.
  • Immediate events count when they are accepted for persistence.
  • A delivery: "when_idle" prompt counts when it is accepted into the durable queue. It is not counted again when the queue drains.
  • Validation, not-found, archived-session, and prompt-conflict failures rejected before persistence do not count. A control event that is persisted but cannot reach a live turn does count.
  • user.steer, user.interrupt, user.approval_response, user.clarify_result, and user.custom_tool_result each count.
The subject comes from the stored session; it cannot be changed on this route. Limited successful event requests include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. See Rate limits and Errors.

Supported inbound events

See the Events Reference for the complete schema for each event.

Action responses

When the stream emits session.status_idle with payload.stop_reason.type: "requires_action", the event_ids array contains the IDs your application needs to answer.

Confirm a tool request

result is allow or deny. scope is optional and can be once, session, or always; omit it to allow once.

Answer a clarify request

Use an empty answer to skip the question and let the agent continue with its own default.

Return a custom tool result

Custom tools are executed by your application, not by the managed agent. When the stream emits agent.custom_tool_use, execute the named tool and return the result with user.custom_tool_result.
Set is_error: true when your application could not complete the tool. The text is still sent back to the model as tool output, so include a concise, model-readable error message. For long-running workflows, return a kickoff result within approximately five minutes:
The kickoff resolves the pending tool call and lets the original turn continue. When the workflow finishes and the session is idle, send a second result with the same tool_use_id:
When a matching running result exists, the completion replaces it in event history, emits session.tool_updated, and starts a new agent turn with the final text. It cannot use delivery: "when_idle"; wait for session.status_idle before posting it. See Long-running custom tools for the complete flow, UI reconciliation, and retry constraints.

Response

The endpoint returns the events persisted by the request. Additional agent events can be retrieved with List Session Events or streamed with Stream Events. A message parked by delivery: "when_idle" is returned with status: "queued" instead of received.

Errors

Last modified on July 25, 2026