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

# Send Message

> Append a message to a session and get the assistant’s answer

Append a user message to an existing session and receive the assistant's answer. This is the multi-turn continuation of a conversation; the scope is taken from the session, so you never resend it. Answers cite their sources: each citation in `message.parts` deep-links to a `recording_id` + `start_ms`.

The request body is the same **message** shape used for `initial_message` when [creating a session](/api-reference/ask/create-session), so a client can use one composer for both.

## Authentication

Requires an [API key](/authentication). The session is looked up within the key's project; an unknown session returns `404`. An end-user-scoped key can only post to its own sessions.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.bota.dev/v1/ask/sessions/as_abc123/messages \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{ "content": "And what about the next sprint?" }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.bota.dev/v1/ask/sessions/as_abc123/messages', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_...',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ content: 'And what about the next sprint?' }),
  });

  const { session_id, message, sources } = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.bota.dev/v1/ask/sessions/as_abc123/messages',
      headers={
          'Authorization': 'Bearer sk_live_...',
          'Content-Type': 'application/json',
      },
      json={'content': 'And what about the next sprint?'},
  )

  result = response.json()
  ```
</RequestExample>

## Path Parameters

<ParamField path="id" type="string" required>
  Session identifier (`as_*`).
</ParamField>

## Request Body

<ParamField body="content" type="string" required>
  The user's message (1–10000 characters).
</ParamField>

<ParamField body="provider" type="string">
  LLM provider to use for this turn (`gemini`, `openai`, or `claude`). Defaults to the project/system default.
</ParamField>

<ParamField body="context_refs" type="array">
  @mentioned recordings to attach to this turn: `[{ "type": "recording", "id": "rec_..." }]` (max 20). Reserved for upcoming features; persisted today.
</ParamField>

## Response

Returns the **ask result** — the assistant message and the session it belongs to.

<ResponseExample>
  ```json 200 theme={null}
  {
    "session_id": "as_abc123",
    "message": {
      "id": "msg_def456",
      "role": "assistant",
      "content": "You agreed to send the budget draft by Friday.",
      "parts": [
        { "type": "text", "text": "You agreed to send the budget draft by Friday." },
        { "type": "citation", "recording_id": "rec_ghi789", "start_ms": 754000 }
      ],
      "tokens": { "input": 1240, "output": 32, "cached": 0 },
      "model": "gemini-2.0-flash",
      "provider": "gemini",
      "finish_reason": "stop",
      "created_at": "2026-05-20T10:23:45Z"
    },
    "sources": [
      {
        "chunk_id": "chk_jkl012",
        "recording_id": "rec_ghi789",
        "transcription_id": "txn_mno345",
        "chunk_text": "I'll send the budget draft by Friday.",
        "speaker": "SPEAKER_1",
        "start_ms": 754000,
        "end_ms": 759000,
        "recorded_at": "2026-05-16T09:12:00Z",
        "score": 0.0312
      }
    ]
  }
  ```
</ResponseExample>

## Response Fields

| Field            | Type   | Description                                                                                                                                                     |
| ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session_id`     | string | The conversation session (`as_*`)                                                                                                                               |
| `message`        | object | The assistant message                                                                                                                                           |
| `message.parts`  | array  | Rich content: `text` segments and `citation` parts (`{ type, recording_id, start_ms }`)                                                                         |
| `message.tokens` | object | Token usage `{ input, output, cached }`                                                                                                                         |
| `sources`        | array  | Retrieved chunks fed into the prompt (cross-recording scopes only; empty for `recording`) — same shape as [Search Recordings](/api-reference/recordings/search) |

<Note>
  The user message is persisted before the model is called, so on an LLM failure (`502`) you can safely retry with the returned `session_id`. A `recording`-scoped session whose transcription isn't ready returns `409`; an oversized single-recording transcript returns `413` (use a cross-recording scope instead).
</Note>
