Skip to main content
Families change on the platform side without telling Phosra. A parent adds a third child to their console, a sibling ages in, a profile is renamed — and now the family Phosra knows about no longer matches the family the platform reports. This recipe pulls both rosters — Phosra’s children and the platform’s profiles — diffs them, and reconciles by adding the children Phosra is missing, so policies and enforcement cover every child the platform actually has. Every request and response below is verbatim live output, captured against https://phosra-api-sandbox-production.up.railway.app. No API key, nothing to install — the platform roster comes from the sandbox reference provider (did:ocss:loopline).
Sandbox-first. These calls run against the open sandbox with no credential. In production the platform roster comes from the real provider you connected (via the OAuth ceremony in Connect a platform) and the family endpoints are family-scoped — the diff-and-reconcile logic is identical.

Before you start

This recipe uses a family with one child in Phosra (Mia) connected to a platform that reports three (Mia, Leo, Ava). Set the base URL and family once:
export PHOSRA_BASE="https://phosra-api-sandbox-production.up.railway.app"
export FAMILY_ID="7805eb97-c3c6-4836-8c66-69c77b81fd2e"
1

Pull the Phosra roster

GET /families/{familyID}/children returns the children Phosra currently knows about for this family.
curl -s "$PHOSRA_BASE/api/v1/families/$FAMILY_ID/children"
Live 200 — Phosra has one child:
[
  {
    "id": "2b26d5ff-22d8-44b0-9992-c6c9f1df1676",
    "family_id": "7805eb97-c3c6-4836-8c66-69c77b81fd2e",
    "name": "Mia",
    "birth_date": "2013-01-15T00:00:00Z"
  }
]
2

Pull the platform roster

The connected platform reports its own view of the family through GET /oauth/profiles — the child list you receive after the connect ceremony, authorized with the access_token that ceremony returned. The authorize step is browser-driven (the parent grants consent), so the TypeScript and Python tabs start from the accessToken you already hold; the cURL tab runs the whole chain inline as a sandbox shortcut.
# Sandbox shortcut: approve inline → code → token → profiles.
# (In production the parent's browser does the authorize step; you receive the code
#  at your redirect_uri — see Connect a platform.)
LOC=$(curl -s -o /dev/null -D - \
  "$PHOSRA_BASE/oauth/authorize?client_id=did:ocss:loopline&decision=approve&redirect_uri=https://loopline.example/callback&state=drift" \
  | grep -i '^location:' | tr -d '\r')
CODE=$(echo "$LOC" | sed -n 's/.*[?&]code=\([^&]*\).*/\1/p')

AT=$(curl -s -X POST "$PHOSRA_BASE/oauth/token" -H "Content-Type: application/json" \
  -d "{\"grant_type\":\"authorization_code\",\"code\":\"$CODE\",\"client_id\":\"did:ocss:loopline\",\"redirect_uri\":\"https://loopline.example/callback\"}" \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")

curl -s "$PHOSRA_BASE/oauth/profiles" -H "Authorization: Bearer $AT"
Live 200 — the platform reports three children:
[
  { "id": "mia", "displayName": "Mia", "subject_ref": "a11ce0fa-0000-4000-8000-0000000000a1", "kind": "child" },
  { "id": "leo", "displayName": "Leo", "subject_ref": "a11ce0fa-0000-4000-8000-0000000000a2", "kind": "child" },
  { "id": "ava", "displayName": "Ava", "subject_ref": "a11ce0fa-0000-4000-8000-0000000000a3", "kind": "child" }
]
Phosra has Mia; the platform has Mia, Leo, and Ava. Leo and Ava are the drift.
3

Diff the rosters

Compute which platform children Phosra is missing. Match on the child’s name (displayName on the platform side, name on Phosra’s) — a platform’s subject_ref is its own identifier and will not equal a Phosra child id, so it is not the join key.
const missing = profiles.filter(
  (p: any) => p.kind === "child" && !phosraNames.has(p.displayName),
);
// missing → [{ displayName: "Leo", … }, { displayName: "Ava", … }]
Diff both directions. This recipe adds children the platform has but Phosra lacks. The reverse — a child in Phosra the platform no longer reports — is a removal signal, but never auto-delete: a missing profile can mean the parent temporarily hid it. Surface reverse drift for review rather than deleting policies and enforcement history.
4

Add the missing children

For each missing child, POST /families/{familyID}/children adds them to the Phosra family. Only a name and birth date are required.
curl -s -X POST "$PHOSRA_BASE/api/v1/families/$FAMILY_ID/children" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Leo", "birth_date": "2016-09-03" }'

curl -s -X POST "$PHOSRA_BASE/api/v1/families/$FAMILY_ID/children" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Ava", "birth_date": "2010-02-20" }'
Live 201 for the Ava add — a new Phosra child id:
{
  "id": "66c24a80-b7ae-4929-88b1-71dd16aea7b7",
  "family_id": "7805eb97-c3c6-4836-8c66-69c77b81fd2e",
  "name": "Ava",
  "birth_date": "2010-02-20T00:00:00Z",
  "created_at": "2026-07-06T11:30:43.321043959Z"
}
The platform profile does not carry a birth date, so supply one from your own records or an age band. A newly-added child has no policy yet — generate an age-appropriate one next (see Onboard a multi-child household, which turns a birth date into a full rule set in one call).
5

Confirm parity

Re-pull the Phosra roster. It now matches the platform — every child the console reports exists in Phosra:
curl -s "$PHOSRA_BASE/api/v1/families/$FAMILY_ID/children"
Live 200 — three children, matching the platform’s three profiles:
[
  { "id": "2b26d5ff-22d8-44b0-9992-c6c9f1df1676", "name": "Mia" },
  { "id": "567b2b02-e576-413c-a679-32af2db346dd", "name": "Leo" },
  { "id": "66c24a80-b7ae-4929-88b1-71dd16aea7b7", "name": "Ava" }
]
The drift is reconciled. Give Leo and Ava a policy, then enforce so the new children are actually covered on every connected platform.

The whole flow at a glance

#StepCallLive result
1Phosra rosterGET /families/{id}/children1 child — Mia
2Platform rosterGET /oauth/profiles3 profiles — Mia, Leo, Ava
3Diff by name(local)missing: Leo, Ava
4Add missingPOST /families/{id}/children ×2Leo 567b2b02…, Ava 66c24a80…
5ConfirmGET /families/{id}/children3 children — in sync

Next steps

Onboard a multi-child household

Turn each newly-added child’s birth date into a full age-appropriate policy in one call.

Connect a platform

The full OAuth ceremony behind the GET /oauth/profiles roster this recipe diffs against.

Recover a disconnected platform

The other reconciliation — a platform link that dropped rather than a roster that drifted.

Change a rule and enforce it

Push policy to every connected platform once the new children have one.