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_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44Two 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_... productionWhat 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
/v1route that exists returns 401 withtype: "unauthenticated",code: "not_authenticated", and aWWW-Authenticate: Bearerchallenge. - An unauthenticated request to a path under
/v1that does not exist returns 404 withcode: "route_not_found". Routing resolves first;resolvePrincipalattaches 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.xyz401
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/warrants401 or 403
The two are not interchangeable, and the API uses them the way RFC 9110 defines them:
| Status | type | Means |
|---|---|---|
| 401 | unauthenticated | No credentials, or credentials that resolved to nobody. Authenticate and try again. |
| 403 | forbidden | We 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 /healthandGET /— service metadata.