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

# Client API

> OpenAI-compatible endpoints available to application keys.

Base URL: `https://<gateway-origin>`

Authentication: `Authorization: Bearer <application-key>`

Every authenticated response includes `X-Request-ID` and `X-Gateway-Instance`.

## Discovery

| Method | Path               | Description                                                                                |
| ------ | ------------------ | ------------------------------------------------------------------------------------------ |
| `GET`  | `/v1/gateway/info` | Canonical URLs, key-visible aliases, and endpoint paths                                    |
| `GET`  | `/v1/models`       | OpenAI-compatible model list filtered to the key                                           |
| `GET`  | `/v1/model/info`   | Capabilities, metadata, safe routing shape, reviewed price, and client-safe audio contract |

For an audio-capable model, `audio` contains the formats and public voice aliases common to every reachable route target, plus any common default voice, realtime protocol, and bounded input limits. Provider-native `voice_ids` and `output_format_ids` are deliberately omitted. `pricing.meters` identifies non-token units such as `input_audio_second` and `input_character`.

## Inference

| Method                    | Canonical path                  | Compatibility path  |
| ------------------------- | ------------------------------- | ------------------- |
| `POST`                    | `/v1/chat/completions`          | `/chat/completions` |
| `POST`                    | `/v1/responses`                 | `/responses`        |
| `POST`                    | `/v1/embeddings`                | `/embeddings`       |
| `POST`                    | `/v1/audio/transcriptions`      | None                |
| `POST`                    | `/v1/audio/translations`        | None                |
| `POST`                    | `/v1/audio/speech`              | None                |
| `POST`                    | `/v1/realtime/browser-sessions` | None                |
| `POST`                    | `/v1/realtime/client_secrets`   | None                |
| `POST`                    | `/v1/realtime/calls`            | None                |
| `GET` + WebSocket upgrade | `/v1/realtime?model=<alias>`    | None                |

### Chat completions

Required fields: `model`, `messages`.

Supported fields include `temperature`, `top_p`, `max_tokens`, `max_completion_tokens`, `stream`, `stream_options`, `stop`, `n`, `tools`, `tool_choice`, `response_format`, `seed`, `logprobs`, `top_logprobs`, `presence_penalty`, `frequency_penalty`, `logit_bias`, `user`, `parallel_tool_calls`, `reasoning_effort`, `metadata`, `modalities`, `service_tier`, and `store`.

Provider-side storage remains disabled: `store: false` is accepted and `store: true` is rejected. Only text output is currently supported, so `modalities` may be omitted or set to `["text"]`.

For streaming requests, the gateway obtains usage from the provider for internal accounting. The separate terminal usage chunk is returned to the client only when the request sets `stream_options.include_usage` to `true`, matching the OpenAI stream contract.

```bash theme={null}
curl -sS "$GATEWAY_URL/v1/chat/completions" \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "fast",
    "messages": [{"role": "user", "content": "Reply with gateway-ok"}],
    "max_completion_tokens": 32
  }'
```

### Responses

Required fields: `model`, `input`.

Supported fields include `instructions`, `temperature`, `top_p`, `max_output_tokens`, `stream`, `stream_options`, `tools`, `tool_choice`, `text`, `reasoning`, `previous_response_id`, `metadata`, `user`, `store`, and `service_tier`.

Use `text.format` for structured output. `response_format` is a Chat Completions field and is rejected on this endpoint. Provider-side storage remains disabled: `store: false` is accepted and `store: true` is rejected.

### Embeddings

Required fields: `model`, `input`.

Optional fields are `encoding_format`, `dimensions`, and `user`. The selected alias must have embedding capability.

### Audio transcriptions

Send `multipart/form-data` with required `model` and `file` parts. Optional fields are `language`, `prompt`, `response_format`, `temperature`, and repeated `timestamp_granularities[]`. The gateway accepts `json` (default) and `verbose_json`; timestamps require `verbose_json`. A deployment's model catalog controls allowed extensions, MIME types, file bytes, duration reservation bounds, and reviewed per-second pricing.

```bash theme={null}
curl -sS "$GATEWAY_URL/v1/audio/transcriptions" \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -F model=transcribe \
  -F language=en \
  -F file=@sample.mp3
```

Uploads are bounded and spooled for the provider operation. Raw audio is never written to structured logs, request payload storage, or request metadata, even when text payload capture is enabled. Request logs retain attribution, route/model/provider, observed audio seconds, cost, latency, and status.

Provider adapters may narrow the route contract advertised by the catalog. Azure Speech aliases accept 16 kHz, 16-bit, mono PCM WAV files up to the configured limit of at most 60 seconds. They support `language`, `json`, and `verbose_json`; when omitted, language defaults to `en-US`, and the OpenAI English code `en` maps to that locale. Other Azure languages require a supported full BCP-47 locale. Prompt, temperature, and timestamp options are rejected for Azure routes before provider spend.

ElevenLabs Scribe v2 aliases support `language`, `temperature`, `json`, `verbose_json`, and word timestamps. They reject `prompt` and segment timestamps before provider spend. The gateway normalizes ElevenLabs results to the OpenAI-shaped response and uses word timing only for client-visible verbose output and duration accounting.

### Audio translations

Send `multipart/form-data` with required `model` and `file` parts. Optional fields are `prompt`, `response_format` (`json` or `verbose_json`), and `temperature`. The selected alias must expose `audio_translation`; the response contains English text.

```bash theme={null}
curl -sS "$GATEWAY_URL/v1/audio/translations" \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -F model=translate \
  -F file=@sample.mp3
```

Translation uses the same upload, MIME, duration, concurrency, accounting, and raw-audio retention boundaries as transcription. The OpenAI Python SDK works with `client.audio.translations.create(...)`.

### Speech synthesis

Send JSON with `model` and non-empty `input`. Optional fields are a configured public `voice` alias, `response_format` (`mp3` by default), and `speed` from `0.25` through `4.0`. A selected provider may enforce a narrower range; Azure Speech accepts `0.5` through `2.0`. The response is chunked raw audio, not JSON or SSE.

```bash theme={null}
curl -sS "$GATEWAY_URL/v1/audio/speech" \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"model":"tts","input":"Gateway speech is ready.","voice":"default","response_format":"mp3"}' \
  --output speech.mp3
```

The OpenAI Python SDK works with `client.audio.speech.with_streaming_response.create(...)`. The gateway validates every alias before provider spend and streams without buffering the complete generated file. Request text follows the configured text payload policy, but generated audio is never written to payload logs or durable payload storage. Request metadata retains only character usage, cost, latency, status, and selected route.

### Realtime WebSocket

`GET /v1/realtime?model=<alias>` is the server-to-server WebSocket surface for aliases with `realtime: true`. Each alias advertises one event protocol through `audio.realtime_protocol`; clients must use that native protocol. The gateway authenticates and authorizes before opening the provider connection, so provider credentials never reach the client.

For `openai-realtime-v1`, clients send native events such as `session.update` and receive OpenAI GA Realtime events unchanged:

```python theme={null}
import asyncio
import json
import os

from websockets.asyncio.client import connect


async def main():
    origin = os.environ["GATEWAY_URL"]
    socket_origin = origin.replace("https://", "wss://").replace("http://", "ws://")
    url = f"{socket_origin}/v1/realtime?model=realtime"
    async with connect(
        url,
        additional_headers={
            "Authorization": f"Bearer {os.environ['GATEWAY_API_KEY']}",
            "OpenAI-Safety-Identifier": "end-user-42",
        },
    ) as ws:
        print(json.loads(await ws.recv()))
        await ws.send(json.dumps({
            "type": "session.update",
            "session": {"type": "realtime", "output_modalities": ["audio"]},
        }))


asyncio.run(main())
```

#### Browser WebSocket sessions

Native browser WebSockets cannot set `Authorization`, and a gateway bearer key must never be embedded in browser code. When `audio_policy.browser_realtime_sessions_enabled` is enabled, a trusted application backend can exchange its scoped gateway key for a short-lived, single-use browser credential:

Enabled gateways advertise the mint path as `endpoints.realtime_browser_sessions` in `GET /v1/gateway/info`; disabled gateways omit it.

```javascript theme={null}
// Trusted application server. Authenticate your application user before this handler.
const response = await fetch(`${process.env.GATEWAY_URL}/v1/realtime/browser-sessions`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GATEWAY_API_KEY}`,
    "Content-Type": "application/json",
    "OpenAI-Safety-Identifier": hashedApplicationUserId,
  },
  body: JSON.stringify({ model: "realtime" }),
});

const session = await response.json();
// Return this no-store response to the authenticated browser. Do not log or persist it.
```

The application key must allow both `/v1/realtime/browser-sessions` and `/v1/realtime`, plus the selected alias. The browser obtains the response through the application's authenticated same-origin endpoint and opens the gateway URL with the returned protocols:

```javascript theme={null}
const session = await fetch("/api/realtime-session", {
  method: "POST",
  credentials: "same-origin",
}).then((response) => response.json());

const socket = new WebSocket(
  session.websocket.url,
  session.websocket.protocols,
);

socket.addEventListener("open", () => {
  socket.send(JSON.stringify({
    type: "session.update",
    session: { type: "realtime", output_modalities: ["audio"] },
  }));
});
```

For profiles with public query controls, the trusted backend supplies an `options` object when minting. For example, realtime TTS can use `{"model":"tts-realtime","options":{"voice":"default","output_format":"pcm"}}`. The returned credential is bound to that exact model and option set, expires after 10-300 seconds, and can initiate only one socket across all gateway replicas. A reconnect needs a newly minted credential. Do not place the secret in a URL, logs, analytics, or local storage.

The established browser socket still terminates at the gateway, so parent-key revocation, shared admission, renewable budgets, retention rules, and shutdown closes remain active for the whole session. This is the correct transport for continuously governed traffic.

#### Provider-direct WebRTC

When `provider_direct_webrtc_enabled` is advertised, a trusted application backend may mint a short-lived bootstrap credential. The application key must allow both bootstrap endpoints and the selected alias. It must not have a gateway budget, and deployment payload persistence must be disabled.

```javascript theme={null}
// Trusted application backend. Never put GATEWAY_API_KEY in browser code.
const tokenResponse = await fetch(`${process.env.GATEWAY_URL}/v1/realtime/client_secrets`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GATEWAY_API_KEY}`,
    "Content-Type": "application/json",
    "OpenAI-Safety-Identifier": hashedApplicationUserId,
  },
  body: JSON.stringify({
    expires_after: { anchor: "created_at", seconds: 60 },
    session: {
      type: "realtime",
      model: "realtime",
      audio: { output: { voice: "marin" } },
    },
  }),
});

const bootstrap = await tokenResponse.json();
// Return bootstrap through an authenticated, Cache-Control: no-store response.
```

The browser creates the peer connection and sends the offer to gateway `/v1/realtime/calls`. Its exact HTTPS origin must be in `provider_direct_webrtc_allowed_origins`.

```javascript theme={null}
const bootstrap = await fetch("/api/realtime-webrtc", {
  method: "POST",
  credentials: "same-origin",
}).then((response) => response.json());

const pc = new RTCPeerConnection();
const audio = document.createElement("audio");
audio.autoplay = true;
pc.ontrack = (event) => { audio.srcObject = event.streams[0]; };

const microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
pc.addTrack(microphone.getAudioTracks()[0]);
const events = pc.createDataChannel("oai-events");

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const answer = await fetch(`${GATEWAY_URL}/v1/realtime/calls`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${bootstrap.value}`,
    "Content-Type": "application/sdp",
  },
  body: offer.sdp,
});
await pc.setRemoteDescription({ type: "answer", sdp: await answer.text() });

events.addEventListener("message", (event) => console.log(JSON.parse(event.data)));
```

The `gwrtc_` credential is opaque, expires in at most 300 seconds, and can relay one SDP offer. After the SDP answer, audio and data channels flow directly to the OpenAI-compatible provider. The gateway cannot revoke the live peer, enforce concurrent-session or renewable-budget leases, capture frames, or calculate session cost. Use the gateway WebSocket when any of those controls are required. OpenRouter's documented audio path remains bounded HTTP/SSE rather than this Realtime WebRTC contract; LiteLLM uses the same provider-direct media boundary.

For `elevenlabs-realtime-stt-v1`, the query may select a configured `audio_format`, `language_code`, `include_timestamps`, `include_language_detection`, `commit_strategy` (`manual` or `vad`), and `no_verbatim`. The socket preserves ElevenLabs Scribe events; send base64 audio in `input_audio_chunk` messages:

```python theme={null}
import asyncio
import base64
import json
import os
from pathlib import Path

from websockets.asyncio.client import connect


async def transcribe(pcm_bytes: bytes):
    origin = os.environ["GATEWAY_URL"]
    socket_origin = origin.replace("https://", "wss://").replace("http://", "ws://")
    url = (
        f"{socket_origin}/v1/realtime?model=stt-realtime"
        "&audio_format=pcm_16000&language_code=en&include_timestamps=true"
    )
    async with connect(
        url,
        additional_headers={"Authorization": f"Bearer {os.environ['GATEWAY_API_KEY']}"},
    ) as ws:
        print(json.loads(await ws.recv()))  # session_started
        await ws.send(json.dumps({
            "message_type": "input_audio_chunk",
            "audio_base_64": base64.b64encode(pcm_bytes).decode("ascii"),
            "sample_rate": 16000,
            "commit": True,
        }))
        while True:
            event = json.loads(await ws.recv())
            if event["message_type"].startswith("committed_transcript"):
                return event["text"]


print(asyncio.run(transcribe(Path("sample.pcm").read_bytes())))
```

Only formats enabled on the alias are accepted. Provider tokens, provider logging controls, keyterms, and VAD thresholds cannot be supplied by clients.

For `elevenlabs-realtime-tts-v1`, select a public output format and optionally a public voice alias. The gateway resolves provider IDs and initializes the upstream connection privately. Send text chunks using the narrow ElevenLabs-compatible event shape; returned JSON includes base64 audio chunks and a final event:

```python theme={null}
import asyncio
import base64
import json
import os

from websockets.asyncio.client import connect


async def synthesize() -> bytes:
    origin = os.environ["GATEWAY_URL"]
    socket_origin = origin.replace("https://", "wss://").replace("http://", "ws://")
    url = (
        f"{socket_origin}/v1/realtime?model=tts-realtime"
        "&voice=default&output_format=pcm"
    )
    chunks = []
    async with connect(
        url,
        additional_headers={"Authorization": f"Bearer {os.environ['GATEWAY_API_KEY']}"},
    ) as ws:
        await ws.send(json.dumps({"text": "Hello from the gateway. ", "flush": True}))
        await ws.send(json.dumps({"text": ""}))
        while True:
            event = json.loads(await ws.recv())
            if event.get("audio"):
                chunks.append(base64.b64decode(event["audio"]))
            if event.get("isFinal") is True:
                return b"".join(chunks)


open("speech.pcm", "wb").write(asyncio.run(synthesize()))
```

Client events may contain only `text` and optional boolean `flush` or `try_trigger_generation`; provider credentials, provider voice IDs, logging controls, and voice settings are deployment-owned. The configured cumulative text limit applies to the session. Usage and budgets settle from accepted characters, while audio and text frames remain excluded from payload capture.

For `elevenlabs-agent-v1`, the alias privately selects an ElevenLabs agent. The gateway obtains and validates a signed URL with its deployment credential, opens the provider socket, and sends `conversation_initiation_client_data`; clients cannot supply agent IDs, signed tokens, environment selection, prompt/LLM/voice overrides, or initiation data. Send native bounded audio, message, ping/pong, context, and client-tool-result events and receive ElevenLabs transcript, agent-response, audio, tool-call, and interruption events unchanged:

```python theme={null}
import asyncio
import json
import os

from websockets.asyncio.client import connect


async def talk_to_agent():
    origin = os.environ["GATEWAY_URL"]
    socket_origin = origin.replace("https://", "wss://").replace("http://", "ws://")
    url = f"{socket_origin}/v1/realtime?model=voice-agent"
    async with connect(
        url,
        additional_headers={"Authorization": f"Bearer {os.environ['GATEWAY_API_KEY']}"},
    ) as ws:
        while True:
            event = json.loads(await ws.recv())
            if event["type"] == "ping":
                await ws.send(json.dumps({
                    "type": "pong",
                    "event_id": event["ping_event"]["event_id"],
                }))
            elif event["type"] == "conversation_initiation_metadata":
                await ws.send(json.dumps({
                    "type": "user_message",
                    "text": "What can you help me with?",
                }))
            elif event["type"] == "agent_response":
                return event["agent_response_event"]["agent_response"]


print(asyncio.run(talk_to_agent()))
```

Accepted client shapes are a single `user_audio_chunk`, `pong`, `user_message`, `user_activity`, `contextual_update`, or `client_tool_result`. Multimodal provider file IDs are rejected because they are not tenant-scoped by the gateway. Frames and transcript/response text remain excluded from payload capture; session duration is the current conservative usage and budget meter.

Bearer authentication remains the server-to-server path. Browser applications use the separately scoped mint endpoint described above; they never receive the parent gateway key. The optional OpenAI safety identifier is tenant-scoped and SHA-256 hashed before forwarding only on the OpenAI profile. Raw realtime frames and audio are never sent to payload logging or storage. Metadata records route, provider, connected seconds or accepted TTS characters, cost, status, and aggregate frame/byte counts.

The gateway closes idle, oversized, over-duration, provider-failed, and shutdown sessions with WebSocket close codes. A normal close (`1000`) is recorded as a successful `101` session; abnormal client disconnects are recorded as `499`, and provider failures keep their mapped error status. Reconnection always creates a new upstream session; established realtime sessions never fail over invisibly.

## Reserved fields

Clients cannot override gateway/provider configuration through request fields such as `api_key`, `api_base`, `base_url`, `litellm_params`, `extra_headers`, or `custom_llm_provider`. Unknown or model-incompatible parameters are rejected before provider spend.

## Liveness

`GET /health` is unauthenticated process liveness. It does not prove providers, Postgres, or Redis are ready.
