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?

TaskCredential
browse courses, duck pull, duck testNone โ€” courses and their challenges are public.
duck submitUser 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 login

Or 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/token

Tokens 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