---
title: "Send events — the one input: user.message, user.interrupt, system.message [write]"
method: POST
path: "/sessions/{id}/events"
---

# Send events — the one input: user.message, user.interrupt, system.message \[write\]

`POST /sessions/{id}/events`

## Request

- Base URL: `https://api.agentsky.dev/v1`
- URL: `https://api.agentsky.dev/v1/sessions/{id}/events`
- Auth: HTTP bearer

The conversation plane (edge-routed to the chat-proxy). Body {events: [...]} (1–50), optionally requireIdle: true for one user.message (and optional trailing system.message). Exclusive admission refuses a busy session with 409 before persisting or delivering the message; a successful echo's user id names the admitted run. Its admission field is created only when this POST committed the exclusive input; an idempotent replay returns replayed. This field is not part of history or SSE. This is a Session admission receipt, not proof that another client's earlier queued joined input has finished on the Pod. Ordinary sends may join an open run. validated all-or-nothing. A user.message carries content blocks: text, image and document with base64 / url / file sources (inline base64 ≤ 20 MiB per request; larger content goes through POST /files). A user.message may set interaction_mode: "plan" (the agent inspects and answers with a reviewable plan, then waits for a choice to implement) or "build"; omitted, the session's own metadata.interaction_mode applies (build when unset). A system.message (text only) must be last and follow the user.message it accompanies: privileged context for that turn and, through the agent's transcript, later turns. user.interrupt stops the in-flight turn; with mode: "save" it instead asks the run to start no new work, save what it has (files plus a progress note in the working directory) and end its turn with a reply listing what was saved, and stops it only if it is still running after a 10 minute grace window (echo status saving, or no_turn when nothing is running). A later user.message continues in the same session. Every event echoes back in the history shape with a stable id; sources are echoed as file sources naming per-session copies (never base64). Optional Idempotency-Key header: a replay returns the original echo. 402 insufficient_credits / budget_reached, 503 egress_policy_failed, 400 session_archived refuse the whole request before any row is written.

## Path parameters

- `id` string, required

## Request body

- SendEvents
  - `events` union[], required
    - union
      - object
        - `type` 'user.message', required
        - `content` union[], required
          - union
            - object
              - …
            - object
              - …
            - object
              - …
        - `interaction_mode` 'plan' | 'build', nullable
      - object
        - `type` 'system.message', required
        - `content` object[], required
          - `type` 'text', required
          - `text` string, required
      - object
        - `type` 'user.interrupt', required
        - `mode` 'immediate' | 'save'
  - `requireIdle` boolean

## Response `200`

OK — one echo per event, in order

- SendEventsResult
  - `data` object[], required: One echo per event, in order, in the events-history shape
    - `id` string, required: Stable event id — the dedupe key across stream ∪ events
    - `rowId` integer: Position in the session's history; the stream resumes after the newest one a reader holds
    - `type` string, required: user.message · system.message (both history only — never on the live stream; they carry `content`) · agent.message · agent.reasoning · agent.tool_use · agent.tool_result · agent.status · user.interrupt · session.status_running · session.status_idle · session.error · session.status_terminated · session.deleted · context.input_observed (authenticated history only: host-admitted input/client UUID/response/gateway identifiers, scope digests and UTF-8 byte coverage; no pod-declared native session authority, raw capture or private pointer)
    - `sessionId` string, required
    - `agentId` string, nullable: The agent id (agent_…)
    - `at` string: ISO 8601
    - `messageId` string: agent.message
    - `parts` MessagePart[]: agent.message
      - union
        - object
          - `index` integer, required
          - `type` 'text', required
          - `text` string
          - `stream_id` string, nullable
          - `stream_index` integer, nullable
        - object
          - `index` integer, required
          - `type` 'reasoning', required
          - `text` string
          - `redacted` boolean
          - `stream_id` string, nullable
          - `stream_index` integer, nullable
        - object
          - `index` integer, required
          - `type` 'tool_call', required
          - `call_id` string, required
          - `tool_name` string, required
          - `args` object
          - `args_partial` string, nullable
        - object
          - `index` integer, required
          - `type` 'tool_result', required
          - `call_id` string, required
          - `tool_name` string, required
          - `status` 'ok' | 'error'
          - `result` object
        - object
          - `index` integer, required
          - `type` 'file', required
          - `name` string, required
          - `media_type` string, required
          - `uri` string, nullable
          - `data` string, nullable
          - `size_bytes` integer, nullable
        - object
          - `index` integer, required
          - `type` 'image', required
          - `media_type` string, required
          - `uri` string, nullable
          - `data` string, nullable
          - `alt` string, nullable
          - `width` integer, nullable
          - `height` integer, nullable
        - object
          - `index` integer, required
          - `type` 'video', required
          - `media_type` string, required
          - `uri` string, nullable
          - `data` string, nullable
          - `alt` string, nullable
          - `width` integer, nullable
          - `height` integer, nullable
          - `duration_ms` integer, nullable
          - `size_bytes` integer, nullable
          - `thumbnail_uri` string, nullable
        - object
          - `index` integer, required
          - `type` 'status', required
          - `level` 'thinking' | 'working' | 'waiting' | 'idle' | 'done', required
          - `text` string, nullable
        - object
          - `index` integer, required
          - `type` 'error', required
          - `code` string, required
          - `message` string, required
          - `retryable` boolean
    - `text` string: agent.message — parts flattened to plain text
    - `content` object[]: user.message / system.message — the CMA content blocks as sent (text · image · document); every image/document source is a `file` source naming a per-session copy readable through GET /files/{file_id}/content
    - `part` union
      - object
        - `index` integer, required
        - `type` 'text', required
        - `text` string
        - `stream_id` string, nullable
        - `stream_index` integer, nullable
      - object
        - `index` integer, required
        - `type` 'reasoning', required
        - `text` string
        - `redacted` boolean
        - `stream_id` string, nullable
        - `stream_index` integer, nullable
      - object
        - `index` integer, required
        - `type` 'tool_call', required
        - `call_id` string, required
        - `tool_name` string, required
        - `args` object
        - `args_partial` string, nullable
      - object
        - `index` integer, required
        - `type` 'tool_result', required
        - `call_id` string, required
        - `tool_name` string, required
        - `status` 'ok' | 'error'
        - `result` object
      - object
        - `index` integer, required
        - `type` 'file', required
        - `name` string, required
        - `media_type` string, required
        - `uri` string, nullable
        - `data` string, nullable
        - `size_bytes` integer, nullable
      - object
        - `index` integer, required
        - `type` 'image', required
        - `media_type` string, required
        - `uri` string, nullable
        - `data` string, nullable
        - `alt` string, nullable
        - `width` integer, nullable
        - `height` integer, nullable
      - object
        - `index` integer, required
        - `type` 'video', required
        - `media_type` string, required
        - `uri` string, nullable
        - `data` string, nullable
        - `alt` string, nullable
        - `width` integer, nullable
        - `height` integer, nullable
        - `duration_ms` integer, nullable
        - `size_bytes` integer, nullable
        - `thumbnail_uri` string, nullable
      - object
        - `index` integer, required
        - `type` 'status', required
        - `level` 'thinking' | 'working' | 'waiting' | 'idle' | 'done', required
        - `text` string, nullable
      - object
        - `index` integer, required
        - `type` 'error', required
        - `code` string, required
        - `message` string, required
        - `retryable` boolean
    - `stop_reason` object: session.status_idle — discriminate on type; never break on bare idle
      - `type` string: "end_turn" (also after an interrupt) | "retries_exhausted" (preceded by an exhausted engine error) | "provider_lost" (confirmed runtime Pod loss, preceded by an exhausted session.error) | "budget_reached"; more reserved
    - `error` object: session.error — the stream stays open; a session.status_idle follows once the turn is over
      - `type` string: e.g. "pod_unavailable"
      - `message` string
      - `retry_status` string: "retrying" (the turn continues) | "exhausted" (the turn is over)
    - `by` string: user.interrupt — who requested the interrupt
    - `mode` 'save': user.interrupt — present on a save interrupt: the run was asked to save its work and end itself
    - `deadline_at` string, date-time: user.interrupt with mode save — when the run is stopped if it has not ended by then
    - `cause` 'save_timeout': user.interrupt — present when the platform stopped a run whose save grace window ran out
    - `actor` object: user.message only, in history and in the send echo: who sent it, present only when that was not the session's owner
      - `id` string
      - `name` string, nullable
    - `admission` 'created' | 'replayed': Only on a requireIdle user.message echo: created means this POST committed the exclusive input; replayed means the idempotency key already named one. This field is absent from history and SSE.

## Other responses

- `default`: Error — { error: { code, message, request_id } }

## Changes

- **2026-11-02** `e5e8f0229a99`: 1 breaking, 9 info
  - removed `subschema #1, subschema #3` from the `events/items/` request property `oneOf` list
  - added `subschema #1, subschema #3` to the `events/items/` request property `oneOf` list
  - added the optional property `data/items/allOf[#/components/schemas/Event]/cause` to the response with the `200` status
  - added the optional property `data/items/allOf[#/components/schemas/Event]/deadline_at` to the response with the `200` status
  - …6 more

[Operation history](https://skmtc.dev/agentsky/apis/agentsky-developer-api/changes/sessions/:id/events/post.md)

---

[API](https://skmtc.dev/agentsky/apis/agentsky-developer-api.md) - [All operations](https://skmtc.dev/agentsky/apis/agentsky-developer-api/llms.txt) - [OpenAPI document](https://skmtc.dev/agentsky/apis/agentsky-developer-api/revisions/8280a065ef6a?raw)
