Cosmo Realtime SDK
Production

End-user credentials

Ship apps whose users never see an API key — mint short-lived, per-user JWTs from your server.

Two roles hold credentials, and they must never swap:

RoleCredentialLives
You (the developer)workspace API key, cosmo_…your server, your laptop — never a shipped binary or a browser bundle
Your end userminted JWT, eyJ…their device, short-lived, scoped to one user

An API key in a shipped app is your whole realtime workspace in every user's hands. The minting flow keeps the key server-side and hands each user a token that can join sessions but nothing else.

The flow

end-user app ──"log me in"──► your server ──POST /auth/token──► Cosmo
     ▲                             │  (API key, user_tokens:mint)
     └───────── JWT ◄──────────────┘

1. Your server mints (holds the API key):

client = CosmoRealtime(api_key=os.environ["COSMO_API_KEY"])
minted = await client.mint_token(external_user_id=user.id)
# → minted.jwt, minted.expires_at
const client = new RealtimeClient({ apiKey: process.env.COSMO_API_KEY });
const { jwt, expiresAt } = await client.mintToken(user.id);

external_user_id is an opaque string of your choosing (1–128 chars) — your user id, not an email. Cosmo uses it to attribute sessions and usage per user; it's the join key between your users and your usage dashboard.

2. The device connects with the JWT:

const client = new RealtimeClient({ token: jwt });
const session = await client.agent({ instructions: '…' }).start();
let session = try await RealtimeSession.start(
    .init(token: jwt, baseURL: URL(string: "https://app.askcosmo.ai")!),
    config: config
)

Provisioning keys

For defense in depth, mint with a provisioning key — an API key carrying only the user_tokens:mint scope. It can create end-user tokens but cannot join sessions itself, so even a leak of your minting endpoint's credential doesn't grant conversational access. Your session-capable key (realtime:use) stays wherever server-side agents actually run. Scopes are managed per key in the dashboard.

Expiry and refresh

Minted tokens are short-lived; expires_at tells you when. The practical pattern:

  • Mint at app launch / login, and re-mint when expires_at approaches — treat it like any OAuth-ish access token.
  • A token only needs to be valid at session start; an in-flight session is not cut off by its token expiring.
  • On a 401/auth error at session start, re-mint and retry once before surfacing an error.

What tokens can and cannot do

API key (realtime:use)Minted JWT
Start sessions
Client tools, hooks, skills
Catalog agents
Dial phone numbers
Transfer-call tools✗ (server-tool references only)
Mint tokenswith user_tokens:mint

The boundary is intentional: anything that spends money on the phone network or touches workspace routing stays behind the key you control.

On this page