robotrace login

A browser-driven sign-in for the Python SDK. Instead of minting an API key in the portal, copying the plaintext, and pasting it into ROBOTRACE_API_KEY, you run one CLI command. The SDK takes care of the round-trip and saves a credentials file your scripts pick up automatically.

$ robotrace login
 
Welcome to RoboTrace!
RoboTrace - sign in via your browser
https://app.robotrace.dev
 
Verification link (already includes your user code):
  https://app.robotrace.dev/cli/auth?code=XKDF-PQ4N
 
Opening your default browser…
If nothing opens, copy the verification link above.
 
Confirm this matches the page before Authorize
  XKDF-PQ4N
 
| Waiting - authorize this device in the browser…
 
 Signed in as art@robotrace.dev.
  Credentials · ~/.robotrace/credentials
  Profile · default
  Portal · https://app.robotrace.dev/portal

The next time you run a script that uses robotrace, no env vars are needed:

import robotrace as rt
 
ep = rt.start_episode(name="pick_and_place v3")
# [robotrace] → https://app.robotrace.dev/portal/episodes/ep_8f3c
ep.upload_video("./run.mp4")
ep.finalize(status="ready")

That [robotrace] → line is a clickable link in modern terminals (iTerm2, the macOS Terminal, Alacritty, Wezterm, VS Code, JetBrains). Cmd-click takes you straight to the new episode's portal page.

How it works

robotrace login implements a device-code flow modelled on RFC 8628:

  1. The CLI hits POST /api/cli/auth/start and gets back two tokens - a device_code (its bearer secret) and a user_code (the human-readable confirmation string).
  2. The CLI prints the verification URL with the user_code embedded, and tries to open your default browser there. You can pass --no-browser to skip the auto-open if you're SSH'd into a headless box and want to copy the URL by hand.
  3. You sign in on the portal as usual, then land on /cli/auth. The page shows the same user_code plus the device fingerprint the CLI sent (your hostname, IP, user-agent). If it all checks out, you click Authorize device.
  4. The portal mints a fresh API key for your account and stamps the approval onto the device session.
  5. The CLI's next poll picks up the key, writes it to ~/.robotrace/credentials, and exits.

The plaintext key sits in the database for at most a few seconds - the polling endpoint nulls it the instant it hands it back, and the session row's status moves to consumed so the same device_code can never be redeemed twice.

What gets saved

# ~/.robotrace/credentials
# managed by `robotrace login` - do not commit.
 
[default]
api_key    = "rt_…"
base_url   = "https://app.robotrace.dev"
client_id  = "<uuid>"
user_email = "art@robotrace.dev"
written_at = "2026-05-04T20:08:14Z"

The file is written with mode 0600, in a directory chmod'd to 0700. Don't commit it. Add .robotrace/ to your global .gitignore if you keep dotfiles in a repo:

echo ".robotrace/" >> ~/.gitignore_global

Each profile is one logged-in deployment. Most teams only need default; if you talk to staging and production from the same laptop, run:

robotrace login --profile staging --base-url https://staging.robotrace.dev
robotrace login --profile prod    --base-url https://app.robotrace.dev

The Python SDK reads default automatically. To target a different profile in code, pass api_key= and base_url= explicitly - full profile-aware loading is on the roadmap.

Resolution order

When you import robotrace, the SDK resolves credentials in this order:

  1. Explicit kwargs - Client(api_key=..., base_url=...)
  2. Environment variables - ROBOTRACE_API_KEY, ROBOTRACE_BASE_URL
  3. The default profile from ~/.robotrace/credentials
  4. Otherwise: ConfigurationError

This means CI machines that already inject env vars don't need to run robotrace login - and if you change a key in the portal, the env var still wins until you remove it.

whoami

Prints which deployment + email the saved profile is for, without exposing the key:

$ robotrace whoami
Profile:    default
User:       art@robotrace.dev
Client ID:  9f8e7d…-c1a2
Base URL:   https://app.robotrace.dev
Saved at:   2026-05-04T20:08:14Z
File:       /Users/art/.robotrace/credentials

Add --json if you want to pipe it into a CI report.

logout

Removes the saved profile from your machine:

$ robotrace logout
 Removed profile 'default' from ~/.robotrace/credentials.
Note: the API key minted for that login is still valid until you
revoke it from the portal API keys, or run `robotrace logout
--revoke` next time.

Plain logout does not revoke the underlying API key on the server - that's intentional, so you can move credentials between machines without invalidating the key.

logout --revoke (kill the key server-side too)

Pass --revoke to also revoke the saved key on the server before deleting the local file. Use this when decommissioning a robot rig, rotating a leaked key, or recovering from a stolen-laptop incident:

$ robotrace logout --revoke
 Revoked rt_aBcDeF123ghI server-side.
 Removed profile 'default' from ~/.robotrace/credentials.

What happens under the hood:

  1. The CLI reads ~/.robotrace/credentials.
  2. It POSTs to /api/cli/auth/revoke with Authorization: Bearer rt_… - the same key it's about to kill.
  3. The server validates the key, flips revoked_at to now(), and echoes back the safe-to-log key_prefix. From the next request onwards the key returns AuthError (401).
  4. The CLI deletes the local credentials file.

The local file is removed even if the server call fails. The point of logout is "this machine no longer trusts the key," and that's a local guarantee. When the network is down or the server returns 5xx, the CLI exits with status 1 so CI catches it, prints a warning pointing at Portal → API keys for manual cleanup, and still wipes the file:

$ robotrace logout --revoke
 Could not revoke the key server-side: connection refused.
  Local credentials will still be removed. If you suspect the key
  has leaked, revoke it manually from Portal API keys.
 Removed profile 'default' from ~/.robotrace/credentials.
$ echo $?
1

Scoping rule: --revoke only ever kills the key you're authenticated with. It cannot revoke a sibling key on the same account, even one minted from the same machine. That keeps the blast radius of a leaked key consistent - an attacker holding key A can only kill A (which hurts them), never B / C / D. To kill a sibling key from someone else's account, head to Portal → API keys where a real Supabase login gates the action.

Why not just paste an API key?

Because the first thing every developer evaluating the SDK does is type something into a terminal. If that something is "follow this link, click a button, watch the URL print itself," the SDK feels alive. If it's "go to the portal, click around, copy a 47-character secret, paste it into .env.local, hope you didn't typo," the SDK feels like a side project.

Both flows still work - the admin can mint a key the old-fashioned way for CI environments - but robotrace login is the one we'd rather you start with.

Troubleshooting

"Login window expired before the device was authorized" - the flow has a 10-minute TTL. Run robotrace login again.

"Device login was refused" - somebody (probably you, in the other tab) clicked Refuse on the verification page. No state change on the server side; safe to re-run.

The browser didn't open - pass --no-browser and copy the URL by hand. Or set BROWSER= in your environment if you have a preferred browser opener.

No ~/.robotrace/credentials after a successful login - check $ROBOTRACE_HOME. If it's set, the file lives at $ROBOTRACE_HOME/credentials instead. We respect the override so tests and CI runs can isolate from the user's real config.