API keys

API keys authenticate the Python SDK (and any direct HTTP client) to the Ingest API. They're scoped per client — every key belongs to exactly one client account and can only log episodes against that client.

Format

rt_<id>_<secret>

For example: rt_8a4f01c2b3_kPcDxQy7mN3eHvL2RtFwBgZsXn5J9aKyM4uV6oTr

  • rt_ — fixed prefix so keys are obvious in logs and grep.
  • <id> — 10-character public identifier. Indexed in the database, so the server can look up the key row in O(1) without scanning every hash.
  • <secret> — 43-character random secret. Only this part is sensitive.

The full key is shown once at creation. We store only its SHA-256 hash + the <id> prefix; if you lose the key, we can't recover it — you mint a fresh one and revoke the old.

Verification is constant-time (hmac.compare_digest on the server) so a leaked prefix doesn't help anyone enumerate secrets.

Mint a new key

In the admin UI:

  1. Go to Admin → Clients → <client>.

  2. Scroll to API access.

  3. Click New API key, give it a label that reminds you what it's for (prod robot rig, casey laptop, ci).

  4. Copy the full key from the reveal screen. It will not be shown again.

  5. Drop it into the SDK config:

    export ROBOTRACE_API_KEY=rt_…

The key is live immediately — no propagation delay.

Rotate a key

There's no "live key rollover" because we don't need overlap — keys are validated per-request. Rotation is two steps:

  1. Mint a new key (as above) and roll it out to wherever the old one is configured (CI, robot rig, training scripts).
  2. Once you've confirmed the new key is in use, revoke the old one from the same panel. Revocation is instant — the next request using the revoked key returns 401.

Aim to rotate at least quarterly. Rotate immediately if a key may have been exposed (committed to a repo, posted in a screenshot, etc.).

Revoke a key

In the same API access panel, click Revoke next to the key row. The key row stays in the table (greyed out, with a "revoked" badge and the revocation timestamp) so you can audit "when was that old key turned off?" later.

Revocation is soft: the row sticks around with revoked_at set, the hash is still on disk. We never re-issue the same key value.

Header conventions

Two equivalent ways to send the key. The SDK uses the first:

Authorization: Bearer rt_8a4f01c2b3_kPcD…
X-RoboTrace-Key: rt_8a4f01c2b3_kPcD…

Use X-RoboTrace-Key only if your HTTP client mangles Authorization headers (some legacy proxies do). Both go through the same validator and the same audit trail.

What gets recorded

Every request updates client_api_keys.last_used_at (best-effort, async — a transient DB hiccup won't fail the ingest call).

The admin API access panel shows for each key:

  • Label
  • Public prefix (the <id> part, never the secret)
  • Created at + by which staff member
  • Last used at
  • Revoked at, if applicable

The full plaintext key is never displayed again after the reveal screen. Not even to super-admins. By design.

Security properties

A few things worth knowing if you're security-reviewing the system:

  • No key sharing across clients. Keys are 1:1 with a client row. Cross-tenant ingest is rejected at the application layer (the finalize endpoint checks the episode belongs to the calling client).
  • No scope yet. All keys for a given client can do everything (start episode, finalize episode, list episodes via future read-API). Per-key scoping (episodes:write, episodes:read, etc.) lands in a follow-up minor.
  • No rate limit yet in Phase 1 (pilot). Coming in 0.2 — likely a default of 60 req/min/key, configurable per client.
  • Server-side hashing uses SHA-256 (not bcrypt) because keys are 43-character high-entropy random strings — there's nothing for a slow KDF to protect against. We never need to hash low-entropy user-chosen passwords here.
  • Key material never logs. The SDK redacts the Authorization header in error messages, and AGENTS.md forbids console.log-ing the request body server-side.

Don'ts

  • Don't check API keys into source control. Use environment variables or your CI's secret manager.
  • Don't share one key across a fleet of robots. Mint one per rig so revocation isolates blast radius if a single robot is compromised.
  • Don't put the key in the request body. The server only reads it from the headers; embedding it elsewhere just leaks it into unrelated logs.
  • Don't treat the <id> prefix as secret. It's already in our database indexes and our internal logs — it's the secret half that matters.