Service API Reference
The orbit-service management API runs on port 3001. The introspect hot path runs separately on port 3000.
All management endpoints require a tenant-scoped session token via Authorization: Bearer <token> unless noted.
?cursor=&limit= query parameters.
Responses return { data: T[], next_cursor: string | null }. Default limit is 20, max is 100.
Introspect (Port 3000)
The hot-path endpoint for real-time authorization checks. Authenticated with an API key, not a session token.
POST /checkAuth: Authorization: Bearer <api_key>
Resolves a user session into an authorization decision. Single CTE query: session → user → roles → role permissions → direct overrides → merged set → optional policy evaluation.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes* | User session token (opaque) |
access_token | string | Yes* | JWT access token (from orbit-oauth2) |
tenant_id | string | Yes | Target tenant UUID |
permission | string | No | Single permission to check |
permissions | string[] | No | Batch permission check |
resource | object | No | Resource context for policy evaluation |
include | string[] | No | Extra fields to return: permissions, roles, flags |
flags | string[] | No | Feature flags to evaluate |
* Provide token (session-based) or access_token (JWT-based), not both.
Response
{
"authenticated": true,
"user": { "id": "...", "email": "...", "name": "...", "tenant_id": "..." },
"granted": true, // present if "permission" was requested
"grants": { "perm": true },// present if "permissions" batch was requested
"permissions": ["..."], // present if include contains "permissions"
"roles": ["..."], // present if include contains "roles"
"feature_flags": { "flag": true } // present if include contains "flags"
}
curl Example
curl -X POST https://orbit.example.com/check \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"token": "user-session-token",
"tenant_id": "tenant-uuid",
"permission": "documents.edit",
"resource": { "owner_id": "user-123" }
}'
Authentication
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /auth/signup | None | Create root tenant + owner user. Returns { token, expires_at }. |
POST | /auth/login | None | Authenticate. With tenant_id: single-step. Without: pre-tenant session (next: "tenant_select"). |
POST | /auth/logout | Session | Delete current session. Returns 204. |
POST | /auth/logout/all | Session | Delete all sessions for the user. Returns 204. |
GET | /auth/me | Session | Current user + tenant + effective permissions. |
GET | /auth/memberships | Session | List tenants the user belongs to. Returns TenantMembership[]. |
POST | /auth/session/exchange | Pre-tenant | Swap pre-tenant session for tenant-scoped session. Body: { tenant_slug }. |
POST | /auth/invite/accept | None | Accept invite, set password, get session. Body: { token, password }. |
POST | /auth/password/reset | None | Request password reset. Body: { email }, optional tenant_id. |
POST | /auth/password/reset/confirm | None | Confirm reset. Body: { token, password }. Returns 204. |
# Step 1: Login (no tenant_id → pre-tenant session)
curl -X POST https://orbit.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "admin@example.com", "password": "secret" }'
# → { "token": "pre-token", "expires_at": "...", "next": "tenant_select" }
# Step 2: List memberships
curl https://orbit.example.com/auth/memberships \
-H "Authorization: Bearer ${PRE_TOKEN}"
# → { "data": [{ "tenant_id": "...", "tenant_slug": "acme", "tenant_name": "Acme Corp", "roles": ["admin"] }] }
# Step 3: Exchange for tenant-scoped session
curl -X POST https://orbit.example.com/auth/session/exchange \
-H "Authorization: Bearer ${PRE_TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "tenant_slug": "acme" }'
# → { "token": "tenant-scoped-token", "expires_at": "...", "next": "complete" }
Tenants
All routes require a tenant-scoped session. Tenant isolation is absolute — you can only access your own tenant's data.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants | tenants.read (system) | List all root tenants. System tenant only. |
POST | /tenants | tenants.create | Create child tenant. Body: { name }. Caller mirrored as owner. |
GET | /tenants/:id | tenants.read | Get own tenant. |
PATCH | /tenants/:id | tenants.update | Update tenant. Body: { name?, plan?, metadata? }. Slug is immutable. |
DELETE | /tenants/:id | tenants.delete | Soft-deactivate. Returns 204. |
GET | /tenants/:id/children | tenants.read | List direct children. Paginated. |
Users
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/users | users.read | List users in tenant. |
POST | /tenants/:id/users | users.create | Create user. Body: { email, name }, optional password. |
POST | /tenants/:id/users/invite | users.create | Invite by email. Body: { email, role_id }. Creates invite token. |
GET | /tenants/:id/users/:uid | users.read | Get user. |
PATCH | /tenants/:id/users/:uid | users.update | Update user. Body: { name?, email?, metadata?, password? }. |
DELETE | /tenants/:id/users/:uid | users.delete | Soft-deactivate. Returns 204. |
User Roles
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/users/:uid/roles | users.read | List roles assigned to user. |
POST | /tenants/:id/users/:uid/roles/:roleId | roles.update | Assign role. Returns 204. |
DELETE | /tenants/:id/users/:uid/roles/:roleId | roles.update | Revoke role. Returns 204. |
User Direct Permissions
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/users/:uid/permissions | users.read | Effective permission names (role + direct, minus explicit deny). |
POST | /tenants/:id/users/:uid/permissions | permissions.attach | Grant direct permission. Body: { permission_id, granted?, expires_at? }. |
PATCH | /tenants/:id/users/:uid/permissions/:permId | permissions.attach | Update granted/expires_at. |
DELETE | /tenants/:id/users/:uid/permissions/:permId | permissions.detach | Remove direct permission. Returns 204. |
Roles
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/roles | roles.read | List all roles in tenant. |
POST | /tenants/:id/roles | roles.create | Create role. Body: { name, description? }. |
GET | /tenants/:id/roles/:roleId | roles.read | Get role. |
PATCH | /tenants/:id/roles/:roleId | roles.update | Update. Body: { name?, description? }. |
DELETE | /tenants/:id/roles/:roleId | roles.delete | Delete (cascades). Returns 204. |
Role Permissions
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/roles/:roleId/permissions | roles.read | List permissions assigned to role. |
POST | /tenants/:id/roles/:roleId/permissions/:permId | roles.update | Assign permission. Returns 204. |
DELETE | /tenants/:id/roles/:roleId/permissions/:permId | roles.update | Remove permission. Returns 204. |
Permissions
Permissions are global (not tenant-scoped). Creating permissions requires the system tenant.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /permissions | permissions.read | List all. Optional query: module_id filter. |
POST | /permissions | permissions.create (system) | Create permission. Body: { module_id, name, description? }. |
GET | /permissions/:id | permissions.read | Get permission. |
Modules
Modules group permissions (e.g. users, roles, oauth2).
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /modules | modules.read | List all modules. |
POST | /modules | modules.create (system) | Create module. Body: { name, slug, description? }. |
GET | /modules/:id | modules.read | Get module. |
PATCH | /modules/:id | modules.update (system) | Update. Slug is immutable. |
Policies
Policies add runtime conditions to permission checks. When a permission has attached policies, the introspect endpoint evaluates each policy handler. AND logic: all must pass.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /policies | policies.read | List all policies. |
POST | /policies | policies.create (system) | Create. Body: { name, type, parameters?, description? }. |
GET | /policies/:id | policies.read | Get policy. |
PATCH | /policies/:id | policies.update (system) | Update. Type is immutable. |
DELETE | /policies/:id | policies.delete (system) | Delete (cascades). Returns 204. |
Policy Attachment
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /permissions/:id/policies | permissions.read | List policies attached to a permission. |
POST | /permissions/:id/policies/:policyId | policies.attach (system) | Attach policy to permission. Returns 204. |
DELETE | /permissions/:id/policies/:policyId | policies.detach (system) | Detach. Returns 204. |
Built-in Policy Types
| Type | Description | Parameters |
|---|---|---|
ownership | Checks if the user owns the resource | { owner_field: "owner_id" } |
department_match | Checks if user's department matches the resource | { field: "department_id" } |
resource_state | Checks resource state against allowed values | { field: "status", allowed: ["draft"] } |
business_hours | Time-of-day restriction | { start: "09:00", end: "17:00", tz: "America/Toronto" } |
API Keys
API keys authenticate consuming applications against the introspect endpoint. The raw key is returned once at creation and stored as a SHA-256 hash.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/api-keys | api-keys.read | List API keys (never exposes key hash). |
POST | /tenants/:id/api-keys | api-keys.create | Create. Body: { name, expires_at? }. Returns raw key once. |
DELETE | /tenants/:id/api-keys/:keyId | api-keys.revoke | Soft-revoke. Returns 204. |
Audit Logs
Every mutation is recorded. Logs capture old/new values, the acting user, IP address, and timestamp.
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/tenants/:id/audit-logs |
audit.read |
List logs. Filters: action, user_id, from, to (ISO timestamps), model_type. Default limit 50, max 100. |
Feature Flags
Flags are defined globally and can be overridden per-tenant. Effective value = tenant override > global default.
Global Flags (System Tenant)
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /feature-flags | feature-flags.read (system) | List all global flags. |
POST | /feature-flags | feature-flags.create (system) | Create. Body: { name, enabled?, description? }. |
GET | /feature-flags/:name | feature-flags.read (system) | Get global flag. |
PATCH | /feature-flags/:name | feature-flags.update (system) | Update global default. |
Tenant Flags
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/feature-flags | feature-flags.read | Effective flags (override + global merged). |
GET | /tenants/:id/feature-flags/:name | feature-flags.read | Single effective flag value. |
PUT | /tenants/:id/feature-flags/:name | feature-flags.update | Set tenant override. Body: { enabled }. |
OAuth2 Clients
OAuth2 client management is proxied through orbit-service to orbit-oauth2's internal API. All mutations are audit-logged.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/:id/oauth2-clients | oauth2.read | List clients. |
POST | /tenants/:id/oauth2-clients | oauth2.create | Create client. Secret returned once for confidential clients. |
GET | /tenants/:id/oauth2-clients/:clientId | oauth2.read | Get client. |
PATCH | /tenants/:id/oauth2-clients/:clientId | oauth2.update | Update client. |
DELETE | /tenants/:id/oauth2-clients/:clientId | oauth2.delete | Delete (cascades). Returns 204. |
POST | /tenants/:id/oauth2-clients/:clientId/rotate-secret | oauth2.update | Rotate secret (confidential only). |
POST | /tenants/:id/oauth2-clients/:clientId/revoke | oauth2.update | Revoke all tokens for client. |
GET | /tenants/:id/oauth2-clients/:clientId/grants | oauth2.read | List grants (subject + scopes). |
DELETE | /tenants/:id/oauth2-clients/:clientId/grants/:subject | oauth2.update | Revoke specific grant. Returns 204. |
Signing Keys (System Tenant)
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /tenants/system/oauth2-keys | oauth2.read | List signing keys. |
POST | /tenants/system/oauth2-keys/rotate | oauth2.create | Generate new EC P-521 keypair. |
POST | /tenants/system/oauth2-keys/:kid/retire | oauth2.update | Retire key (stays in JWKS for validation). |