HTTP API
All the HTTP routes kubelatch serves, who can call them, and what they do. The web interface uses this same API; you can call it with curl using a session cookie.
Who can call
| Access |
What's needed |
| Anonymous |
Nothing. |
| Session |
The kubelatch_session cookie of a person with an enabled account. Without it, 401. |
| Admin |
Session of a person with the administrator role. Without the role, 403. |
Bearer klt_ |
Authorization: Bearer klt_…, a kubelatch credential. |
| Actions OIDC token |
The id_token of a GitHub Actions job, in the body. |
| GitHub signature |
X-Hub-Signature-256 header with the HMAC of the body and GITHUB_WEBHOOK_SECRET. |
/api conventions
- JSON bodies up to 64 KiB. Unknown fields and extra data are rejected (
400); a larger body is 413.
- Every response carries
Cache-Control: no-store.
- Errors are
{"error": "<message in Spanish>"} (see Errors).
- An unknown route under
/api/ is 404; a known route with another method is 405 with an Allow header.
- Modifying requests (
POST, PATCH, DELETE) sent by a browser from another origin are rejected with 403. curl and other non-browser clients pass.
Health
| Method |
Route |
Access |
What it does |
GET |
/healthz |
Anonymous |
200 ok if the process is alive. |
GET |
/readyz |
Anonymous |
200 ok if Postgres responds within 2 s; otherwise, 503 database unavailable. |
They only accept GET and HEAD; any other method is 405.
Proxy
| Method |
Route |
Access |
What it does |
| Any |
/clusters/<id>/<Kubernetes API route> |
Bearer klt_ |
Forwards the request to cluster <id>'s API server, acting as the credential's owner (impersonation). /clusters/<id> and /clusters/<id>/ are equivalent to /. Ignores cookies. See The journey of a request. |
Proxy errors are Kubernetes Status objects, so kubectl shows them well (Proxy errors). /clusters without a trailing slash responds 404.
CI and GitHub (outside /api)
These two routes use no session, cookies or origin protection: authentication travels in the request itself. They only accept POST (405 with Allow for anything else).
| Method |
Route |
Access |
What it does |
POST |
/v1/ci/github-actions/token |
Actions OIDC token |
Exchanges {"token": "<id_token>"} for a bot credential according to the matching trust rule. Responds 201 with credential, token, kubeconfig, clusters and expires_at, once only. Limit: 60 requests per minute per IP. See Credentials for GitHub Actions. |
POST |
/v1/github/webhook |
GitHub signature |
Receives the GitHub App's events. organization/member_removed instantly disables the linked account; installation/deleted or suspend are logged; the rest are accepted and ignored. Requires Content-Type: application/json and a body up to 1 MiB. Without GITHUB_WEBHOOK_SECRET it responds 404. |
Session and own account
| Method |
Route |
Access |
What it does |
POST |
/api/auth/login |
Anonymous |
Password login: {"login", "password"}. Opens a session and returns the account. |
POST |
/api/auth/logout |
Session |
Closes all of the person's sessions. 204. |
POST |
/api/auth/link |
Anonymous |
Looks up an invitation or linking link without consuming it: {"token": "kli_…"} → login, display_name, purpose, expires_at, github_required. |
POST |
/api/auth/link/complete |
Anonymous |
Completes a link by setting the password: {"token", "password"}. Opens a session. |
GET |
/api/me |
Session |
Your own account: login, name, email, admin, whether it has and can use a password, break-glass status, and GitHub link. |
POST |
/api/me/password |
Session |
Changes your own password: {"current_password", "new_password"}. Closes the other sessions. |
GitHub login
| Method |
Route |
Access |
What it does |
GET |
/api/auth/methods |
Anonymous |
{"github": <boolean>, "org": "<org>"}: what the login page offers. |
GET |
/api/auth/github/login |
Anonymous |
Sets the state cookie and redirects (302) to GitHub to authorize the App. |
GET |
/api/auth/github/callback |
Anonymous with state cookie |
Return from GitHub. Redirects to / with a session, to /mi-cuenta?github=linked after linking from Mi cuenta (My account), or to /login?error=<code> (/mi-cuenta?error=<code>) if it fails. |
POST |
/api/auth/github/link |
Anonymous |
Starts completing an invitation or linking link with GitHub: {"token": "kli_…"} → {"url"} to navigate to. |
POST |
/api/me/github/link |
Session |
Starts linking your own account, if not already linked (409 if it already is) → {"url"}. |
GET |
/api/github/status |
Admin |
Configuration (configured, org, webhook, require_org_2fa) and the result of the last member sync (last_sync). |
Without GitHub configured, /api/auth/github/login, /api/auth/github/callback, /api/auth/github/link and /api/me/github/link respond 404.
Users and bots
All require admin.
| Method |
Route |
What it does |
GET |
/api/subjects |
Lists people and bots. |
POST |
/api/subjects |
Creates a person ({"login", "display_name", "email", "is_admin"}) and returns its invite_link once only; or a bot ({"kind": "bot", "login", "display_name"}). |
PATCH |
/api/subjects/{id} |
Changes a person's display_name, email or is_admin. |
POST |
/api/subjects/{id}/reset-link |
Generates a reset or linking link (24 h) and returns it once as reset_link. Revokes pending ones and instantly closes that person's sessions. 409 if the account is disabled. |
POST |
/api/subjects/{id}/disable |
Disables a person or bot: closes sessions and revokes credentials. |
POST |
/api/subjects/{id}/enable |
Re-enables them. Revoked credentials stay revoked. |
POST |
/api/subjects/{id}/logout |
Closes all of a person's sessions. |
DELETE |
/api/subjects/{id}/github |
Unlinks a person's GitHub account and closes their sessions. |
Clusters
All require admin.
| Method |
Route |
What it does |
GET |
/api/clusters |
Lists clusters with status, last reconciliation and last error. |
POST |
/api/clusters |
Registers a cluster: {"id", "name"}. The id is the proxy URL's slug. |
GET |
/api/clusters/{id} |
A cluster. |
DELETE |
/api/clusters/{id} |
Cleans up the RBAC kubelatch manages in the cluster and deletes the cluster, its permissions and the credentials restricted to it. Returns rbac_cleanup with status (ok, error or skipped), error, bindings_deleted, roles_deleted and lock_held. |
GET |
/api/clusters/{id}/bootstrap.yaml |
Downloads the bootstrap manifest to apply in the cluster. |
POST |
/api/clusters/{id}/tokens |
Pastes the bootstrap JSON: {"server", "ca", "proxyToken", "reconcilerToken"}. Validates, encrypts, and kicks off the first reconciliation. |
GET |
/api/clusters/{id}/namespaces |
The cluster's namespaces with their PSA level (psa_enforce). |
POST |
/api/clusters/{id}/reconcile |
Reconciles now, synchronously. 502 with the error if the cluster side fails; 409 if another pass is in progress or tokens are missing. |
Permissions
| Method |
Route |
Access |
What it does |
GET |
/api/grants |
Session |
Lists permissions. Filters: subject, cluster, include_revoked=true. Without the admin role, only your own (403 if you ask for another subject). |
POST |
/api/grants |
Admin |
Grants: {"subject_id", "cluster_id", "tier", "scope", "expires_at", "note"}. |
DELETE |
/api/grants/{id} |
Admin |
Revokes a permission. |
GET |
/api/tiers |
Session |
Catalog of tiers, protected namespaces, and whether PSA is required. |
Credentials
| Method |
Route |
Access |
What it does |
GET |
/api/credentials |
Session |
Inventory: an admin sees all of them; a person, their own. |
POST |
/api/credentials |
Session |
Issues a credential: {"ttl", "name", "note", "cluster_id", "subject_id"}. ttl is required (7d, 12h). Without cluster_id, it's valid in every cluster where the subject has permissions. Only an admin issues one for another subject. Returns token and kubeconfig once only. |
POST |
/api/credentials/{id}/revoke |
Session (owner or admin) |
Revokes a credential. Optional body {"reason"}. |
Audit
| Method |
Route |
Access |
What it does |
GET |
/api/audit |
Session |
Requests to the proxy, 50 at a time. Filters: subject, cluster, namespace, verb, resource, credential, from and to (RFC 3339), include_discovery=true, page (1 to 10000). Without the admin role, only your own. |
GET |
/api/audit/{id} |
Session |
One request by its Audit-ID. Someone else's responds 404 if you're not an admin. |
GET |
/api/control-events |
Admin |
Control plane actions and login attempts, 50 at a time. Filter: page. |
By default /api/audit hides rows with no resource (API discovery and authentication failures); include_discovery=true shows them. Both listings return events, page, page_size and has_more.
CI trust rules
All require admin.
| Method |
Route |
What it does |
GET |
/api/ci/trust-rules |
Lists the rules plus the audience, issuer, token lifetime, and exchange route a workflow should use. |
POST |
/api/ci/trust-rules |
Creates a rule: {"name", "bot_subject_id", "repository_owner_id", "repository_id", "ref", "environment"}. Empty ref and environment match any. |
DELETE |
/api/ci/trust-rules/{id} |
Deletes a rule. Does not revoke credentials already issued. |
Web interface
Any other route serves the embedded SPA: the file if it exists, or index.html for interface routes (/login, /cuenta, /mi-cuenta, /admin/…). A missing file under /assets/ is 404. It only accepts GET and HEAD.