Tokens & API keys
Rubber Duck has two kinds of credentials. A personal CLI token lets the duck CLI submit solutions as you; an agent API key lets an agent (or a human with a terminal) publish and manage courses through the REST API. Both are random secrets shown exactly once โ the server stores only a SHA-256 hash, so a lost token can't be recovered, only replaced.
Which one do I need?
| Task | Credential |
|---|---|
| browse courses, duck pull, duck test | None โ courses and their challenges are public. |
| duck submit | User CLI token (gc_u_โฆ), tied to your account so the points land on your profile. |
| publish / update / delete courses (/api/v1) | Agent API key (gc_โฆ), minted by an operator. |
User CLI tokens (gc_u_โฆ)
duck submit authenticates every request with a bearer token. The easiest setup is duck login, which prompts for your username and password, mints a token, and saves it where the CLI looks for it:
duck loginOr mint one by hand: open your profile page, use "Create CLI token" (name it after the machine it lives on), copy the token immediately, and put it in one of the two places the CLI checks โ the DUCK_TOKEN environment variable wins over the file when both are set:
export DUCK_TOKEN=gc_u_โฆ # this shell only
# or persist it for every shell:
mkdir -p ~/.config/duck
echo gc_u_โฆ > ~/.config/duck/token
chmod 600 ~/.config/duck/tokenTokens never expire on their own. Revoke one anytime from the same profile section (lost laptop, leaked shell history) โ revocation is immediate, and the CLI will start getting 401 unauthorized. Mint one token per machine so you can revoke them individually.
Agent API keys (gc_โฆ)
Course publishing โ the PUT/DELETE endpoints under /api/v1 โ requires an agent API key. There's deliberately no web UI for minting these: an operator with database access creates them with the server binary, and the raw key is printed exactly once.
# from a repo checkout โ local dev (compose postgres running):
make apikey KEY_NAME=writer-1
# production (starts cloud-sql-proxy and reads tofu outputs for you):
make apikey-prod KEY_NAME=writer-1
# or the underlying command against any database:
go run ./cmd/duckserver apikey create --name writer-1 --db "<postgres url>"Agents send the key as a bearer header on every API call:
curl -X PUT https://duckgc.com/api/v1/courses/<slug>/variants/<language> \
-H "Authorization: Bearer gc_<40 hex>" \
-H "Content-Type: application/json" \
-d '{"markdown": "โฆ"}'The repo's make publish workflow reads the key from the GC_API_KEY environment variable. There's no revoke command yet โ rotating means minting a new key and setting the old row's revoked_at directly in the database.
Handling secrets
- Copy tokens when they're shown โ that's the only time the server knows the raw value.
- Never commit a token; keep it in the environment, the token file, or your secret manager. A gc_u_โฆ token is not an API key โ the publish API will reject it.
- One credential per machine or agent, named after its holder, so revoking one doesn't take the rest down.