Error codes
Broker HTTP API
Section titled “Broker HTTP API”The broker refuses a request with an HTTP status and a JSON body {"code": "…", "error": "…"}. agent-secrets prints a refusal as <error> (<code>). A message shown as an error’s own text may arrive wrapped in more detail; (varies) marks a message built at run time.
| Code | Status | Message | Where |
|---|---|---|---|
ACCOUNT_LACKS_POWER |
403 | the caller … may not call … | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyevery Caller ServiceAccount token route |
ACCOUNT_NOT_LISTED |
403 | this ServiceAccount does not log in as this service this ServiceAccount is not in BROKER_SERVICE_LOGINS this account is not in BROKER_API_ACCOUNTS |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/launcher-credentials/tokenevery Caller ServiceAccount token route every Service login ServiceAccount token route |
ACCOUNT_TOKENS_OFF |
401 | this broker trusts no Kubernetes token issuer, so no service authenticates with a ServiceAccount token this broker trusts no token issuer for its API callers, so no caller authenticates with an identity token |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyevery Caller ServiceAccount token route every Service login ServiceAccount token route |
ACCOUNT_TOKEN_EXPIRED |
401 | the ServiceAccount token has expired the caller’s identity token has expired |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyevery Caller ServiceAccount token route every Service login ServiceAccount token route |
ACCOUNT_TOKEN_INVALID |
401 | the Google identity token names no verified account, so it names no caller (ask for it with format=full) the ServiceAccount token does not verify the caller’s identity token does not verify the caller’s token names no issuer this broker trusts for its API callers |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyevery Caller ServiceAccount token route every Service login ServiceAccount token route |
ACCOUNT_TOKEN_TOO_LONG_LIVED |
401 | the ServiceAccount token lives longer than an hour from its issue the caller’s identity token lives longer than an hour from its issue the caller’s identity token names no issue time (iat) |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyevery Caller ServiceAccount token route every Service login ServiceAccount token route |
ALREADY_ENROLLED |
409 | this runtime is already enrolled and live | POST /v1/enrollments |
APPROVER_REQUIRED |
400 | approver is required | GET /v1/grantsGET /v1/launcher-credentialsGET /v1/pendingPOST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/grants/{id}/revoke-by-approverPOST /v1/launcher-credentials/{id}/revoke-by-approver |
CODE_MISMATCH |
403 | confirmation code does not match | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/deny |
CODE_REQUIRED |
400 | code is required code is required to decide a machine login |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/machine-logins/lookup |
CREDENTIAL_ID_INPUT |
400 | launcher credential ids are UUIDs | POST /v1/launcher-credentials/{id}/revoke-by-approverPOST /v1/operator/machines/{id}/retirePOST /v1/operator/machines/{id}/revoke |
DATABASE_UNAVAILABLE |
503 | introspect token failed: a dependency the broker needs is unavailable ping Postgres failed: a dependency the broker needs is unavailable read launcher credential failed: a dependency the broker needs is unavailable read the sign-in status failed: a dependency the broker needs is unavailable verify launcher proof failed: a dependency the broker needs is unavailable verify proof failed: a dependency the broker needs is unavailable |
GET /healthzGET /signin/statusPOST /v1/oauth/introspectevery Machine credential route every Renewal session proof route every Session proof route |
DECIDE_ON_BROKER_PAGE |
403 | a secret request is decided only on the secrets broker’s own page, by a person signed in to the broker: … | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/deny |
ENROLLMENT_ID_INPUT |
400 | enrollment ids are UUIDs | DELETE /v1/enrollments/{id}POST /v1/enrollments/{id}/renew |
ENROLLMENT_NOT_LIVE |
401 | enrollment is not live the machine login that enrolled this session has ended |
POST /v1/enrollments/{id}/renewevery Renewal session proof route |
GRANT_CHAIN_INVALID |
403 | this grant’s approval chain no longer verifies | POST /v1/grants/{id}/values |
GRANT_ID_INPUT |
400 | grant ids are UUIDs | POST /v1/grants/{id}/revoke-by-approverPOST /v1/grants/{id}/revokePOST /v1/grants/{id}/valuesPOST /v1/operator/grants/{id}/revoke |
GRANT_NOT_LIVE |
403 | grant is expired, revoked, or its session ended | POST /v1/grants/{id}/values |
GRANT_REQUIRES_LIFETIME |
400 | a requested secret requires a shorter grant lifetime | POST /v1/requests |
INTERNAL |
500 | cancel request failed create enrollment failed create request failed decide machine login failed list grants for approver failed list launcher credentials failed list pending requests failed list person sign-ins failed lookup machine login failed read credential request failed read credential request kind failed read enrollment failed read grant values failed read live grants failed read request failed read request owner failed read the grant log failed register the pod’s session name failed renew enrollment failed reread secret failed retire launcher credential failed revoke enrollment failed revoke grant failed revoke launcher credential failed revoke person sign-in failed token login failed |
DELETE /v1/enrollments/{id}GET /v1/enrollments/selfGET /v1/grant-logGET /v1/grantsGET /v1/launcher-credentialsGET /v1/operator/grantsGET /v1/operator/machinesGET /v1/pendingGET /v1/person-signinsGET /v1/requests/{id}POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/enrollments/{id}/renewPOST /v1/enrollmentsPOST /v1/grants/{id}/revoke-by-approverPOST /v1/grants/{id}/revokePOST /v1/grants/{id}/valuesPOST /v1/launcher-credentials/tokenPOST /v1/launcher-credentials/{id}/revoke-by-approverPOST /v1/machine-logins/lookupPOST /v1/oauth/exchange/pod-sessionsPOST /v1/operator/grants/{id}/revokePOST /v1/operator/machines/{id}/retirePOST /v1/operator/machines/{id}/revokePOST /v1/person-signins/{id}/revokePOST /v1/requests/{id}/cancelPOST /v1/requestsPOST /v1/secrets/{name}/reread |
INVALID_DECISION |
400 | body must be one valid JSON object: (varies) | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/deny |
INVALID_ENROLLMENT |
400 | body must be one valid JSON object: (varies) | POST /v1/enrollments |
INVALID_KIND |
400 | kind must be “session” or “pod” | POST /v1/enrollments |
INVALID_LOOKUP |
400 | body must be one valid JSON object: (varies) | POST /v1/machine-logins/lookup |
INVALID_NARROWING |
400 | body must be one valid JSON object: (varies) github_app.… … |
POST /v1/grants/{id}/values |
INVALID_RENEWAL |
400 | body must be one valid JSON object: (varies) | POST /v1/enrollments/{id}/renew |
INVALID_REQUEST |
400 | body must be one valid JSON object: (varies) the form must parse and be at most … bytes |
POST /v1/launcher-credentials/tokenPOST /v1/oauth/exchange/pod-sessionsPOST /v1/oauth/introspectPOST /v1/requests |
INVALID_REVOKE |
400 | body must be one valid JSON object: (varies) | POST /v1/grants/{id}/revoke-by-approverPOST /v1/launcher-credentials/{id}/revoke-by-approverPOST /v1/person-signins/{id}/revoke |
INVALID_SLOT |
400 | a slot is only for a pod enrollment and must match ^[a-z][a-z0-9-]{0,62}$ | POST /v1/enrollments |
KEY_HOLDS_LIVE_CREDENTIAL |
409 | this machine login’s key already holds a live launcher credential | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/launcher-credentials/token |
LAUNCHER_INVALID |
401 | the launcher credential is not valid this route needs a launcher proof, not a session proof |
POST /v1/enrollmentsevery Machine credential route |
MINT_FAILED |
502 | the broker could not mint a token from this secret | POST /v1/grants/{id}/values |
MIXED_APPROVERS |
400 | the requested secrets need different approvers; request them separately | POST /v1/requests |
NARROWING_BEHIND_AIRLOCK |
409 | this grant holds a name behind the airlock, which takes no narrowing | POST /v1/grants/{id}/values |
NARROWING_NOT_APPLICABLE |
400 | this grant holds no github-app secret to narrow | POST /v1/grants/{id}/values |
NARROWING_OWNER_NOT_LISTED |
403 | the narrowing names an account a github-app secret of this grant does not serve | POST /v1/grants/{id}/values |
NARROWING_REFUSED |
422 | the narrowing cannot be served | POST /v1/grants/{id}/values |
NOT_APPROVER |
403 | only the grant’s approver or its enrollment’s operator may revoke it only the person who approved a person’s machine login may revoke it only the record’s approver may decide it |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/grants/{id}/revoke-by-approverPOST /v1/launcher-credentials/{id}/revoke-by-approverPOST /v1/operator/grants/{id}/revokePOST /v1/operator/machines/{id}/revoke |
NOT_CALLING_CREDENTIAL |
403 | a machine login retires only itself: sign with the credential the path names | POST /v1/operator/machines/{id}/retire |
NOT_ENROLLING_CREDENTIAL |
403 | only the machine login that made an enrollment carrying no operator may end it | DELETE /v1/enrollments/{id} |
NOT_FOUND |
404 | no sign-in of that person has that id no such credential request no such grant no such machine login no such request |
GET /v1/requests/{id}POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/grants/{id}/revoke-by-approverPOST /v1/grants/{id}/revokePOST /v1/launcher-credentials/{id}/revoke-by-approverPOST /v1/operator/grants/{id}/revokePOST /v1/operator/machines/{id}/revokePOST /v1/person-signins/{id}/revokePOST /v1/requests/{id}/cancel |
NOT_YOURS |
403 | a session may only renew its own enrollment this request belongs to another session this request or grant belongs to another session |
GET /v1/requests/{id}POST /v1/enrollments/{id}/renewPOST /v1/grants/{id}/revokePOST /v1/grants/{id}/valuesPOST /v1/requests/{id}/cancel |
NO_SUCH_CODE |
404 | no pending machine login has this code | POST /v1/machine-logins/lookup |
OPERATOR_MISMATCH |
403 | this launcher credential enrols a different operator | DELETE /v1/enrollments/{id}POST /v1/enrollments |
PAGE_INVALID |
400 | after is not a whole number of zero or more limit is not a whole number of zero or more |
GET /v1/grant-log |
PERIOD_INVALID |
400 | since is not an RFC 3339 time, such as 2026-01-01T00:00:00Z until is not an RFC 3339 time, such as 2026-01-01T00:00:00Z |
GET /v1/grant-log |
PERIOD_NOT_CLOSED |
400 | until must be at least an hour before now, so that every row of the period has committed before the review reads it | GET /v1/grant-log |
PERIOD_REQUIRED |
400 | since is required: an RFC 3339 time, such as 2026-01-01T00:00:00Z until is required: an RFC 3339 time, such as 2026-01-01T00:00:00Z |
GET /v1/grant-log |
PERSON_REQUIRED |
400 | person is required | GET /v1/person-signinsPOST /v1/person-signins/{id}/revoke |
POD_ACCOUNT_NOT_LISTED |
403 | exchange: no entry lists this identity | POST /v1/oauth/exchange/pod-sessions |
POD_CALLER_IS_THE_POD |
403 | this is the caller’s own pod, … of …, and a registrar registers a pod other than the one it runs in | POST /v1/oauth/exchange/pod-sessions |
POD_IDENTITY_MISMATCH |
403 | the projected token does not identify this pod | POST /v1/enrollments/{id}/renewPOST /v1/enrollments |
POD_NOT_BOUND |
400 | exchange: the subject token is bound to no pod | POST /v1/oauth/exchange/pod-sessions |
POD_NOT_LAUNCHED |
403 | this machine login does not launch the pods of that service account | POST /v1/enrollments |
POD_SELF_ENROLL_NO_SESSION |
403 | a pod enrolling itself states no session name | POST /v1/enrollments |
POD_SELF_ENROLL_ONLY |
403 | a pod’s own machine login enrols that pod alone | POST /v1/enrollments |
POD_TOKEN_EXPIRED |
401, 403 | the pod’s ServiceAccount token has expired the projected token has expired |
POST /v1/enrollments/{id}/renewPOST /v1/enrollmentsPOST /v1/oauth/exchange/pod-sessions |
POD_TOKEN_INVALID |
401 | the pod’s ServiceAccount token does not verify | POST /v1/oauth/exchange/pod-sessions |
POD_TOKEN_REQUIRED |
403 | a pod enrollment’s renewal must carry its projected token | POST /v1/enrollments/{id}/renew |
PROOF_INVALID |
401 | enrollment is not live proof invalid proof invalid: not live this route needs a session proof, not a launcher proof |
GET /v1/enrollments/selfPOST /v1/requestsevery Renewal session proof route every Session proof route |
RATE_LIMITED |
429 | too many launcher credential requests; try again later too many requests from this address; try again later too many requests with a ServiceAccount token; try again later too many secret rereads; try again later too many token logins for this ServiceAccount; try again later |
GET /signin/statusPOST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/launcher-credentials/tokenPOST /v1/secrets/{name}/rereadevery Caller ServiceAccount token route every Service login ServiceAccount token route |
RECORD_ID_INPUT |
400 | record ids are lowercase-hex sha256 hashes | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/deny |
RECORD_TERMINAL |
409 | this machine login expired before its approver acted on it this machine login has already been decided |
POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/deny |
REQUEST_ENDED |
503 | the request ended before the reread finished; send it again | POST /v1/secrets/{name}/reread |
REQUEST_ID_INPUT |
400 | request ids are UUIDs | GET /v1/requests/{id}POST /v1/requests/{id}/cancel |
REQUEST_INVALID |
400 | request object invalid | POST /v1/launcher-credentials/tokenPOST /v1/requests |
REQUEST_PENDING |
409 | a request for one or more of these secrets is already pending | POST /v1/requests |
REQUEST_TERMINAL |
409 | request is already decided | POST /v1/requests/{id}/cancel |
REQUEST_TOO_LARGE |
413 | request body exceeds … bytes | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/denyPOST /v1/enrollments/{id}/renewPOST /v1/enrollmentsPOST /v1/grants/{id}/revoke-by-approverPOST /v1/grants/{id}/valuesPOST /v1/launcher-credentials/tokenPOST /v1/launcher-credentials/{id}/revoke-by-approverPOST /v1/machine-logins/lookupPOST /v1/oauth/exchange/pod-sessionsPOST /v1/person-signins/{id}/revokePOST /v1/requests |
SECRET_NAME_INVALID |
400 | not an agent secret name | POST /v1/secrets/{name}/reread |
SECRET_NOT_IN_STORE |
404 | secret is not in the secrets store | POST /v1/grants/{id}/values |
SERVICE_CREDENTIAL |
403 | a service’s machine login has no operator; machine and grant commands are a person’s | GET /v1/operator/grantsGET /v1/operator/machinesPOST /v1/operator/grants/{id}/revokePOST /v1/operator/machines/{id}/revoke |
SERVICE_LOGS_IN_WITH_TOKEN |
400 | a service logs in with its own projected Kubernetes ServiceAccount token at POST /v1/launcher-credentials/token, never through a code a person approves | POST /v1/credential-requests/{record}/approvePOST /v1/credential-requests/{record}/deny |
SESSION_FIXED |
409 | exchange: the entry fixes the session name it writes under | POST /v1/oauth/exchange/pod-sessions |
SESSION_HELD |
403 | another identity holds this session name | POST /v1/enrollments |
SESSION_ID_INPUT |
400 | session id must be at most 128 characters with no whitespace or control character | POST /v1/requests |
SESSION_INVALID |
400 | exchange: the request names an unusable session | POST /v1/oauth/exchange/pod-sessions |
SHUTTING_DOWN |
503 | the broker is shutting down and is no longer taking new traffic | GET /healthz |
SIGNIN_ID_INPUT |
400 | sign-in ids are UUIDs | POST /v1/person-signins/{id}/revoke |
TOKEN_EXPIRED |
401 | the ServiceAccount token has expired | POST /v1/launcher-credentials/token |
TOKEN_INVALID |
401 | the ServiceAccount token does not verify | POST /v1/launcher-credentials/token |
TOKEN_LOGIN_OFF |
404 | this broker trusts no Kubernetes token issuer, so no service logs in with a token | POST /v1/launcher-credentials/token |
TOKEN_REQUIRED |
400 | token is required | POST /v1/oauth/introspect |
TOKEN_TOO_LONG_LIVED |
401 | the ServiceAccount token lives longer than an hour from its issue | POST /v1/launcher-credentials/token |
UNKNOWN_SECRET |
400 | no agent secret has this name | POST /v1/requests |
Host helper
Section titled “Host helper”agent-secrets-helper answers a request it refuses with one of these codes and an error message; agent-secrets prints them as <code>: <error>.
| Code | Meaning |
|---|---|
BAD_REQUEST |
Answers a request that is not one JSON object per line, names an unknown op, leaves out a field its op needs, or asks sign-launcher for anything but one of the four operator routes under this helper’s broker. |
DRAINING |
Answers register and enroll-session-key once drain found no sessions: the helper is about to be restarted, and the caller should retry against the new one. |
ENROLL_FAILED |
Answers an enroll-session-key the broker refused or could not be asked; the error says why. |
IN_SESSION |
Answers sign-launcher from a process inside a registered session’s process tree, or one whose ancestry the helper cannot follow to init (a parent it cannot read, a loop, or past 64 processes), so a session’s own commands cannot act as its operator. A process that leaves its session’s tree (a double fork or setsid, reparented to init or a subreaper) is not refused, and the operator’s user can stop the helper anyway: it guards a session’s own process tree, not a boundary against code running as that user. |
KEYGEN |
Answers a register the helper could not make the new session’s signing key for; the error says why. |
LOGIN_FAILED |
Answers a login the helper could not start with the broker; the error says why. |
NOT_A_SESSION |
Answers sign, sign-request or unregister from a process that descends from no session agent-secrets register registered. |
NOT_ENROLLED |
Answers sign or sign-request for a registered session that is still enrolling: the helper holds a launcher credential, and its enroll loop has not succeeded yet. |
NO_CREDENTIAL |
Answers sign, sign-request or sign-launcher while the helper holds no launcher credential: the machine was never logged in, its machine file is gone, or the broker refused its login (revoked, lapsed, or a reused refresh token), until the operator logs the machine in. A restart resumes a kept login, so it alone does not cause this. The helper then enrolls no one, so a session has no broker identity, and the operator’s shell has no machine login to act as. A register reply for such a session carries it too, beside OK. |
PEER_UNIDENTIFIED |
Answers a caller the kernel could not identify, or one whose process exited or changed while the helper was identifying it. |
SIGN |
Answers sign, sign-request or sign-launcher when signing the proof or the request object failed; the error says why. |
UNENROLL_FAILED |
Answers an unenroll-session-key the broker refused or could not be asked; the error says why. |
agent-secrets exit codes
Section titled “agent-secrets exit codes”agent-secrets exits 0 when it is done and 1 when it failed; these are the other codes it exits with.
| Exit code | Meaning |
|---|---|
| 2 | Usage error: a flag, argument, token file, or required environment value is invalid |
| 75 | Request still waiting for a person to approve it |
| 76 | Renew reached a refusal in terminalRenewal; the worker shim must not restart it |
| 77 | Request denied |
| 126 | Register –exec command exists but could not run |
| 127 | Register –exec command was not found |
| 129 | Secret prompt received SIGHUP |
| 130 | Secret prompt received SIGINT |
| 131 | Secret prompt received SIGQUIT |
| 143 | Secret prompt received SIGTERM |