guides

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:

  1. The profile works with either credential — a user JWT or an sk_live_ API key (the key needs the opt-in account:read / account:write scopes).
  2. 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 requestId to 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.
  • retained carries 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 requestId with alreadyDeleted: true instead of recording a second erasure.
  • 503 unavailable means deletion is not armed on this deployment and nothing was deleted. 500 erasure_incomplete means 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.