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.
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.Which census serves which Link lane
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/linkor@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’sCMHWy3vU…. Read the pin from the credential; do not copy it from this page. sandbox.phosra.comis a byte-identical alias of the partner sandbox (trust-list sha25668cb7056…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 explicittrustRoot; configuringphosra-api-sandbox-production.up.railway.appneeds 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.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:
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 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.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 a401 on someone’s very first call.
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.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
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: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.app → https://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 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 that404s 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.
See also Production Accreditation.