Which credential do I use?
Most integrations use the developer API key — start there. The WorkOS JWT path is for
first-party parent apps that sign users in; the device key is for the iOS on-device agent only.
The one correct header
A developer API key goes in theAuthorization: Bearer header. This is the canonical form and
the one we use in every example on this site:
X-Api-Key: phosra_… is also accepted as an exact equivalent. The API checks
Authorization: Bearer first and falls back to X-Api-Key; when both are present, Authorization
wins. Use X-Api-Key only if a proxy or framework reserves the Authorization header — otherwise
prefer Authorization: Bearer so keys, WorkOS JWTs, and MCP tokens all share one header. Both were
verified against the live sandbox: a well-formed-but-unknown key returns the same 401 invalid API key on either header.phosra_sk_ infix, no Basic auth,
no query-string key. A request with no phosra_-prefixed credential falls through to the next auth
layer and, on a /developer/* route, returns 403 no API scopes in context.
Test keys vs live keys
Every developer key is bound to one environment at creation time. The prefix tells you which (the full per-environment base-URL matrix lives on Environments & base URLs):
The sandbox is designed to be tried without any signup: its consumer routes (
/api/v1/families,
/api/v1/children/…) are open so you can explore request/response shapes with zero credentials. The
/api/v1/developer/* routes still require a real phosra_test_ key so you can exercise the exact
scope + rate-limit behavior you will hit in production. See Test in the sandbox.
Mint a server key
Key creation is self-serve and needs no pre-existing key — you authenticate the creation call with your WorkOS session bearer (from signing in), then the mintedphosra_ key authenticates
everything after. Full walkthrough: Create your account & get keys.
1
Sign in to the developer console
Sign up or log in via WorkOS AuthKit at
dashboard.phosra.com/signup. The
developer dashboard auto-provisions
your developer org on first visit.
2
Create a key — in the browser or via API
The Keys page mints keys in the UI.
Or call the API with your WorkOS session bearer:Set
"environment": "test" for a phosra_test_ key or "live" for phosra_live_.3
Copy the key — it is shown exactly once
The response body carries the raw key in the
key field. It is never retrievable again —
only a SHA-256 hash is stored. Save it to a secret manager immediately.Use the key
PointbaseUrl at the sandbox and send the key as a bearer token. Every tab below is copy-paste and
runs against the live sandbox as-is:
/api/v1/{resource} (WorkOS JWT) and /api/v1/developer/{resource} (API key) are the same
handlers — the prefix just selects the auth path. Pick the prefix that matches your credential;
both return identical response shapes. See the surface map.Scoping
Every key carries an explicit list of scopes. A request to a route whose scope your key lacks returns403, never silent success. Grant the fewest scopes the integration needs.
These are the scopes you can attach to a key at creation time:
Rotation
Rotate on a schedule and immediately on any suspected exposure. Two operations, both requiring your WorkOS session bearer:regenerate cutting you off mid-flight:
1
Mint a second key
POST …/keys with the same scopes. You now have two valid keys.2
Deploy the new secret
Roll the new
phosra_… value into your secret manager and restart workers. Both keys work, so
there is no gap.3
Revoke the old key
DELETE …/keys/{oldKeyId}. Confirm traffic is all on the new key (check the key’s last_used_at
via GET …/keys), then you are done.Consumer path — WorkOS AuthKit JWT
First-party parent apps sign users in with WorkOS AuthKit and pass the resulting access token to the API. The API verifies each token as an RS256 JWT against WorkOS’s JWKS:- Web: the
authkit-nextjsSDK manages the session cookie and refresh automatically. - Native (iOS/Android): run AuthKit’s hosted login, exchange the code through your own BFF, then
send the WorkOS access token to the API. Token lifetime and refresh are handled entirely by WorkOS
(native clients call your BFF’s
/api/auth/mobile/refresh— a Next.js route, not an API route).
/auth that do exist: GET /auth/me (authenticated profile) and POST /auth/logout
(no-op; WorkOS owns revocation).
On-device path — Device key
For iOS on-device enforcement via Apple FamilyControls. Device keys are issued byPOST /children/{childID}/devices, stored in the iOS Keychain, and sent in X-Device-Key:
GET /device/policy, POST /device/report,
POST /device/ack.
X-Device-Key is for Apple FamilyControls on-device enforcement only. The CompiledPolicy it
returns is shaped for iOS ManagedSettings and does not carry engagement rules (e.g.
infinite_scroll_block, addictive_pattern_block). Web / server-side platforms use the OCSS
provider path instead: RFC-9421 DID-signed requests + the signed enforcement profile. See
Platform integration.Consumer vs developer surface
/api/v1/{resource} and /api/v1/developer/{resource} are the same handlers — the split is an
auth-path decision, not a capability split. Choose the prefix that matches your credential.
Both return identical response shapes.
Error responses
Every auth failure returns a JSON body of the shape{"error", "message", "code"}. These are the
exact responses the live API returns (verified against the sandbox):
401 — bad or unusable key
401 — bad or unusable key
"API key has been revoked" (after DELETE) and "API key has expired". For the WorkOS
JWT path: "missing authorization header", "invalid authorization header format", or
"invalid or expired token".403 — authenticated but missing scope
403 — authenticated but missing scope
/developer/* request with no phosra_ key at all returns
{ "error": "Forbidden", "message": "no API scopes in context", "code": 403 }.400 — bad key-creation request
400 — bad key-creation request
POST …/keys when a requested scope isn’t one of the 14 above (also
"name is required", "environment is required").Verify your setup
Run this against the sandbox before wiring anything up. A200 proves your key, header, and base URL
all line up; a 401/403 maps directly to the errors above:
https://prodapi.phosra.com/api/v1 and the key to your phosra_live_…
value to go to production — the request shapes are identical.