If your credential was minted before /apps
This page is for platforms and providers that were provisioned the old way: a keypair minted
by hand or by a seeding script, a Trust List entry published separately, and a credential issued
against it. That arrangement works right up until the two halves stop agreeing — and when they
do, the symptom is uninformative and the cause is invisible from outside.
The failure class, precisely
A credential embeds a signing key. The census separately publishes signing keys for that DID. On every request the SDK compares them:- A re-seed that redefines an existing
kid. A seeder whose upsert overwrites the published key material for a kid that already exists changes what the census publishes without changing what any deployed credential holds. Nothing rotates, nothing is versioned, and every credential naming that kid stops verifying at once. - A rotation that drops a kid. A new kid is published and the old one removed, while a deployment still holds a credential naming the old one.
- A credential re-issued against a different key than the one on the entry.
/api/phosra/* route returns 503 PHOSRA_NOT_READY,
with a message identical to the one you would get from a missing credential, a lapsed standing,
or an unmigrated database.
Detecting it: one call
Upgrade to@phosra/gatekeeper ≥ 0.8.68 and read the readiness endpoint. No code changes are
needed — the route is mounted by the same handler that already mounts your protocol routes.
If your
package.json declares ^0.6.0, npm will not resolve 0.8.68 — a caret on a 0.x
version pins the minor. Change the range to ^0.8.68. If you vendored the package, delete the
vendored copy first.PLATFORM_SIGNING_KEY_UNPUBLISHED means the census publishes no key under
that kid at all (the rotation-dropped-it case), which needs the same fix.
Every precondition and what to do about it
The full readiness contract — thirteen codes, each with its cause and its fix.
Checking from outside, without deploying anything
The trust list is world-readable, so you can inspect what the census publishes for any DID right now:did:ocss:custo — note the dropped link-1 discussed below:
document is served as a JSON string, not a nested object — it is the exact byte sequence
the root signature covers, which is why fromjson is needed.Fixing it
1
Confirm it is a divergence, not something else
PLATFORM_SIGNING_KEY_MISMATCH or PLATFORM_SIGNING_KEY_UNPUBLISHED. If you see
PLATFORM_STANDING_NOT_ACCREDITED instead, your keys are fine — an attestation probably
lapsed, which is a different and much smaller fix.2
Re-provision so both halves come from one key
POST /api/v1/developers/orgs/{orgId}/apps generates the keypair,
registers and claims the DID, publishes the Trust List entry, and mints the credential
from that same key. They cannot disagree afterwards, because there is only one key.3
Set the returned credential and redeploy
Two environment variables. Capture
writer_seed in the same step — it is shown once and
Phosra does not store it.4
Verify before you close the ticket
curl -s .../api/phosra/status | jq .precondition → PLATFORM_READY. Do not infer success
from a route that stopped 503ing under load.Who is exposed today
Verified against the production census (prodapi.phosra.com, trust list issue 144, root
root-prod-bootstrap-2026-07) and by probing each deployment directly, 2026-07-27.
One standing issue worth knowing, unrelated to key divergence.
did:ocss:propagate is served
tier: accredited with a conformance attestation 16.5 days old against a 7-day TTL. Every
other accredited entry is 1.2 days old or newer. If your integration depends on propagate’s
standing, be aware it is presently accredited on an attestation that has passed its stated
lifetime.What the new path guarantees
For anything created through/apps, this failure class does not exist:
one keypair is generated once and used for both the credential and the published Trust List
entry, inside a single request, and the census refuses to release the credential envelope unless
the freshly recompiled, root-verified Trust List already resolves that key id to those exact
bytes.
Credential and census cannot diverge, because there is only ever one key.