unauthenticated
HTTP 401 retryable: false REST and MCP
The request carried no usable bearer credential, so there is no principal to authorise and the server stopped before looking at what you asked for.
Why it happens
Credential resolution happens in one place and fails closed. A missing Authorization header, a key that does not verify, and an expired one all land on the same code, because distinguishing them in the response would tell an unauthenticated caller which of their guesses was closer. The MCP endpoint adds a second, stricter gate on top: it inspects the token shape before resolution and rejects human session tokens outright, with a WWW-Authenticate header naming the protected resource.
Every condition below was read in the implementation at the file and line given. They are the branches that raise this code, not a summary of what the name suggests.
- The Authorization header is absent or empty.
packages/domain/src/services.ts:133 - The bearer token does not verify, or it has expired. Verification failure and expiry are not distinguished in the response.
packages/domain/src/services.ts:151 - A request to /mcp has no Authorization header, or one that does not start with the bearer scheme. The response sets WWW-Authenticate with realm and resource.
packages/mcp/src/index.ts:198 - A request to /mcp presents a JWT or a test principal token, recognised by its eyJ, efa_principal_ or efa_clerktest_ prefix. A human console session is refused before it is ever resolved.
packages/mcp/src/index.ts:211
How to fix it
- Send Authorization: Bearer <credential>. The scheme is checked case-insensitively but it must be present.
- Check the key prefix against the environment you are calling. A credential is efa_live_ or efa_test_ followed by eight hex characters and a 43-character body, and the secret is returned exactly once at creation.
- For /mcp, use the access token from a completed OAuth connection grant. A console session token is rejected by shape at the endpoint, before resolution.
- Rotate rather than retry. This code is not retryable and a credential that failed verification will keep failing.
Why it works this way
The MCP endpoint refuses a human session token by prefix rather than resolving it and refusing later. The gate is cheap, it cannot be reached by a token that happens to resolve to something surprising, and it produces a message that names the correct credential type instead of a generic denial.
Messages you will see
The message field is written for a human reading a log and is not a contract
value. Branch on code instead. These are the exact strings this code is sent
with today, listed so that a search for one of them lands here.
Where it appears
These are the documented surfaces that raise it. The REST base is https://api.emailforagents.ai/v1, and the remote MCP endpoint is https://mcp.emailforagents.ai/mcp.
GET /meEvery /v1 routePOST https://mcp.emailforagents.ai/mcp
The response
Read request_id off the body or the x-request-id header before
doing anything else. It is the only handle on one specific attempt, and for a 503 it is
the only handle at all, because the underlying detail stays server-side.
HTTP/1.1 401
content-type: application/json
x-request-id: req_...
{
"error": {
"code": "unauthenticated",
"message": "Missing bearer credential.",
"request_id": "req_...",
"retryable": false
}
}
Verified against packages/domain/src/services.ts:133. The status comes from ERROR_STATUS in packages/contracts/src/errors.ts, which is the one
place a code is mapped to a status.