> ## 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 Link for iOS

> PhosraLinkKit — the native-iOS Connect sheet a parental-controls app presents in ~10 lines (the Plaid-Link analog).

**PhosraLinkKit** is the native-iOS **Phosra Link** — a Phosra-branded *Connect*
sheet a parental-controls app (PCA) presents in about ten lines, with
`onSuccess` / `onExit` callbacks. It's the iOS peer of the web/React-Native
[`@phosra/connect`](/sdks/link) drop-in, and it drives the same
[`@phosra/link`](/sdks/link) ceremony — `init → complete → bind → grant` — through
**your own backend (BFF)**.

The parent's authentication and your OCSS signing key **never leave your app**
(self-custody by default). The SDK holds no keys; your BFF signs the consent
attestation. Same trust model as the rest of OCSS, same ergonomics as Plaid's
iOS SDK.

## Install

Swift Package Manager, iOS 16+:

```swift theme={null}
.package(url: "https://github.com/Phosra-Inc/phosra-link-kit-ios.git", from: "0.1.0")
```

<Note>
  During monorepo development you can also depend on it by path
  (`.package(path: "packages/ios-connect")`).
</Note>

## Quickstart

```swift theme={null}
import PhosraLinkKit

let config = PhosraLinkConfiguration(
    bffBaseURL:   URL(string: "https://app.yourpca.com")!,   // your backend
    sessionToken: myParentSessionToken,                      // the token YOUR login issued
    platform:     ConnectPlatform(did: "did:ocss:snaptr", name: "Snaptr"),
    rules: [
        ConnectRule(category: "addictive_pattern_block", label: "Turn off the infinite feed"),
        ConnectRule(category: "dm_restriction",          label: "Limit who can message them"),
    ],
    grantedScope: ["addictive_pattern_block", "dm_restriction"],
    redirectUri:  "yourapp://phosra-link",                   // a custom scheme your app registers
    childId:      "child:uuid"                               // optional
)

let handler = PhosraLink(configuration: config)
handler.present(
    from: self,                                              // any UIViewController
    onSuccess: { success in
        print("granted:", success.grantId)                  // the OCSS consent grant
    },
    onExit: { exit in
        // .userCanceled or .error — no rules were changed
    }
)
```

That's the whole integration. Exactly one of `onSuccess` / `onExit` fires per
presentation — including if the presenter is torn down out from under the sheet —
and you do **not** need to retain the handler (it self-retains while presented).

<Info>
  The platform's OAuth runs in a system `ASWebAuthenticationSession`, so your app
  never sees the parent's platform credentials. The Connect sheet renders **in your
  app** — there is no Phosra-hosted page or web view.
</Info>

<Note>
  `did:ocss:snaptr` above is illustrative. When testing your BFF against the production
  sandbox census, use **`did:ocss:loopline`** — the DID with a live sandbox connect
  config. Other DIDs return the "accredited-but-unconfigured" `404`; email
  [developers@phosra.com](mailto:developers@phosra.com) to have one published. See
  [How platforms are resolved](/sdks/link#how-platforms-are-resolved).
</Note>

## End-to-end

The whole integration on one page — client **and** backend:

<Steps>
  <Step title="Register your redirect scheme">
    Add your custom scheme (e.g. `yourapp`) to the app target's `Info.plist`
    `CFBundleURLTypes`, so `ASWebAuthenticationSession` can receive the platform's
    OAuth callback at `yourapp://phosra-link`.
  </Step>

  <Step title="Authenticate the parent (your own login)">
    The parent signs in with **your** app's auth — Phosra never sees their credentials.
    On success, mint a **server-side** session token (the reference BFF issues a signed
    `phosra_parent` cookie via `issueParentSession`). That token is the `sessionToken`
    you pass the SDK. Never accept it from the client.
  </Step>

  <Step title="Stand up the 3 BFF routes">
    `POST /connect/init | /connect/complete | /connect/bind`, each wrapping
    `@phosra/link` server-side — see the [reference BFF](/sdks/link). Your `bind` route
    signs the consent with your OCSS key and returns `{ grant_id }`.
  </Step>

  <Step title="Present the sheet">
    Build `PhosraLinkConfiguration` (the Quickstart above) and call
    `PhosraLink(configuration:).present(from:onSuccess:onExit:)`.
  </Step>

  <Step title="Handle the result">
    `onSuccess` delivers the `grantId` — persist it and drive rule directives with
    `@phosra/link`'s `directive(...)`. `onExit` means the parent canceled or errored;
    nothing changed.
  </Step>
</Steps>

Metered census usage requires a Phosra API key — provision one in the developer
console or via `POST /api/v1/developers/orgs/{orgID}/keys`.

## What your backend provides

`PhosraLinkKit` never talks to Phosra directly. It calls **your** BFF — the three
routes the [reference BFF](/sdks/link) exposes, each wrapping `@phosra/link`
server-side:

| Route                    | Wraps (`@phosra/link`)                                     |
| ------------------------ | ---------------------------------------------------------- |
| `POST /connect/init`     | `initPlatformOAuth` → `{ authorizeUrl, state, sessionId }` |
| `POST /connect/complete` | `completePlatformOAuth` → `{ sessionId, childProfiles }`   |
| `POST /connect/bind`     | `bindProfile` → `runConnectCeremony` → `{ grant_id }`      |

Your `bind` route signs the consent attestation with **your** OCSS key and posts it
to the census. The SDK relays your parent-session token (a `phosra_parent` cookie by
default; override `sessionHeaderName` / `sessionHeaderValue` for a bearer). This is
Plaid's `link_token` pattern — a thin backend mints the session and finalizes the
signature.

## Platform logos

The sheet shows the platform's **verified** logo — the one from its accredited
Trust-List entry, not a logo the app pastes in. `GET /api/v1/providers/{did}/connect`
returns the provider's `name` and `icon_url` (from the registry); pass that URL into
`ConnectPlatform`:

```swift theme={null}
ConnectPlatform(
    did: "did:ocss:snaptr",
    name: "Snaptr",
    logoURL: URL(string: providerIconURL) // from /providers/{did}/connect → icon_url
)
```

The sheet renders it, falling back to a generic glyph while it loads or if it's
missing. For a bundled asset instead of a URL, pass `platformLogo:` on the
configuration. Because the logo comes from the accredited registry, *the logo you
see is the one Phosra verified* — it's part of the provider's identity, not decoration.

## The honesty contract

The sheet ports the approved design's honesty contract verbatim, and the SDK **cannot
show a fake green**:

* The header reads **phosra · OCSS** (a subordinate mark) and the trust claim is the
  precise **"Accredited on the OCSS Trust List"** — Phosra is an accredited router,
  not OCSS itself.
* Success shows **"Verified on the OCSS Trust List"** — the only green — and only
  after the server binds the grant.
* On failure: *"No rules were changed — you can try again."*

<Warning>
  **Load-bearing invariant:** your `bind` route must return a `grant_id` **only once**
  the consent is minted *and verified to the OCSS root*. The green "Verified" rests on
  that signal alone — return an unverified `grant_id` and you have made the SDK show a
  fake green.
</Warning>

## States

The sheet walks: **Connect** (intro + accreditation) → **What gets applied** (the
rules preview) → the platform's OAuth → **Choose account** → **Verified** success —
or an honest **error** at any step. All rendered natively (SwiftUI); the marks (the
Phosra spark, the wordmark, the OCSS glyph) ship as source and tint by color, so
there is no asset bundle to manage.

## Advanced

* **Custom transport.** The 4-argument `PhosraLinkConfiguration.init` takes your own
  `ConnectTransport` if you route the three calls differently.
* **Fresh platform login.** `PhosraLinkConfiguration(prefersEphemeralSession: true)`
  forces a new platform sign-in (no shared cookies).
* **Granular events.** Pass `onEvent` to observe each ceremony transition.

## Notes

* iOS 16+. The core (controller, transport, sheet) is UI-framework-agnostic and
  unit-tested; the `ASWebAuthenticationSession` presenter is iOS-only.
* **MIT-licensed.** All signing and verification are your backend's; metered census
  usage requires a Phosra API key, billed server-side — never in this package.
