> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phosra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Provider Quickstart

> Add the Golden Phosra Link parent flow to a parental-controls application

Provider applications use `@phosra/link` to connect an authenticated
parent and child to an accredited platform. The Golden path replaces manual
OAuth, trust, signing, endpoint minting, and retry wiring with one credential
and three application adapters. **This page is the recommended provider
quickstart** — the one new integrations start from.

<Note>
  A **legacy writer-plane lane** (`createLink({ census, writerSeed, writerKeyId, db })`)
  also exists and remains supported for existing integrations and for product-side grant
  reads around the Golden server — see
  [Provider · createLink (writer-plane compatibility)](/integration/link-provider).
</Note>

<Warning>
  **Hosted/private release candidate.** The Railway qualification fleet vendors
  the Golden `@phosra/link` surface, now published on public npm. Production manifest,
  trust, and client-directory discovery are live. Environment-bound credentials
  are issued through the approved operator workflow and are not public
  self-service yet.
</Warning>

## What you build

1. A server-only Link singleton imported by one same-origin catch-all route and one worker entry point.
2. Adapters for your existing parent auth, child ownership, and policy facts.
3. One durable worker process.
4. The SDK-owned `PhosraLink` component in your parent UI.

Link owns the rest: environment and Trust List verification, platform discovery,
signed Intent V2, public consent copy, OAuth handoff, durable delivery, retry,
and evidence-based status.

## 1. Create the Golden server

```ts theme={null}
// lib/phosra-link-server.ts — server-only shared singleton
// Node-only module; never import from client components.
import { Pool } from "pg"
import { createGoldenLinkServer } from "@phosra/link/server"

export const phosraLinkServer = await createGoldenLinkServer({
  credential: process.env.PHOSRA_CREDENTIAL!,
  database: new Pool({ connectionString: process.env.DATABASE_URL }),

  authenticate: async (request) => {
    const session = await requireYourParentSession(request)
    return session === null ? null : {
      sessionID: session.id,
      parentID: session.parentId,
    }
  },

  children: {
    resolve: async ({ principal, childRef }) => {
      const child = await children.findOwned(principal.parentID, childRef)
      return child === null ? null : {
        // The CENSUS child UUID — a Phosra-issued id you stored alongside
        // your own child row. NEVER your app's own row id (see below).
        id: child.censusChildId,
        displayName: child.name,
        ageBand: child.ageBand,
        householdRef: child.familyId,
      }
    },
  },

  policy: {
    resolve: ({ child, platformDid }) =>
      policies.rulesFor(child.id, platformDid),
  },
})
```

Then mount that same singleton in one catch-all route:

```ts theme={null}
// app/api/phosra/link/[...phosra]/route.ts
import { phosraLinkServer } from "@/lib/phosra-link-server"

export const GET = phosraLinkServer.handler
export const POST = phosraLinkServer.handler
export const DELETE = phosraLinkServer.handler
```

Use a v3 `PHOSRA_CREDENTIAL`. It carries the pinned environment authority; do
not copy service URLs, trust roots, signing keys, or platform endpoints into
browser code.

<Warning>
  `authenticate` and `children.resolve` are authorization boundaries. A child
  reference from the request is only a lookup key. Return a child only after
  proving it belongs to the authenticated parent.
</Warning>

<Warning>
  **`children.resolve` must return the child's *census* identity, not your row id.**
  The `id` you return must be a **census child UUID** (a UUID the Phosra census
  issued for this child; the SDK canonicalises it to `child:<uuid>`). It becomes
  the grant's `target_ref` and the consent attestation's target on the census.

  * A non-UUID app id (nanoid, cuid, serial) makes the Link route answer an
    opaque **`502 LINK_OPERATION_FAILED`** — the SDK rejects it with
    *"child\_ref must be a census child UUID … pass the Phosra child id, not the
    platform profile id"* before anything reaches the census.
  * A UUID that is merely your **local** primary key passes that check but names
    a child the census has never heard of, and fails later at endpoint minting.

  Store the census child id in its own column next to your child record (e.g.
  `census_child_id`) when the child is provisioned on the census, and return
  that value here. The Link ceremony cannot create a census child for you.
</Warning>

### Household authority and Intent V2

Return a stable provider-local family identifier as `householdRef`. Link commits
that value into a receiver-specific digest in the signed Intent V2. This enables
safe one-to-many linking—for example, two children in one parental-controls
household can share one Notflix Kids profile—without exposing the raw family ID
to the browser or platform.

The platform still receives a separate, parent-authorized member for each child.
Gatekeeper combines those members on the shared target and computes the effective
policy. Your provider does not send a platform account ID or choose the merge
algorithm.

### No provider-owned sharing modal

If the parent selects an occupied profile from the same verified family,
Gatekeeper's authorize handler owns the recommendation and explicit consent. It
recommends a separate profile, explains strictest-rule aggregation and shared
activity attribution, and offers **Choose another profile**, **Confirm sharing**,
and **Cancel and return** in that order.

Keep the existing `PhosraLink` `createSession`, `onEvent`, and `onExit` contract.
Do not pass family, child, platform-profile, target, or policy authority through
browser props, and do not recreate the decision in your application. The initial
screen identifies the authenticated platform catalog label recovered from the
platform's server-side sealed catalog and the same-family member count. The
signed selected-profile presentation follows token exchange; it is not the
source of the confirmation label. Member names and numeric ages remain deferred
until a future provider-signed member-display resolver is available.

## 2. Run the worker

```ts theme={null}
// phosra-link-worker.ts
import { phosraLinkServer } from "@/lib/phosra-link-server"

const shutdown = new AbortController()
process.once("SIGTERM", () => shutdown.abort())
process.once("SIGINT", () => shutdown.abort())

await phosraLinkServer.runWorker({ signal: shutdown.signal })
```

Run one loop. It serializes durable delivery and signed retry processing. Do not
also schedule `runWorkerOnce()` in the same process while the loop is active.

## 3. Open Link from the parent UI

```tsx theme={null}
import { PhosraLink } from "@phosra/link/react"

{open && <PhosraLink
  createSession={async ({ signal }) => {
    const response = await fetch("/api/phosra/link/sessions", {
      method: "POST",
      signal,
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        child_ref: selectedChildId,
        platform_did: selectedPlatformDid,
        return_to: window.location.pathname,
      }),
    })
    if (!response.ok) throw new Error("Unable to start Phosra Link")
    return response.json()
  }}
  onEvent={analytics.record}
  onExit={(reason) => {
    setOpen(false)
    // "linked" and "completed" are the only exits after which a connection
    // may exist — refresh the connected surface so the UI doesn't still
    // render "Connect".
    if (reason === "linked" || reason === "completed") router.refresh()
  }}
/>}
```

Forward the `AbortSignal` exactly. The component owns the branded review,
handoff, bounded polling, cancellation, retry, and restart-safe recovery.

### Exit reasons and events

`onExit` receives one of six `PhosraLinkExitReason` values:

| Reason      | Meaning                                                                                                                                      | Safe host reaction                                                                                                                 |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `completed` | **Terminal success** — the SDK observed the connection reach `applied`. The only exit that asserts a finished connection.                    | Record connected; advance any queue.                                                                                               |
| `linked`    | A **durable binding exists**, but the SDK did *not* observe independent verification at close — it may still be finishing in the background. | Refresh the connected surface and read the server-side grant snapshot before claiming "connected". Do **not** treat it as failure. |
| `cancelled` | The parent cancelled the ceremony.                                                                                                           | Close; no connection.                                                                                                              |
| `dismissed` | The dialog was dismissed without finishing.                                                                                                  | Close; no connection.                                                                                                              |
| `expired`   | The session expired before completion.                                                                                                       | Close; offer to start again.                                                                                                       |
| `error`     | The ceremony failed.                                                                                                                         | Close; the matching `onEvent` `error` carries the code.                                                                            |

`onEvent` receives a `PhosraLinkEvent` union: `opened`, `handoff_started`,
`cancelled`, `status_changed`, `linked`, `completed` (the latter three carry
`stage` and `evidence_level`), `expired`, and
`{ type: "error", code }` where `code` is one of `START_FAILED`,
`STATUS_FAILED`, `STATUS_TIMEOUT`, `CANCEL_UNCONFIRMED`, `RETRY_UNCONFIRMED`.
Treating `onEvent` purely as an analytics sink is fine; treating `onExit` as a
bare close handler is not — a host that ignores the reason renders a stale
"Connect" button after every successful link.

## 4. Read status honestly

The session snapshot is the authority for parent-facing status:

| Stage            | Safe parent meaning                                                      |
| ---------------- | ------------------------------------------------------------------------ |
| `bound`          | The connection is durable; platform confirmation is pending.             |
| `rule_recorded`  | Controls were recorded; application is not yet confirmed.                |
| `profile_ready`  | The platform has the verified profile and results are being checked.     |
| `applied` + `E4` | Every requested control has current concrete platform evidence.          |
| `degraded`       | Some controls are limited; a bounded retry may be offered.               |
| `refused`        | The platform declined at least one required control.                     |
| `stale`          | Prior evidence is no longer current.                                     |
| `revoked`        | Provider authority is revoked; release completion is tracked separately. |

Never translate an HTTP 2xx, delivery acknowledgement, or adapter return into
“controls active.”

## 5. Grant operations and disconnect

Every grant operation on the Golden server — `getSnapshotForGrant`,
`enforceRuleForGrant`, `retryGrant`, `retryPlatformForGrant`,
`restartIncompleteGrant`, `abandonFailedGrant`, `disconnectGrant` — takes the
same exact authority tuple:

```ts theme={null}
{ grantId: string, platformDid: string, targetRef: string }
```

Where each value comes from:

| Field         | Source                                                                                                                                                    |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `grantId`     | The grant's id, persisted by the SDK when the ceremony issued it.                                                                                         |
| `platformDid` | The platform DID the ceremony targeted (`audience_did` on the stored grant).                                                                              |
| `targetRef`   | The child's census reference, `child:<census-child-uuid>` — the same census child id your `children.resolve` adapter returned, in its prefixed wire form. |

The Golden server does not expose a grant-listing method, but the grants live
in the same Postgres the server owns, and the package-root store reads them:

```ts theme={null}
import { makeLinkStore } from "@phosra/link"

const store = makeLinkStore(pool)
const grants = await store.listGrantsByChild(`child:${censusChildId}`)
// each grant: { grant_id, audience_did, target_ref, status, granted_scope, … }
// The tuple is { grantId: grant.grant_id, platformDid: grant.audience_did,
//                targetRef: grant.target_ref } — filter status === "active"
// and authorize the row against YOUR tenant boundary before using it.
```

To disconnect, authorize the exact grant in your own tenant boundary, then call:

```ts theme={null}
import { phosraLinkServer } from "@/lib/phosra-link-server"

const result = await phosraLinkServer.disconnectGrant({
  grantId: grant.grant_id,
  platformDid: grant.audience_did,
  targetRef: grant.target_ref,
})
```

Reuse the exact `targetRef` persisted with the issued grant. Never synthesize it
from a raw child ID or `householdRef`: `householdRef` establishes authenticated
provider-local family authority during issuance, while the saved `targetRef`
binds this exact grant operation.

`enforceRuleForGrant(authority, rule)` takes the same tuple plus a rule
`{ category, decision?, params?, revision? }` — `revision` must be unique per
edit and reused verbatim when retrying the same edit.

The returned snapshot proves provider-side revocation. Platform removal is
complete only when `releaseStatus` becomes `complete` through the signed release
lifecycle. See [Disconnect & reconnect](/guides/disconnect-reconnect).

## Production checklist

* Keep `PHOSRA_CREDENTIAL` and Postgres server-only.
* Mount GET, POST, and DELETE on the complete catch-all path.
* Derive parent and child authority from your authenticated server session.
* Use a stable, non-display `householdRef`.
* Run one worker with graceful abort.
* Render `PhosraLink`; do not replace its evidence language with optimistic copy.
* Let the platform SDK own shared-profile confirmation; add no host modal or browser authority.
* Log only your correlation IDs—never credentials, authorization URLs, or raw household identifiers.

For the complete API surface, see [`@phosra/link`](/sdks/link). Platform teams
should use the [Platform quickstart](/integration/platform).
