The founding constraint: no permanent secret may ship in a browser or mobile binary. A secret key would be extractable by any user. Instead, client apps authenticate with a short-lived client token — an ES256 JWT scoped to a single end user, carrying that user’s limits, valid for at most an hour (15 minutes by default). The SDK’s browser guard enforces this: constructing new Roadie({ apiKey: 'rd_sk_…' }) in a browser-like environment throws immediately.

What a client token carries

A client token is signed by Roadie (ES256) and encodes the tenant and the end user it acts as:
  • sub — the end-user id. Every request the token makes is attributed to this user and enforced against that user’s limits.
  • plan — the end user’s effective entitlement tier, which drives per-plan policy.
  • scopes — which endpoints the token may call: ai.chat, ai.embed, ai.images, feedback. A token requests a subset of these at mint (the default is all four).
  • org / proj / env — the tenant it belongs to.
The token also inherits the restrictions (model and endpoint allowlists, max cost) of the key that minted it. Apps never parse or construct these JWTs by hand — the SDK does.

The SDK surface

A client app never handles JWTs directly. You give the SDK a tokenProvider and it caches and refreshes the token (single-flighting concurrent refreshes, refreshing at ~80% of the token’s TTL):
There are three ways to produce a token. Which one you use depends on whether you have a backend, an existing identity provider, or neither.

Path A — mint from your backend

If you already have any backend, mint tokens there with your secret key and hand them to your app. Your server calls the mint endpoint:
You may also pass scopes (a subset of the four) and an attributes object (≤4 KB) stored on the end user. Your tokenProvider fetches from your route; the SDK does the rest.

Path B — federated identity, no backend (the differentiator)

If your app already authenticates users with Firebase, Supabase, Auth0, Cognito, or any OIDC provider, Roadie can exchange that provider’s ID token for a client token — no customer backend required. You configure the issuer, JWKS URL, audience, and claim mapping per project in the dashboard; the app sends its existing ID token plus your publishable key to the same mint endpoint:
The gateway verifies that token against your IdP’s JWKS (with a strict exact-issuer + audience check and asymmetric-only algorithms), extracts the end-user id and optional plan claim, and mints. Your auth system stays the root of trust; Roadie never sees your user database. The SDK ships a federatedTokenProvider helper for exactly this exchange:
A known-public issuer (Firebase, Auth0, Cognito, …) requires an audience in its federated config. Without one, any token that shared IdP mints for any app would be accepted — the confused-deputy trap — so the dashboard refuses to save such a config. For a full walkthrough, see the zero-backend federated identity tutorial.

Device attestation — no accounts at all

For iOS apps with no user accounts, a device can mint a client token by proving it is a genuine build on real hardware via Apple App Attest — using a publishable key and a three-step flow:
  1. POST /v1/device/challenge — the gateway issues a single-use challenge (valid ~5 minutes).
  2. POST /v1/device/attest — first launch: the app returns its App Attest attestation over the challenge. The gateway verifies it against Apple’s pinned root, stores the device’s public key, and mints a token for a per-device end user (device:<hash>).
  3. POST /v1/device/assert — a returning device proves liveness with an assertion (a signature over a fresh challenge, plus a strictly increasing counter that defeats replay), and mints again.
Device auth is off by default and enabled per project. See the mobile device auth guide and the App Attest tutorial.

The plan is decided at the server, not the client

Whatever plan a client or IdP asserts, the mint uses the effective entitlement Roadie holds for that end user when one exists — the developer-authoritative tier always wins. A client or device can never raise its own tier by asserting a higher plan; the asserted value is only a fallback when no entitlement is on record. This is enforced in the one shared mint path that all three routes flow through. See End users & entitlements.

Why this is safe

  • Tokens are short-lived and scoped to one end user’s limits — the blast radius of a leaked token is exactly that user’s quota.
  • A leaked publishable key alone mints nothing: minting requires a valid federated identity token (Path B) or a genuine device attestation.
  • A client token can never mint another token — the mint routes reject client-token callers.
  • Minting is rate-limited per key and per client IP.
  • Per-end-user and per-IP rate limits apply regardless of token validity.

Cutting off a compromised session

To stop a compromised session immediately, block the end user: the gateway’s fast denylist rejects that user’s requests within a second, across every gateway instance. Because a client token lives at most an hour, access also lapses on its own as the token expires. There is no need to track or revoke individual tokens.
Client tokens are available now. If you are coming from a server-only integration, Path A is three lines; Path B and device attestation let you ship with no backend at all.