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/portalThe 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:
- The CLI hits
POST /api/cli/auth/startand gets back two tokens - adevice_code(its bearer secret) and auser_code(the human-readable confirmation string). - The CLI prints the verification URL with the
user_codeembedded, and tries to open your default browser there. You can pass--no-browserto skip the auto-open if you're SSH'd into a headless box and want to copy the URL by hand. - You sign in on the portal as usual, then land on
/cli/auth. The page shows the sameuser_codeplus the device fingerprint the CLI sent (your hostname, IP, user-agent). If it all checks out, you click Authorize device. - The portal mints a fresh API key for your account and stamps the approval onto the device session.
- 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_globalEach 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.devThe 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:
- Explicit kwargs -
Client(api_key=..., base_url=...) - Environment variables -
ROBOTRACE_API_KEY,ROBOTRACE_BASE_URL - The
defaultprofile from~/.robotrace/credentials - 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/credentialsAdd --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:
- The CLI reads
~/.robotrace/credentials. - It POSTs to
/api/cli/auth/revokewithAuthorization: Bearer rt_…- the same key it's about to kill. - The server validates the key, flips
revoked_attonow(), and echoes back the safe-to-logkey_prefix. From the next request onwards the key returnsAuthError(401). - 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 $?
1Scoping 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.