> ## 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.

# @phosra/connect — PhosraConnect

> The embeddable, Plaid-grade parent modal. Drop PhosraConnect into your web or React Native app, wire a four-call transport, and connect a child in one contained flow that never leaves your origin.

`@phosra/connect` is the parent-facing connect surface — a calm, branded, in-app modal that
never navigates away, shows exactly which child and which rules are being granted, and ends on
an explicit success. It is the Plaid Link of OCSS: the same recognizable surface everywhere,
which is the trust mechanism.

The component is **presentation + state machine only**. Every network call goes through a
`ConnectTransport` **you** implement on your own backend — the parent never sees a secret,
token, or signature, and your provider key never reaches the browser.

```bash theme={null}
npm install @phosra/connect
```

| Import                   | Use                                                        |
| ------------------------ | ---------------------------------------------------------- |
| `@phosra/connect`        | Web (React) — `PhosraConnect`, `ConnectFlow`, `useConnect` |
| `@phosra/connect/native` | React Native — same components                             |
| `@phosra/connect/core`   | Shared types + the headless `useConnect` controller        |

***

## The transport contract

The component calls your backend through a `ConnectTransport`. Three calls are required; the
fourth (`provision`) is optional and unlocks in-modal create-and-link. Each maps 1:1 to a
[`createLink` connect leg](/integration/link-provider#the-connect-flow) on your BFF.

```ts theme={null}
import type { ConnectTransport } from "@phosra/connect/core"

const transport: ConnectTransport = {
  // → { authorizeUrl, state, sessionId }        (wraps link.connect.start)
  async init(req)     { return post("/api/phosra/connect/init", req) },

  // → { sessionId, childProfiles, provisioningForm? }   (wraps link.connect.resume)
  async complete(req) { return post("/api/phosra/connect/complete", req) },

  // → { grant_id, verified?, receiptFingerprint? }      (wraps link.connect.finish)
  async bind(req)     { return post("/api/phosra/connect/bind", req) },

  // OPTIONAL → { grant_id, verified, provisioned }      (wraps link.provision)
  async provision(req){ return post("/api/phosra/connect/provision", req) },
}
```

| Transport call           | Returns                                           | BFF wraps                  |
| ------------------------ | ------------------------------------------------- | -------------------------- |
| `init`                   | `{ authorizeUrl, state, sessionId }`              | `link.connect.start(...)`  |
| `complete`               | `{ sessionId, childProfiles, provisioningForm? }` | `link.connect.resume(...)` |
| `bind`                   | `{ grant_id, verified?, receiptFingerprint? }`    | `link.connect.finish(...)` |
| `provision` *(optional)* | `{ grant_id, verified, provisioned }`             | `link.provision(...)`      |

<Note>
  **`verified` drives the trust cue — never fake it.** Success shows
  **"Verified on the OCSS Trust List"** only when `verified === true` (your BFF confirmed the
  census signed-receipt actually verified to root). Otherwise the modal shows
  `<Platform> connected — confirming enforcement…` (where `<Platform>` is the platform name).
  Return `verified` honestly; a green badge with no verified receipt is the one thing the
  branding rule forbids.
</Note>

<Note>
  **`provisioningForm` is server-advertised.** If `complete` returns
  `provisioningForm: "batch"`, that overrides the component prop — the platform told the census
  it accepts batch provisioning, and the modal offers the create-and-link step. Your `bind` /
  `provision` handlers deliver **signed to the OCSS root**; there is no connect secret anywhere in
  this flow.
</Note>

***

## Web quickstart

```tsx theme={null}
import { PhosraConnect } from "@phosra/connect"
import type { ConnectTransport } from "@phosra/connect/core"

const transport: ConnectTransport = { init, complete, bind, provision }

export function ConnectNotflix() {
  return (
    <PhosraConnect
      provider={{
        did: "did:ocss:custo",
        name: "Custo",
        linkIcon: { lightUrl: custoMark, darkUrl: custoMarkDark, fallbackInitials: "CU" },
      }}
      platform={{
        did: "did:ocss:notflix",
        name: "Notflix",
        linkIcon: { lightUrl: notflixMark, darkUrl: notflixMarkDark, fallbackInitials: "NO" },
      }}
      rules={[{ category: "dm_restriction", label: "Restrict direct messages" }]}
      grantedScope={["dm_restriction"]}
      childId="child:a11ce0fa-..."
      ageHint="13_15"
      childName="Ava"                  // names the child in the grant preview
      redirectUri="https://custo.app/phosra/callback"
      transport={transport}
      onSuccess={(r) => {
        // r.grant_id, r.verified, r.receiptFingerprint
        router.push("/family")
      }}
      onExit={() => router.back()}
    />
  )
}
```

The two entity icons remain independent. Phosra Link derives each standardized
tile from that entity's approved Mark and composes provider → Phosra → platform
at render time; it does not create a combined image or require a separate app-icon upload.

The parent sees three earned trust cues rendered as calm badges — **"Accredited on the OCSS
Trust List"** (intro), the **concrete rules preview** (the real `rules` you pass), and
`Verified on <Platform>` (only when `bind` returned `verified === true`). No secret, no
token, no signature is ever surfaced.

### Props

| Prop                | Type                          | Notes                                                                                         |
| ------------------- | ----------------------------- | --------------------------------------------------------------------------------------------- |
| `platform`          | `{ did, name }`               | Target platform. `did` gates against the Trust List; `name` co-brands the modal.              |
| `rules`             | `{ category, label }[]`       | The concrete grant preview — the exact rules the parent is consenting to.                     |
| `grantedScope`      | `string[]`                    | The rule categories bound in the grant (drives `bind`).                                       |
| `childId`           | `string`                      | The OCSS child ref (`child:<uuid>`).                                                          |
| `ageHint`           | `AgeHint`                     | `"under_13" \| "13_15" \| "16_17"`.                                                           |
| `childName`         | `string` *(opt)*              | Names the child in the preview and success copy.                                              |
| `provisioningForm`  | `"batch" \| "single"` *(opt)* | Enables create-and-link; a server-advertised `provisioningForm` from `complete` overrides it. |
| `provisionChildren` | `ProvisionChild[]` *(opt)*    | Ages/names to create when there are no existing profiles.                                     |
| `redirectUri`       | `string`                      | Your OAuth callback.                                                                          |
| `transport`         | `ConnectTransport`            | Your BFF calls.                                                                               |
| `onSuccess`         | `(r: BindResult) => void`     | `{ grant_id, verified?, receiptFingerprint? }`.                                               |
| `onExit`            | `() => void`                  | Parent dismissed the modal.                                                                   |

`ConnectFlow` takes the identical props if you want the flow inline rather than in a modal
shell. React Native is the same API from `@phosra/connect/native`.

***

## Create-and-link in the modal

When there are **no existing profiles** on the platform and you pass a `provisioningForm`
plus a `transport.provision`, the modal shows a `no_profiles` step with a **"Create &
connect"** button. The parent creates the banded child profiles and binds them in one contained
step — no new tab, no dead-end.

```tsx theme={null}
<PhosraConnect
  platform={{ did: "did:ocss:notflix", name: "Notflix" }}
  rules={[{ category: "content_rating", label: "Age-appropriate titles only" }]}
  grantedScope={["content_rating"]}
  childId="child:a11ce0fa-..."
  ageHint="under_13"
  childName="Leo"
  provisioningForm="batch"                    // + transport.provision → in-modal create
  provisionChildren={[{ ageHint: "under_13", displayName: "Leo" }]}
  redirectUri="https://custo.app/phosra/callback"
  transport={transport}
  onSuccess={(r) => router.push("/family")}
  onExit={() => router.back()}
/>
```

Under the hood the modal advances through two new statuses: `no_profiles` (the create step is
offered) and `creating` (the signed `provision` delivery is in flight). Your `transport.provision`
returns `{ grant_id, verified, provisioned }` — `provisioned` is the count created.

***

## Headless: `useConnect`

For a fully custom UI, drive the state machine directly with `useConnect` and render your own
chrome (branding still required — see below).

```tsx theme={null}
import { useConnect } from "@phosra/connect/core"

function CustomConnect() {
  const c = useConnect({
    platform: { did: "did:ocss:notflix", name: "Notflix" },
    grantedScope: ["dm_restriction"],
    childId: "child:a11ce0fa-...",
    ageHint: "13_15",
    provisioningForm: "batch",
    provisionChildren: [{ ageHint: "13_15", displayName: "Ava" }],
    redirectUri: "https://custo.app/phosra/callback",
    transport,
  })

  // c.status: … | "no_profiles" | "creating" | …
  if (c.status === "no_profiles") {
    return <button onClick={() => c.createProfiles()}>Create &amp; connect</button>
  }
  // render per c.status; c.state carries provisioningForm and the child profiles
}
```

`createProfiles()` is valid **only** while `status === "no_profiles"`; it runs the signed
`transport.provision` and advances the machine.

***

## Branding is mandatory

The drop-ins render the **Phosra Link branding** — the `phosra · OCSS` lockup, the
"Accredited / Verified on the OCSS Trust List" trust signals, and the **never-a-fake-green**
honesty rule (the verified badge only ever appears when the census receipt actually verified).
This is **required, not optional**: any Phosra Link connect/consent surface MUST use the
published kit, a provider may not hand-roll or restyle the branded consent, and the platform
auth leg MUST co-brand `Phosra Link · <Platform>`. Consistency is the trust mechanism, and it
is **assessed** at accreditation. See the
[Phosra Link Branding Requirement](/sdks/branding).

***

## Next

* [Provider · createLink (writer-plane compatibility)](/integration/link-provider) — the BFF your transport wraps
* [`@phosra/link` → Reference BFF](/sdks/link#reference-bff) — a copy-pasteable BFF for the three legs
* [PhosraLinkKit (iOS)](/sdks/link-ios) — the native equivalent
* [Phosra Link Branding Requirement](/sdks/branding)
