# Create session (/docs/api-reference/sessions/createSession)

> Create a voice Session: a short-lived, single-conversation reservation that lets a client hold a live voice chat with your agent. This is step 1 of 2. Creating the Session does not start any audio on its own; it returns a ws_url that a client then connects to over WebSocket to actually talk.

**POST** `/sessions/create`

Create a voice Session: a short-lived, single-conversation
reservation that lets a client hold a live voice chat with your
agent. This is step 1 of 2. Creating the Session does not start any
audio on its own; it returns a `ws_url` that a client then connects
to over WebSocket to actually talk.

Call this endpoint from your server with your API key, and return
only the `ws_url` to your client. The API key must never reach the
browser. The `ws_url` is the only thing the client needs, and it is
safe to hand out because it is single-use and expires. For how to
connect and talk, see "Connect the client and talk" below the
request details.

```yaml
operationId: createSession
requestBody:
  required: true
  content:
    application/json:
      schema:
        type: object
        required:
          - agent_id
          - type
        properties:
          agent_id:
            type: integer
            description: ID of the agent the session talks to.
            example: 158910
          type:
            type: string
            enum:
              - voice
            description: The session type. Only `voice` is supported.
          custom_variables:
            type: object
            additionalProperties: true
            description: |
              Per-session variables that personalize the conversation.
              Set server-side, so visitors cannot tamper with them.
            example:
              name: Demo User
responses:
  '201':
    description: Session created.
    content:
      application/json:
        schema:
          type: object
          properties:
            session_id:
              type: integer
              description: Correlate this with call logs and support requests.
              example: 4521
            token:
              type: string
              description: The Session Token. Single conversation, unguessable.
              example: sess_51gF2qw8LxNz0vY4mT7Ka3RjD9pBcE6HuWiQnZsX0oM
            expires_at:
              type: string
              format: date-time
              description: End of the 15-minute connect window (UTC).
              example: '2026-07-17T12:15:00Z'
            ws_url:
              type: string
              description: Connect your client to this URL as returned.
              example: >-
                wss://live.omnidim.io/chat/start_voice_chat?request_token=sess_51gF2qw8LxNz0vY4mT7Ka3RjD9pBcE6HuWiQnZsX0oM
  '400':
    description: |
      Unsupported session type (`unsupported_type`) or a malformed
      request body (`invalid_request`).
    content:
      application/json:
        schema:
          type: object
          properties:
            error:
              type: string
              description: Machine-readable error code.
            error_description:
              type: string
              description: Human-readable explanation.
        example:
          error: unsupported_type
          error_description: Only type 'voice' is supported.
  '401':
    description: Missing or invalid API key.
  '402':
    description: The organization balance is too low to start a call.
    content:
      application/json:
        schema:
          type: object
          properties:
            error:
              type: string
              description: Machine-readable error code.
            error_description:
              type: string
              description: Human-readable explanation.
        example:
          error: insufficient_balance
          error_description: Balance is low. Please Choose Appropriate plan.
  '404':
    description: Agent not found in your organization.
    content:
      application/json:
        schema:
          type: object
          properties:
            error:
              type: string
              description: Machine-readable error code.
            error_description:
              type: string
              description: Human-readable explanation.
        example:
          error: agent_not_found
          error_description: Agent not found or access denied
  '429':
    description: |
      The organization is at its concurrent call limit. Wait for a
      call to finish or purchase more concurrency, then retry.
    content:
      application/json:
        schema:
          allOf:
            - type: object
              properties:
                error:
                  type: string
                  description: Machine-readable error code.
                error_description:
                  type: string
                  description: Human-readable explanation.
            - type: object
              properties:
                limit:
                  type: integer
                  description: Your organization's concurrent call limit.
        example:
          error: concurrency_limit_reached
          error_description: >-
            Your organization is at its concurrent call limit (2). Wait for a call to finish or
            purchase more concurrency.
          limit: 2
```

**Browser SDK (connect after create)**

```js
import { WebSession } from '@omnidim-ai/client';

// 1. Ask YOUR server to create the session (keeps the API key
//    server-side) and return the ws_url.
const { ws_url } = await fetch('/create-omnidim-session', {
  method: 'POST',
}).then((r) => r.json());

// 2. The browser connects to the ws_url and talks. No API key here.
const session = new WebSession();
session.on('transcript', (t) => console.log(t.role, t.text));
await session.start({ wsUrl: ws_url });
```

**curl**

```bash
curl -X POST "https://omnidim.io/api/v1/sessions/create" \
  -H "Authorization: Bearer $OMNIDIM_API_KEY"
```

## Connect the client and talk [#connect-the-client-and-talk]

The `ws_url` alone does nothing until a client connects to it. Creating the
Session is step 1; connecting a client is step 2. Pick one of the two client
paths below. Both use the same `ws_url`.

  Your API key stays on your server. The browser (or any client) only ever
  receives the `ws_url`, which is single-use and expires, so it is safe to
  hand out.

### Token and URL rules [#token-and-url-rules]

* The token is valid to connect for 15 minutes after creation.
* It covers one conversation and dies when that conversation ends.
* Connect to the `ws_url` exactly as returned. Never build, edit, or cache
  WebSocket URLs; the format is not part of the contract.
* If the connection drops mid-call, reconnecting to the same `ws_url`
  re-attaches to the same conversation, so a brief network blip does not lose
  the call.

### Option A: browser, with the web SDK (recommended) [#option-a-browser-with-the-web-sdk-recommended]

The [`@omnidim-ai/client` web SDK](/docs/sdks/web) turns the `ws_url` into a
working conversation: microphone capture, agent audio playback, barge-in, and
transcripts are all handled for you.

```bash
npm install @omnidim-ai/client
```

```js
import { WebSession } from '@omnidim-ai/client';

// wsUrl came from POST /sessions/create on YOUR server.
const session = new WebSession();
session.on('status', (s) => console.log('status', s));       // connecting | active | { state: 'ended', reason }
session.on('transcript', (t) => console.log(t.role, t.text)); // { role: 'user' | 'agent', text, final }
session.on('error', (e) => console.error(e));

await session.start({ wsUrl }); // asks for mic permission, then talks
session.mute(true);             // toggle the microphone
session.stop();                 // hang up
```

No bundler? Load the UMD build from a script tag and use the
`OmnidimensionClient` global:

```html
<script src="https://unpkg.com/@omnidim-ai/client"></script>
<script>
  const session = new OmnidimensionClient.WebSession();
  await session.start({ wsUrl });
</script>
```

Full guide, events, and ended reasons: [web SDK](/docs/sdks/web).

### Option B: any platform, raw WebSocket protocol [#option-b-any-platform-raw-websocket-protocol]

For iOS, Android, Flutter, kiosks, or any client without the SDK, connect to
the `ws_url` and speak the wire protocol directly: send 16 kHz mono PCM16 audio
frames, and receive audio plus transcript events.

```js
const ws = new WebSocket(wsUrl);

// Send microphone audio, one JSON frame per chunk:
ws.send(JSON.stringify({ type: 'audio', data: base64Pcm16 }));

// Receive agent audio, transcripts, and control events:
ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  switch (msg.event) {
    case 'media':
      play(msg.media.payload); // base64 audio to play
      break;
    case 'clear':
      stopPlaybackNow(); // barge-in: flush playback immediately
      break;
    case 'end_call':
      hangUp(msg.media.payload.reason);
      break;
    // 'user' | 'partial_text' | 'system' | 'last_system_message' -> transcripts
  }
};

// Hang up by closing the socket:
ws.close();
```

Full frame reference, event table, and close codes:
[web call protocol](/docs/api-reference/web-call-protocol).

### Recap [#recap]

1. Your server calls this endpoint and gets a `ws_url`.
2. Your server returns the `ws_url` to your client.
3. Your client connects to the `ws_url` (SDK or raw protocol) and the
   conversation runs until it hangs up or the socket closes.