API authentication and tenant resolution
The two credentials the API accepts, how the gateway resolves the tenant, which headers are trusted, and what a caller needs to reach the API at all.
Credentials#
| Credential | Header | Obtain |
|---|---|---|
| JWT | Authorization: Bearer eyJ… | Kelta’s OIDC provider — the app’s session, a connected app’s OAuth flow, or the CLI’s browser login |
| Personal access token | Authorization: Bearer klt_… | Profile → API tokens in the app, kelta token create, or an administrator minting one for a service user |
Both authenticate a user: a PAT acts exactly as its owner. Apart from the inbound-webhook endpoints documented
under Triggers, a request with no credential is 401 — unless the tenant has created a
profile named Guest, in which case it runs as that profile. See
Anonymous access (Guest profile).
The caller’s profile must grant API_ACCESS; without it every /api/** call answers 403 API access not permitted, whatever the credential.
Tenant resolution#
The gateway resolves the tenant before authentication, in this order:
- Custom domain — the request host matches a verified domain.
- Path prefix —
https://api.example.com/<slug>/api/...; the slug is stripped before forwarding. - Header —
X-Tenant-Slug: <slug>orX-Tenant-ID: <uuid>on a bare/api/...path.
The JWT or PAT must belong to that tenant; a token from another tenant is rejected.
Trusted and stripped headers#
Client-supplied identity headers (X-User-*, X-Forwarded-User, X-Geo-*, X-Cerbos-*) are stripped at the
edge; the gateway stamps its own after authentication. Never rely on setting them. X-Forwarded-For is honoured
for rate-limit keys and the IP allowlist only as far as the deployment’s trusted-proxy configuration allows.
Identity introspection#
GET /api/me/identity # { userId, email, profileId, userType, tenantId }
GET /api/me/permissions # effective system permissions
userId is the canonical UUID to use in bodies that reference the current user.
GET /api/me/profile returns the caller’s own user record, and PATCH /api/me/profile changes the fields a user
may edit about themselves — firstName, lastName, locale, timezone — without MANAGE_USERS. Any other
attribute is rejected with FIELD_NOT_EDITABLE.
Realtime and MCP#
- The WebSocket endpoint takes the JWT as a query parameter on the upgrade — see Realtime.
- MCP endpoints accept only PATs — see MCP server overview.
Errors#
401 UNAUTHORIZED — missing, expired or revoked credential. 403 — authenticated but not permitted (the detail
names the action and collection). See Error responses.