Skip to content

Send events

POST /v1/sessions/{session_id}/events

Sends a message, a tool approval or an interrupt to a session.

Sends are not idempotent. If a send times out or its result is unknown, read the session’s events before you send it again.

The server checks every event against the session before any of them acts, then applies them in order. If an event is refused while they are applied, because the session changed in the meantime, the earlier events stay accepted and the error message says how many were. Don’t resend those.

Each event gets a receipt with a sevt_receipt_ ID and processed_at: null. The stored event, with its real ID, then appears on the stream and in the history. The receipt ID is not the stored event’s ID, so match the stored event on the stream rather than by ID.

Before a session starts, it accepts exactly one user.message per request. Send more only after the stream shows session.status_running. A user.message sent while a turn runs is added to that turn instead of starting a new one.

A user.tool_confirmation also needs the tasks:approve capability and a personal key.

The key must have the tasks:create capability. For what each capability allows, see Keys.

Path parameters

NameTypeRequiredDefaultDescription
session_idstringYesThe ID of the session to send events to.

Body

NameTypeRequiredDefaultDescription
eventsarray<object>YesThe events to send, applied in order.
events[].typestringYesThe event type. One of user.message, user.tool_confirmation or user.interrupt.
events[].contentarray<object>NoOne or more {type: "text", text} blocks, up to 100,000 characters in all. Required for user.message.
events[].tool_use_idstringNoThe ID of the agent.tool_use event to answer, from stop_reason.event_ids on the session.status_idle event. Required for user.tool_confirmation.
events[].resultstringNoOne of allow (approves this one call) or deny. Required for user.tool_confirmation.
events[].deny_messagestring | nullNoText stored with your answer. The receipt echoes it whenever you send it. The stored user.tool_confirmation event carries it only for a deny. Its length is limited only by the 10 MiB request body limit.

Response

Returns 200 with a data array that holds one receipt for each event you sent.

{
"data": [
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Now chart the top three."
}
],
"id": "sevt_receipt_219b3afe-157c-452d-981b-c71353fb80bc",
"processed_at": null
}
]
}

Errors

This endpoint can return these errors as well as the common errors.

Statuserror.typeCause and fix
400invalid_request_errorAn event is malformed, or its text is empty or over 100,000 characters in all. Fix the event the message names.
400invalid_request_errorThe session is archived. Archived sessions take no events. Create a new session.
400invalid_request_errorThe session has not started and the request holds more than one user.message, or the first turn is still starting. Send one message, then wait for session.status_running before you send the next.
400invalid_request_errorA tool_use_id is unknown, repeated or already answered. Send only IDs from the latest stop_reason.event_ids that you have not answered.
400invalid_request_errorAn event has a type this endpoint does not accept: user.tool_result, user.custom_tool_result, user.define_outcome or system.message. Send only user.message, user.tool_confirmation or user.interrupt.
400invalid_request_errorAn event carries session_thread_id. Sessions have one thread. Remove session_thread_id and retry.
400invalid_request_errorA user.tool_confirmation follows a user.interrupt in the same request. Send the confirmation before the interrupt, or in a separate request.
403permission_errorThe key does not act for the session’s owner, or the owner is no longer an active member. Use the owner’s personal key or the service account key that created the session.
403permission_errorA user.tool_confirmation was sent with a key that is not personal, or that lacks tasks:approve. Use a personal key with tasks:approve.
404not_found_errorNo session with this ID exists, or this key can’t read it. Check the session ID.
409invalid_request_errorThe session’s state changed while the request was processed. The tool call was already answered, or the session is stopping or moving. Read the events, then decide whether to send again.

Example

cURL
# A message
curl -sS -X POST "$CONTEXT_BASE_URL/v1/sessions/dd13d728-93e3-440a-874c-4efeb58c97e0/events" -H "x-api-key: $CONTEXT_API_KEY" \
-H "content-type: application/json" \
-d '{"events": [{"type": "user.message", "content": [{"type": "text", "text": "Now chart the top three."}]}]}'
# Approve the waiting tool call (needs tasks:approve)
curl -sS -X POST "$CONTEXT_BASE_URL/v1/sessions/dd13d728-93e3-440a-874c-4efeb58c97e0/events" -H "x-api-key: $CONTEXT_API_KEY" \
-H "content-type: application/json" \
-d '{"events": [{"type": "user.tool_confirmation", "result": "allow", "tool_use_id": "sevt_29_0"}]}'
# Stop the current turn
curl -sS -X POST "$CONTEXT_BASE_URL/v1/sessions/dd13d728-93e3-440a-874c-4efeb58c97e0/events" -H "x-api-key: $CONTEXT_API_KEY" \
-H "content-type: application/json" -d '{"events": [{"type": "user.interrupt"}]}'
Python
sid = "dd13d728-93e3-440a-874c-4efeb58c97e0"
client.beta.sessions.events.send(sid, events=[{"type": "user.message", "content": [{"type": "text", "text": "Now chart the top three."}]}])
client.beta.sessions.events.send(sid, events=[{"type": "user.tool_confirmation", "result": "allow", "tool_use_id": "sevt_29_0"}])
client.beta.sessions.events.send(sid, events=[{"type": "user.interrupt"}])
TypeScript
const sid = "dd13d728-93e3-440a-874c-4efeb58c97e0"
await client.beta.sessions.events.send(sid, { events: [{ type: "user.message", content: [{ type: "text", text: "Now chart the top three." }] }] })
await client.beta.sessions.events.send(sid, { events: [{ type: "user.tool_confirmation", result: "allow", tool_use_id: "sevt_29_0" }] })
await client.beta.sessions.events.send(sid, { events: [{ type: "user.interrupt" }] })