temper init, run temper auth login, then temper auth request-access and401 UNAUTHORIZED. Therequest-access moves it to403 SYSTEM_ACCESS_REQUIRED with a payload explaining why. There is no path from<origin>/api — the origin plus an /api prefix the client prepends perhttps://temperkb.io/api. Verify it live:GET https://temperkb.io/api/health → 200 (unauthenticated)GET <origin>/.well-known/oauth-authorization-serverresource field carrying exactly the audience the HTTP surface{ "issuer": "https://temperkb.io/",
"resource": "https://temperkb.io/api" }resource field. Thatresource parameter (or Auth0's audience spelling) at /oauth/authorize. A requested resourceGET <origin>/.well-known/oauth-protected-resourceresource is the MCP surface's own audience — MCP_AUDIENCE when the operator set itresource-matches-the-server-URL-or-origin check), AUTH_AUDIENCE otherwise. A conformant MCPEach discovery door answers for its surface; both doors accept the same tokens. The HTTP
and MCP doors accept the same instance audience set. WhenMCP_AUDIENCEdiffers fromAUTH_AUDIENCE, a token naming either resource authenticates at either door. This is
deliberate: the MCP service relays the caller through the API, and the two transports are one
authorization domain. The audience split satisfies resource discovery and token targeting; it
is not a least-privilege boundary between the transports. A token naming any other audience is
refused at both.
resource the flow requested is carried through the authorization code and every refresh of{"error":{"code":"…","message":"…","details"?:…}}. The codedetails rides only SYSTEM_ACCESS_REQUIRED and PLAN_REFUSED.| status | code | means | caller does | retry |
|---|---|---|---|---|
| 401 | UNAUTHORIZED | missing or malformed Bearer | fix the header | no |
| 401 | UNAUTHORIZED | invalid or expired token — signature, issuer, audience, expiry | refresh; re-check audience against discovery | once |
| 401 | UNAUTHORIZED | auth service unavailable (JWKS fetch) | back off | yes |
| 401 | UNAUTHORIZED | account deactivated | stop; contact operator | no |
| 401 | UNAUTHORIZED | machine client not registered — message names the exact provision command | hand the client_id to an operator | no |
| 403 | SYSTEM_ACCESS_REQUIRED | authentic, no approved standing; details.refusal.kind says which | human: request-access. machine: an admin must approve | no |
| 403 | FORBIDDEN | refused, deliberately message-less (no capability disclosure) | obtain the grant out of band | no |
| 403 | FORBIDDEN_DETAIL | refused, message names the capability | obtain it | no |
| 404 | NOT_FOUND | absent or masked (a probe is not an existence oracle) | infer nothing either way | no |
| 400 | PRECONDITION_NOT_HONOURED | a write carried If-Match, If-None-Match or If-Unmodified-Since on a route that does not evaluate it; nothing was applied. Answered only to a caller the route already admits: an unauthenticated or refused caller gets the answer it gets without the header | resend without the header to write unconditionally | no |
| 400 | PLAN_REFUSED | composition invalid; details.refusals[] lists every reason | repair all in one round trip | no |
| 400, 413, 415, 422 | UNREADABLE_PLAN | the body is not a readable composition (malformed JSON, wrong content type, over the door's limit, a wrong type); no details, and the message repeats at most 1024 bytes of what failed | fix the body; there is no plan to refuse yet | no |
| 409 | INGEST_ENDED | append or finalize on an ingest that has ended (cancelled or abandoned); not resumable | start a new upload | no |
| 422 | CONTENT_INTEGRITY | stored bytes fail the hash; not resumable | re-upload from scratch | no |
| 500 | INTERNAL_ERROR | server fault | back off | yes |
code, no details: a-32602,-32600, everything else as -32603. An integrator writing a service targets HTTP; MCP is an agent-runtime target.SYSTEM_ACCESS_REQUIREDcli_command (temper auth request-access), but machines can neverdenied to requested istemper admin access approve <profile_id>, and| Variable | Purpose | Required? |
|---|---|---|
TEMPER_TOKEN | JWT access token for the API | Yes |
TEMPER_API_URL | API base URL (e.g. https://temperkb.io/api) | Yes — without it the client has no endpoint and fails at send time |
TEMPER_PROVIDER | Auth provider name that issued the token | No — defaults to auth0 |
TEMPER_DEVICE_ID | Stable device id for this session | No — a fresh UUIDv7 is generated if unset |
TEMPER_AUTH=ambient instead of TEMPER_TOKEN. The CLI then sends requests without anAuthorization header for the proxy to add, never reads or writes auth.json, and learns whoGET /api/profile.TEMPER_AUTH=ambient and TEMPER_TOKEN is refused as a configuration error, and so isTEMPER_AUTH value.temper init either. Set TEMPER_VAULT to a directory for the local~/.config/temper/config.toml astemper auth login, the CLI caches the token, and the agent inherits it by driving the/mcp, no /api prefix) with a JSON-RPCMCP_AUDIENCE), same twoopenapi.json, the tool count from the