Skip to content
Warrantv0.1

Authentication

API keys for agents, sessions for people.

Every /v1 route needs a caller: an agent holding an API key, or a person holding a session cookie. They are not interchangeable.

Authorization: Bearer wr_test_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44

Two kinds of caller

An agent holds an API key. Keys carry no role: an agent can open and verify warrants, because that is an agent's job, and it cannot resolve an escalation. If a key could approve a payment then the approval step above the threshold would be decorative, since anyone holding the key could route around it.

A person holds a session, minted through Privy and stored as an httpOnly cookie. People have a role in exactly one organisation — viewer, operator, admin or owner — and the role is what each route checks.

Key prefixes decide the environment and nothing else does:

wr_test_...   sandbox
wr_live_...   production

What an unauthenticated request gets

Only on routes that exist

The brief for this product says an unauthenticated request to any /v1 route returns 401 whether the route exists or not, because auth runs before routing. The second half is not true, and it is the half that matters when you are debugging.

What actually happens, verified against the running API:

  • An unauthenticated request to a /v1 route that exists returns 401 with type: "unauthenticated", code: "not_authenticated", and a WWW-Authenticate: Bearer challenge.
  • An unauthenticated request to a path under /v1 that does not exist returns 404 with code: "route_not_found". Routing resolves first; resolvePrincipal attaches whoever is calling without rejecting anyone, and each route's own guard is what refuses.

So a 404 from /v1 means the path is wrong, not that your key is. That is useful, and it is the opposite of what a uniform 401 would tell you.

GET /v1/warrants HTTP/1.1
Host: api.gowarrant.xyz

401

WWW-Authenticate: Bearer realm="warrant"

{
"error": {
  "type": "unauthenticated",
  "code": "not_authenticated",
  "message": "Sign in, or present an API key.",
  "doc_url": "https://docs.gowarrant.xyz/api-reference/errors#not_authenticated",
  "request_id": "req_9c2a41f0..."
}
}
curl -i https://api.gowarrant.xyz/v1/warrants

401 or 403

The two are not interchangeable, and the API uses them the way RFC 9110 defines them:

StatustypeMeans
401unauthenticatedNo credentials, or credentials that resolved to nobody. Authenticate and try again.
403forbiddenWe know who you are. The answer is still no.

A 403 arrives with a code saying which rule you hit: insufficient_role when your role is too low, user_session_required when an API key tried something that needs a person. Both mean the same thing to a client — do not retry, and do not prompt for a sign-in, because signing in again changes nothing.

A key that has been revoked, a cookie whose membership was removed, and a sandbox request carrying a wr_live_ key all return 401: each resolves to no principal at all, so there is nobody to refuse.

Routes that need no caller

Three are public on purpose:

  • GET /t/:id — a trace receipt. The entire point is a link an outside party can open, so requiring an account would defeat it.
  • POST /bench/run — the verifier bench. It reads nothing and writes nothing, so the only thing to protect is CPU, and it has a tighter rate limit instead.
  • GET /health and GET / — service metadata.