phosra_live_… key.
Sandbox — start here, no key
https://phosra-api-sandbox-production.up.railway.app — open, seeded, safe to hammer.Production — real families
https://prodapi.phosra.com — requires a phosra_live_… key.The environment matrix
Three environments. One host per environment — the same host serves both planes (see Control plane vs data plane — the plane is a routing/credential choice, not a different domain). Append/api/v1 for the REST API; well-known paths (/health,
/.well-known/ocss/…) hang off the bare host.
| Local dev | Sandbox (partner) | Production | |
|---|---|---|---|
| Host | http://localhost:8080 | https://phosra-api-sandbox-production.up.railway.app | https://prodapi.phosra.com |
| REST base URL | http://localhost:8080/api/v1 | …up.railway.app/api/v1 | https://prodapi.phosra.com/api/v1 |
| Key prefix | phosra_test_ | phosra_test_ | phosra_live_ |
| Touches real families? | No | No — isolated sandbox data | Yes |
| Consumer routes need a key? | No (open) | No (open) | Yes (WorkOS JWT) |
/developer/* routes need a key? | Yes (phosra_test_) | Yes (phosra_test_) | Yes (phosra_live_) |
Trust List root key_id | your local root | root-sandbox-2026-06 | root-prod-2026-06 |
| Where to get a key | dashboard → test | dashboard → test | dashboard → live |
| Status | your machine | live ✔ | live ✔ |
One sandbox, one base URL.
phosra-api-sandbox-production.up.railway.app is the open partner
sandbox — every curl, SDK snippet, and quickstart on this site targets it. You may notice a
-staging host elsewhere in the census; that one is a Phosra-internal mirror (it does not
expose the reference-provider OAuth surface and may run ahead of the partner sandbox), so build
against the -production host above. See The two sandbox hosts.Control plane vs data plane
Phosra separates two surfaces, but not two domains. Within a single environment host, the plane is selected by the route prefix and the credential you present — there is no separatecontrolplane.… or dataplane.… hostname to configure.
Control plane
Manage your account — orgs,
phosra_ keys, usage, advisor agents, MCP tokens. Routes under
/api/v1/developers/orgs/…. Auth: your WorkOS session bearer.Data plane
The enforcement path — families, children, policies, rules, enforcement profiles, receipts.
Consumer routes (
/api/v1/…) take a WorkOS JWT; developer routes (/api/v1/developer/…) take
a phosra_ API key.| Control plane | Data plane | |
|---|---|---|
| Same host per env? | Yes — prodapi.phosra.com / the sandbox host | Yes — same host |
| Route prefix | /api/v1/developers/orgs/…, /api/v1/mcp/… | /api/v1/… (consumer) · /api/v1/developer/… (key) |
| Credential | WorkOS session bearer | WorkOS JWT (consumer) · phosra_ key (developer) |
| Reference | Control-plane API | Data-plane API |
The open OCSS protocol surface (the Trust List, succession record, signed receipts) is served on
the same host too, at well-known paths —
GET /.well-known/ocss/trust-list and
GET /.well-known/ocss-succession — and needs no credential. See Architecture.Test keys vs live keys
Every developer key is bound to one environment at creation — the prefix tells you which, and crossing them fails closed.| Sandbox / test | Production / live | |
|---|---|---|
| Prefix | phosra_test_<64 hex> | phosra_live_<64 hex> |
| Valid against | the sandbox host only | prodapi.phosra.com only |
| Header | Authorization: Bearer phosra_test_… | Authorization: Bearer phosra_live_… |
| Mint at | dashboard → environment: test | same page → environment: live |
/api/v1/families, /api/v1/children/…, /api/v1/setup/quick) 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 exercise the exact scope + rate-limit behavior you will hit in production.
Test-data isolation & reset
The sandbox has no destructive “reset” button — instead it gives you two levers so tests stay clean and repeatable, and it never shares state with production.Per-caller isolation — X-Sandbox-Session
Per-caller isolation — X-Sandbox-Session
Pass
X-Sandbox-Session: <any-string> and the sandbox keys all your writes to a stable sandbox
user for that value. Reuse the same string to accumulate against one caller; use a fresh string
for a clean slate. Omit it and you share the default session.Idempotent retries — family_id
Idempotent retries — family_id
POST /setup/quick deduplicates on (family_id, child_name, birth_date). A repeat call that
threads back the family_id from a prior response returns the same child and policy rather
than minting a duplicate — safe to retry after a network failure.Production isolation
Production isolation
Sandbox and production run on separate hosts backed by separate databases. Nothing you create
in the sandbox is visible in production, and there is no path that reads production data into the
sandbox. Switching environments is a base-URL + key swap — no data migrates.
Point each SDK at an environment
Each SDK exposes one knob for the base URL. The important gotcha:@phosra/sdk and the MCP
server expect the base URL with the /api/v1 suffix, while @phosra/link, @phosra/gatekeeper,
and the CLI take the bare host (they append paths themselves).
| SDK / tool | Knob | Suffix | Sandbox value | Production |
|---|---|---|---|---|
@phosra/sdk (PhosraClient) | baseUrl | with /api/v1 | …up.railway.app/api/v1 | default https://prodapi.phosra.com/api/v1 |
@phosra/link | censusBaseUrl | bare host | …up.railway.app | https://prodapi.phosra.com |
@phosra/gatekeeper (createGatekeeper) | censusBaseUrl | bare host | …up.railway.app | https://prodapi.phosra.com |
@phosra/cli | PHOSRA_CENSUS_URL (env) | bare host | …up.railway.app | https://prodapi.phosra.com |
@phosra/mcp | --api-url / PHOSRA_API_URL | with /api/v1 | …up.railway.app/api/v1 | default https://prodapi.phosra.com/api/v1 |
| raw HTTP | $PHOSRA_BASE_URL | with /api/v1 | …up.railway.app/api/v1 | https://prodapi.phosra.com/api/v1 |
Local dev is the same shape: point any knob at
http://localhost:8080 (bare host) or
http://localhost:8080/api/v1 (with suffix), matching the column above, after
go run ./cmd/server/ is up on port 8080.Verify every URL
Copy-paste this to prove each live host end-to-end. These are the real responses captured while writing this page:The sandbox Trust List grows as developers self-register DIDs, so treat its total entry count as
a live number — verify each entry’s signature and accreditation tier rather than asserting on the
total. Production returning
entries: 0 is the honest, correct state (a validly-signed empty list),
not a bug: no test or sandbox keys are ever present in production.Going to production
Three changes flip you from sandbox to production — the request and response shapes are identical:Swap the base URL
https://phosra-api-sandbox-production.up.railway.app → https://prodapi.phosra.com
(update the SDK knob from the table above).Add a live key
Attach
Authorization: Bearer phosra_live_…. Create one in the
dashboard under environment live.Authentication
Test vs live keys, the one correct header, scoping, and rotation.
Sandbox test data
Every fixed, seeded value in the sandbox — the Stripe-test-cards of Phosra.
Architecture
Why the control plane, data plane, and OCSS protocol are kept separate.
Go-live checklist
Everything to confirm before you point at production.
The two sandbox hosts (why)
For completeness: the census runs in two Railway environments, so two sandbox hosts exist. You only ever use the first one.| Host | Role | Use it? |
|---|---|---|
phosra-api-sandbox-production.up.railway.app | Partner sandbox — open, seeded, reference-provider OAuth surface live. The canonical testing endpoint. | Yes — every doc targets this. |
phosra-api-sandbox-staging.up.railway.app | Phosra-internal mirror — where new census changes land first. May run ahead of the partner sandbox. | No — internal only. |
root-sandbox-2026-06), but only the -production
partner sandbox is a supported integration target. If a Phosra-authored example ever shows the
-staging host, treat the -production host as the correct one.