Keystone exposes a versioned REST API at /api/v1 for mobile apps, partner integrations and trust-built tooling. Every endpoint is JSON in / JSON out, tenant-scoped, and audit-logged the same way the in-app routes are.
This page is the canonical reference. The same endpoints back the official Keystone mobile app, so anything documented here is supported and won’t change without a deprecation window.
Bearer token, Laravel Sanctum personal access token. Get one of:
POST /api/v1/auth/local — email + password (rate-limited 5/min).
POST /api/v1/auth/oauth/exchange — OAuth code exchange for trusts using SSO (Microsoft Entra ID, Google Workspace).
POST /api/v1/auth/mfa/challenge — required when the account has MFA enrolled; complete the flow before the bearer token is issued.
All three return a JSON payload containing the access_token (Sanctum personal access token) and a refresh_token. Pass the access token as Authorization: Bearer <token> on every request after that.
To rotate the access token without re-prompting the user:
Every authenticated endpoint runs in exactly one workspace (one trust / tenant). The client picks via:
X-Workspace-Id: <workspace-uuid>
If omitted, the workspace defaults to the user’s first workspace membership. A mismatch — token’s user not a member of the requested workspace — returns 403.
This isolation is enforced by EnsureApiWorkspaceContext middleware, not the controllers; you cannot accidentally read or write across trusts.
JSON for request bodies and responses. Content-Type: application/json.
UUIDs for all resource IDs ([0-9a-f-]{36}).
ISO 8601 timestamps in UTC.
Pagination: index endpoints support ?page=<n> and ?per_page=<n> (max 100). Responses include data, meta.total, meta.per_page, meta.current_page.
Errors follow Laravel’s standard JSON shape:
422 for validation failures, with errors: { field: [...] }.
401 for missing / expired token.
403 for permission or workspace-scope failures.
404 for resources not in this workspace.
429 for rate-limited routes (auth + write throttles).
Idempotency: state-changing endpoints are idempotent when called with identical bodies within the same logical transition (e.g. setting a ticket status to its current value is a no-op).
The API uses the same permission model as the web app — every endpoint enumerates the permission(s) it requires below. Pass a token whose user holds the permission in the workspace; otherwise the gate returns 403. Admin users pass every gate via Gate::before (the super-admin escape hatch).
Every write request produces a row in the trust audit log (the same one the web app populates) — DPO-defensible, HMAC-SHA256 signed by the Audit::creating hook. The audit row is tagged by source so an export later can be filtered to API traffic.
Platform admins can mint a long-lived API key in the trust admin console (/admin/api-keys). API keys carry an explicit subset of permissions and never expire; revoke from the same screen. Use them for headless integrations (overnight sync jobs, monitoring) rather than embedding user credentials.