Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.
Endpoints
Everything the control plane answers, in one place. The contract for each is in the v0.1 API surface; the links go to the section that defines it.
Public
Section titled “Public”No credential. All three are documents a verifier has to be able to fetch before it holds anything.
| Method | Path | What it answers |
|---|---|---|
GET |
/.well-known/openid-configuration |
Discovery: the issuer, the three endpoints, the JWKS URL, the grant types and the one client authentication method |
GET |
/.well-known/jwks.json |
The public keys task tokens are signed with. Every configured key is published; only the active one signs |
GET |
/agents/{agent_id}/jwks.json |
An agent’s public keys, when its registration carries them rather than naming a URL. 404 when it names one |
A disabled agent still publishes its keys: tokens it was already issued are still being presented, and whoever is checking them needs the key that signed them.
Tokens
Section titled “Tokens”| Method | Path | Authenticated by | What it does |
|---|---|---|---|
POST |
/oauth2/token |
The agent’s private_key_jwt assertion |
Exchange a user’s token for a task token, or refresh one with a task grant |
POST |
/oauth2/introspect |
Holding the token | Whether the token is active, answered from storage |
POST |
/oauth2/revoke |
The agent’s private_key_jwt assertion |
Revoke a task token by jti, or a task grant — which revokes its task and everything under it |
Both grants use the same endpoint, as RFC 8693 and RFC 6749 intend: grant_type=urn:ietf:params:oauth:grant-type:token-exchange
to start a task, grant_type=refresh_token to renew one. What comes back is described field by
field in your first exchange.
Introspection asks for no client credential in v0.1, and the reason is worth knowing: a task token is unforgeable and readable by whoever holds it, so introspection reveals nothing the bearer does not already have — except whether it has been revoked, which is the thing they are entitled to know.
Revocation is RFC 7009, and only the agent a token was issued to can use it. Anything else — a
token that belongs to somebody else, a token that never existed — gets the same empty 200, so
the endpoint cannot be used to find out which tokens are real.
Every one of these takes Authorization: Bearer <the admin API key>. Without
Onbe:Admin:ApiKey set the whole surface answers 503; with a wrong key, 401 and an
admin.denied record in the ledger. The contract is the admin API.
| Method | Path | Result |
|---|---|---|
POST |
/admin/agents |
201 with the agent; 400 with per-field errors; 409 if the id exists |
GET |
/admin/agents |
200 with every agent |
GET |
/admin/agents/{agent_id} |
200, or 404 |
PATCH |
/admin/agents/{agent_id} |
200 with the merged agent, validated as a whole; 400; 404 |
DELETE |
/admin/agents/{agent_id} |
204; 404; 409 while the agent still has tasks |
DELETE |
/admin/agents/{agent_id}/tasks |
200 with {"revoked_tasks": n}; 404 |
DELETE |
/admin/tasks/{task_id} |
200 with {"revoked_tasks": n}; 404 |
GET |
/audit |
200 with one page of the ledger; 400 with per-field errors |
The kill switches are idempotent: a repeat answers 200 with revoked_tasks: 0 and writes
nothing.
Health
Section titled “Health”| Method | Path | What it means |
|---|---|---|
GET |
/healthz |
The process is up. It does not mean it can do anything |
GET |
/readyz |
The database, the signing key and the identity provider’s keys are all reachable. 503 until they are |
Point a load balancer at /readyz and a restart policy at /healthz. The difference is the
point: an instance that cannot reach Keycloak should stop taking traffic without being killed
and restarted into the same problem.
Every other route is behind the per-source rate limiter and can answer 429 slow_down with
Retry-After; /healthz and /readyz are never limited, so a busy instance is not pulled from
service for being busy.
© 2026 Onbe