Skip to main content
This is the single page to bookmark for “which host do I call, in which environment, with which credential.” Every host and every capability claim below was probed live on 2026-07-27 — including the negative ones, which are the load-bearing half: a census that does not serve a lane returns plain-text 404 page not found, exactly like a path that never existed. Two different credentials are in play and they are not interchangeable. A phosra_ API key authenticates REST calls to the product API. A PHOSRA_CREDENTIAL is the signed v3 envelope that @phosra/link and @phosra/gatekeeper boot from — it names its own census, root, environment, and application origin. The key is self-serve; the credential is not. See Getting a Link credential.

Sandbox — start here, no key

https://phosra-api-sandbox-production.up.railway.app — open, seeded, safe to hammer. Good for REST and the protocol surface; it does not serve the Link lanes.

Production — real families

https://prodapi.phosra.com — one host for the control plane, the data plane, the OCSS protocol surface, and the production OCSS census.

The environment matrix

Three environments. Within an environment, one 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” means your app on localhost, not a Phosra server on your laptop. You do not run a census, you do not mint a root, and there is nothing to go run. Local development is: your app on http://localhost:<port>, a sandbox credential pinned to that exact origin, pointed at a hosted sandbox census. The full recipe — including the ports/callback rules and the cookie-name hazard — is in @phosra/link → Local development.
Link credentials are environment-bound, like keys. A phosra_cred_v3 envelope names its own census, trust root, and environment label, so there is no separate mode switch — but the two labels are not interchangeable. A production credential’s application.origin must be canonical HTTPS, which is why it can never be used from http://localhost; a sandbox credential may carry a loopback origin, because the sandbox census that issues it publishes redirect_policy.allow_http_loopback: true with http_loopback_hosts: ["127.0.0.1","localhost"] in its signed environment manifest. Mixing them fails closed at bind time. See local development and ORIGIN_REJECTED.Verified live 2026-07-27, the two ends of that rule side by side: GET /api/v1/link/clients/did:ocss:custo returns "redirect_uris":["http://localhost:3005/api/phosra/link/callback"] on the sandbox census that issues sandbox credentials, and "redirect_uris":["https://custo.up.railway.app/api/phosra/link/callback"] on the production census.
Never hand-pin a trust root you were not given by your own credential. The literals in the matrix are correct for the two hosts they name, but the authoritative value for your integration is inside your credential at environment_bootstrap.ocss_root_pins[0].x — the SDK reads it from there. This matters because both sandbox censuses publish the same key_id (root-sandbox-2026-06) over different key material: copying a root literal that belongs to the other sandbox host produces an Ed25519 signature error that points nowhere near the real cause. See The two sandbox hosts.
The REST/protocol surface and the Link surface are not served by the same set of hosts. This table is why so many “it 404s” reports are really “wrong census” reports. Probed live 2026-07-27 against a positive control (/health) and a bogus-path negative control on each host. Three consequences worth stating plainly, because every one of them has cost someone an afternoon:
  • The partner sandbox cannot bootstrap @phosra/link or @phosra/gatekeeper. Those SDKs fetch and verify the signed environment manifest at boot; that host serves none. Use the partner sandbox for REST, the protocol surface, and the Trust List — not for a Link ceremony.
  • Sandbox Link credentials are bound to the census that issued them, which today is the Phosra-internal staging sandbox. That is why a sandbox credential’s root pin is jDWZfB70DM8B3ZnPG8FQ0tnanqLgwJX4ZMr_E1Ugv3w, not the partner sandbox’s CMHWy3vU…. Read the pin from the credential; do not copy it from this page.
  • sandbox.phosra.com is a byte-identical alias of the partner sandbox (trust-list sha256 68cb7056… on both), but it is not in @phosra/link’s bundled root-pin table, and the SDK refuses trust-on-first-use. If you configure the branded alias you must pass an explicit trustRoot; configuring phosra-api-sandbox-production.up.railway.app needs no extra field.
Which sandbox host should I build against? For everything on this site that is a curl, an @phosra/sdk call, or a quickstart: phosra-api-sandbox-production.up.railway.app — it is open, seeded, and the canonical partner sandbox. For a Link or gatekeeper integration you need a credential, and that is not a host you choose — the credential names its own census. Ask for one before you start wiring: Getting a Link credential.
PHOSRA_CREDENTIAL is the one required input for @phosra/link and @phosra/gatekeeper, and the dashboard does not mint it — that page mints phosra_test_ / phosra_live_ API keys, an unrelated credential type. There is no self-service issuance today, in any environment. Saying so plainly is more useful than a link that dead-ends, so:
Credential issuance is operator-issued today. On the production census the issuing lane is POST /api/v1/admin-ops/link/credentials, which answers 401 — it exists and is gated to Phosra operators. On the partner sandbox it does not exist at all (404). The self-serve lane (POST /developers/orgs/{orgId}/link/credentials) is deployed only on the Phosra-internal staging sandbox census, and even there it refuses a provisional Trust List tier. So the thing to pursue is accreditation, not a request for someone to hand you a credential.
How to ask. Email developers@phosra.com with these fields — all of them are baked into the signed envelope, so a missing one means a re-issue: What you get back is a single opaque string beginning phosra_cred_v3. — set it as PHOSRA_CREDENTIAL and nothing else. It already carries the census origin, the manifest URL and signer pin, the OCSS root pin, your signing and authority keys, the environment label, and the application origin. Do not also set a census URL or a trust root: for the credential-driven SDKs there is no such knob (see Point each SDK at an environment). Rotation and re-issue. The origin is inside the signed envelope, so a domain or port change means a new credential — there is no way to edit an issued one. If your credential and your published Trust List keys ever disagree, the symptom is an opaque 503 PHOSRA_NOT_READY: see Migrating a hand-minted credential.
The route to production is accreditation, and that part is self-serve to start: see Production Accreditation. The current boundary of self-service across every lane is catalogued at What you cannot self-serve yet.

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.
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, just as importantly, to the census database it was minted in. Those are two separate facts, and conflating them is the most common cause of a 401 on someone’s very first call.
401 invalid API key does not tell you which mistake you made. Verified live 2026-07-27: a syntactically valid but unknown phosra_test_… returns the byte-identical {"error":"Unauthorized","message":"invalid API key","code":401} from both the sandbox host and prodapi.phosra.com. So the error cannot distinguish “wrong environment” from “right key, wrong host” from “genuinely bad key”.Check the host first, before you re-mint anything: a key authenticates only against the census it was created in, and mode (test vs live) filters which data you see rather than which hostname answers. If your first call fails, copy the exact base URL out of the dashboard’s own “make your first call” panel rather than assembling one from this page. 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 are gated — with no credential at all they answer 403 on both the sandbox host and production — so you exercise the exact scope and rate-limit behaviour you will hit later.

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.
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 reference platforms, and connect scopes — see the flat lookup table: Sandbox test data.

Point each SDK at an environment

There are two kinds of SDK here, and the difference matters more than the /api/v1 suffix gotcha that used to headline this section.

Credential-driven — there is no URL knob

The recommended Link and gatekeeper paths take a credential and nothing else. The credential names the census, the trust root, the manifest, the environment, and your application origin. Setting a census URL or a trust root alongside it does not switch environments — there is no such field to set.

URL-driven — the /api/v1 suffix gotcha

The raw/legacy paths do take a base URL, and they disagree about the suffix: @phosra/sdk and the MCP server expect the base URL with /api/v1, while @phosra/cli and the low-level createLink take the bare host (they append paths themselves).
Local dev for the URL-driven tools is the same shape — point the knob at a hosted sandbox host, with or without the /api/v1 suffix as the table says. There is no localhost census to run: see Local development.

Verify every URL

Copy-paste this to prove each live host end-to-end. These are the real responses captured while writing this page:
Do not assert on the entry count. Both lists grow — production as parties are accredited through OCSS governance, the sandbox as developers self-register DIDs. Assert on the key_id and on a successful Ed25519 verification against the pinned root public X, then on each entry’s tier.In particular, an empty production list is now a red flag, not the expected state: a validly signed entries: 0 list under the old root id root-prod-2026-06 is the signature of the retired production census. If you see it, you are talking to a dead host — re-check the origin, do not accept the root.

Going to production

For a Link or gatekeeper integration the blocking change is the credential, not the URL. Request and response shapes are identical between environments.
1

Step 0 — get a production credential (this is the gate)

A production PHOSRA_CREDENTIAL is pinned to your canonical HTTPS origin and requires an accredited Trust List entry. It is not self-serve — see Getting a Link credential and Production Accreditation. Because the origin is inside the signed envelope, a later domain or port change means a re-issue, so settle the production origin before you ask.
2

Swap the credential — for the credential-driven SDKs, that is the whole swap

createGoldenLinkServer and createPlatform read whichever credential is present; the credential decides the census, root, and environment. No code changes, and no census URL to update.
3

Swap the base URL — only for the URL-driven tools

@phosra/sdk, @phosra/mcp, @phosra/cli, raw HTTP: https://phosra-api-sandbox-production.up.railway.apphttps://prodapi.phosra.com (same census as https://phosra-api-prod-bootstrap-production.up.railway.app). Control plane and census are the same host in production. Update the knob from the table above.
4

Attach the right credential for the route

Consumer routes (/api/v1/families, /api/v1/children/…, /api/v1/setup/quick) take the signed-in parent’s WorkOS session JWT in production — not a phosra_ key. /developer/* routes take Authorization: Bearer phosra_live_…. Create a key in the dashboard under environment live if your integration uses the developer path.
5

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)

The census runs in two Railway environments, so there are two sandbox censuses — and three names, because one of them has a branded alias. Neither sandbox census touches production data, and both serve validly signed lists. Pick by capability: partner sandbox for REST and the protocol surface; the staging census is where sandbox Link credentials live because it is the only one that serves the lanes they need.
The two sandbox censuses publish the SAME root key_id over DIFFERENT key material. Both advertise root-sandbox-2026-06, but each verifies only under its own root:Re-verified 2026-07-27 by Ed25519-verifying each served document against every known Phosra root: each host verifies under exactly one, and every cross pairing fails. So key the pin on the census host and the root public X — never on key_id. Anyone who caches or looks up a root by key_id will pair one environment’s document with the other’s root and fail closed with a signature error that points nowhere near the real cause. Better still: read the pin from your credential rather than from any page.

The production host (why there is only one)

Production has ONE census, reachable under two names, and it is also the control plane. Earlier revisions of this page described a separate control plane that 404s the census routes; that topology is retired. @phosra/link pins both names to the same root, so either works without extra configuration. Pin the root public X out-of-band, never the key_id and never a root fetched from the census you are verifying. The key_id is only a label and labels collide: both sandbox censuses publish root-sandbox-2026-06 over different key material.
Certificate caveat. Railway’s edge has intermittently served prodapi.phosra.com its *.up.railway.app wildcard certificate instead of the custom-domain certificate, surfacing as SSL: no alternative certificate subject name matches target host name. Re-sampled 2026-07-27: 24/24 handshakes clean, so this appears to have settled — but if you do hit it, retry, or use https://phosra-api-prod-bootstrap-production.up.railway.app (same census, same root) for a guaranteed-deterministic run such as CI.census.phosra.com is the intended canonical production alias but is not standing up: it resolves and terminates TLS, but no service is attached, so every path — including /health — returns {"status":"error","code":404,"message":"Application not found"}. @phosra/link ships a pin for it against the day it is attached; do not configure it until this note is removed. Use prodapi.phosra.com.
See also Production Accreditation.