Skip to main content
This is the single page to bookmark for “which URL do I call, in which environment, with which key.” Every base URL below was verified live while writing this page. Nothing here touches a real family until you deliberately point at production with a 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 devSandbox (partner)Production
Hosthttp://localhost:8080https://phosra-api-sandbox-production.up.railway.apphttps://prodapi.phosra.com
REST base URLhttp://localhost:8080/api/v1…up.railway.app/api/v1https://prodapi.phosra.com/api/v1
Key prefixphosra_test_phosra_test_phosra_live_
Touches real families?NoNo — isolated sandbox dataYes
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_idyour local rootroot-sandbox-2026-06root-prod-2026-06
Where to get a keydashboard → testdashboard → testdashboard → live
Statusyour machinelivelive
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 separate controlplane.… 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 planeData plane
Same host per env?Yesprodapi.phosra.com / the sandbox hostYes — same host
Route prefix/api/v1/developers/orgs/…, /api/v1/mcp/…/api/v1/… (consumer) · /api/v1/developer/… (key)
CredentialWorkOS session bearerWorkOS JWT (consumer) · phosra_ key (developer)
ReferenceControl-plane APIData-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 / testProduction / live
Prefixphosra_test_<64 hex>phosra_live_<64 hex>
Valid againstthe sandbox host onlyprodapi.phosra.com only
HeaderAuthorization: Bearer phosra_test_…Authorization: Bearer phosra_live_…
Mint atdashboard → environment: testsame page → environment: live
A phosra_test_ key against production — or a phosra_live_ key against the sandbox — returns 401 invalid API key. The hash simply isn’t in that environment’s key store; keys never span environments. Full auth details: Authentication.
The sandbox is designed to be tried without any signup: its consumer routes (/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.
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.
curl -s -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/setup/quick" \
  -H "Content-Type: application/json" \
  -H "X-Sandbox-Session: my-test-run-42" \
  -d '{"child_name":"Nora","birth_date":"2015-05-01","strictness":"strict"}'
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.
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.
For every fixed, seeded sandbox value — the demo children (Mia/Leo/Ava), reference providers, the 22 reference platforms, and connect scopes — see the flat lookup table: Sandbox test data.

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 / toolKnobSuffixSandbox valueProduction
@phosra/sdk (PhosraClient)baseUrlwith /api/v1…up.railway.app/api/v1default https://prodapi.phosra.com/api/v1
@phosra/linkcensusBaseUrlbare host…up.railway.apphttps://prodapi.phosra.com
@phosra/gatekeeper (createGatekeeper)censusBaseUrlbare host…up.railway.apphttps://prodapi.phosra.com
@phosra/cliPHOSRA_CENSUS_URL (env)bare host…up.railway.apphttps://prodapi.phosra.com
@phosra/mcp--api-url / PHOSRA_API_URLwith /api/v1…up.railway.app/api/v1default https://prodapi.phosra.com/api/v1
raw HTTP$PHOSRA_BASE_URLwith /api/v1…up.railway.app/api/v1https://prodapi.phosra.com/api/v1
import { PhosraClient } from "@phosra/sdk";

// Sandbox — no key required. baseUrl includes /api/v1.
const client = new PhosraClient({
  baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
});

// Production — drop baseUrl (defaults to https://prodapi.phosra.com/api/v1) and add a live key.
const prod = new PhosraClient({ apiKey: process.env.PHOSRA_API_KEY }); // phosra_live_…
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:
# Production control plane — live, no key needed for /health
curl -fsS https://prodapi.phosra.com/health
# → {"status":"ok"}

# Partner sandbox — live, open
curl -fsS https://phosra-api-sandbox-production.up.railway.app/health
# → {"status":"ok"}

# A well-formed request with NO key to a /developer/* route → 403 (proves the route is real + gated)
curl -s -o /dev/null -w "%{http_code}\n" \
  https://prodapi.phosra.com/api/v1/developer/families
# → 403

# The signed Trust List — no key. Sandbox has a growing signed list; production is a
# validly-signed EMPTY list until accreditation entries are added via OCSS governance.
curl -fsS https://prodapi.phosra.com/.well-known/ocss/trust-list \
  | python3 -c "import sys,json; d=json.load(sys.stdin); doc=json.loads(d['document']); \
print('prod  root:', d['key_id'], '| entries:', len(doc['entries']))"
# → prod  root: root-prod-2026-06 | entries: 0

curl -fsS https://phosra-api-sandbox-production.up.railway.app/.well-known/ocss/trust-list \
  | python3 -c "import sys,json; d=json.load(sys.stdin); doc=json.loads(d['document']); \
print('sbx   root:', d['key_id'], '| entries:', len(doc['entries']))"
# → sbx   root: root-sandbox-2026-06 | entries: <a live, growing number>
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:
1

Swap the base URL

https://phosra-api-sandbox-production.up.railway.apphttps://prodapi.phosra.com (update the SDK knob from the table above).
2

Add a live key

Attach Authorization: Bearer phosra_live_…. Create one in the dashboard under environment live.
3

Use real DIDs

The reference providers (did:ocss:loopline, …) and demo children are sandbox-only. In production you connect real accredited providers and real family data.

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.
HostRoleUse it?
phosra-api-sandbox-production.up.railway.appPartner sandbox — open, seeded, reference-provider OAuth surface live. The canonical testing endpoint.Yes — every doc targets this.
phosra-api-sandbox-staging.up.railway.appPhosra-internal mirror — where new census changes land first. May run ahead of the partner sandbox.No — internal only.
Both are validly-signed sandbox censuses (root 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.