Skip to content

HTTP API

The broker’s API answers JSON. A refusal is {"code": "…", "error": "…"} with the HTTP status below, and a refusal that carries more lists its body under its route; the error reference lists every code. The broker’s own approval page, whose routes take a broker sign-in session, answers a person’s browser with HTML pages and redirects instead, listed by status.

Each route takes one kind of credential, or none. These are the refusals the broker answers for the credential itself, before the route runs.

Credential Description Refusals
None A route anyone may call, with no credential. none
Machine credential A route for a machine’s launcher (agent-secrets-helper, or the Legion daemon), which signs each call’s Proof header with the key its machine login’s credential is bound to. 503 DATABASE_UNAVAILABLE
401 LAUNCHER_INVALID
Caller ServiceAccount token A route for a service calling the broker with its own Kubernetes ServiceAccount token: Dispatch’s server, which lists what waits on a person, relays a person’s machine-login decisions and revokes, and links the broker’s own page for a secret request; and whatever resource server reads a token back through introspection. The token is verified against the broker’s Kubernetes issuer and its account must be one BROKER_API_ACCOUNTS lists, and the handler is given that entry’s name — the caller’s identity, which it logs when it acts. Each such route declares the power its caller must hold, and the caller’s name answers the powers it has (accountPowers), which authenticate checks before the handler runs: being listed reaches the routes of that caller’s own powers and no others. 403 ACCOUNT_LACKS_POWER
403 ACCOUNT_NOT_LISTED
401 ACCOUNT_TOKENS_OFF
401 ACCOUNT_TOKEN_EXPIRED
401 ACCOUNT_TOKEN_INVALID
401 ACCOUNT_TOKEN_TOO_LONG_LIVED
429 RATE_LIMITED
Service login ServiceAccount token A route for a service calling the broker with its own Kubernetes ServiceAccount token, the account BROKER_SERVICE_LOGINS lets log in as that service — a deployment’s Legion daemon, which registers the pods it starts. The token is verified and mapped to its service by the check a token login makes (machine.Service.VerifyServiceToken). The handler is given that check’s own answer, one machine.ServiceAccount: the service the caller logs in as, the account whose token it presented, and the pod that token was bound to. The account and the pod come with it because BROKER_SERVICE_LOGINS may name an account BROKER_SERVICES binds to a service: every pod of such an account logs that service in, so a caller here may be the very pod a request names, and the account and the pod uid are what tell the two apart. It is a class of its own rather than a power of accountAuth’s because the two lists are two authorities: BROKER_API_ACCOUNTS names the callers that relay a person’s decisions and read tokens back, and a daemon that registers its own pods is not one of them. A deployment already lists its per-project daemons in BROKER_SERVICE_LOGINS for their token logins, so these routes need no new setting and no new account. 403 ACCOUNT_NOT_LISTED
401 ACCOUNT_TOKENS_OFF
401 ACCOUNT_TOKEN_EXPIRED
401 ACCOUNT_TOKEN_INVALID
401 ACCOUNT_TOKEN_TOO_LONG_LIVED
429 RATE_LIMITED
Renewal session proof Session authentication for the one route that gives an ended enrollment a distinct refusal. A skewed/malformed proof remains PROOF_INVALID; only proof.ErrNotLive is ENROLLMENT_NOT_LIVE so the pod renewer can stop rather than retry an enrollment that cannot recover. 503 DATABASE_UNAVAILABLE
401 ENROLLMENT_NOT_LIVE
401 PROOF_INVALID
Session proof A route for an enrolled agent session, which signs each call’s Proof header with its own key, and which acts only on its own enrollment, requests and grants. 503 DATABASE_UNAVAILABLE
401 PROOF_INVALID
Caller ServiceAccount token, checked by the handler A route whose handler authenticates its own caller, because what it may answer is settled before any credential is read: the two decide routes read the record’s kind first and refuse a secret request 403 DECIDE_ON_BROKER_PAGE naming the broker’s own page, to every caller, token or none. Register checks nothing for such a row; the handler spends the account class’s per-address bucket and then authenticates a machine login’s decision exactly as accountAuth does (decideRecord), so one is still decided only on a listed service’s ServiceAccount token. The row declares the power that caller must hold all the same, and the handler is given it: authenticate cannot check it here, since it is the handler that reads the caller, so the check is decideRecord’s, against the same accountPowers and with the same 403 ACCOUNT_LACKS_POWER as every other row of that authority. none
Broker sign-in session A route of the broker’s own approval page, for a person in a browser signed in to the broker itself (OpenID Connect with the broker’s own client, BROKER_SIGNIN_*). The handler is given the session the request’s __Host-broker-session cookie names, or the zero session when it names none that is live, since what that means is the route’s to answer: the page starts a sign-in, a decision answers a page linking the record it names. Every route of the class spends its source address’s bucket first, the one the account class spends (authenticate), so no caller drives a record read, a token request to the sign-in provider or a session write unmetered. Mounted only when the broker builds its approval page, which every broker does. 429 (an HTML page)
500 (an HTML page)
Method Path Credential What it does
GET /.well-known/oauth-authorization-server None The broker’s own OAuth 2.0 authorization server metadata (RFC 8414): its issuer, its token, device authorization, introspection and revocation endpoints, the grants it serves, how a client authenticates at the token endpoint, the algorithms a DPoP proof may be signed with, and the subject token types its RFC 8693 exchange takes. It names the device code and the device authorization endpoint only while the broker serves the device grant, which is while BROKER_MACHINE_LOGIN_URL is set. It needs no credential: it is what a client reads before it holds one, and every word of it is already public in the broker’s own reference. Mounted only when the broker runs its OAuth provider, since with none it would name endpoints that are not there.
POST /v1/enrollments Machine credential Enroll a session’s or pod’s signing key under the caller’s machine credential.
DELETE /v1/enrollments/{id} Machine credential Revoke an enrollment made under the caller’s machine credential’s operator, or a pod enrollment the caller’s own machine credential made.
POST /v1/launcher-credentials/token None Log a service in with its own projected Kubernetes ServiceAccount token, which no person approves: answers its launcher credential’s id and expiry.
POST /v1/oauth/device_authorization None Start a person’s machine login as an OAuth device authorization (RFC 8628): answers the device code the machine polls with and the code the person types. Rate-limited per source address and per person its login_hint names; mounted only when the broker runs its OAuth provider.
POST /v1/oauth/token None Exchange an approved device code, or a refresh token, for a DPoP-bound token pair naming the machine login’s launcher credential; mounted only when the broker runs its OAuth provider.
POST /v1/oauth/introspect Caller ServiceAccount token Read one of the broker’s access tokens back (RFC 7662): whether it is live and, when it is, the session, the person and the project it carries, and the key it is bound to. Mounted only when the broker runs its OAuth provider.
POST /v1/oauth/revoke Caller ServiceAccount token End one of the broker’s own access tokens (RFC 7009), an exchanged pod’s or workflow run’s included, which nothing else can end before its own expiry: the form names the token, the client it was issued to, and optionally which kind to look for first. It needs relaysDecisions rather than readsTokensBack because ending one of the broker’s tokens is the same authority class as ending a machine login or a grant, both already behind that power; a caller listed only to read a token back must not be able to end one. Mounted only when the broker runs its OAuth provider.
POST /v1/oauth/exchange/pod-sessions Service login ServiceAccount token Register that one pod may exchange under one session name: the row the token exchange reads before it mints a token for a pod of an account that fixes no session, written by the deployment’s own daemon as it starts that pod. Mounted only when the broker runs its OAuth provider.
POST /v1/enrollments/{id}/renew Renewal session proof Extend the calling session’s lease.
GET /v1/enrollments/self Session proof Describe the calling session’s enrollment and its live grants.
POST /v1/requests Session proof Request secrets: the policy grants or denies them at once, or a person must approve them.
GET /v1/requests/{id} Session proof Read one of the calling session’s requests.
POST /v1/requests/{id}/cancel Session proof Cancel one of the calling session’s pending requests.
POST /v1/grants/{id}/values Session proof Release a live grant’s values to the session that holds it.
POST /v1/grants/{id}/revoke Session proof End a grant the calling session holds.
GET /v1/pending Caller ServiceAccount token List the pending requests and machine logins the named person decides, each secret request with the broker’s own page where that person decides it.
POST /v1/credential-requests/{record}/approve Caller ServiceAccount token, checked by the handler Approve a pending machine login as the named person, who must type its code: the broker takes the person’s name on the caller’s word, from a listed service’s ServiceAccount token, which the handler verifies itself — and whose name must carry relaysDecisions, the power this row declares — after it has read the record’s kind. It reads the kind first because a secret request is refused before any caller is looked at: it is decided only on the broker’s own page by a person signed in to the broker, so this answers 403 DECIDE_ON_BROKER_PAGE naming that page to every caller, token or none, writing nothing.
POST /v1/credential-requests/{record}/deny Caller ServiceAccount token, checked by the handler Deny a pending machine login as the named person, who must type its code, on the caller’s word and behind the same authentication and the same power as approve. A secret request is refused the same way, writing nothing, with 403 DECIDE_ON_BROKER_PAGE naming the page where a person signed in to the broker denies it.
POST /v1/machine-logins/lookup Caller ServiceAccount token Find a pending machine login by the confirmation code its machine shows.
GET /v1/launcher-credentials Caller ServiceAccount token List the machine logins the named person may revoke, every service’s, such as the Legion daemon’s, whoever approved it, and the person’s own machines’, that are not revoked and are unexpired or expired with a session still running, each naming who approved it. The person is the one the caller names.
POST /v1/launcher-credentials/{id}/revoke-by-approver Caller ServiceAccount token End a machine login, expired or not: a service’s (its token login, or one a person approved before token logins) as anyone signed in, a person’s machine’s as the person who approved it. Its launcher proofs stop authenticating and every session it enrolled, pods included, ends with its grants and pending requests. The revoking person is the login the request names, which the broker still takes on the caller’s word: a revoke only takes power away.
GET /v1/grants Caller ServiceAccount token List the live grants of the named person’s sessions, automatic or approved, and those the person approved. The person is the one the caller names.
POST /v1/grants/{id}/revoke-by-approver Caller ServiceAccount token End a grant as its approver or as its enrollment’s operator. The operator’s revoke also withholds the secrets the grant’s request got automatically from that session, even when the grant had already ended: its other grants that got them automatically end too, and it asks before it gets them again. The revoking person is the login the request names, which the broker still takes on the caller’s word: a revoke only takes power away.
GET /v1/person-signins Caller ServiceAccount token List the sign-ins the named person made on their machines with agent-secrets login, by machine: one row per box key they signed in with and have not revoked, each naming the machine’s Google account, the box key’s thumbprint, and when the sign-in was made and last used. The person is the one the caller names, as it is for every list Dispatch’s server relays.
POST /v1/person-signins/{id}/revoke Caller ServiceAccount token End one of the named person’s sign-ins: that box key trades no token at the exchange again, and every access token already minted on that box under that sign-in ends in the revoke’s own transaction, so none of them outlives it. The person’s sign-ins on their other boxes keep working, and the box signs in again with a key of its own. The revoking person is the one the request names, which the broker takes on the caller’s word: a revoke only takes power away.
GET /v1/grant-log Caller ServiceAccount token Read the broker’s grant log for one closed period, for a quarterly access review: every audit row between ?since and ?until (RFC 3339, half-open), oldest first, each naming the session that wrote it, that session’s person, the service the broker verified, the secrets asked for and their decisions, the state, the approver and who decided. ?after resumes after an id and ?limit bounds the page, which is a thousand rows at most. ?until must be at least an hour before now, so that every row of the period has committed before the review reads it. It is the one route readsGrantLog reaches: the review reads the broker’s record and changes nothing in it.
GET /v1/operator/machines Machine credential List the logins of the calling credential’s operator’s own machines, as Dispatch’s machine-logins page lists them, without the service logins that page also lists.
POST /v1/operator/machines/{id}/revoke Machine credential End one of the operator’s own machines’ logins: no proof signed with it authenticates again and every session it enrolled ends. A service’s login is not found here, like an unknown id. Revoking the calling machine’s own login ends this machine’s access.
POST /v1/operator/machines/{id}/retire Machine credential Retire the calling machine login after its machine logged in again: it enrolls nothing more and cannot refresh, while the sessions it enrolled keep running and renewing with their own keys. {id} must be the calling credential.
GET /v1/operator/grants Machine credential List the live grants of the operator’s sessions and those the operator approved, exactly as Dispatch’s Live grants page lists them.
POST /v1/operator/grants/{id}/revoke Machine credential End a grant as the operator: the revocation Dispatch’s page offers, which also withholds an automatic grant’s secrets from its session.
GET /v1/settings None The namespace prefix, AWS account and region, and the agent-secrets key the broker serves: what the CLI needs so it and the broker never disagree about which secret a name means.
POST /v1/secrets/{name}/reread None Reread one secret from Secrets Manager now, as whoever just wrote it may ask: the change is served, or gone, within seconds instead of at the next five-minute reload. Open to any caller, and rate-limited both per source address and over every caller at once, since each reread holds the policy’s writer lock while it reads; a reread only moves the broker toward AWS’s own truth.
GET /approve/{record} Broker sign-in session A person’s approval page for one credential-request record, an HTML page on the broker’s own origin: the record’s secret names, the enrollment the broker verified asked, the session the request claims when it differs, the reason (empty only on a record an older build wrote before the broker required one wherever a person decides), the lifetime, the times, the approver and the state, one sentence saying what Approve gives and for how long and one saying what Deny and the expiry do, then every other pending secret request the person may decide, with Approve and Deny for each the person may decide and one “Approve all N shown”. It checks the id’s shape, then the session, then the record: a malformed id is a 400, a request with no live session, or with ?switch=1, redirects to the sign-in provider and returns here, and only a signed-in person’s unknown record id is a 404. A machine login’s record is printed with the note that it is decided by typing its code on Dispatch’s machine-login page, and offers no button.
POST /approve Broker sign-in session Decide records an approval page printed, as the signed-in person: a form from the broker’s own origin only, carrying the page’s token, which binds it to the session and to exactly the records the page printed with a button, for an hour. Each record is decided on its own, and the answer is a 303 back to the page, which says what became of each.
GET /signin/callback Broker sign-in session Finish a sign-in to the approval page, the provider’s redirect back with a code, checked against the sign-in this browser started (its __Host-broker-signin cookie, used once): state, PKCE, the ID token and the Workspace domain. It makes a new session, ending the one the browser held, and answers a 303 to the record’s page the sign-in started from.
POST /signin/logout Broker sign-in session Sign out of the approval page, from the broker’s own origin only: the session ends and its cookie is cleared, and the answer links back to the record.
GET /signin/status None Report the broker’s sign-in: whether its sign-in provider accepted the broker’s own client at the last hourly check and when, when a person last signed in, when a sign-in last failed and the coded reason it failed with, and when a decision was last made on the approval page. It needs no credential because a production check fetches it from a pod where no person is signed in, and because it names no login, no secret and no provider’s message: every word it answers is one of two closed sets this broker chose (internal/broker/store’s signin_status). It spends its source address’s bucket in its own handler, as the page’s routes spend it in authenticate, so this read of the broker’s own row is metered too.
GET /approve/assets/approve.css None The approval page’s one stylesheet, embedded in the broker; it needs no credential and spends no rate-limit bucket, since every page it dresses is read before anyone has signed in and the bytes are the binary’s.
GET /healthz None Report whether the broker can reach its database. From SIGTERM onward it answers 503 SHUTTING_DOWN instead (Deps.Draining) while the process goes on serving every other route, which is what takes a stopping pod out of its Service’s endpoints before it stops accepting connections. So this is a readiness probe and must never be used as a liveness probe: a liveness probe on it would have the kubelet kill the container in the middle of the drain, dropping the requests the pod was draining.

GET /.well-known/oauth-authorization-server

Section titled “GET /.well-known/oauth-authorization-server”

The broker’s own OAuth 2.0 authorization server metadata (RFC 8414): its issuer, its token, device authorization, introspection and revocation endpoints, the grants it serves, how a client authenticates at the token endpoint, the algorithms a DPoP proof may be signed with, and the subject token types its RFC 8693 exchange takes. It names the device code and the device authorization endpoint only while the broker serves the device grant, which is while BROKER_MACHINE_LOGIN_URL is set. It needs no credential: it is what a client reads before it holds one, and every word of it is already public in the broker’s own reference. Mounted only when the broker runs its OAuth provider, since with none it would name endpoints that are not there.

  • Credential: None
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_metadata.go:87

Response body (JSON):

Field Type Description
issuer string The broker’s own issuer identifier (BROKER_PUBLIC_URL), which is the URL this document is published under and the audience every signed proof and request object names. RFC 8414 requires it.
token_endpoint string The token endpoint: the device code grant, the refresh grant and the RFC 8693 token exchange. RFC 8414 requires it of a server that serves a grant using it, as all three do.
device_authorization_endpoint string or absent The device authorization endpoint (RFC 8628), absent from a broker that serves no device grant, which is one started with no BROKER_MACHINE_LOGIN_URL: there is then no page for a person to type a code into, the route is not mounted, and grant_types_supported names no device code either.
introspection_endpoint string The introspection endpoint (RFC 7662), where a resource server listed in BROKER_API_ACCOUNTS reads one of the broker’s access tokens back.
revocation_endpoint string The revocation endpoint (RFC 7009), where a caller listed for that power ends one of the broker’s tokens.
grant_types_supported array of strings The grant types this broker serves: always the refresh token grant and the RFC 8693 token exchange, and the RFC 8628 device code only while the broker serves the device grant. RFC 8414 makes the member optional with a default of the authorization code and implicit grants, neither of which this broker has, so leaving it out would say something untrue.
response_types_supported array of strings The response types this broker supports, which is none: it has no authorization endpoint at all, since nobody signs in to it through a browser to get a token — a person’s part in a machine login is typing a code, and every other caller presents an identity it already holds. RFC 8414 requires the member, so it is written empty rather than omitted.
token_endpoint_auth_methods_supported array of strings How a client authenticates at the token endpoint: “none” and nothing else (authMethodNone).
dpop_signing_alg_values_supported array of strings The algorithms a DPoP proof (RFC 9449) may be signed with, the member RFC 9449 Section 5.1 defines. Every token this broker issues is DPoP-bound and a request with no acceptable proof gets none, so a client that cannot sign with one of these gets nothing at all.
subject_token_types_supported array of strings The token types this broker’s RFC 8693 exchange takes as a subject token, which is what a caller puts in subject_token_type. RFC 8693 registers no metadata member for them and RFC 8414 names nothing of the kind, so this is the broker’s own member, named after RFC 8693’s own subject_token_type parameter and RFC 8414’s <thing>_supported convention. A caller that does not know it ignores it, as RFC 8414 says an unknown member must be; one that does learns which identities this deployment accepts without trying each in turn.

Enroll a session’s or pod’s signing key under the caller’s machine credential.

  • Credential: Machine credential
  • Success: 200, 201
  • Source: packages/envoy/internal/broker/api/handlers_enroll.go:89

Request body (JSON):

Field Type Description
kind string What the session is: “session” (an agent session on a machine its person logged in, or a machine enrolling under the launcher credential its token exchange minted, which acts for that machine’s fleet rather than for a person) or “pod”.
runtime_id string The session’s runtime: a pod’s UID, or whatever names the session on its machine (the helper’s host:pid:start time, a container’s id). A launcher enrolls one live session per runtime id (and slot).
slot string A pod only, optional: which of the pod’s independent enrollments this is.
operator string or null Optional: the operator the session runs for. The broker records the machine credential’s own operator whatever is sent, and refuses any other login. Absent or null for a pod.
thumbprint string The RFC 7638 JWK thumbprint of the session’s P-256 signing key.
session_id string or null Optional: the Envoy session the broker notifies when one of this session’s pending requests expires.
pod_token string or null A pod only: its projected service-account token, which proves the runtime id is its UID.

Response body (JSON):

Field Type Description
enrollment_id string The enrollment’s id, which the session’s Proof headers name.
lease_expires_at string (RFC 3339 time) When the enrollment’s lease lapses unless the session renews it.
slot string or null The pod slot it holds; null for every enrollment without one.

Refusals:

Status Code Message
409 ALREADY_ENROLLED this runtime is already enrolled and live
500 INTERNAL create enrollment failed
400 INVALID_ENROLLMENT body must be one valid JSON object: (varies)
400 INVALID_KIND kind must be “session” or “pod”
400 INVALID_SLOT a slot is only for a pod enrollment and must match ^[a-z][a-z0-9-]{0,62}$
401 LAUNCHER_INVALID the launcher credential is not valid
403 OPERATOR_MISMATCH this launcher credential enrols a different operator
403 POD_IDENTITY_MISMATCH the projected token does not identify this pod
403 POD_NOT_LAUNCHED this machine login does not launch the pods of that service account
403 POD_SELF_ENROLL_NO_SESSION a pod enrolling itself states no session name
403 POD_SELF_ENROLL_ONLY a pod’s own machine login enrols that pod alone
403 POD_TOKEN_EXPIRED the projected token has expired
413 REQUEST_TOO_LARGE request body exceeds … bytes
403 SESSION_HELD another identity holds this session name

Revoke an enrollment made under the caller’s machine credential’s operator, or a pod enrollment the caller’s own machine credential made.

  • Credential: Machine credential
  • Path parameters: id
  • Success: 204, with no body
  • Source: packages/envoy/internal/broker/api/handlers_enroll.go:171

Refusals:

Status Code Message
400 ENROLLMENT_ID_INPUT enrollment ids are UUIDs
500 INTERNAL revoke enrollment failed
403 NOT_ENROLLING_CREDENTIAL only the machine login that made an enrollment carrying no operator may end it
403 OPERATOR_MISMATCH this launcher credential enrols a different operator

Log a service in with its own projected Kubernetes ServiceAccount token, which no person approves: answers its launcher credential’s id and expiry.

  • Credential: None
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_launcher.go:59

Request body (JSON):

Field Type Description
token string The service’s projected Kubernetes ServiceAccount token, whose audience is the broker’s (BROKER_K8S_OIDC_AUDIENCE) and whose account BROKER_SERVICE_LOGINS names.
request string A machine request object signed by the key the credential is bound to: a compact ES256 JWS carrying that public key, with one launcher_credential detail naming the account’s service.

Response body (JSON):

Field Type Description
credential_id string The launcher credential’s id, which every launcher proof the key signs names.
expires_at string (RFC 3339 time) When the credential expires: the token’s own expiry, at most an hour after the login. The service logs in again before then with its current token, and no person approves it.

Refusals:

Status Code Message
403 ACCOUNT_NOT_LISTED this ServiceAccount does not log in as this service
500 INTERNAL token login failed
400 INVALID_REQUEST body must be one valid JSON object: (varies)
409 KEY_HOLDS_LIVE_CREDENTIAL this machine login’s key already holds a live launcher credential
429 RATE_LIMITED too many launcher credential requests; try again later
too many token logins for this ServiceAccount; try again later
400 REQUEST_INVALID request object invalid
413 REQUEST_TOO_LARGE request body exceeds … bytes
401 TOKEN_EXPIRED the ServiceAccount token has expired
401 TOKEN_INVALID the ServiceAccount token does not verify
404 TOKEN_LOGIN_OFF this broker trusts no Kubernetes token issuer, so no service logs in with a token
401 TOKEN_TOO_LONG_LIVED the ServiceAccount token lives longer than an hour from its issue

Start a person’s machine login as an OAuth device authorization (RFC 8628): answers the device code the machine polls with and the code the person types. Rate-limited per source address and per person its login_hint names; mounted only when the broker runs its OAuth provider.

  • Credential: None
  • Source: packages/envoy/internal/broker/api/handlers_oauth.go:27

Exchange an approved device code, or a refresh token, for a DPoP-bound token pair naming the machine login’s launcher credential; mounted only when the broker runs its OAuth provider.

  • Credential: None
  • Source: packages/envoy/internal/broker/api/handlers_oauth.go:96

Read one of the broker’s access tokens back (RFC 7662): whether it is live and, when it is, the session, the person and the project it carries, and the key it is bound to. Mounted only when the broker runs its OAuth provider.

  • Credential: Caller ServiceAccount token
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_introspect.go:35

Response body (JSON):

Field Type Description
active boolean Active is whether the token is live: one of the broker’s access tokens, not revoked, not expired. Everything else is set only when it is.
session string or absent Session is the session the token’s writes are recorded as.
person string or absent Person is the person the session acts for.
service string or absent Service is the service a token that names no person was minted for: the fleet its machine belongs to, as BROKER_VM_SERVICES names it. A token names a person or a service and never both, so a reader that records who is answerable for a write reads Person, and one that decides what a machine may reach reads this.
machine string or absent Machine is the machine a person signed in on acted through: the Google account of the actor token the exchange was made with, as BROKER_VM_SERVICES lists it. It is set only beside Person, and only for a token a person’s sign-in on a machine minted; every other token names no machine. The token’s own RFC 8693 ‘act’ claim records the same delegation as the actor token’s issuer spells it, which is a Google subject rather than an account, so this is the name a reader can act on.
project_scope string or absent ProjectScope is what Project means: ProjectScopeNamed or ProjectScopePerson. A machine login’s own token, which no exchange decided for, carries neither this nor Project.
project string or absent Project is the one project key a ProjectScopeNamed token may write.
scope string or absent Scope is the OAuth scope the token was granted, space-separated as RFC 7662 spells it.
exp number or absent ExpiresAt is when the token expires, in seconds since the epoch (RFC 7662’s ‘exp’), so a caller that remembers an affirmative answer can keep it no longer than the token lives.
client_id string or absent ClientID is the OAuth client the token was issued to.
jkt string or absent Thumbprint is the JWK thumbprint of the key the token is DPoP-bound to (RFC 9449’s ‘jkt’). It is what makes a stolen token useless: whoever reads this answer checks the caller’s proof against it.

Refusals:

Status Code Message
503 DATABASE_UNAVAILABLE introspect token failed: a dependency the broker needs is unavailable
400 INVALID_REQUEST the form must parse and be at most … bytes
400 TOKEN_REQUIRED token is required

End one of the broker’s own access tokens (RFC 7009), an exchanged pod’s or workflow run’s included, which nothing else can end before its own expiry: the form names the token, the client it was issued to, and optionally which kind to look for first. It needs relaysDecisions rather than readsTokensBack because ending one of the broker’s tokens is the same authority class as ending a machine login or a grant, both already behind that power; a caller listed only to read a token back must not be able to end one. Mounted only when the broker runs its OAuth provider.

  • Credential: Caller ServiceAccount token
  • Source: packages/envoy/internal/broker/api/handlers_revoke.go:26

Register that one pod may exchange under one session name: the row the token exchange reads before it mints a token for a pod of an account that fixes no session, written by the deployment’s own daemon as it starts that pod. Mounted only when the broker runs its OAuth provider.

  • Credential: Service login ServiceAccount token
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_pod_sessions.go:66

Request body (JSON):

Field Type Description
pod_token string The pod’s own projected Kubernetes ServiceAccount token, whose audience is the broker’s (BROKER_K8S_OIDC_AUDIENCE): the very token the registrar hands POST /v1/enrollments as it enrolls that pod. It is what names the pod — its account is the token’s subject and its uid the kubernetes.io pod uid — so a registrar cannot register a pod whose token it does not hold, and nothing is taken on the registrar’s word but the session name.
session string The session name that pod may exchange under.

Response body (JSON):

Field Type Description
pod_uid string The uid of the pod the registration is for, read from the token the body carried.
session string The session name that pod may exchange under.
expires_at string (RFC 3339 time) When the broker stops honouring the registration unless it is written again.

Refusals:

Status Code Message
500 INTERNAL register the pod’s session name failed
400 INVALID_REQUEST body must be one valid JSON object: (varies)
403 POD_ACCOUNT_NOT_LISTED exchange: no entry lists this identity
403 POD_CALLER_IS_THE_POD this is the caller’s own pod, … of …, and a registrar registers a pod other than the one it runs in
400 POD_NOT_BOUND exchange: the subject token is bound to no pod
401 POD_TOKEN_EXPIRED the pod’s ServiceAccount token has expired
401 POD_TOKEN_INVALID the pod’s ServiceAccount token does not verify
413 REQUEST_TOO_LARGE request body exceeds … bytes
409 SESSION_FIXED exchange: the entry fixes the session name it writes under
400 SESSION_INVALID exchange: the request names an unusable session

Extend the calling session’s lease.

  • Credential: Renewal session proof
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_enroll.go:209

Request body (JSON):

Field Type Description
pod_token string or null A pod only: its current projected service-account token, which must name the pod UID and service account the enrollment was made for. Required once the enrollment has renewed with it (enroll.Service.Renew).

Response body (JSON):

Field Type Description
lease_expires_at string (RFC 3339 time) When the renewed lease lapses.

Refusals:

Status Code Message
400 ENROLLMENT_ID_INPUT enrollment ids are UUIDs
401 ENROLLMENT_NOT_LIVE enrollment is not live
the machine login that enrolled this session has ended
500 INTERNAL renew enrollment failed
400 INVALID_RENEWAL body must be one valid JSON object: (varies)
403 NOT_YOURS a session may only renew its own enrollment
403 POD_IDENTITY_MISMATCH the projected token does not identify this pod
403 POD_TOKEN_EXPIRED the projected token has expired
403 POD_TOKEN_REQUIRED a pod enrollment’s renewal must carry its projected token
413 REQUEST_TOO_LARGE request body exceeds … bytes

Describe the calling session’s enrollment and its live grants.

  • Credential: Session proof
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_enroll.go:251

Response body (JSON):

Field Type Description
enrollment_id string The session’s enrollment id.
grants array of objects The session’s live grants, soonest to expire first.
kind string “session” or “pod”.
lease_expires_at string (RFC 3339 time) When the session’s lease lapses unless it is renewed.
operator string or null The person the session runs for; null for a pod.
slot string or null The pod slot it holds; null for every enrollment without one.

Fields of grants[]:

Field Type Description
grant_id string The grant’s id, which the values and revoke routes take.
request_id string The request it answered.
approver string or null The login that approved it; null for a grant the policy gave automatically.
expires_at string (RFC 3339 time) or null When it expires; null for a grant that lasts until its session ends.

Refusals:

Status Code Message
500 INTERNAL read enrollment failed
read live grants failed
401 PROOF_INVALID enrollment is not live

Request secrets: the policy grants or denies them at once, or a person must approve them.

  • Credential: Session proof
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_requests.go:72

Request body (JSON):

Field Type Description
request string The session’s signed request: a compact ES256 JWS, signed with the session’s key and addressed to the broker’s public URL, naming the secrets (authorization_details) and the reason.
session_id string or null Optional: the Envoy session the broker notifies if this request expires undecided.

Response body (JSON):

Field Type Description
request_id string The request’s id, which GET /v1/requests/{id} and its cancel route take.
state string “granted”, “pending” (a person must decide it) or “denied”.
secrets array of objects How the policy decided each name asked for.
grant_id string or null Once granted, the grant to read the values from; null otherwise.
record_id string or null The credential-request record the approver decides; null when nobody needs to.
approver string or null Who decides it, as its record names them: a person’s login (their lowercase email), or “anyone” for a shared secret, which anyone signed in to the broker’s approval page may decide. While it is pending it is listed in that person’s Dispatch Inbox, or in everyone’s for “anyone”, linking approve_url. Null when the policy decided it at once, so no record names one.
lifetime_seconds number or null How many seconds its grant lasts, as the signed request asked; or, when it asked for none, 12 hours; null when every name is tagged default-grant=session and the grant lasts until the session ends. A reused or coalesced request answers its own, which equals the one asked for. A client that asked for a lifetime and reads null, or no such field from a broker before it, was not given that lifetime.
coalesced boolean or absent True when the request joined an identical one this session already had pending.
detail string or null The broker’s one sentence about why it denied the request: the secret that refused it and the rule that did, as “GCLOUD_SAMI: requester-not-listed”, several joined with “; “. Null when the broker recorded none — every request it did not deny, and a denial whose rule it cannot name. A session reads it to learn that asking for that secret again will not help.
approve_url string or absent While it is pending, its record’s page on the broker, <BROKER_PUBLIC_URL>/approve/<record>, where a person signed in to the broker decides it; absent when the policy decided it at once.

Fields of secrets[]:

Field Type Description
name string The secret’s name.
decision string “automatic”, “approval” or “deny”.

Refusals:

Status Code Message
400 GRANT_REQUIRES_LIFETIME a requested secret requires a shorter grant lifetime
500 INTERNAL create request failed
400 INVALID_REQUEST body must be one valid JSON object: (varies)
400 MIXED_APPROVERS the requested secrets need different approvers; request them separately
401 PROOF_INVALID enrollment is not live
400 REQUEST_INVALID request object invalid
409 REQUEST_PENDING a request for one or more of these secrets is already pending
413 REQUEST_TOO_LARGE request body exceeds … bytes
400 SESSION_ID_INPUT session id must be at most 128 characters with no whitespace or control character
400 UNKNOWN_SECRET no agent secret has this name

Read one of the calling session’s requests.

  • Credential: Session proof
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_requests.go:245

Response body (JSON):

Field Type Description
state string “pending”, “granted”, “denied”, “expired” or “cancelled”.
grant_id string or null Once granted, the grant to read the values from; null otherwise.
record_id string or null The credential-request record the approver decides; null when nobody needs to.
approver string or null Who decides it, as its record names them: a person’s login (their lowercase email), or “anyone” for a shared secret, which anyone signed in to the broker’s approval page may decide. The record fixes it when the request is made, so a later change to the secret’s owner does not move it, and it stays once the request is decided. Null when the policy decided it at once, so no record names one.
lifetime_seconds number or null How many seconds its grant lasts: as the signed request asked, else 15 minutes when any name is human tier and 12 hours otherwise; null for a grant that lasts until the session ends (every name tagged default-grant=session).
decided_at string (RFC 3339 time) or null When it left “pending”; null while pending.
decision object or null Who decided it and when; null while pending, when the policy decided it at once, and when it expired.
detail string or null The broker’s one sentence about why it denied the request, as POST /v1/requests answers it: the secret that refused it and the rule that did, as “GCLOUD_SAMI: requester-not-listed”. Null when the broker recorded none, a request it did not deny among them.
approve_url string or absent While it is pending, its record’s page on the broker, <BROKER_PUBLIC_URL>/approve/<record>, where a person signed in to the broker decides it; absent once it is no longer pending, and when the policy decided it at once.

Fields of decision:

Field Type Description
by string Who decided it: the approver’s login, “session:<enrollment id>” (the session cancelled it), “launcher:<credential id>” (its launcher ended the session, or its operator ended its machine login from one of their machines) or “broker” (the session’s lease lapsed).
at string (RFC 3339 time) When it was decided.

Refusals:

Status Code Message
500 INTERNAL read request failed
read request owner failed
404 NOT_FOUND no such request
403 NOT_YOURS this request belongs to another session
400 REQUEST_ID_INPUT request ids are UUIDs

Cancel one of the calling session’s pending requests.

  • Credential: Session proof
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_requests.go:280

Response body (JSON):

Field Type Description
state string The state the call left the request, record or grant in: “denied”, “cancelled” or “revoked”. Every writer of this body states one of the three as a literal (a denial, a cancel, a grant’s revoke and a launcher credential’s).

Refusals:

Status Code Message
500 INTERNAL cancel request failed
404 NOT_FOUND no such request
403 NOT_YOURS this request or grant belongs to another session
400 REQUEST_ID_INPUT request ids are UUIDs
409 REQUEST_TERMINAL request is already decided

Release a live grant’s values to the session that holds it.

  • Credential: Session proof
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_requests.go:303

Request body (JSON):

Field Type Description
github_app object or null Optional: narrows the token of every github-app secret the grant holds to one account’s installation and, within it, to named repositories, to read access, or to both. A grant that holds no github-app secret refuses it (NARROWING_NOT_APPLICABLE), one whose github-app secret does not list the account refuses it (NARROWING_OWNER_NOT_LISTED), and one holding a name behind the airlock refuses it (NARROWING_BEHIND_AIRLOCK), since the proxy spends that grant’s values and mints them owner-wide. It narrows this release alone: the grant’s next release without it is owner-wide.

Fields of github_app:

Field Type Description
owner string The one GitHub account whose installation the token is minted for: a login the secret’s github-owners lists, compared without regard to case. The answer names it lowercased.
repositories array of strings or absent Optional: names of the account’s repositories, without the account, at least one and at most 100: the token reaches those repositories alone, but keeps every permission the App holds, its organization permissions among them, unless read_only is set too. The answer names them sorted, each once.
read_only boolean or absent Optional: true limits the token to contents: read, metadata: read, pull_requests: read and actions: read, less the Actions grant the installation does not hold. A machine that must only read needs it, with or without repositories.

Response body (JSON):

Field Type Description
expires_at string (RFC 3339 time) or null When the grant expires; the session can read the values again until then. Null for a grant that lasts until the session ends.
values object of string values Each granted name’s value, read fresh from the secret store.
github_app object or null or absent The narrowing this release applied to its GitHub App tokens, absent when it applied none. A client that asked for one and finds none here is answered by a broker from before narrowing, which released the owner-wide tokens.
airlock object or null or absent How to reach the values this release did not hand over: the names the airlock holds back are not in “values” at all, and this says where the proxy that holds them is. Absent when no granted name is behind the airlock, which is every release of a broker with no rules file.

Fields of github_app:

Field Type Description
owner string The one GitHub account whose installation the token is minted for: a login the secret’s github-owners lists, compared without regard to case. The answer names it lowercased.
repositories array of strings or absent Optional: names of the account’s repositories, without the account, at least one and at most 100: the token reaches those repositories alone, but keeps every permission the App holds, its organization permissions among them, unless read_only is set too. The answer names them sorted, each once.
read_only boolean or absent Optional: true limits the token to contents: read, metadata: read, pull_requests: read and actions: read, less the Actions grant the installation does not hold. A machine that must only read needs it, with or without repositories.

Fields of airlock:

Field Type Description
proxy_url string The URL the command points HTTPS_PROXY at (BROKER_AIRLOCK_PROXY_URL).
ca_certificate string The PEM certificate the command must trust to reach the proxy, which signs a certificate for each host it carries a request to.
proxy_password string The password every proxy connection authenticates with: one of this grant’s own placeholders, the first (name, slot) pair in sorted order.
names object of object values Each name the airlock holds back, by the name the command knows it as.

Fields of airlock.names:

Field Type Description
value string What the command’s environment variable of that name holds: the released shape of the secret with a placeholder wherever its value would be.
slots object of string values Each slot of the secret and the placeholder standing for it, so a command that takes one value of the secret apart knows which placeholder is which.
hosts array of strings The hosts the secret’s airlock entry allows, which the command keeps off its NO_PROXY so their requests reach the proxy.

Refusals:

Status Code Message
403 GRANT_CHAIN_INVALID this grant’s approval chain no longer verifies
400 GRANT_ID_INPUT grant ids are UUIDs
403 GRANT_NOT_LIVE grant is expired, revoked, or its session ended
500 INTERNAL read grant values failed
400 INVALID_NARROWING body must be one valid JSON object: (varies)
github_app.… …
502 MINT_FAILED the broker could not mint a token from this secret
409 NARROWING_BEHIND_AIRLOCK this grant holds a name behind the airlock, which takes no narrowing
400 NARROWING_NOT_APPLICABLE this grant holds no github-app secret to narrow
403 NARROWING_OWNER_NOT_LISTED the narrowing names an account a github-app secret of this grant does not serve
422 NARROWING_REFUSED the narrowing cannot be served
403 NOT_YOURS this request or grant belongs to another session
413 REQUEST_TOO_LARGE request body exceeds … bytes
404 SECRET_NOT_IN_STORE secret is not in the secrets store

End a grant the calling session holds.

  • Credential: Session proof
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_requests.go:390

Response body (JSON):

Field Type Description
state string The state the call left the request, record or grant in: “denied”, “cancelled” or “revoked”. Every writer of this body states one of the three as a literal (a denial, a cancel, a grant’s revoke and a launcher credential’s).

Refusals:

Status Code Message
400 GRANT_ID_INPUT grant ids are UUIDs
500 INTERNAL revoke grant failed
404 NOT_FOUND no such grant
403 NOT_YOURS this request or grant belongs to another session

List the pending requests and machine logins the named person decides, each secret request with the broker’s own page where that person decides it.

  • Credential: Caller ServiceAccount token
  • Query parameters: approver
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_pending.go:19

Response body (JSON):

Field Type Description
pending array of objects The undecided records the named person decides, newest first.

Fields of pending[]:

Field Type Description
record_id string The credential-request record’s id, which the approval page and the machine login’s approve and deny routes take.
kind string “agent_secret” (a secret request) or “launcher_credential” (a machine login).
identifiers array of strings The secrets a secret request asks for, or the machine a machine login names.
requested_at string (RFC 3339 time) When it was asked.
session object The session this request names, read independently of each other (LEGION-587); see RecordSessionResp, this row’s exact shape here too.
approve_url string or absent A secret request’s page on the broker, <BROKER_PUBLIC_URL>/approve/<record>, where a person signed in to the broker decides it; absent for a machine login, which is decided with its typed code.

Fields of pending[].session:

Field Type Description
request string or null The id the request itself stated as an unsigned override in its body; null when it named none.
enrollment string or null The id the requesting session’s own enrollment stated when it enrolled; null when it named none, or for a machine login, which has no requesting enrollment at all.

Refusals:

Status Code Message
400 APPROVER_REQUIRED approver is required
500 INTERNAL list pending requests failed

POST /v1/credential-requests/{record}/approve

Section titled “POST /v1/credential-requests/{record}/approve”

Approve a pending machine login as the named person, who must type its code: the broker takes the person’s name on the caller’s word, from a listed service’s ServiceAccount token, which the handler verifies itself — and whose name must carry relaysDecisions, the power this row declares — after it has read the record’s kind. It reads the kind first because a secret request is refused before any caller is looked at: it is decided only on the broker’s own page by a person signed in to the broker, so this answers 403 DECIDE_ON_BROKER_PAGE naming that page to every caller, token or none, writing nothing.

  • Credential: Caller ServiceAccount token, checked by the handler
  • Path parameters: record
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_records.go:77

Request body (JSON):

Field Type Description
approver string The login of the person deciding, which the record’s rule must admit: its approver, or anyone signed in for the denial of a service’s machine login.
code string or null The machine login’s confirmation code, typed again.

Response body (JSON):

Field Type Description
credential_id string or null The machine credential the approval minted.
state string “approved”: decideMachineLogin writes this body for an approval alone (handlers_ui_records.go), and a denial answers StateResponse instead.

Refusals:

Status Code Message
403 ACCOUNT_LACKS_POWER the caller … may not call …
403 ACCOUNT_NOT_LISTED this account is not in BROKER_API_ACCOUNTS
401 ACCOUNT_TOKENS_OFF this broker trusts no token issuer for its API callers, so no caller authenticates with an identity token
401 ACCOUNT_TOKEN_EXPIRED the caller’s identity token has expired
401 ACCOUNT_TOKEN_INVALID the Google identity token names no verified account, so it names no caller (ask for it with format=full)
the caller’s identity token does not verify
the caller’s token names no issuer this broker trusts for its API callers
401 ACCOUNT_TOKEN_TOO_LONG_LIVED the caller’s identity token lives longer than an hour from its issue
the caller’s identity token names no issue time (iat)
400 APPROVER_REQUIRED approver is required
403 CODE_MISMATCH confirmation code does not match
400 CODE_REQUIRED code is required to decide a machine login
403 DECIDE_ON_BROKER_PAGE a secret request is decided only on the secrets broker’s own page, by a person signed in to the broker: …
500 INTERNAL decide machine login failed
read credential request kind failed
400 INVALID_DECISION body must be one valid JSON object: (varies)
409 KEY_HOLDS_LIVE_CREDENTIAL this machine login’s key already holds a live launcher credential
403 NOT_APPROVER only the record’s approver may decide it
404 NOT_FOUND no such credential request
429 RATE_LIMITED too many requests with a ServiceAccount token; try again later
400 RECORD_ID_INPUT record ids are lowercase-hex sha256 hashes
409 RECORD_TERMINAL this machine login expired before its approver acted on it
this machine login has already been decided
413 REQUEST_TOO_LARGE request body exceeds … bytes
400 SERVICE_LOGS_IN_WITH_TOKEN a service logs in with its own projected Kubernetes ServiceAccount token at POST /v1/launcher-credentials/token, never through a code a person approves

The DECIDE_ON_BROKER_PAGE refusal’s body (JSON):

Field Type Description
code string The refusal’s code.
error string What was refused, naming the page.
approve_url string The record’s page on the broker’s own approval page, <BROKER_PUBLIC_URL>/approve/<record>.

POST /v1/credential-requests/{record}/deny

Section titled “POST /v1/credential-requests/{record}/deny”

Deny a pending machine login as the named person, who must type its code, on the caller’s word and behind the same authentication and the same power as approve. A secret request is refused the same way, writing nothing, with 403 DECIDE_ON_BROKER_PAGE naming the page where a person signed in to the broker denies it.

  • Credential: Caller ServiceAccount token, checked by the handler
  • Path parameters: record
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_records.go:81

Request body (JSON):

Field Type Description
approver string The login of the person deciding, which the record’s rule must admit: its approver, or anyone signed in for the denial of a service’s machine login.
code string or null The machine login’s confirmation code, typed again.

Response body (JSON):

Field Type Description
state string The state the call left the request, record or grant in: “denied”, “cancelled” or “revoked”. Every writer of this body states one of the three as a literal (a denial, a cancel, a grant’s revoke and a launcher credential’s).

Refusals:

Status Code Message
403 ACCOUNT_LACKS_POWER the caller … may not call …
403 ACCOUNT_NOT_LISTED this account is not in BROKER_API_ACCOUNTS
401 ACCOUNT_TOKENS_OFF this broker trusts no token issuer for its API callers, so no caller authenticates with an identity token
401 ACCOUNT_TOKEN_EXPIRED the caller’s identity token has expired
401 ACCOUNT_TOKEN_INVALID the Google identity token names no verified account, so it names no caller (ask for it with format=full)
the caller’s identity token does not verify
the caller’s token names no issuer this broker trusts for its API callers
401 ACCOUNT_TOKEN_TOO_LONG_LIVED the caller’s identity token lives longer than an hour from its issue
the caller’s identity token names no issue time (iat)
400 APPROVER_REQUIRED approver is required
403 CODE_MISMATCH confirmation code does not match
400 CODE_REQUIRED code is required to decide a machine login
403 DECIDE_ON_BROKER_PAGE a secret request is decided only on the secrets broker’s own page, by a person signed in to the broker: …
500 INTERNAL decide machine login failed
read credential request kind failed
400 INVALID_DECISION body must be one valid JSON object: (varies)
409 KEY_HOLDS_LIVE_CREDENTIAL this machine login’s key already holds a live launcher credential
403 NOT_APPROVER only the record’s approver may decide it
404 NOT_FOUND no such credential request
429 RATE_LIMITED too many requests with a ServiceAccount token; try again later
400 RECORD_ID_INPUT record ids are lowercase-hex sha256 hashes
409 RECORD_TERMINAL this machine login expired before its approver acted on it
this machine login has already been decided
413 REQUEST_TOO_LARGE request body exceeds … bytes
400 SERVICE_LOGS_IN_WITH_TOKEN a service logs in with its own projected Kubernetes ServiceAccount token at POST /v1/launcher-credentials/token, never through a code a person approves

The DECIDE_ON_BROKER_PAGE refusal’s body (JSON):

Field Type Description
code string The refusal’s code.
error string What was refused, naming the page.
approve_url string The record’s page on the broker’s own approval page, <BROKER_PUBLIC_URL>/approve/<record>.

Find a pending machine login by the confirmation code its machine shows.

  • Credential: Caller ServiceAccount token
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_machinelogin.go:30

Request body (JSON):

Field Type Description
code string The XXXX-XXXX confirmation code the machine shows, as the approver typed it.

Response body (JSON):

Field Type Description
record_id string The record’s id: the SHA-256 of its text.
kind string “agent_secret” (a secret request) or “launcher_credential” (a machine login).
state string “pending”, or how it ended: “approved”, “denied”, “expired”, “cancelled” or “revoked”.
approver string The one login that may decide it, or “anyone”: any signed-in person may decide a request for a shared human-tier secret, and may deny a service’s machine login (which only a broker before token logins opened, and which nobody approves any more), whatever person it names.
enrollment object or null The session asking; null for a machine login.
session object The session this record names, read independently of each other; null fields where that id was never set (LEGION-587).
identifiers array of strings The secrets asked for, or the machine logging in.
service string or null The service a machine login is for, when it logs a service in rather than a person’s machine; null otherwise.
reason string The reason the session gave, verbatim.
lifetime_seconds number or null How many seconds the grant or credential lasts once approved; null for a grant that lasts until its session ends. A machine login’s credential always names one.
rules_version string The version of the secret policy it was decided under (the SHA-256 of every agent secret’s name, owner, tier and ARN); for a machine login, the version current when it was asked.
expires_at string (RFC 3339 time) When it expires undecided.
requested_at string (RFC 3339 time) When it was asked.
decided object or null How it was decided; null while pending.

Fields of enrollment:

Field Type Description
kind string “session” (an agent session on a machine its person logged in) or “pod”.
runtime_id string The session’s runtime: whatever names the session on its machine (the helper’s host:pid:start time, a container’s id), or a pod’s UID.
operator string The person the session runs for; empty for a pod.
slot string or null The pod slot it holds; null for every enrollment without one.

Fields of session:

Field Type Description
request string or null The id the request itself stated as an unsigned override in its body; null when it named none.
enrollment string or null The id the requesting session’s own enrollment stated when it enrolled; null when it named none, or for a machine login, which has no requesting enrollment at all.

Fields of decided:

Field Type Description
event string “approved”, “denied”, “expired” or “cancelled”.
at string (RFC 3339 time) When it was decided.
credential_id string or null The machine credential an approved machine login minted; null otherwise.

Refusals:

Status Code Message
400 CODE_REQUIRED code is required
500 INTERNAL lookup machine login failed
read credential request failed
400 INVALID_LOOKUP body must be one valid JSON object: (varies)
404 NO_SUCH_CODE no pending machine login has this code
413 REQUEST_TOO_LARGE request body exceeds … bytes

List the machine logins the named person may revoke, every service’s, such as the Legion daemon’s, whoever approved it, and the person’s own machines’, that are not revoked and are unexpired or expired with a session still running, each naming who approved it. The person is the one the caller names.

  • Credential: Caller ServiceAccount token
  • Query parameters: approver
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_machinelogin.go:64

Response body (JSON):

Field Type Description
credentials array of objects The machine logins that are not revoked and are either unexpired or expired with a session still running, newest first: every service’s and the named person’s own machines’ on Dispatch’s page, the calling operator’s own machines’ alone on the operator route.

Fields of credentials[]:

Field Type Description
credential_id string The launcher credential’s id, which the revoke route takes.
host string The machine’s host name, as a person’s machine login named it; for a token login, its ServiceAccount’s <namespace>/<name>, or a machine’s <project>/<zone>/<instance name>.
service string or null The service a service’s login is for (legion-daemon), whose sessions are its pods, or the fleet a machine belongs to; null for a person’s own machine.
subject string or null What a token login proved: its ServiceAccount (system:serviceaccount:<namespace>:<name>), or a machine as the token exchange names it (exchange.MachineSubject); null for every login a person approved.
approved_by string The login of the person who approved the login (anyone signed in may have approved a service’s login before token logins); “” for a service’s token login, which no person approves.
issued_at string (RFC 3339 time) When the login was approved, or a token login’s first login.
expires_at string (RFC 3339 time) When the credential expires. Each refresh of a person’s device login moves it to BROKER_LAUNCHER_CREDENTIAL_SECONDS from then; a login through the earlier route never refreshes; a token login moves it later at each login, at most an hour away.
expired boolean True once expires_at has passed: the login enrolls nothing more, but sessions it enrolled still run (each renews with its own key), and revoking it ends them.

Refusals:

Status Code Message
400 APPROVER_REQUIRED approver is required
500 INTERNAL list launcher credentials failed

POST /v1/launcher-credentials/{id}/revoke-by-approver

Section titled “POST /v1/launcher-credentials/{id}/revoke-by-approver”

End a machine login, expired or not: a service’s (its token login, or one a person approved before token logins) as anyone signed in, a person’s machine’s as the person who approved it. Its launcher proofs stop authenticating and every session it enrolled, pods included, ends with its grants and pending requests. The revoking person is the login the request names, which the broker still takes on the caller’s word: a revoke only takes power away.

  • Credential: Caller ServiceAccount token
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_machinelogin.go:96

Request body (JSON):

Field Type Description
approver string The login of the person revoking: for a grant, its approver or its session’s operator; for a person’s machine login, the person who approved it; for a service’s machine login, anyone signed in.

Response body (JSON):

Field Type Description
state string The state the call left the request, record or grant in: “denied”, “cancelled” or “revoked”. Every writer of this body states one of the three as a literal (a denial, a cancel, a grant’s revoke and a launcher credential’s).

Refusals:

Status Code Message
400 APPROVER_REQUIRED approver is required
400 CREDENTIAL_ID_INPUT launcher credential ids are UUIDs
500 INTERNAL revoke launcher credential failed
400 INVALID_REVOKE body must be one valid JSON object: (varies)
403 NOT_APPROVER only the person who approved a person’s machine login may revoke it
404 NOT_FOUND no such machine login
413 REQUEST_TOO_LARGE request body exceeds … bytes

List the live grants of the named person’s sessions, automatic or approved, and those the person approved. The person is the one the caller names.

  • Credential: Caller ServiceAccount token
  • Query parameters: approver
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_grants.go:26

Response body (JSON):

Field Type Description
grants array of objects The live grants of the named person’s sessions, and those the person approved, newest first.

Fields of grants[]:

Field Type Description
grant_id string The grant’s id, which the revoke route takes.
granted string How the session got it: “automatic” (the policy gave it without asking) or “approval”. GrantsForApprover writes one of the two (machine_read.go, policy.Automatic and policy.Approval), so it is a closed set.
record_id string or null The credential-request record its approval rests on; null for an automatic grant.
enrollment object The session holding it.
names array of strings The secrets it covers.
approver string or null The person who approved it; null for an automatic grant. It is who the grant belongs to, which is what the revoke rule reads (requests.Machine.RevokeByApprover’s mayRevoke), and for a grant the model judge approved it is the record’s own approver — the owner, or “anyone” for a shared secret — in whose stead the judge decided. Who decided it is DecidedBy, which for such a grant is the judge and not this person.
decided_by string or null Who decided the request this grant rests on (requests.decided_by): the approving person’s own login, or “judge:<model id>” where the model judge approved it. Null for an automatic grant, which nobody decided. A page that says who decided a grant says this, so that a judged grant names the judge rather than a person who never saw the request.
expires_at string (RFC 3339 time) or null When it expires; null for a grant that lasts until its session ends.
created_at string (RFC 3339 time) When it was granted.

Fields of grants[].enrollment:

Field Type Description
kind string “session” (an agent session on a machine its person logged in) or “pod”.
runtime_id string The session’s runtime: whatever names the session on its machine (the helper’s host:pid:start time, a container’s id), or a pod’s UID.
operator string The person the session runs for; empty for a pod.
slot string or null The pod slot it holds; null for every enrollment without one.

Refusals:

Status Code Message
400 APPROVER_REQUIRED approver is required
500 INTERNAL list grants for approver failed

End a grant as its approver or as its enrollment’s operator. The operator’s revoke also withholds the secrets the grant’s request got automatically from that session, even when the grant had already ended: its other grants that got them automatically end too, and it asks before it gets them again. The revoking person is the login the request names, which the broker still takes on the caller’s word: a revoke only takes power away.

  • Credential: Caller ServiceAccount token
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_grants.go:73

Request body (JSON):

Field Type Description
approver string The login of the person revoking: for a grant, its approver or its session’s operator; for a person’s machine login, the person who approved it; for a service’s machine login, anyone signed in.

Response body (JSON):

Field Type Description
state string The state the call left the request, record or grant in: “denied”, “cancelled” or “revoked”. Every writer of this body states one of the three as a literal (a denial, a cancel, a grant’s revoke and a launcher credential’s).

Refusals:

Status Code Message
400 APPROVER_REQUIRED approver is required
400 GRANT_ID_INPUT grant ids are UUIDs
500 INTERNAL revoke grant failed
400 INVALID_REVOKE body must be one valid JSON object: (varies)
403 NOT_APPROVER only the grant’s approver or its enrollment’s operator may revoke it
404 NOT_FOUND no such grant
413 REQUEST_TOO_LARGE request body exceeds … bytes

List the sign-ins the named person made on their machines with agent-secrets login, by machine: one row per box key they signed in with and have not revoked, each naming the machine’s Google account, the box key’s thumbprint, and when the sign-in was made and last used. The person is the one the caller names, as it is for every list Dispatch’s server relays.

  • Credential: Caller ServiceAccount token
  • Query parameters: person
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_personsignin.go:36

Response body (JSON):

Field Type Description
signins array of objects The named person’s live sign-ins, newest first: one per box key they signed in with and have not revoked. A revoked sign-in is not listed, and neither is anyone else’s.

Fields of signins[]:

Field Type Description
signin_id string The sign-in’s id, which the revoke route takes.
machine string The Google account of the machine the person signed in on, as BROKER_VM_SERVICES lists it: the fleet’s account, which is what names the box on the person’s own list.
thumbprint string The RFC 7638 thumbprint of the box key the sign-in is bound to, which is what tells this sign-in from the person’s others on the same fleet. It is public: it is what the box’s DPoP proofs already carry.
created_at string (RFC 3339 time) When the person first signed in on that box.
last_used_at string (RFC 3339 time) When that box last traded a token under this sign-in.

Refusals:

Status Code Message
500 INTERNAL list person sign-ins failed
400 PERSON_REQUIRED person is required

End one of the named person’s sign-ins: that box key trades no token at the exchange again, and every access token already minted on that box under that sign-in ends in the revoke’s own transaction, so none of them outlives it. The person’s sign-ins on their other boxes keep working, and the box signs in again with a key of its own. The revoking person is the one the request names, which the broker takes on the caller’s word: a revoke only takes power away.

  • Credential: Caller ServiceAccount token
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_ui_personsignin.go:66

Request body (JSON):

Field Type Description
person string The login of the person whose sign-in it is. A sign-in of anyone else is answered as an unknown id.

Response body (JSON):

Field Type Description
state string Always “revoked”: the sign-in no longer trades, whether this call ended it or an earlier one did.
tokens_ended number How many of that person’s live access tokens this call ended with it, the ones minted on that box under that sign-in. It is 0 for a sign-in revoked already, and for one whose tokens had all expired.

Refusals:

Status Code Message
500 INTERNAL revoke person sign-in failed
400 INVALID_REVOKE body must be one valid JSON object: (varies)
404 NOT_FOUND no sign-in of that person has that id
400 PERSON_REQUIRED person is required
413 REQUEST_TOO_LARGE request body exceeds … bytes
400 SIGNIN_ID_INPUT sign-in ids are UUIDs

Read the broker’s grant log for one closed period, for a quarterly access review: every audit row between ?since and ?until (RFC 3339, half-open), oldest first, each naming the session that wrote it, that session’s person, the service the broker verified, the secrets asked for and their decisions, the state, the approver and who decided. ?after resumes after an id and ?limit bounds the page, which is a thousand rows at most. ?until must be at least an hour before now, so that every row of the period has committed before the review reads it. It is the one route readsGrantLog reaches: the review reads the broker’s record and changes nothing in it.

  • Credential: Caller ServiceAccount token
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_grantlog.go:59

Response body (JSON):

Field Type Description
events array of objects The period’s audit rows this page holds, oldest first (audit id order, which is write order), each as store.GrantLogEvent’s own JSON tags name it; an empty array when the page holds none.
next_after number The id to send as ?after= for the next page: the last event’s id, or the request’s own ?after= when this page is empty, so a caller that has reached the end of the period and asks again reads the same empty page rather than starting the period over.

Fields of events[]:

Field Type Description
id number ID is the audit row’s own id, which is also the paging cursor: GrantLogQuery.After.
at string (RFC 3339 time) At is when the row was written.
kind string Kind is the audit row’s kind, request.created and grant.revoked among them.
actor string Actor is who the broker recorded as having done it: session:<enrollment id>, human:<login>, launcher:<credential id>, or the broker itself.
enrollment_id string EnrollmentID is the enrollment the row names, empty for a row that names none (every launcher_credential.* kind).
request_id string RequestID is the request the row names, empty for a row that names none (a grant.moved row, and every enrollment and launcher-credential kind).
grant_id string GrantID is the grant the row names, empty for a row that names none.
session object Session is the enrollment’s own identity: its kind, its session id and its runtime id.
person string Person is the operator the enrollment acts for (enrollments.operator), empty for a pod and for a machine’s own session.
service string Service is the service the broker VERIFIED for the request, read from the request’s own request.created audit row: the service account the enrollment proved still named this service when the request was decided. It is what the policy decided under.
claimed_service string ClaimedService is the service the enrollment’s launcher credential claims (launcher_credentials.service), which the policy does NOT trust: a credential may name a service whose account no longer proves it, and then Service is empty while this is not. The review reads the pair to see a claim the broker refused.
secrets array of objects Secrets are the request’s secrets and each one’s decision, in name order; empty for a row that names no request.
state string State is the request’s state now: pending, granted, denied, cancelled or expired.
approver string Approver is the login the request’s record allows to decide it (requests.allowed_approver), or record.AnyoneApprover for a shared secret; empty when the policy decided the request itself.
decided_by string DecidedBy is who the request records as having decided it: the approving login, the broker for an expiry, requests.PolicyDeniedActor for a denial the policy made under an approval.
lifetime_seconds number LifetimeSeconds is how long the request’s grant lasts; zero when the request records none, which is a grant that lasts until its session ends.
reason string Reason is the reason the session signed into its request object, the sentence a person reads on the approval page.
detail string Detail is the request’s decision_detail: why it was denied, or why an approval granted nothing.
raw any JSON value Raw is the audit row’s own detail object, verbatim, so a kind this type says nothing about is still readable.

Fields of events[].session:

Field Type Description
kind string Kind is the enrollment’s kind, session or pod.
session_id string SessionID is the Envoy session the enrollment recorded, empty for an enrollment that names none (a pod enrolled before its session had a name, say).
runtime_id string RuntimeID is what the enrollment proved it runs on: the box’s id for a session, the pod’s UID for a pod.

Fields of events[].secrets[]:

Field Type Description
name string Name is the secret’s name as the session asked for it.
decision string Decision is the request_secrets row’s decision: automatic, approval or deny.

Refusals:

Status Code Message
500 INTERNAL read the grant log failed
400 PAGE_INVALID after is not a whole number of zero or more
limit is not a whole number of zero or more
400 PERIOD_INVALID 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
400 PERIOD_NOT_CLOSED until must be at least an hour before now, so that every row of the period has committed before the review reads it
400 PERIOD_REQUIRED 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

List the logins of the calling credential’s operator’s own machines, as Dispatch’s machine-logins page lists them, without the service logins that page also lists.

  • Credential: Machine credential
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_operator.go:33

Response body (JSON):

Field Type Description
credentials array of objects The machine logins that are not revoked and are either unexpired or expired with a session still running, newest first: every service’s and the named person’s own machines’ on Dispatch’s page, the calling operator’s own machines’ alone on the operator route.

Fields of credentials[]:

Field Type Description
credential_id string The launcher credential’s id, which the revoke route takes.
host string The machine’s host name, as a person’s machine login named it; for a token login, its ServiceAccount’s <namespace>/<name>, or a machine’s <project>/<zone>/<instance name>.
service string or null The service a service’s login is for (legion-daemon), whose sessions are its pods, or the fleet a machine belongs to; null for a person’s own machine.
subject string or null What a token login proved: its ServiceAccount (system:serviceaccount:<namespace>:<name>), or a machine as the token exchange names it (exchange.MachineSubject); null for every login a person approved.
approved_by string The login of the person who approved the login (anyone signed in may have approved a service’s login before token logins); “” for a service’s token login, which no person approves.
issued_at string (RFC 3339 time) When the login was approved, or a token login’s first login.
expires_at string (RFC 3339 time) When the credential expires. Each refresh of a person’s device login moves it to BROKER_LAUNCHER_CREDENTIAL_SECONDS from then; a login through the earlier route never refreshes; a token login moves it later at each login, at most an hour away.
expired boolean True once expires_at has passed: the login enrolls nothing more, but sessions it enrolled still run (each renews with its own key), and revoking it ends them.

Refusals:

Status Code Message
500 INTERNAL list launcher credentials failed
403 SERVICE_CREDENTIAL a service’s machine login has no operator; machine and grant commands are a person’s

End one of the operator’s own machines’ logins: no proof signed with it authenticates again and every session it enrolled ends. A service’s login is not found here, like an unknown id. Revoking the calling machine’s own login ends this machine’s access.

  • Credential: Machine credential
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_operator.go:45

Response body (JSON):

Field Type Description
state string The state the call left the request, record or grant in: “denied”, “cancelled” or “revoked”. Every writer of this body states one of the three as a literal (a denial, a cancel, a grant’s revoke and a launcher credential’s).

Refusals:

Status Code Message
400 CREDENTIAL_ID_INPUT launcher credential ids are UUIDs
500 INTERNAL revoke launcher credential failed
403 NOT_APPROVER only the person who approved a person’s machine login may revoke it
404 NOT_FOUND no such machine login
403 SERVICE_CREDENTIAL a service’s machine login has no operator; machine and grant commands are a person’s

Retire the calling machine login after its machine logged in again: it enrolls nothing more and cannot refresh, while the sessions it enrolled keep running and renewing with their own keys. {id} must be the calling credential.

  • Credential: Machine credential
  • Path parameters: id
  • Success: 204, with no body
  • Source: packages/envoy/internal/broker/api/handlers_operator.go:62

Refusals:

Status Code Message
400 CREDENTIAL_ID_INPUT launcher credential ids are UUIDs
500 INTERNAL retire launcher credential failed
403 NOT_CALLING_CREDENTIAL a machine login retires only itself: sign with the credential the path names

List the live grants of the operator’s sessions and those the operator approved, exactly as Dispatch’s Live grants page lists them.

  • Credential: Machine credential
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_operator.go:80

Response body (JSON):

Field Type Description
grants array of objects The live grants of the named person’s sessions, and those the person approved, newest first.

Fields of grants[]:

Field Type Description
grant_id string The grant’s id, which the revoke route takes.
granted string How the session got it: “automatic” (the policy gave it without asking) or “approval”. GrantsForApprover writes one of the two (machine_read.go, policy.Automatic and policy.Approval), so it is a closed set.
record_id string or null The credential-request record its approval rests on; null for an automatic grant.
enrollment object The session holding it.
names array of strings The secrets it covers.
approver string or null The person who approved it; null for an automatic grant. It is who the grant belongs to, which is what the revoke rule reads (requests.Machine.RevokeByApprover’s mayRevoke), and for a grant the model judge approved it is the record’s own approver — the owner, or “anyone” for a shared secret — in whose stead the judge decided. Who decided it is DecidedBy, which for such a grant is the judge and not this person.
decided_by string or null Who decided the request this grant rests on (requests.decided_by): the approving person’s own login, or “judge:<model id>” where the model judge approved it. Null for an automatic grant, which nobody decided. A page that says who decided a grant says this, so that a judged grant names the judge rather than a person who never saw the request.
expires_at string (RFC 3339 time) or null When it expires; null for a grant that lasts until its session ends.
created_at string (RFC 3339 time) When it was granted.

Fields of grants[].enrollment:

Field Type Description
kind string “session” (an agent session on a machine its person logged in) or “pod”.
runtime_id string The session’s runtime: whatever names the session on its machine (the helper’s host:pid:start time, a container’s id), or a pod’s UID.
operator string The person the session runs for; empty for a pod.
slot string or null The pod slot it holds; null for every enrollment without one.

Refusals:

Status Code Message
500 INTERNAL list grants for approver failed
403 SERVICE_CREDENTIAL a service’s machine login has no operator; machine and grant commands are a person’s

End a grant as the operator: the revocation Dispatch’s page offers, which also withholds an automatic grant’s secrets from its session.

  • Credential: Machine credential
  • Path parameters: id
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_operator.go:92

Response body (JSON):

Field Type Description
state string The state the call left the request, record or grant in: “denied”, “cancelled” or “revoked”. Every writer of this body states one of the three as a literal (a denial, a cancel, a grant’s revoke and a launcher credential’s).

Refusals:

Status Code Message
400 GRANT_ID_INPUT grant ids are UUIDs
500 INTERNAL revoke grant failed
403 NOT_APPROVER only the grant’s approver or its enrollment’s operator may revoke it
404 NOT_FOUND no such grant
403 SERVICE_CREDENTIAL a service’s machine login has no operator; machine and grant commands are a person’s

The namespace prefix, AWS account and region, and the agent-secrets key the broker serves: what the CLI needs so it and the broker never disagree about which secret a name means.

  • Credential: None
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_secrets.go:42

Response body (JSON):

Field Type Description
store string Which secret store holds the agent secrets: aws-secrets-manager or gcp-secret-manager. A broker from before this field answered none, and held them in AWS Secrets Manager.
secrets_prefix string The name prefix every agent secret sits under: with aws-secrets-manager a Secrets Manager name prefix ending in “/”, with gcp-secret-manager projects/<project number>/secrets/<secret-id prefix>-. The secret a session asks for as DEEL_API_KEY is <prefix>deel-api-key.
kms_key_arn string or absent With aws-secrets-manager, the ARN of the agent-secrets KMS key, which every agent secret must be encrypted with; absent otherwise.
aws_account_id string or absent With aws-secrets-manager, the AWS account the agent-secrets key, and so every agent secret, is in; absent otherwise.
aws_region string or absent With aws-secrets-manager, the AWS region the agent-secrets key, and so every agent secret, is in; absent otherwise.
kms_key_name string or absent With gcp-secret-manager, the resource name of the agent-secrets Cloud KMS key, which every agent secret must be encrypted with; absent otherwise.
gcp_project string or absent With gcp-secret-manager, the number of the project every agent secret is in, the one the prefix names; absent otherwise.

Reread one secret from Secrets Manager now, as whoever just wrote it may ask: the change is served, or gone, within seconds instead of at the next five-minute reload. Open to any caller, and rate-limited both per source address and over every caller at once, since each reread holds the policy’s writer lock while it reads; a reread only moves the broker toward AWS’s own truth.

  • Credential: None
  • Path parameters: name
  • Success: 200
  • Source: packages/envoy/internal/broker/api/handlers_secrets.go:82

Response body (JSON):

Field Type Description
name string The name reread, as the path gave it.
served boolean True when the broker now serves the secret.
reason string or absent Why the broker does not serve it, absent when it does: “absent” (no secret under the prefix carries the name, or the one that does is scheduled for deletion) or the reason the broker refused the secret, the one its policy-refused log line names (such as owner-tag-missing or no-current-value).
kind string or absent The kind the broker serves the secret as, while it serves it: github-app or google-login, or absent for a stored secret, whose grants release its stored value. A person who retags a secret to a kind reads it here before storing the key the kind mints from: a stale store read can still serve the secret as stored.

Refusals:

Status Code Message
500 INTERNAL reread secret failed
429 RATE_LIMITED too many secret rereads; try again later
503 REQUEST_ENDED the request ended before the reread finished; send it again
400 SECRET_NAME_INVALID not an agent secret name

A person’s approval page for one credential-request record, an HTML page on the broker’s own origin: the record’s secret names, the enrollment the broker verified asked, the session the request claims when it differs, the reason (empty only on a record an older build wrote before the broker required one wherever a person decides), the lifetime, the times, the approver and the state, one sentence saying what Approve gives and for how long and one saying what Deny and the expiry do, then every other pending secret request the person may decide, with Approve and Deny for each the person may decide and one “Approve all N shown”. It checks the id’s shape, then the session, then the record: a malformed id is a 400, a request with no live session, or with ?switch=1, redirects to the sign-in provider and returns here, and only a signed-in person’s unknown record id is a 404. A machine login’s record is printed with the note that it is decided by typing its code on Dispatch’s machine-login page, and offers no button.

  • Credential: Broker sign-in session
  • Path parameters: record
  • Query parameters: decided, switch
  • Answers (an HTML page, or a redirect): 200, 302, 400, 404, 500, 503
  • Source: packages/envoy/internal/broker/approval/page.go:113

Decide records an approval page printed, as the signed-in person: a form from the broker’s own origin only, carrying the page’s token, which binds it to the session and to exactly the records the page printed with a button, for an hour. Each record is decided on its own, and the answer is a 303 back to the page, which says what became of each.

  • Credential: Broker sign-in session
  • Answers (an HTML page, or a redirect): 200, 303, 400, 403, 500
  • Source: packages/envoy/internal/broker/approval/page.go:413

Finish a sign-in to the approval page, the provider’s redirect back with a code, checked against the sign-in this browser started (its __Host-broker-signin cookie, used once): state, PKCE, the ID token and the Workspace domain. It makes a new session, ending the one the browser held, and answers a 303 to the record’s page the sign-in started from.

  • Credential: Broker sign-in session
  • Answers (an HTML page, or a redirect): 303, 400, 403, 500, 503
  • Source: packages/envoy/internal/broker/approval/signin.go:101

Sign out of the approval page, from the broker’s own origin only: the session ends and its cookie is cleared, and the answer links back to the record.

  • Credential: Broker sign-in session
  • Answers (an HTML page, or a redirect): 200, 400, 403, 500, 503
  • Source: packages/envoy/internal/broker/approval/signin.go:184

Report the broker’s sign-in: whether its sign-in provider accepted the broker’s own client at the last hourly check and when, when a person last signed in, when a sign-in last failed and the coded reason it failed with, and when a decision was last made on the approval page. It needs no credential because a production check fetches it from a pod where no person is signed in, and because it names no login, no secret and no provider’s message: every word it answers is one of two closed sets this broker chose (internal/broker/store’s signin_status). It spends its source address’s bucket in its own handler, as the page’s routes spend it in authenticate, so this read of the broker’s own row is metered too.

  • Credential: None
  • Success: 200
  • Source: packages/envoy/internal/broker/api/server.go:684

Response body (JSON):

Field Type Description
client_check string or null What the last check of the broker’s client at its sign-in provider found: “ready” (the provider accepted the client), “client_rejected” (it refused it, so nobody can sign in) or “issuer_unreachable” (it could not be asked, which says nothing about the client).
client_checked_at string (RFC 3339 time) or null When that check was taken (RFC 3339).
last_sign_in_at string (RFC 3339 time) or null When a sign-in last completed.
last_failed_sign_in_at string (RFC 3339 time) or null When a sign-in last failed at the token exchange or at the ID-token check.
last_failed_sign_in_reason string or null Why that sign-in failed, one of invalid_client, unauthorized_client, redirect_uri_mismatch, invalid_grant, issuer_unreachable, bad_issuer, bad_audience, bad_signature, bad_nonce, not_in_domain and email_not_verified.
last_decision_at string (RFC 3339 time) or null When a decision was last made on the approval page.

Refusals:

Status Code Message
503 DATABASE_UNAVAILABLE read the sign-in status failed: a dependency the broker needs is unavailable
429 RATE_LIMITED too many requests from this address; try again later

The approval page’s one stylesheet, embedded in the broker; it needs no credential and spends no rate-limit bucket, since every page it dresses is read before anyone has signed in and the bytes are the binary’s.

  • Credential: None
  • Source: packages/envoy/internal/broker/api/server.go:706

Report whether the broker can reach its database. From SIGTERM onward it answers 503 SHUTTING_DOWN instead (Deps.Draining) while the process goes on serving every other route, which is what takes a stopping pod out of its Service’s endpoints before it stops accepting connections. So this is a readiness probe and must never be used as a liveness probe: a liveness probe on it would have the kubelet kill the container in the middle of the drain, dropping the requests the pod was draining.

  • Credential: None
  • Success: 200
  • Source: packages/envoy/internal/broker/api/server.go:636

Response body (JSON):

Field Type Description
status string “ok”.

Refusals:

Status Code Message
503 DATABASE_UNAVAILABLE ping Postgres failed: a dependency the broker needs is unavailable
503 SHUTTING_DOWN the broker is shutting down and is no longer taking new traffic