Account & API keys over the API
Your TRaX account is now on /v1: read and update your own profile,
manage your developer API keys, and — if it comes to that — delete the account
of the account itself. This is the surface a native app uses to build its
"Account" screen without ever touching a browser.
Which host. These endpoints are on dev only for now:
https://api-dev.traxstreaming.live/v1. This page will say so when they reach production.
Two rules shape everything on this page:
- The profile works with either credential — a user JWT or an
sk_live_API key (the key needs the opt-inaccount:read/account:writescopes). - Everything else is sign-in only. Managing API keys and requesting
deletion require an interactive OIDC login (a user JWT). An API key —
no matter what scopes it holds — is refused with
403 interactive_auth_required. There is no scope that unlocks these routes for a key, and that is the point: a leaked key must never be able to mint itself broader successors, enumerate its owner's other credentials, or erase the account that owns it.
Your profile
GET /v1/account
{
"userId": "312778367937681292",
"username": "ejbev",
"usernameSet": true,
"createdAt": "2026-07-16T18:22:04Z",
"updatedAt": "2026-08-30T11:02:51Z"
}
The profile is your @username identity — the handle other users find you
by. A user who has never claimed one gets usernameSet: false, which is a
normal state, not an error.
Deliberately not here: your display name and email. Those are OIDC claims
on the identity token your app already holds from login — read them from the
IdP, not from /v1. An API key has no claims at all, which is exactly why the
profile carries only what the account service itself records.
Claim or change your username
PATCH /v1/account
{ "username": "@EjBev" }
Mixed case and a leading @ are fine — the handle is canonicalized to
lowercase (ejbev). The rules are the same ones the TRaX account app
enforces: 3–20 characters of a-z 0-9 _, no leading/trailing/double
underscore, and a reserved blocklist. Refusals are typed so your UI can react
precisely:
| Status | error.code |
Meaning |
|---|---|---|
| 422 | invalid_username |
Can never be a handle; the message names the rule that failed (too_short, charset, …). |
| 409 | username_taken |
Held by another user (decided race-safely server-side). |
| 409 | username_reserved |
Well-formed but spoken for (admin, support, …). |
| 429 | username_cooldown |
You changed your handle too recently. Honor the Retry-After header. |
Re-saving the handle you already hold is an idempotent no-op — it succeeds and does not burn the change cooldown.
API keys — the full lifecycle
All four operations require a user JWT (see rule 2 above).
List
GET /v1/account/api-keys
Returns metadata only, newest first, revoked keys included (with
revokedAt set): id, label, the display prefix (sk_live_7Qm2xB), the
scope ceiling, created / last-used / expiry timestamps. The full secret is
never in any list. It cannot be — only a peppered hash of it is stored.
Create — and the one-time secret
POST /v1/account/api-keys
{ "name": "CI uploader", "scopes": ["studios:read", "media:write"] }
The 201 response carries key: the full sk_live_ secret, shown exactly
once, ever. It is not stored, cannot be recovered, and no later call will
repeat it. Hand it to the user with copy-to-clipboard UI, or write it
straight into your secret manager — then drop it.
Omit scopes for the safe read-only default set. An unknown scope refuses
the whole request with a 400 naming the offending value — you never get a
key silently missing a grant. An optional expiresAt (RFC 3339) bounds the
key's lifetime; omitted means it never expires.
Revoke
DELETE /v1/account/api-keys/{keyId}
204. The key stops verifying within seconds (the gateway holds a short
verification cache) and stays listable as history with revokedAt set.
Idempotent; a key id that is not yours is the same 404 as one that does
not exist.
Delete your account
DELETE /v1/account
{ "reason": "optional, in the user's words" }
This executes the deletion — immediately, and it cannot be undone. It is the same legally recorded flow as "Delete my account" in the TRaX account app: a row is opened in the append-only deletion record first, then all account data is erased and the identity deleted last. There is no undo and no grace period — your app must present its own confirmation step before calling.
The response is the user's receipt:
{
"requestId": "8c33d2f1-…",
"alreadyDeleted": false,
"ok": true,
"retained": { "billing": "…", "deletionRecord": "…" },
"confirmationEmailSent": false
}
- Show
requestIdto the user. No confirmation email is sent on this path (the API hop carries no email address), so the id on screen is their only reference. retainedcarries the deletion record's own wording for the few categories kept for legal reasons — render it verbatim rather than paraphrasing a legal statement.- Repeating the call is safe: it returns the original
requestIdwithalreadyDeleted: trueinstead of recording a second erasure. 503 unavailablemeans deletion is not armed on this deployment and nothing was deleted.500 erasure_incompletemeans the account still exists — retry.
There is also a public, no-login form at https://traxstreaming.live/delete for people who cannot sign in; that path confirms by email before erasing anything.
What is not on /v1, and why
Passwords, passkeys, MFA, and sign-in itself are not API endpoints and never will be. They are OIDC ceremonies performed directly against the TRaX identity provider — the flows where the human proves who they are on a surface we (and the browser's WebAuthn machinery) control. Your app gets a user in and out via the standard Authorization Code + PKCE login described in Build a client; once that returns a JWT, everything on this page works. A REST endpoint that accepted a password or enrolled a passkey on the user's behalf would turn every integration into a credential-phishing surface, so no such endpoint exists.
Email and display name changes live in the TRaX account app for the same
reason: they are identity-provider records, verified through the IdP's own
flows (email confirmation among them), not /v1 resources.