> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send events

> Send user or system events to a Session.

`POST /api/v1/cloud/sessions/{session_id}/events`

Sends one or more accepted events to a Session. User messages trigger asynchronous Agent processing; use the list or stream endpoints to read Agent output.

## Path parameters

| Parameter    | Type   | Description                        |
| ------------ | ------ | ---------------------------------- |
| `session_id` | string | Session ID with the `sess_` prefix |

## Headers

| Header          | Required | Description         |
| --------------- | -------- | ------------------- |
| `Authorization` | Yes      | `Bearer $QODER_PAT` |
| `Content-Type`  | Yes      | `application/json`  |

## Request body

| Field    | Type  | Required | Description                      |
| -------- | ----- | -------- | -------------------------------- |
| `events` | array | Yes      | Non-empty array of event objects |

## Supported event types

| Type                      | Required fields         | Notes                                                                                                                                                                                                                                                                                        |
| ------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user.message`            | `content`               | `content` must be a non-empty array of content blocks                                                                                                                                                                                                                                        |
| `user.interrupt`          | none                    | `session_thread_id` is optional                                                                                                                                                                                                                                                              |
| `user.tool_confirmation`  | `tool_use_id`, `result` | `result` must be `allow` or `deny`. `deny_message` is optional and only allowed when `result = "deny"`                                                                                                                                                                                       |
| `user.tool_result`        | `tool_use_id`           | Use this to return a built-in tool result; `content` and `is_error` are optional                                                                                                                                                                                                             |
| `user.custom_tool_result` | `custom_tool_use_id`    | Use this to return a client-side custom tool result; `content` and `is_error` are optional                                                                                                                                                                                                   |
| `user.define_outcome`     | `description`, `rubric` | `rubric` is an object: either `{"type":"text","content":"..."}` or `{"type":"file","file_id":"file_..."}`; unknown fields are rejected. `max_iterations` is optional and must be an integer between 1 and 20. The server assigns an `outcome_id` (prefix `outc_`) — clients must not pass it |
| `system.message`          | `content`               | `content` must be a non-empty array of text content blocks. At most one `system.message` per request, and it must be the final event of the batch, immediately following `user.message`, `user.tool_result`, or `user.custom_tool_result`                                                    |

Plain string `content` is not supported for `user.message` or `system.message`.

## Example request

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/sessions/sess_019e392c0d1e74e095d21ea4c6b41def/events \
  -H "Authorization: Bearer $QODER_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "type": "user.message",
        "content": [
          {"type": "text", "text": "Help me analyze the performance of this code."}
        ]
      }
    ]
  }'
```

## Example response

**HTTP 200 OK**

```json theme={null}
{
  "data": [
    {
      "id": "evt_019e3bb2c153764da54e4d3acbef52b6",
      "type": "user.message",
      "content": [
        {"type": "text", "text": "Help me analyze the performance of this code."}
      ],
      "processed_at": "2026-05-18T15:27:11.187413896Z"
    }
  ]
}
```

## Human-in-the-loop responses

When the stream emits an `agent.tool_use` that requires confirmation, reply with `user.tool_confirmation`:

```json theme={null}
{
  "events": [
    {
      "type": "user.tool_confirmation",
      "tool_use_id": "evt_01JZ6Q3FB6SG8F7J1M2N",
      "result": "deny",
      "deny_message": "Do not delete files in this directory."
    }
  ]
}
```

When the stream emits `agent.custom_tool_use`, execute the tool in your client and reply with `user.custom_tool_result`:

```json theme={null}
{
  "events": [
    {
      "type": "user.custom_tool_result",
      "custom_tool_use_id": "evt_01JZ6R1V9Z8K2M3N4P5Q",
      "content": [{"type": "text", "text": "Order status: shipped"}]
    }
  ]
}
```

## Errors

| HTTP | Type                    | Trigger                                                                                                                                 |
| ---- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `invalid_request_error` | Empty `events`, unsupported event type, missing required field, invalid content blocks, or unsupported legacy field                     |
| 401  | `authentication_error`  | PAT invalid or expired                                                                                                                  |
| 404  | `not_found_error`       | Session or pending action does not exist                                                                                                |
| 409  | `invalid_request_error` | Session is currently processing a turn, or other Session state conflict (note: `type` is `invalid_request_error`, not `conflict_error`) |

### Example: 400 invalid `content`

Sending `user.message` with a plain string instead of a content block array:

```json theme={null}
{
  "error": {
    "message": "Field 'content' must be a non-empty array of content blocks.",
    "type": "invalid_request_error"
  },
  "request_id": "2c9d3d8a-dd0f-4c27-b2ad-24c8719c97e1",
  "type": "error"
}
```

### Example: 404 Session not found

```json theme={null}
{
  "error": {
    "message": "Session 'sess_doesnotexist_xxxxxxxxxxxxxxxxxxxxxxxx' was not found.",
    "type": "not_found_error"
  },
  "request_id": "590c2a0c-1656-42d3-ba0c-32c755d22e77",
  "type": "error"
}
```

### Example: 409 Session is running

Sending a `user.message` while the Session is already processing a turn:

```json theme={null}
{
  "error": {
    "message": "Session is currently processing a turn. Cancel the current turn or wait for completion.",
    "type": "invalid_request_error"
  },
  "request_id": "ba018d2f-d6b0-4bbe-a53c-2241baca34fe",
  "type": "error"
}
```

See [Errors](/cloud-agents/api/conventions/errors) for the full error envelope.

## Related

<CardGroup cols={2}>
  <Card title="Session event stream" icon="wave-pulse" href="/cloud-agents/events-stream">
    Stream agent thinking, messages, tool calls, and status over SSE.
  </Card>
</CardGroup>
