Skip to content

Authentication

Everything under /api/* requires a Bearer token, with two exceptions: POST /api/waitlist (the landing page form) and GET /_debug/:slug, which are public by design.

Terminal window
curl -s https://301.so/api/links \
-H "Authorization: Bearer $TOKEN" \
-A "my-client/1.0"

Without it:

{ "error": "unauthorized" }

…with HTTP 401. The same 401 covers a missing header, a malformed one, an unknown token and a revoked one — an error that distinguishes those is an error that helps whoever is guessing.

Tokens are created from the repo, not from the API — there is no endpoint that creates credentials:

Terminal window
cd apps/worker
node scripts/mint-token.mjs "my laptop"

The token is printed once. The database only ever stores its SHA-256, so a lost token cannot be recovered, only replaced. Lookup happens by hash through SQLite’s unique index: no plaintext secret is ever compared, and the response time has nothing to teach an attacker.

A token belongs to a team, and every API query is constrained to that team. Today there is exactly one team, one domain and one user — but the schema has carried team_id since the first commit, because retrofitting it onto a live database is the migration you never get to do.

Each successful call updates last_used_at, best-effort: if that write fails, your request still succeeds. Knowing a token is still in use is worth less than serving the request.

The reference client already does this — see Clients.