🛡️Gatekeeper/ SDKs

The Gatekeeper HTTP API is a single Hono app (apps/api/src/index.ts). Two public routes sit at the root and every functional resource lives under a versioned /v1 sub-app. The SDKs are thin transports over this surface, so the route table below is the contract the clients call.

Versioning#

Two routes are mounted at the root and are unversioned:

MethodPathPurpose
GET/healthLiveness probe. Returns { ok: true, service: 'gatekeeper-api' }.
GET/.well-known/jwks.jsonRS256 public key set for verifying minted tokens. Returns { keys: [] } when asymmetric signing is unconfigured.

Everything else is mounted under /v1. A middleware on the /v1 sub-app stamps X-API-Version: v1 on every response.

Response envelope#

Every JSON response is one of three shapes, all produced by apps/api/src/http.ts:

ShapeHelperBody
Success (single)ok(c, result, 200|201){ "ok": true, "data": <T> | null }
Success (list)page(c, items, nextCursor){ "ok": true, "data": { "items": [...], "nextCursor": string | null } }
Errorfail(c, error, status, code){ "ok": false, "error": string, "code": string }

On success the SDK core unwraps { ok, data } and returns data directly. On ok !== true or a non-2xx status it throws a GatekeeperError carrying the status, error message, machine code, and any Retry-After. See the errors reference.

Two routes bypass the envelope and return raw bodies:

The root app.notFound and app.onError return { ok: false, error } without a code at 404 / 500 respectively.

Pagination#

List endpoints accept limit and cursor query params. limit is a positive integer clamped to a maximum of 100; absent or invalid values fall back to 50. The response carries data.nextCursor (an opaque string, or null when there is no further page). Several list endpoints (GET /v1/tenants, members, roles, plans, provider-configs, webhook endpoints) return the full set with nextCursor: null to keep the list envelope uniform.

Authentication#

Credentials arrive in the Authorization header (or, for a few public routes, in the request body or as a provider signature). Each credential resolves to an actor kind. Full detail is in the authentication reference.

CredentialHeader / sourceActor kindUsed on
User JWTAuthorization: Bearer <jwt>usertenant routes
API keyrequest body field (/keys/validate, /keys/token)apiKey (tenant-bound, scoped)key validate / token
Platform key (gkp_)Authorization: Bearer <key>platform (service account)platform routes
Bootstrap tokenAuthorization: Bearer <token>platformBootstrapplatform management only
none(absent)anonymouspublic / pre-auth

Each protected handler runs the same pipeline: parse and validate input, call guard() with an AccessPolicy, then on allow delegate to a domain service and wrap the result in the envelope. The policy kinds a route can require:

Policy kindMeaning
authenticatedany valid user bearer token
tenantMembercaller is a member of the named tenant
tenantManagecaller is owner/admin of the named tenant
tenantSelfOrManagecaller is the targetUserId, or can manage the tenant
platformPermissionplatform credential holds a platform permission (optionally requireServiceAccount)
publicno identity (used only by the rate-limit pre-check)

hideNotFound: true remaps a NOT_A_MEMBER 403 deny to a 404 so non-members cannot probe resource existence.

Route map#

All paths include the /v1 prefix. The Auth column is the guard policy unless the route is marked public. Rate-limited public routes run enforceRateLimit() before any work and set Retry-After on a 429.

Auth - /v1/auth#

MethodPathAuth / policyPurpose
POST/v1/auth/signuppublic, rate-limitedCreate a user; returns auth tokens (201).
POST/v1/auth/loginpublic, rate-limitedPassword login; returns tokens or an MFA challenge.
POST/v1/auth/refreshpublic, rate-limitedExchange a refresh token for a new access token.
POST/v1/auth/logoutpublic, rate-limitedRevoke a refresh token / session.
POST/v1/auth/verify-emailpublic, rate-limitedConfirm email with a verification token.
POST/v1/auth/password/reset-requestpublic, rate-limitedBegin password reset (email a token).
POST/v1/auth/password/resetpublic, rate-limitedComplete password reset with token + new password.
GET/v1/auth/meauthenticatedReturn the caller's public user profile.

Tenants - /v1/tenants#

MethodPathAuth / policyPurpose
POST/v1/tenantsauthenticatedCreate a tenant; caller becomes owner (201).
GET/v1/tenantsauthenticatedList the caller's tenants with their role (full set, nextCursor: null).
POST/v1/tenants/:id/memberstenantManageAdd a member (default role member); enforces team_members entitlement (402).
GET/v1/tenants/:id/membersauthenticatedList members; membership enforced inside the service.

MFA - /v1/mfa#

MethodPathAuth / policyPurpose
POST/v1/mfa/enrollauthenticatedBegin TOTP enrollment; returns secret, otpauth URI, recovery codes (201).
POST/v1/mfa/verify-setupauthenticatedConfirm enrollment with a TOTP code.
POST/v1/mfa/disableauthenticatedDisable MFA (requires a current code).
POST/v1/mfa/verify-challengepublic, rate-limitedComplete login using the challenge token from /auth/login + a TOTP/recovery code.

OAuth - /v1/oauth#

MethodPathAuth / policyPurpose
GET/v1/oauth/:provider/authorizepublic, rate-limitedReturn the provider authorization URL + state.
GET/v1/oauth/:provider/callbackpublic, rate-limitedHandle the provider callback, then oauthLogin and return tokens.

API keys - /v1/keys#

MethodPathAuth / policyPurpose
POST/v1/keys/validatepublic, rate-limited; key is the body credentialIntrospect a key; returns its public projection (never key_hash).
POST/v1/keys/tokenpublic, rate-limited; key is the body credentialExchange a valid key for a short-lived RS256 JWT. Rejects scopeless keys (403).
POST/v1/keystenantManageCreate a key; enforces api_keys entitlement (402) (201).
GET/v1/keystenantManage (via tenantId query)List keys for a tenant (cursor-paginated).
GET/v1/keys/:idauthenticate, then tenantManage on the key's tenant (hideNotFound)Fetch one key.
DELETE/v1/keys/:idauthenticate, then tenantManage (hideNotFound)Revoke a key.

Usage / quota - /v1/usage#

MethodPathAuth / policyPurpose
POST/v1/usage/checktenantManageCheck remaining quota for a tenant (optionally per API key).
POST/v1/usage/recordtenantManageRecord usage for a service/action (201). Supports idempotencyKey.
POST/v1/usage/check-and-recordtenantManageAtomic check + record.
GET/v1/usagetenantManage (via tenantId query)Usage summary, optional period (hour/day/month).

Permissions / roles - /v1/permissions#

MethodPathAuth / policyPurpose
POST/v1/permissions/rolestenantManageCreate a tenant role (caller role gates which permissions) (201).
GET/v1/permissions/rolestenantManage (via tenantId query)List a tenant's roles.
PATCH/v1/permissions/roles/:idauthenticate, then tenantManage (hideNotFound)Update a role; system roles are immutable/hidden (404).
DELETE/v1/permissions/roles/:idauthenticate, then tenantManage (hideNotFound)Delete a role.
POST/v1/permissions/assigntenantManageAssign a role to a user.
POST/v1/permissions/revoketenantManageRevoke a role from a user.
POST/v1/permissions/checktenantSelfOrManage (targetUserId = userId)Boolean: does a user hold a permission.
GET/v1/permissionstenantSelfOrManage (via userId + tenantId query)List a user's effective permissions in a tenant.

Webhooks - /v1/webhooks#

MethodPathAuth / policyPurpose
POST/v1/webhooks/endpointstenantManageRegister an endpoint; enforces webhooks entitlement (402) (201).
GET/v1/webhooks/endpointstenantManage (via tenantId query)List endpoints (public projection).
PATCH/v1/webhooks/endpoints/:idauthenticate, then tenantManage (hideNotFound)Update url/secret/events/active.
DELETE/v1/webhooks/endpoints/:idauthenticate, then tenantManage (hideNotFound)Delete an endpoint.
GET/v1/webhooks/endpoints/:id/deliveriesauthenticate, then tenantManage (hideNotFound)List delivery attempts (cursor-paginated).
POST/v1/webhooks/dispatchtenantManageDispatch an event to a tenant's matching endpoints.

Audit - /v1/audit#

MethodPathAuth / policyPurpose
POST/v1/audit/eventstenantManageRecord an audit event; for actorType: user the caller's id is forced as the actor (201).
GET/v1/audit/eventstenantManage (via tenantId query)Query events with filters (actorId, actionPrefix, time range, limit, cursor); cursor-paginated.
GET/v1/audit/exporttenantManage (via tenantId query)Export events as ndjson (default) or csv. Raw body, not the JSON envelope.

Billing - /v1/billing#

MethodPathAuth / policyPurpose
GET/v1/billing/plansauthenticatedList available plans.
GET/v1/billing/provider-configstenantManage (via tenantId query)List payment provider configs for a tenant.
POST/v1/billing/provider-configstenantManageCreate a provider config (provider/mode/credentials) (201).
POST/v1/billing/plan-mappingstenantManageMap a plan to provider product/price/variant ids (201).
POST/v1/billing/checkout-sessionstenantManageCreate a hosted checkout session (201).
POST/v1/billing/portal-sessionstenantManageCreate a customer portal session (201).
POST/v1/billing/webhooks/:provider/:configIdpublic; provider signature, rate-limited, 256 KiB capIngest a provider webhook; verified inside the payment service.
GET/v1/billing/subscriptionstenantMember (via tenantId query)Get the tenant's current subscription.
GET/v1/billing/entitlementstenantMember (via tenantId + feature query)Check a feature entitlement at a usage level.
GET/v1/billing/invoice-previewtenantMember (via tenantId query)Compute a billing preview from plan + seat + monthly usage.

Rate limit - /v1/ratelimit#

MethodPathAuth / policyPurpose
POST/v1/ratelimit/checktenantManageStandalone fixed-window check; returns 200 with { success, ... }. Caps: limit ≤ 1000000, windowSeconds ≤ 86400.

Platform - /v1/platform#

All platform routes authenticate a platform credential (bootstrap token or gkp_ key) and require a platformPermission. Operational routes set requireServiceAccount: true, which rejects the bootstrap token and forces a real service account. Each route also enforces its own platform rate-limit bucket.

MethodPathAuth / policyPurpose
POST/v1/platform/service-accountsplatformPermission service_accounts:writeCreate a service account (delegation gated by the operator's own permissions) (201).
GET/v1/platform/service-accountsplatformPermission service_accounts:writeList service accounts (non-bootstrap actors see only ones they can manage).
DELETE/v1/platform/service-accounts/:idplatformPermission service_accounts:writeRevoke a service account.
GET/v1/platform/jobsplatformPermission jobs:read (service account required)List background jobs (payload redacted).
GET/v1/platform/jobs/:idplatformPermission jobs:read (service account required)Get one job (payload redacted).
POST/v1/platform/jobs/:id/retryplatformPermission jobs:write (service account required)Retry a job.
POST/v1/platform/billing/subscriptionsplatformPermission billing:write (service account required)Override a tenant's subscription (admin action) (201).