VOICE / mint-ephemeral-token-for-voice

Voice

Mint an ephemeral token for Voice

Mint an ephemeral token for Voice

Mint a short-lived token on your server, pass it to the client, then open wss://api.x.ai/v1/realtime?model=grok-voice-latest. The long-lived API key stays on the server.

POST https://api.x.ai/v1/realtime/client_secrets with a Bearer API key. Example body: { "expires_after": { "seconds": 300 } }. The endpoint does not accept session or expires_after.anchor.

Mint on the server

FastAPI:

import os
import httpx
from fastapi import FastAPI

app = FastAPI()

@app.post("/session")
async def get_ephemeral_token():
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://api.x.ai/v1/realtime/client_secrets",
            headers={
                "Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
                "Content-Type": "application/json",
            },
            json={"expires_after": {"seconds": 300}},
        )
    return response.json()

Express:

app.post("/session", async (req, res) => {
  const r = await fetch("https://api.x.ai/v1/realtime/client_secrets", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.XAI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ expires_after: { seconds: 300 } }),
  });
  res.json(await r.json());
});

Hand the token from that JSON to the client.

Open the socket

Node and other servers can send the ephemeral token as Authorization: Bearer. Browsers cannot set that header on WebSocket. Open with protocol xai-client-secret.<token>:

const url = "wss://api.x.ai/v1/realtime?model=grok-voice-latest";
const ws = new WebSocket(url, [`xai-client-secret.${token}`]);

Pitfalls

  • Never put XAI_API_KEY in client code. Mint on the server; send only the ephemeral token.
  • Do not send session or expires_after.anchor on the mint request.
  • Console API credits are a separate bill from SuperGrok's weekly pool on grok.com.