# Start a conversation session

URL: https://www.atmee.ai/docs/v2/api/reference/session/createSession

`POST /v1/sessions`

Starts a full conversation: the avatar listens, thinks and answers
in a LiveKit room the visitor joins. One request and one response
whatever the credential; the credential decides who pays:

- **API key** (`X-Api-Key`) — your backend starts a session for a
  visitor of yours, billed to your account. Called server to
  server, never from a browser.
- **Signed-in user** (`Authorization: Bearer <jwt>`) — a visitor of
  the Atmee website, signed in or anonymous. The avatar's settings
  decide whether the visitor or the avatar's owner pays.
- **No credential** — a visitor on an avatar whose owner sponsors
  anonymous chat, paid from the avatar's sponsor budget and
  rate-limited per client IP. `visitorId` is required.

Responds `201` once the avatar has joined the room, with what
`livekit-client`'s `Room.connect()` needs. **Length.**
`maxDurationSeconds` is capped server-side (the key's and the plan's
limits, and for `reserve` billing what the balance can pay for); the
response reports what you asked for, what you got and whether it was
cut. The visitor must join within 60 seconds of the avatar, or the
session ends `failed` with nothing billed.

**Ending.** `POST /v1/sessions/{id}/end` with the credential that
started it. Read an API session's state, end reason and cost with
`GET /v1/sessions/{id}`. An avatar renders one live
conversation at a time per account: a second start answers `409
avatar_busy`.

**App tools** (API key only). `appTools.tools` declares functions
the avatar may call; each call reaches the visitor's LiveKit client
as an RPC (method `atmee.tool`, payload `{id, name, arguments}`).
With `x-atmee.mode` `await` its answer is the result the avatar is
given. The request body may be up to 128 KiB.

## Request body

- `application/json`: `SessionCreateRequest`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `avatarId` | string | yes | ID of the avatar to launch. With an API key, it must be on the key's allow-list. |
| `maxDurationSeconds` | integer | no | Maximum duration of the session in seconds. If not provided, uses the key's/plan's/balance's maximum. Capped server-side. |
| `visitorId` | string | no | Opaque identifier for this visitor. With an API key it is at least 16 characters and only meaningful when the key has cross-visit memory enabled; it must be an unguessable value derived from your own server-side session (whoever can name it reads that visitor's conversation history). Without a credential it is required, at most 64 characters, and counts the visitor's free daily time. Ignored for a signed-in user. |
| `language` | string | no | Per-session language override (BCP-47), e.g. `en`, `zh-TW`, `ja`. Omit to use the avatar's own language. Any well-formed tag is accepted and forwarded; the runtime routes speech recognition and responses for `en`, `zh-TW` and `ja` today and falls back to language auto-detection for other tags. |
| `metadata` | object | no | Optional free-form metadata to attach to the session (stored as JSONB). |
| `location` | object | no | Optional location metadata to attach to the session (stored as JSONB). |
| `sttMultilingual` | boolean | no | Recognise speech in any supported language instead of the session's language only. |
| `appTools` | `AppTools` | no | Functions the avatar may call in this session, and how to use them. Absent or null, the session has none. |
| `checkInAfterS` | number | no | Seconds of silence before the avatar speaks up in character, from 0 to 3600; a second check-in waits twice as long, then it stays quiet until the visitor speaks. `0` turns the check-in off. Absent or null keeps the default, about 10 seconds. Needs a v2 avatar that launches on the default conversation runtime, as every avatar built through this API does; any other answers `400 check_in_not_supported`, with nothing billed. Billing and session length are the same either way. |
| `welcome` | boolean | no | `false` starts the session without the avatar's welcome: no self-introduction and no welcome back, first visit or return. It waits for the visitor to speak and answers as usual; a long silence still gets its check-in, which `checkInAfterS` sets or turns off. Absent, null or `true`, the avatar speaks first, as it always has. Needs a v2 avatar that launches on the default conversation runtime, as every avatar built through this API does; any other answers `400 welcome_not_supported`, with nothing billed. Billing and session length are the same either way. |

## Responses

| Status | Description | Schema |
| --- | --- | --- |
| `201` | The session started; the avatar has already joined the room. | `EmbedSessionStartResponse` |
| `400` | `invalid_tools` (the message names the rule that failed), `invalid_request` (`appTools` without an API key), `welcome_not_supported` (`welcome: false` on an avatar that cannot start without it), `check_in_not_supported` (`checkInAfterS` on an avatar that cannot set it), or a request body over 128 KiB. With both an API key and a bearer token, the key's start applies. | `ApiErrorResponse` |
| `401` | A missing, invalid or revoked API key or bearer token. | none |
| `402` | Out of credits, a spend cap or daily limit reached, too many sessions at once, paid credits required for a monetized avatar, or (no credential) the avatar's sponsor budget is exhausted or this visitor used up their free daily time. | `PaymentRequiredResponse` |
| `403` | The avatar is not on the key's allow-list, the user account is marked for deletion, or (no credential) the avatar does not allow anonymous chat. | none |
| `404` | User profile or account not found (signed-in user). | none |
| `409` | `avatar_not_ready` — a v2 avatar has no candidate serving yet; poll `GET /v1/avatars/{id}` until `readyToChat` is true \| `avatar_busy` — the avatar is already in a live session of this account (`liveSessionId` when it is an API session); nothing is billed \| `account_ambiguous`. | `ApiErrorResponse` |
| `422` | Request body failed schema validation, `visitorId` is shorter than 16 characters with an API key, or (without a credential) `visitorId` is missing or longer than 64 characters or `avatarId` is not a UUID. | `FrameworkErrorResponse` |
| `429` | Too many anonymous session starts from this IP — retry later. | none |
| `500` | Internal server error | none |
| `503` | `no_capacity` — no rendering capacity right now; nothing is billed, retry after the `Retry-After` seconds. Also sessions switched off on this deployment. | `ApiErrorResponse` |

## `201` response fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `links` | `SessionLinks` | no | Absent when this deployment does not know the website's address. |
| `sessionId` | string | yes | ID of the created session |
| `serverUrl` | string | yes | Bare LiveKit server URL — pass directly to Room.connect(). Never has a room path appended. |
| `roomName` | string | yes | LiveKit room name, informational only — the token already selects the room. |
| `userToken` | string | yes | LiveKit access token for the visitor's browser to join with. |
| `actualDurationSeconds` | integer | no | Session duration granted by the key/plan/balance limits, in seconds, counted from the moment both the visitor and the avatar are in the room. Omitted when the session is uncapped (`billingMode` `metered` on a key without `max_session_seconds`): do not start a countdown from a missing value. A metered session ends when the account's balance or the key's spend cap runs out, after a short grace, or at the operator ceiling. |
| `requestedDurationSeconds` | integer | no | The `maxDurationSeconds` you sent; omitted when you sent none. |
| `durationCapped` | boolean | no | True when `actualDurationSeconds` is shorter than `requestedDurationSeconds`: the key's, the plan's or the balance's limit cut the session you asked for. |
| `billingMode` | string (`reserve`, `metered`) | yes | How the session is billed. `reserve` locks the whole granted duration when the session starts and releases the unused part at the end. `metered` charges the elapsed time in one-minute ticks while the session runs; nothing is locked up front. |
