codai docs
Gateway

Embeddings, audio & realtime

Vectors, speech-to-text, text-to-speech, realtime voice over WebSocket and the ephemeral tokens that make browser clients safe.

Beyond chat, the gateway serves four OpenAI-compatible media surfaces. They take the same key, the same rate limits and write the same usage records — but none of them starts a billable task, so they never count against a task cap.

Embeddings

POST /v1/embeddings. Default model codai-embed resolves to voyage-code-3, chosen for code retrieval. Input is a string or a non-empty array of strings; optional dimensions and user. Only encoding_format: "float" is accepted.

curl https://ai.codai.ro/v1/embeddings \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "codai-embed", "input": ["hello world", "vector me up"] }'
{
  "object": "list",
  "model": "voyage-code-3",
  "data": [
    { "object": "embedding", "index": 0, "embedding": [0.0123, -0.0456, …] },
    { "object": "embedding", "index": 1, "embedding": [0.0789, 0.0012, …] }
  ],
  "usage": { "prompt_tokens": 7, "total_tokens": 7 }
}

If the requested embedding model is unavailable the gateway may serve a compatible alternative and discloses it in x-codai-embed-fallback: <requested>-><served>. Check the header when vector spaces must stay identical across writes and reads.

Speech-to-text

POST /v1/audio/transcriptions, multipart form, 25 MB maximum. Fields: file (required), model (default codai-transcribe), and the optional language, prompt, response_format, temperature — forwarded as-is.

modelBackend
codai-transcribe, whisper-1, whisperWhisper
gpt-4o-mini-transcribegpt-4o-mini-transcribe
curl https://ai.codai.ro/v1/audio/transcriptions \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -F [email protected] \
  -F model=codai-transcribe \
  -F language=ro
{ "text": "Let's start with the deploy checklist." }

Billing is flat per audio minute, estimated from the upload size (about 1 MB per minute of compressed voice, minimum one minute) and recorded as cost_micro_usd on the usage row.

Text-to-speech

POST /v1/audio/speech. JSON body: input (required, ≤ 4 096 characters), model (default codai-tts), voice, response_format (default mp3), speed, and — for the expressive model only — instructions (≤ 1 000 characters). The response body is the audio bytes.

modelBackendDefault voiceNotes
codai-tts, tts-1, ttsStandard TTSalloyCheap, neutral.
codai-tts-expressive, gpt-4o-mini-ttsgpt-4o-mini-ttsmarinTakes instructions such as "calm, warm, short Romanian, slightly amused". Voices: alloy, ash, ballad, coral, echo, fable, onyx, nova, sage, shimmer, verse, marin, cedar.
curl https://ai.codai.ro/v1/audio/speech \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "codai-tts", "input": "Hello from codai.", "voice": "alloy" }' \
  --output hello.mp3

Billing is flat per 1 000 input characters.

Ephemeral tokens

Browsers and webviews must never hold your long-lived key. Instead, mint a short-lived scoped token server-side with POST /v1/tokens and hand that to the client. Scopes: realtime, audio, embeddings — chat is deliberately not mintable. TTL 60–3 600 s, default 600.

curl https://ai.codai.ro/v1/tokens \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "realtime", "ttl_seconds": 600 }'
{ "token": "codai_eph_v1.…", "expires_at": "2026-09-23T12:10:00.000Z", "scope": "realtime" }

The token is used exactly like an API key — Authorization: Bearer codai_eph_v1.… — but only on its scope's surface. It cannot mint further tokens (403 forbidden). Revoking the minting key or downgrading its tier invalidates outstanding tokens within the principal-cache window.

Realtime voice

WSS /v1/realtime?model=<alias> — bidirectional audio over a single WebSocket, relayed verbatim to the upstream protocol.

modelProtocolBackend
codai-realtime (default for OpenAI-style clients)OpenAI Realtimegpt-realtime-2.1
codai-transcribe-liveOpenAI Realtime, intent=transcriptionStreaming STT — partials and turn events, no model reply
codai-voiceGemini Live BidiGenerateContentgemini-live-2.5-flash-native-audio

If model is omitted, the gateway assumes codai-voice.

Authenticating the upgrade

Raw codai_… keys are rejected in the query string (close code 4401). They would leak into request logs, proxies and browser history. Browsers must use an ephemeral realtime token.

  • Browser: wss://ai.codai.ro/v1/realtime?model=codai-realtime&key=codai_eph_v1.…
  • Server: send Authorization: Bearer <key or token> on the upgrade request — ?key= is not needed.
browser.ts
// token fetched from your own backend, which called POST /v1/tokens
const ws = new WebSocket(
  `wss://ai.codai.ro/v1/realtime?model=codai-realtime&key=${encodeURIComponent(token)}`,
);
ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'session.update', session: { instructions: 'Answer briefly.' } }));
};
ws.onmessage = (ev) => {
  const event = JSON.parse(ev.data);
  if (event.type === 'response.audio.delta') {
    /* append base64 PCM to your player */
  }
};

Close codes you may see: 4401 invalid or disallowed credential, 4503 the requested lane is not configured on this gateway, 1011 internal error. Usage is recorded when the session closes, with its duration.

On this page