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

> ## Agent Instructions
> The OpenAPI document at /api-reference/openapi.yaml defines the target contract. Its x-reflections-availability field and each operation's availability notice identify supported test and live forms. Do not infer implementation from a schema or code sample. Live keys support account details, reads of the licenses the calling account holds as licensee or issued as talent (GET /v1/licenses?role=issuer), and GET /v1/generations?license=… for the state of outputs under such a license (summaries only: no prompt, reference, bytes or metadata). Live image checks, submissions and receipts are built but not activated: until activation, a live POST /v1/checks or /v1/generations returns 403 live_not_activated, which must not be retried. Discovery and JWKS are at the origin root (https://app.reflections.ai/.well-known/...), not under /v1.
> Three rules hold everywhere: a decision is never an error (deny and escalate are 200 responses, and a live check still evaluating is a 202 with status checking); generate nothing for a named talent without an allow; deliver nothing until the generation is allow.
> The sandbox accepts personal rk_test_ keys. Persist a separate Idempotency-Key and body for each logical /v1 POST; retry an unknown outcome with the same key and body. OAuth and Talent API MCP remain planned.

# Session stream (planned)

> Proposed WebSocket contract for session changes and termination.

<Note>
  Planned protocol reference. Live sessions and this WebSocket stream are not served by the
  current Talent API sandbox.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

The planned session stream pushes changes to a live voice or video session as they happen. Stay connected
for the whole session: a revocation or a denied sample reaches you here before your next renewal
would fail.

## Connect

Open a WebSocket to the `stream_url` returned by
[`POST /v1/sessions`](/api-reference/sessions/create-a-session):

```text theme={null}
wss://app.reflections.ai/v1/sessions/ses_q8v1m3n7p4r6s9t0wxyhk5zba2/stream
```

Authenticate in one of two ways:

| From | How |
| - | - |
| A client, such as a browser or an app | Offer two subprotocols in `Sec-WebSocket-Protocol`: `tlp.session.v1`, and `tlp.session.<token>`, where `<token>` is the session's current token. |
| A server | Offer `tlp.session.v1`, and send your secret key in `Authorization: Bearer rk_…`. |

Reflections selects `tlp.session.v1` and never echoes the token, so it stays out of response
headers and proxy logs. The token is checked once, when the connection opens. Renewing it with
[`POST /v1/sessions/{session_id}/renew`](/api-reference/sessions/renew-a-session) keeps the session
alive and does not reconnect the stream. Message fields follow the API version the session was
created with.

The first message is `session.ready`. Messages arrive in order on one connection. Send a `ping` at
least every 30 seconds when nothing else is sent; Reflections answers with a `pong` carrying the
same `id`. The stream closes when the session ends.

| Message | What to do |
| - | - |
| `session.ready` | Start output. `expires_at` is when the current token expires. |
| `session.warning` | Act before `act_by`: renew (`renewal_due`), submit a sample (`sample_overdue`), or wrap up (`license_ending`, `max_duration`). |
| `session.terminated` | Stop output within one second. Renewals now fail. |
| `sample.decided` | The decision on a sample you sent on the stream, with the `ref` you sent. A `deny` is followed by `session.terminated`. |
| `error` | A message you sent could not be used. The connection stays open. |

## Send samples

You can send samples on the stream as `sample.submit` instead of calling
[`POST /v1/sessions/{session_id}/samples`](/api-reference/sessions/submit-a-sample), which saves a
round trip per sample. Send them at least as often as the session's `sampling` requires. A sample
sent with `POST` gets its decision in the response; a sample sent on the stream gets it as
`sample.decided`.

| Field | Value |
| - | - |
| `type` | `sample.submit` |
| `ref` | Your reference for the sample, up to 64 characters. `sample.decided` echoes it. |
| `kind` | `audio`, `transcript` or `frame` |
| `window` | `start_ms` and `end_ms`: where the sample falls, in milliseconds from the session's start |
| `data` | For `audio` or `frame`: the bytes, base64, up to 2 MB |
| `media_type` | `audio/wav` or `audio/ogg` for audio, `image/jpeg` for a frame |
| `text` | For `transcript`: the words of one turn, up to 8,000 characters |

```json theme={null}
{
  "type": "sample.submit",
  "ref": "turn-42",
  "kind": "transcript",
  "window": { "start_ms": 81200, "end_ms": 84600 },
  "text": "Welcome back to the Ridge Runners podcast."
}
```

```json theme={null}
{
  "type": "sample.decided",
  "session": "ses_q8v1m3n7p4r6s9t0wxyhk5zba2",
  "ref": "turn-42",
  "sample": "smp_4r6s9t0wxyhk5zba2q8v1m3n7p",
  "decision": "allow",
  "reasons": []
}
```

A sample that gets an `error` was not received. It does not count toward `sampling`, so send a
corrected one before the next `act_by`:

```json theme={null}
{
  "type": "error",
  "ref": "turn-43",
  "code": "parameter_invalid",
  "detail": "`window.end_ms` must be greater than `window.start_ms`.",
  "param": "window.end_ms"
}
```

`code` is one of `invalid_request` (text that is not JSON, or an unknown `type`),
`parameter_missing`, `parameter_invalid`, `unsupported_media_type`, `media_unreadable` and
`payload_too_large`. `detail` is for you, not your user.

## Stop within one second

When `session.terminated` arrives, stop generating and stop playing what you already generated,
within one second. Do not wait for the current sentence or frame to finish. Then end the session on
your side and report its usage up to `at`. The
[`session.terminated`](/api-reference/webhooks/session-terminated) webhook reports the same
termination, usually later, in `data.object.termination`; act on whichever arrives first.

## Close codes

A request that is not a WebSocket upgrade gets `426 Upgrade Required`, and one that does not offer
`tlp.session.v1` gets `400 invalid_request`, each with a [problem details](/api-reference/errors)
body. Every other failure happens after the upgrade, so a browser can read it too: Reflections
closes the connection with a code and a reason, without sending `session.ready`.

| Code | Reason | When | What to do |
| - | - | - | - |
| `1000` | `session_ended` | The session ended. | Do not reconnect. |
| `1000` | `session_terminated` | Reflections terminated the session, after `session.terminated`. | Stop output. Do not reconnect. |
| `1001` | `going_away` | Reflections is moving the connection to another server. | Reconnect at once with the current token. |
| `1003` | `binary_not_supported` | You sent a binary frame. | Send samples as JSON, with media in base64 `data`. |
| `1011` | `internal_error` | An error on Reflections' side. | Read the session, then reconnect after 1 second, doubling the wait each time. |
| `1013` | `service_unavailable` | Reflections cannot take the connection now. | The same as `1011`. |
| `4401` | `authentication_required` | No token and no secret key. | Offer the token, or send the key. |
| `4401` | `token_invalid` | The token is malformed, has expired or names another session. | Reconnect with the token from your latest renewal. |
| `4401` | `invalid_api_key` | The secret key is not valid. | Use a valid key. |
| `4403` | `insufficient_scope` | A restricted key without `sessions:write`. | Use a key with that scope. |
| `4404` | `resource_not_found` | No session has this ID in the key's account and mode. | Check the ID and the key's mode. |
| `4408` | `ping_timeout` | Nothing arrived from you for 60 seconds. | Reconnect, and send a `ping` every 30 seconds when idle. |
| `4409` | `session_not_active` | The session was not active when you connected. | Read the session and stop output. |
| `4429` | `rate_limited` | The session already has 4 open stream connections. | Close a connection you no longer use. |

The `44xx` codes are the HTTP status plus 4000, and their reasons are the
[error codes](/api-reference/errors) the same failure gets over REST.

## Reconnect

If the connection drops with any code other than `1000`, reconnect with the session's current
token, the one from your last renewal. Before you reconnect, read the session with
[`GET /v1/sessions/{session_id}`](/api-reference/sessions/retrieve-a-session): if `status` is
`terminated` or `ended`, a `session.terminated` you missed is recorded in `termination`, and you
stop output instead of reconnecting. If you cannot read the session, stop output no later than
when the current token expires; the example stops at once. Renewal is the second safeguard: when
[a renewal](/api-reference/sessions/renew-a-session) fails with `session_not_active`, stop output
at once.

## Example

<CodeGroup>
  ```typescript TypeScript theme={null}
  import WebSocket from "ws";

  function followSession(session: { id: string; stream_url: string }, currentToken: () => string, stopOutput: () => void) {
    const ws = new WebSocket(session.stream_url, ["tlp.session.v1", `tlp.session.${currentToken()}`]);
    let pings: NodeJS.Timeout | undefined;

    ws.on("open", () => {
      pings = setInterval(() => ws.send(JSON.stringify({ type: "ping", id: String(Date.now()) })), 25_000);
    });

    ws.on("message", (raw) => {
      const message = JSON.parse(raw.toString());
      switch (message.type) {
        case "session.terminated":
          stopOutput(); // Within one second.
          ws.close();
          break;
        case "session.warning":
          console.warn(`${message.code}: act by ${message.act_by}`);
          break;
        case "sample.decided":
          if (message.decision === "deny") stopOutput(); // session.terminated follows
          break;
      }
    });

    ws.on("error", (err) => console.error("session stream:", err.message)); // "close" follows

    ws.on("close", async (code) => {
      clearInterval(pings);
      if (code === 1000) return stopOutput(); // The session ended or was terminated.
      try {
        const res = await fetch(`https://app.reflections.ai/v1/sessions/${session.id}`, {
          headers: {
            Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
            "Reflections-Version": "2026-10-15",
          },
        });
        if (!res.ok) throw new Error(`Retrieve session failed: ${res.status}`);
        const current = await res.json();
        if (current.status === "active") {
          setTimeout(() => followSession(session, currentToken, stopOutput), 1_000);
          return;
        }
      } catch (err) {
        console.error(err);
      }
      stopOutput(); // Ended, terminated or unknown.
    });
  }
  ```

  ```python Python theme={null}
  import asyncio, json, os, time
  import requests
  import websockets

  async def follow_session(session, current_token, stop_output):
      while True:
          try:
              async with websockets.connect(
                  session["stream_url"], subprotocols=["tlp.session.v1", f"tlp.session.{current_token()}"]
              ) as ws:
                  pinger = asyncio.create_task(send_pings(ws))
                  try:
                      async for raw in ws:
                          message = json.loads(raw)
                          if message["type"] == "session.terminated":
                              stop_output()  # Within one second.
                              return
                          if message["type"] == "session.warning":
                              print(f"{message['code']}: act by {message['act_by']}")
                          if message["type"] == "sample.decided" and message["decision"] == "deny":
                              stop_output()  # session.terminated follows
                  finally:
                      pinger.cancel()
          except websockets.ConnectionClosed as closed:
              if closed.rcvd is not None and closed.rcvd.code == 1000:
                  stop_output()  # The session ended or was terminated.
                  return
          except (websockets.InvalidHandshake, OSError):
              pass  # Read the session before reconnecting.

          try:
              res = await asyncio.to_thread(
                  requests.get,
                  f"https://app.reflections.ai/v1/sessions/{session['id']}",
                  headers={
                      "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
                      "Reflections-Version": "2026-10-15",
                  },
                  timeout=5,
              )
              res.raise_for_status()
              active = res.json()["status"] == "active"
          except (requests.RequestException, ValueError, KeyError):
              active = False
          if not active:
              stop_output()  # Ended, terminated or unknown.
              return
          await asyncio.sleep(1)

  async def send_pings(ws):
      while True:
          await asyncio.sleep(25)
          await ws.send(json.dumps({"type": "ping", "id": str(int(time.time()))}))
  ```
</CodeGroup>

## Related

<Columns cols={2}>
  <Card title="Real-time sessions" icon="microphone" href="/build/real-time-sessions">
    Renewal, sampling, termination and the one-second stop.
  </Card>

  <Card title="Session token" icon="key" href="/api-reference/formats/session-token">
    The claims in the token, and how a provider verifies it.
  </Card>
</Columns>


## AsyncAPI

````yaml api-reference/asyncapi.yaml sessionStream
id: sessionStream
title: Session stream
description: >
  Every message is a JSON text frame with a `type`. Messages are delivered in
  order on one

  connection. If the connection drops, read the session before you reconnect: a
  missed

  `session.terminated` is recorded there and in the webhook.


  A request that is not a WebSocket upgrade gets `426 Upgrade Required`, and one
  that does not

  offer `tlp.session.v1` gets `400 invalid_request`, each with a problem details
  body. Every

  other failure happens after the upgrade, so a browser can read it: Reflections
  closes the

  connection with one of these codes and reasons, without sending
  `session.ready`.


  | Code | Reason | When | What to do |

  |---|---|---|---|

  | `1000` | `session_ended` | The session ended. | Do not reconnect. |

  | `1000` | `session_terminated` | Reflections terminated the session, after
  `session.terminated`. | Stop output. Do not reconnect. |

  | `1001` | `going_away` | Reflections is moving the connection to another
  server. | Reconnect at once with the current token. |

  | `1003` | `binary_not_supported` | You sent a binary frame. | Send samples as
  JSON, with media in base64 `data`. |

  | `1011` | `internal_error` | An error on Reflections' side. | Read the
  session, then reconnect after 1 second, doubling the wait each time. |

  | `1013` | `service_unavailable` | Reflections cannot take the connection now.
  | The same as `1011`. |

  | `4401` | `authentication_required` | No token and no secret key. | Offer the
  token, or send the key. |

  | `4401` | `token_invalid` | The token is malformed, has expired or names
  another session. | Reconnect with the token from your latest renewal. |

  | `4401` | `invalid_api_key` | The secret key is not valid. | Use a valid key.
  |

  | `4403` | `insufficient_scope` | A restricted key without `sessions:write`. |
  Use a key with that scope. |

  | `4404` | `resource_not_found` | No session has this ID in the key's account
  and mode. | Check the ID and the key's mode. |

  | `4408` | `ping_timeout` | Nothing arrived from you for 60 seconds. |
  Reconnect, and send a `ping` every 30 seconds when idle. |

  | `4409` | `session_not_active` | The session was not active when you
  connected. | Read the session and stop output. |

  | `4429` | `rate_limited` | The session already has 4 open stream connections.
  | Close a connection you no longer use. |
servers:
  - id: production
    protocol: wss
    host: app.reflections.ai
    bindings: []
    variables: []
address: /sessions/{session_id}/stream
parameters:
  - id: session_id
    jsonSchema:
      type: string
      description: The session ID, `ses_…`.
    description: The session ID, `ses_…`.
    type: string
    required: true
    deprecated: false
bindings:
  - protocol: ws
    version: 0.1.0
    value:
      method: GET
      headers:
        type: object
        required:
          - Sec-WebSocket-Protocol
        properties:
          Sec-WebSocket-Protocol:
            type: string
            description: >-
              From a client: `tlp.session.v1, tlp.session.<token>`. From a
              server: `tlp.session.v1`.
          Authorization:
            type: string
            description: 'From a server only: `Bearer` and your secret key.'
    schemaProperties:
      - name: method
        type: string
        description: GET
        required: false
      - name: headers
        type: object
        required: false
        properties:
          - name: Sec-WebSocket-Protocol
            type: string
            description: >-
              From a client: `tlp.session.v1, tlp.session.<token>`. From a
              server: `tlp.session.v1`.
            required: true
          - name: Authorization
            type: string
            description: 'From a server only: `Bearer` and your secret key.'
            required: false
operations:
  - &ref_5
    id: receiveSessionEvents
    title: Receive session events
    description: Reflections pushes these messages to you.
    type: receive
    messages:
      - &ref_8
        id: sessionReady
        contentType: application/json
        payload:
          - name: Session ready
            description: Sent once, when the connection is authenticated.
            type: object
            properties:
              - name: type
                type: string
                description: session.ready
                required: true
              - name: session
                type: string
                description: The session ID.
                required: true
              - name: status
                type: string
                description: >-
                  Always `active`: a session that is not active closes the
                  stream with `4409`.
                enumValues:
                  - active
                required: true
              - name: expires_at
                type: string
                description: When the current session token expires.
                required: true
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - session
            - status
            - expires_at
          properties:
            type:
              type: string
              const: session.ready
              x-parser-schema-id: <anonymous-schema-3>
            session:
              type: string
              pattern: ^ses_[0-9a-hjkmnp-tv-z]{26}$
              description: The session ID.
              x-parser-schema-id: <anonymous-schema-4>
            status:
              type: string
              enum:
                - active
              description: >-
                Always `active`: a session that is not active closes the stream
                with `4409`.
              x-parser-schema-id: <anonymous-schema-5>
            expires_at:
              type: string
              format: date-time
              description: When the current session token expires.
              x-parser-schema-id: <anonymous-schema-6>
          x-parser-schema-id: <anonymous-schema-2>
        title: Session ready
        description: Sent once, when the connection is authenticated.
        example: |-
          {
            "type": "session.ready",
            "session": "ses_q8v1m3n7p4r6s9t0wxyhk5zba2",
            "status": "active",
            "expires_at": "2026-10-17T18:05:00Z"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: sessionReady
      - &ref_9
        id: sessionWarning
        contentType: application/json
        payload:
          - name: Session warning
            description: Something will end the session unless you act.
            type: object
            properties:
              - name: type
                type: string
                description: session.warning
                required: true
              - name: session
                type: string
                description: The session ID.
                required: true
              - name: code
                type: string
                description: >
                  `renewal_due`: renew the token now. `sample_overdue`: submit a
                  sample now.

                  `license_ending`: the license window ends at `act_by`.
                  `max_duration`: the session

                  reaches its `max_duration` at `act_by`.
                enumValues:
                  - renewal_due
                  - sample_overdue
                  - license_ending
                  - max_duration
                required: true
              - name: act_by
                type: string
                description: >-
                  The time by which to act. The session terminates if you have
                  not.
                required: true
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - session
            - code
            - act_by
          properties:
            type:
              type: string
              const: session.warning
              x-parser-schema-id: <anonymous-schema-8>
            session:
              type: string
              pattern: ^ses_[0-9a-hjkmnp-tv-z]{26}$
              description: The session ID.
              x-parser-schema-id: <anonymous-schema-9>
            code:
              type: string
              enum:
                - renewal_due
                - sample_overdue
                - license_ending
                - max_duration
              description: >
                `renewal_due`: renew the token now. `sample_overdue`: submit a
                sample now.

                `license_ending`: the license window ends at `act_by`.
                `max_duration`: the session

                reaches its `max_duration` at `act_by`.
              x-parser-schema-id: <anonymous-schema-10>
            act_by:
              type: string
              format: date-time
              description: >-
                The time by which to act. The session terminates if you have
                not.
              x-parser-schema-id: <anonymous-schema-11>
          x-parser-schema-id: <anonymous-schema-7>
        title: Session warning
        description: Something will end the session unless you act.
        example: |-
          {
            "type": "session.warning",
            "session": "ses_q8v1m3n7p4r6s9t0wxyhk5zba2",
            "code": "sample_overdue",
            "act_by": "2026-10-17T18:02:15Z"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: sessionWarning
      - &ref_10
        id: sessionTerminated
        contentType: application/json
        payload:
          - name: Session terminated
            description: Stop output within one second. Renewals now fail.
            type: object
            properties:
              - name: type
                type: string
                description: session.terminated
                required: true
              - name: session
                type: string
                description: The session ID.
                required: true
              - name: reason
                type: string
                description: >-
                  A reason code from the reason code catalog, such as
                  `license_revoked`, `license_suspended` or `prohibited_speech`.
                required: true
              - name: sentence
                type: string
                description: >-
                  The reason in the license's own words. Show it to your user as
                  written.
                required: true
              - name: talent
                type: string
                description: The talent it concerns, when the session has several.
                required: false
              - name: term
                type: string
                description: >-
                  The license term or standing rule it rests on, as an address
                  such as `license:lic_…/terms.voiceLineGrant.never`.
                required: false
              - name: revocation
                type: &ref_0
                  - string
                  - 'null'
                description: >-
                  The revocation that ended the session, for `license_revoked`
                  and `license_suspended`.
                required: true
              - name: sample
                type: &ref_1
                  - string
                  - 'null'
                description: The sample that was denied, if one was.
                required: true
              - name: at
                type: string
                description: >-
                  When Reflections terminated the session. Report usage up to
                  this time.
                required: true
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - session
            - reason
            - sentence
            - revocation
            - sample
            - at
          properties:
            type:
              type: string
              const: session.terminated
              x-parser-schema-id: <anonymous-schema-13>
            session:
              type: string
              pattern: ^ses_[0-9a-hjkmnp-tv-z]{26}$
              description: The session ID.
              x-parser-schema-id: <anonymous-schema-14>
            reason:
              type: string
              description: >-
                A reason code from the reason code catalog, such as
                `license_revoked`, `license_suspended` or `prohibited_speech`.
              x-parser-schema-id: <anonymous-schema-15>
            sentence:
              type: string
              description: >-
                The reason in the license's own words. Show it to your user as
                written.
              x-parser-schema-id: <anonymous-schema-16>
            talent:
              type: string
              pattern: ^tal_[0-9a-hjkmnp-tv-z]{26}$
              description: The talent it concerns, when the session has several.
              x-parser-schema-id: <anonymous-schema-17>
            term:
              type: string
              description: >-
                The license term or standing rule it rests on, as an address
                such as `license:lic_…/terms.voiceLineGrant.never`.
              x-parser-schema-id: <anonymous-schema-18>
            revocation:
              type: *ref_0
              pattern: ^rvk_[0-9a-hjkmnp-tv-z]{26}$
              description: >-
                The revocation that ended the session, for `license_revoked` and
                `license_suspended`.
              x-parser-schema-id: <anonymous-schema-19>
            sample:
              type: *ref_1
              pattern: ^smp_[0-9a-hjkmnp-tv-z]{26}$
              description: The sample that was denied, if one was.
              x-parser-schema-id: <anonymous-schema-20>
            at:
              type: string
              format: date-time
              description: >-
                When Reflections terminated the session. Report usage up to this
                time.
              x-parser-schema-id: <anonymous-schema-21>
          x-parser-schema-id: <anonymous-schema-12>
        title: Session terminated
        description: Stop output within one second. Renewals now fail.
        example: |-
          {
            "type": "session.terminated",
            "session": "ses_q8v1m3n7p4r6s9t0wxyhk5zba2",
            "reason": "license_revoked",
            "sentence": "Maya Okafor revoked this license.",
            "revocation": "rvk_s9t0wxyhk5zba2q8v1m3n7p4r6",
            "sample": null,
            "at": "2026-10-17T18:03:41Z"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: sessionTerminated
      - &ref_11
        id: sampleDecided
        contentType: application/json
        payload:
          - name: Sample decided
            description: >-
              The decision on a sample you sent on the stream. A `deny` is
              followed by `session.terminated`.
            type: object
            properties:
              - name: type
                type: string
                description: sample.decided
                required: true
              - name: session
                type: string
                description: The session ID.
                required: true
              - name: ref
                type: &ref_2
                  - string
                  - 'null'
                description: The `ref` you sent with the sample.
                required: false
              - name: sample
                type: string
                description: The sample's ID, the same as a sample created over REST.
                required: true
              - name: decision
                type: string
                description: >-
                  `deny` is followed by `session.terminated`; stop output within
                  one second.
                enumValues:
                  - allow
                  - deny
                required: true
              - name: reasons
                type: array
                description: >-
                  Empty on `allow`. On `deny`, why, with the same fields as
                  `reasons` on a sample from the REST API.
                required: false
                properties:
                  - name: code
                    type: string
                    description: >-
                      A reason code from the reason code catalog, such as
                      `prohibited_speech`. Branch on it.
                    required: true
                  - name: stage
                    type: string
                    description: Where it was decided. Always `session` on the stream.
                    enumValues:
                      - session
                    required: true
                  - name: talent
                    type: string
                    description: The talent it concerns, when the session has several.
                    required: false
                  - name: term
                    type: string
                    description: >-
                      The license term or standing rule it rests on, as an
                      address.
                    required: false
                  - name: sentence
                    type: string
                    description: >-
                      The reason in the license's own words. Show it to your
                      user as written.
                    required: true
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - session
            - sample
            - decision
          properties:
            type:
              type: string
              const: sample.decided
              x-parser-schema-id: <anonymous-schema-23>
            session:
              type: string
              pattern: ^ses_[0-9a-hjkmnp-tv-z]{26}$
              description: The session ID.
              x-parser-schema-id: <anonymous-schema-24>
            ref:
              type: *ref_2
              description: The `ref` you sent with the sample.
              x-parser-schema-id: <anonymous-schema-25>
            sample:
              type: string
              pattern: ^smp_[0-9a-hjkmnp-tv-z]{26}$
              description: The sample's ID, the same as a sample created over REST.
              x-parser-schema-id: <anonymous-schema-26>
            decision:
              type: string
              enum:
                - allow
                - deny
              description: >-
                `deny` is followed by `session.terminated`; stop output within
                one second.
              x-parser-schema-id: <anonymous-schema-27>
            reasons:
              type: array
              description: >-
                Empty on `allow`. On `deny`, why, with the same fields as
                `reasons` on a sample from the REST API.
              items:
                type: object
                description: >-
                  Why a sample was denied. The same shape as a reason in the
                  REST API.
                required:
                  - code
                  - stage
                  - sentence
                properties:
                  code:
                    type: string
                    description: >-
                      A reason code from the reason code catalog, such as
                      `prohibited_speech`. Branch on it.
                    x-parser-schema-id: <anonymous-schema-29>
                  stage:
                    type: string
                    enum:
                      - session
                    description: Where it was decided. Always `session` on the stream.
                    x-parser-schema-id: <anonymous-schema-30>
                  talent:
                    type: string
                    pattern: ^tal_[0-9a-hjkmnp-tv-z]{26}$
                    description: The talent it concerns, when the session has several.
                    x-parser-schema-id: <anonymous-schema-31>
                  term:
                    type: string
                    description: >-
                      The license term or standing rule it rests on, as an
                      address.
                    x-parser-schema-id: <anonymous-schema-32>
                  sentence:
                    type: string
                    description: >-
                      The reason in the license's own words. Show it to your
                      user as written.
                    x-parser-schema-id: <anonymous-schema-33>
                x-parser-schema-id: Reason
              x-parser-schema-id: <anonymous-schema-28>
          x-parser-schema-id: <anonymous-schema-22>
        title: Sample decided
        description: >-
          The decision on a sample you sent on the stream. A `deny` is followed
          by `session.terminated`.
        example: |-
          {
            "type": "sample.decided",
            "session": "ses_q8v1m3n7p4r6s9t0wxyhk5zba2",
            "ref": "turn-42",
            "sample": "smp_4r6s9t0wxyhk5zba2q8v1m3n7p",
            "decision": "allow",
            "reasons": []
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: sampleDecided
      - &ref_12
        id: error
        contentType: application/json
        payload:
          - name: Error
            description: A message you sent could not be used. The connection stays open.
            type: object
            properties:
              - name: type
                type: string
                description: error
                required: true
              - name: ref
                type: &ref_3
                  - string
                  - 'null'
                description: >-
                  The `ref` of the message at fault, or null when it had none or
                  was not JSON.
                required: true
              - name: code
                type: string
                description: >-
                  An error code from the error catalog. `invalid_request` covers
                  text that is not JSON and an unknown `type`.
                enumValues:
                  - invalid_request
                  - parameter_missing
                  - parameter_invalid
                  - unsupported_media_type
                  - media_unreadable
                  - payload_too_large
                required: true
              - name: detail
                type: string
                description: What was wrong, for a developer. Do not show it to your user.
                required: true
              - name: param
                type: string
                description: The field at fault, as a path such as `window.end_ms`.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - ref
            - code
            - detail
          properties:
            type:
              type: string
              const: error
              x-parser-schema-id: <anonymous-schema-35>
            ref:
              type: *ref_3
              description: >-
                The `ref` of the message at fault, or null when it had none or
                was not JSON.
              x-parser-schema-id: <anonymous-schema-36>
            code:
              type: string
              enum:
                - invalid_request
                - parameter_missing
                - parameter_invalid
                - unsupported_media_type
                - media_unreadable
                - payload_too_large
              description: >-
                An error code from the error catalog. `invalid_request` covers
                text that is not JSON and an unknown `type`.
              x-parser-schema-id: <anonymous-schema-37>
            detail:
              type: string
              description: What was wrong, for a developer. Do not show it to your user.
              x-parser-schema-id: <anonymous-schema-38>
            param:
              type: string
              description: The field at fault, as a path such as `window.end_ms`.
              x-parser-schema-id: <anonymous-schema-39>
          x-parser-schema-id: <anonymous-schema-34>
        title: Error
        description: A message you sent could not be used. The connection stays open.
        example: |-
          {
            "type": "error",
            "ref": "turn-43",
            "code": "parameter_invalid",
            "detail": "`window.end_ms` must be greater than `window.start_ms`.",
            "param": "window.end_ms"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: error
      - &ref_13
        id: pong
        contentType: application/json
        payload:
          - name: Pong
            description: The answer to a `ping`.
            type: object
            properties:
              - name: type
                type: string
                description: pong
                required: true
              - name: id
                type: string
                description: The `id` of the ping it answers.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
          properties:
            type:
              type: string
              const: pong
              x-parser-schema-id: <anonymous-schema-54>
            id:
              type: string
              description: The `id` of the ping it answers.
              x-parser-schema-id: <anonymous-schema-55>
          x-parser-schema-id: <anonymous-schema-53>
        title: Pong
        description: The answer to a `ping`.
        example: |-
          {
            "type": "pong",
            "id": "1"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: pong
    bindings: []
    extensions: &ref_4
      - id: x-parser-unique-object-id
        value: sessionStream
  - &ref_6
    id: sendSample
    title: Submit a sample
    description: >-
      Send a sample of what the session produced, at least as often as
      `sampling` requires.
    type: send
    messages:
      - &ref_14
        id: sampleSubmit
        contentType: application/json
        payload:
          - name: Submit a sample
            description: >-
              A sample of what the session produced, with the same fields as the
              REST call.
            type: object
            properties:
              - name: type
                type: string
                description: sample.submit
                required: true
              - name: ref
                type: string
                description: Your reference, echoed in `sample.decided`.
                required: false
              - name: kind
                type: string
                description: >-
                  `audio` or `frame` carries `data`; `transcript` carries
                  `text`.
                enumValues:
                  - audio
                  - transcript
                  - frame
                required: true
              - name: window
                type: object
                description: >-
                  Where the sample falls in the session, in milliseconds from
                  its start.
                required: false
                properties:
                  - name: start_ms
                    type: integer
                    description: Where the sample starts.
                    required: false
                  - name: end_ms
                    type: integer
                    description: Where the sample ends, after `start_ms`.
                    required: false
              - name: data
                type: string
                description: >-
                  Audio (`audio/wav`, `audio/ogg`) or a frame (`image/jpeg`), up
                  to 2 MB.
                required: false
              - name: media_type
                type: string
                description: 'The type of `data`: `audio/wav`, `audio/ogg` or `image/jpeg`.'
                required: false
              - name: text
                type: string
                description: 'For `transcript`: the words of one turn.'
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - kind
          properties:
            type:
              type: string
              const: sample.submit
              x-parser-schema-id: <anonymous-schema-41>
            ref:
              type: string
              maxLength: 64
              description: Your reference, echoed in `sample.decided`.
              x-parser-schema-id: <anonymous-schema-42>
            kind:
              type: string
              enum:
                - audio
                - transcript
                - frame
              description: '`audio` or `frame` carries `data`; `transcript` carries `text`.'
              x-parser-schema-id: <anonymous-schema-43>
            window:
              type: object
              description: >-
                Where the sample falls in the session, in milliseconds from its
                start.
              properties:
                start_ms:
                  type: integer
                  minimum: 0
                  description: Where the sample starts.
                  x-parser-schema-id: <anonymous-schema-45>
                end_ms:
                  type: integer
                  minimum: 0
                  description: Where the sample ends, after `start_ms`.
                  x-parser-schema-id: <anonymous-schema-46>
              x-parser-schema-id: <anonymous-schema-44>
            data:
              type: string
              contentEncoding: base64
              description: >-
                Audio (`audio/wav`, `audio/ogg`) or a frame (`image/jpeg`), up
                to 2 MB.
              x-parser-schema-id: <anonymous-schema-47>
            media_type:
              type: string
              description: 'The type of `data`: `audio/wav`, `audio/ogg` or `image/jpeg`.'
              x-parser-schema-id: <anonymous-schema-48>
            text:
              type: string
              maxLength: 8000
              description: 'For `transcript`: the words of one turn.'
              x-parser-schema-id: <anonymous-schema-49>
          x-parser-schema-id: <anonymous-schema-40>
        title: Submit a sample
        description: >-
          A sample of what the session produced, with the same fields as the
          REST call.
        example: |-
          {
            "type": "sample.submit",
            "ref": "turn-42",
            "kind": "transcript",
            "window": {
              "start_ms": 81200,
              "end_ms": 84600
            },
            "text": "Welcome back to the Ridge Runners podcast."
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: sampleSubmit
    bindings: []
    extensions: *ref_4
  - &ref_7
    id: sendPing
    title: Keep the connection alive
    description: Send a ping at least every 30 seconds when no other traffic flows.
    type: send
    messages:
      - &ref_15
        id: ping
        contentType: application/json
        payload:
          - name: Ping
            description: >-
              Keeps the connection open. Send one at least every 30 seconds when
              nothing else is sent.
            type: object
            properties:
              - name: type
                type: string
                description: ping
                required: true
              - name: id
                type: string
                description: Echoed in the pong.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
          properties:
            type:
              type: string
              const: ping
              x-parser-schema-id: <anonymous-schema-51>
            id:
              type: string
              maxLength: 64
              description: Echoed in the pong.
              x-parser-schema-id: <anonymous-schema-52>
          x-parser-schema-id: <anonymous-schema-50>
        title: Ping
        description: >-
          Keeps the connection open. Send one at least every 30 seconds when
          nothing else is sent.
        example: |-
          {
            "type": "ping",
            "id": "1"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: ping
    bindings: []
    extensions: *ref_4
sendOperations:
  - *ref_5
receiveOperations:
  - *ref_6
  - *ref_7
sendMessages:
  - *ref_8
  - *ref_9
  - *ref_10
  - *ref_11
  - *ref_12
  - *ref_13
receiveMessages:
  - *ref_14
  - *ref_15
extensions:
  - id: x-parser-unique-object-id
    value: sessionStream
securitySchemes:
  - id: sessionToken
    name: Sec-WebSocket-Protocol
    type: httpApiKey
    description: >-
      `tlp.session.<token>` with the session's current token, offered with
      `tlp.session.v1`. Use this from a client.
    in: header
    extensions: []
  - id: secretKey
    name: secretKey
    type: http
    description: Your secret key. Use this only from a server.
    scheme: bearer
    extensions: []

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.