Authentication
Every route under /api requires a bearer JWT, issued by the tenant’s
Keycloak realm. Two public routes exist outside /api: GET /healthz and
GET /readyz.
curl -sS "$RULEFLOW_API/api/whoami" -H "Authorization: Bearer $TOKEN"
{ "sub": "f2a1...", "email": "alice@acme.com", "tenant": "acme", "roles": ["business-analyst", "approver"] }
Token verification
The token is verified against the realm’s JWKS (issuer + /protocol/openid-connect/certs), algorithm RS256, expiry required, issuer
matched case-insensitively. On success, the claims are attached to the
request context for the rest of the handler chain.
Claims
| Field | JSON key | Notes |
|---|---|---|
| Subject | sub | Keycloak user id |
email | omitted if the user has none | |
| Tenant | tenant | required on every tenant-scoped route |
| Roles | roles | realm roles (realm_access.roles) |
A request with no tenant claim (or an empty one) is rejected 403 on
every tenant-scoped route — "no tenant in token". There is no default
tenant. See Tenancy & isolation for how the claim
is provisioned and why it can be trusted.
Roles
Roles gate specific actions, independent of tenancy:
| Role | Required for |
|---|---|
approver (or admin) | Deploying to / rolling back a protected environment (staging, prod, production) |
approver (or admin) | Completing an assigned human task on someone else’s behalf |
admin | Every route under /admin/* (/admin/usage, /admin/tenants*) — deliberately cross-tenant surfaces |
A missing role returns 403 with a message naming the required role, e.g.
"admin/tenants requires the 'admin' role".
Errors
Every error response has the same shape:
{ "error": "human-readable message" }
Common status codes across the API: 400 invalid body/params, 403 no
tenant claim or missing role, 404 not found (including cross-tenant
lookups — see Tenancy),
409 idempotency-key conflict, 429 quota exceeded, 503 a backing
service (engine, runtime, search) is not configured in this deployment.