Which lane am I on? Look at your secret.
Credentials are issued by Phosra through the operator workflow, not minted by you —
see the issuance note on the Provider Quickstart. If you are
still waiting on one, that is the reason to read this page; it is not a reason to ship on
this lane permanently.
createLink() is the ergonomic front door to the writer plane of @phosra/link. It
derives the whole hand-built LinkConfig (writer DID, router DID, router payload key, the
HKDF-derived household and parent keys) from one Ed25519 writer key you already hold,
ships and migrates its own product tables, and delivers rules to platforms by signing
each delivery to the OCSS root — so there is no shared HMAC secret to mint, hand off,
store, sync, or rotate.
The lower-level primitives
createLink wraps — createLinkSession, completeLink,
runConnectCeremony, directive, provisionProfiles, deliverLabelToPlatform — are
unchanged and still exported; see the @phosra/link reference.You will likely run both lanes
The two lanes are not mutually exclusive, and the reference provider integration uses both in one application — nothing here is a fork in the road you can never come back from:createGoldenLinkServerowns the connect ceremony, the durable worker, thePhosraLinkcomponent, and the evidence-backed session status. It is the durability boundary.createLink/makeLinkStorestay useful for the product-side reads and writes around it — listing a child’s grants (listGrantsByChild), revoking one, and signing direct census calls with the writer key fromlink.config.
Install
@phosra/link is ESM-only. Import it from an ESM module ("type": "module", a .mjs
file, or TypeScript compiled to ESM). From CommonJS, load it with a dynamic
await import('@phosra/link') — not require.Integrate in ~15 lines
writerSeed is the only secret, and it is
shared with no platform — the same key signs your census writes and your platform
deliveries.
What each field does
Trust root: pinned vs. explicit. For a census in
PINNED_TRUST_ROOTS
(resolvePinnedTrustRoot(census) returns it), the SDK verifies against the bundled pin and
you pass nothing. For any other host — a self-hosted or one-off census — pass trustRoot
explicitly. Root verification exists precisely so you don’t fetch the root from the census
you’re about to verify.link.ready() runs ensureSchema (the idempotent product DDL) and verifyAccreditation
(root-verify + writer-pub match + active enforcement-agent role). Prefer to control timing?
Call link.migrate() and link.verifyAccreditation() yourself.
Phosra-issued production credentials publish this standing for you. Issuance writes the
credential’s active Link public key and the required
enforcement-agent role to the Trust List
source in one transaction. You should not add a separate role-setting deployment step. If the
existing DID already carries a different role, issuance fails closed instead of overwriting it.The connect flow
The connect flow links a parent’s account on a platform to a child’s OCSS policy. It is three legs on thelink.connect sub-object. The parent authenticates with your auth —
Phosra never sees parent credentials; parentSessionRef is the server-derived binding
between that login and the ceremony (never accept it from the client).
connect.finish does everything the old four-step ceremony did — derives the unlinkable
household hash, mints the grant, ingests the §8.3.2 parent-consent attestation, binds the
enforcement endpoint, and delivers the label to the platform, signed to the OCSS root.
deliveryScheme tells you which lane carried the label. On a secret-free platform it is
"ed25519-did" (signed). It is "hmac" only if you passed a legacy __legacyConnectSecret
during a migration drain window — see Migrating HMAC → Signed.
delivered + deliveryStatus report the receiver’s HTTP result.Two connect vocabularies, one flow. The factory’s legs are
start / resume /
finish; the embeddable @phosra/connect transport names the
same three legs init / complete / bind. They map 1:1 — start↔init,
resume↔complete, finish↔bind — so your BFF wraps the factory to serve the modal.Create-and-link (one parent action → N banded profiles)
When the platform advertises batch provisioning, a single signed call creates the child profiles on the platform and binds them — the create-and-link path (ingestConsentAttestation → mintEnforcementEndpoint(standingRef) → signed
provisionProfiles, all wrapped):
Write a rule — link.enforce
Once a grant is active, link.enforce signs and posts the rule write to the census.
granted_scope
throws before any network call. The standing (consent:attestation:<ref>) is supplied
automatically from the grant.
Manage grants
Typed errors
@phosra/link throws typed LinkError subclasses so you can turn a raw census status into
an actionable message. Guard with isLinkError(e) and branch on e.code.
DeliveryFailedError with .status === 401 almost always means the platform has not yet
added your provider DID to its createConnectReceiver({ authorize }) allowlist — the one
trust config on the platform side. That is the signed-delivery analogue of “wrong HMAC
secret,” and the fix is a one-line allowlist edit on the platform, not a secret exchange.
What the SDK owns (and never sends)
Signing and verification are local; billing is enforced server-side against your org, never
inside the package and never on the safety path.
What the platform (your partner) must do
The signed model shifts almost all of the work to your side. To receive your deliveries, the platform implements one route and adds one allowlist line — no shared secret:- Install
@phosra/gatekeeperand addPOST /api/ocss/connectviacreateConnectReceiver({ env, did, seed, authorize: ["did:ocss:<you>"], store, onBound })— see Platform · createConnectReceiver. (A platform on the Golden Platform Quickstart instead receives deliveries at its facade-owned/api/phosra/deliveryroute — no receiver to add.) - Put your provider DID in that
authorizeallowlist. That is the entire trust configuration; there is nothing to mint, store, sync, or rotate. - Nothing else. The platform never holds a Phosra secret; your signature to the pinned root is the auth.
Next
- Provider Quickstart — the Golden path (
createGoldenLinkServer+PhosraLink), where a new integration starts - Platform · createConnectReceiver (legacy connect receiver) — the one-route receiver
- Migrating HMAC → Signed — retire the shared connect secret
- PhosraConnect component — the Plaid-grade in-app parent modal
@phosra/linkreference — the low-level primitives and the reference BFF