Mint a short-lived, scoped session token for a mobile client
Exchanges your long-lived partner key for a short-lived, scoped
Unkey key that a mobile client can call /v1/query and /v1/chat
with directly — see “Mobile-direct access” in the introduction for
the full pattern and a runnable example.
Requires your long-lived partner key. A session key can never
call this endpoint — it always gets 403 forbidden, whether that’s
a mistaken integration (the mobile client was accidentally handed
the mint URL) or an attacker trying to extend a leaked session
key’s life. There is no session-key chaining.
The minted key:
- Can call only
/v1/queryand/v1/chat— every other endpoint, including this one, returns403 forbidden. - Expires automatically between 60 seconds and 60 minutes after
minting (
ttl_seconds, default 900 = 15 minutes). Unkey deletes expired keys on its own; there is nothing to clean up. An expired key returns401 unauthorizedwith an “API key expired” message — your mobile client should treat that as “ask my backend for a fresh token”, not as an error to alert on. - Shares your long-lived key’s rate-limit budget and telemetry — minting session keys does not create additional rate-limit headroom. A budget change to your long-lived key reaches outstanding session keys only after they expire (at most 60 minutes later), since each key’s limits are captured at mint time, not read through live.
- Stops working after
max_requeststotal requests (default 200; session keys are never uncapped), independent of the rate-limit window. An exhausted key returns403 forbiddenwith a “mint a new session token” message — permanently, so do not retry; mint a new token. - Stops working everywhere, within one deploy, if the session-token feature is disabled — the feature flag is a kill switch for outstanding keys, not just for new mints.
Minting itself is a normal partner API call: authenticated and
rate-limited exactly like /v1/query and /v1/chat, and it
charges your long-lived key’s own requests-per-minute (and, if
configured, requests-per-day) budget — one mint call is one
metered request.
An empty request body is valid and mints a 15-minute key with the default 200-request allowance; both fields are optional.
Authorizations
Bearer token used by every /v1/* endpoint. Two kinds of key work here:
- Long-lived partner key (
cxh_pk_live_*) — issued by CollectiveX Health (contact partners@collectivex.health). Keep it server-side only — never expose it in client bundles or mobile app code. This rule is absolute and does not change for any endpoint. - Short-lived session key — minted server-side via
POST /v1/session-tokensfrom a long-lived key. Safe to hand to a native mobile client only — not a browser or other JavaScript context, see "API keys" in the description above. It's scoped to/v1/queryand/v1/chatonly, expires in at most 60 minutes, and can't mint further session keys.POST /v1/session-tokensitself only ever accepts the long-lived key.
Body
Request body for POST /v1/session-tokens. Uses additionalProperties: false (mirrors Zod .strict() on the server). Both fields are optional — an empty body (or no body at all) mints a 15-minute key capped at 200 requests.
How long the minted key stays valid, in seconds. Must be
60–3600 (1 minute to 1 hour) — values outside the range are
REJECTED with 400 invalid_request, not clamped. A
"short-lived" credential that can outlive an hour isn't one.
Defaults to 900 (15 minutes).
60 <= x <= 3600Total request allowance for the minted key, independent of the
rate-limit window (maps to the Unkey key's remaining-uses
credits). Defaults to 200 — session keys are never uncapped.
Once exhausted, the key returns 403 forbidden even if it
hasn't expired yet; mint a new token. Note the allowance
counts every authenticated request made with the key,
INCLUDING ones rejected with 4xx (e.g. 429s from a retry
loop). A mobile session that needs more than 10,000 calls in
under an hour is a scraping pattern, not a chat session —
contact partners@collectivex.health if your use case genuinely
needs a higher cap.
1 <= x <= 10000Response
Created. The plaintext session key is returned exactly once —
it is never retrievable again, and CollectiveX Health never
logs or persists it server-side. Store it only for as long as
it takes to hand it to the client. The response carries
Cache-Control: no-store (RFC 6749 §5.1 discipline for a
bearer-secret response); anything you put in front of your
mint call must honour it.
UUID for this request. Include when contacting support.
The plaintext short-lived key, shown exactly once. It is never
retrievable again, and CollectiveX Health never logs or stores
it server-side — if you lose it, mint a new one. Treat it as
an opaque bearer token: its exact format is not part of the
API contract and may differ from the long-lived key's
cxh_pk_live_* format.
The minted key's Unkey key id (not a secret — safe to log).
Distinct from key; use this, not the plaintext key, for
support correlation.
ISO 8601 timestamp when the key stops working. Calls made with
it after this time return 401 unauthorized.
Seconds until expires_at, computed at response time from the
same instant expires_at describes (so the two fields never
disagree). Equal to the applied ttl_seconds give or take the
mint round-trip. expires_at is authoritative and is enforced
against Unkey's server clock.
Fixed allowlist of endpoints this key may call — always
["query", "chat"] today. Server-assigned; the mint request
cannot choose or widen it.
query, chat The request allowance actually applied — the request value, or the 200-request default. Always present; session keys are never uncapped.