GET /providers/{did}/connect) →
send the parent to consent (authorize_url) → exchange the returned code for a token
(token_url) → read the approved profiles (profiles_url). Each step below carries a
Fields & errors reference you can expand.
Sandbox-first. The reference provider used here —
did:ocss:loopline — is seeded into the
partner sandbox at https://phosra-api-sandbox-production.up.railway.app. Nothing you connect
there touches a real family. The endpoint shapes are identical in production; you swap the base
URL for https://prodapi.phosra.com and connect a real provider DID.Before you start
Set the base URL once so every step is copy-paste:did:ocss:loopline). It is a
real, accredited entry on the sandbox Trust List with a live OAuth
surface — the same shape a production provider exposes.
Discover the provider's connect endpoints
Every connectable provider publishes its OAuth endpoints at
Real response (200):
GET /providers/{did}/connect. Start there — never hard-code the URLs.A provider that is accredited but has not configured a connect surface returns
404 provider connect config not available. In the sandbox, did:ocss:courier is deliberately
left unconfigured so you can test that branch.Fields & errors — GET /providers/{did}/connect
Fields & errors — GET /providers/{did}/connect
Path parameter
Response fields (200)
Errors
| Field | Type | Required | Description |
|---|---|---|---|
did | string | yes | The provider’s decentralized identifier, e.g. did:ocss:loopline. URL-encode the : characters if your client does not do it for you. |
| Field | Type | Description |
|---|---|---|
authorize_url | string (URL) | Where you send the parent’s browser to grant consent (step 2). |
token_url | string (URL) | Where you exchange the authorization code for a token (step 4). |
profiles_url | string (URL) | Where you read the approved child profiles with the token. |
scopes | string[] | The OAuth scopes the provider will request. Loopline requests child_profiles.read — read-only access to the approved children. |
name | string | Human-readable provider name, safe to show a parent. |
| Status | message | When it happens |
|---|---|---|
404 | provider connect config not available | The DID is accredited but has no connect surface configured (e.g. did:ocss:courier). Do not retry — the provider must publish endpoints first. |
404 | provider not found | The DID is not on the Trust List at all. Check the value against GET /.well-known/ocss/trust-list. |
Send the parent to the consent page
Redirect the parent’s browser to The sandbox serves a real consent screen. The parent sees exactly which child profiles they
are about to share, and chooses Approve or Deny:
authorize_url with your client_id (the provider DID),
a redirect_uri, and an opaque state you generate:
Fields & errors — GET /oauth/authorize
Fields & errors — GET /oauth/authorize
Receive the authorization code
On Approve, Phosra redirects back to your On Deny, no code is issued — you get
redirect_uri with a short-lived code and the
state you sent (verify it matches before continuing):?error=access_denied&state=… instead. Handle both.
See Disconnect & reconnect for the decline path in full.Fields & errors — the callback query string
Fields & errors — the callback query string
Query parameters Phosra appends to your
redirect_uri| Field | Type | Present when | Description |
|---|---|---|---|
code | string | approved | Short-lived authorization code (sbxauth_… in the sandbox). Exchange it once at token_url. |
state | string | always | The exact state you sent. Reject the response if it does not match the value you generated. |
error | string | denied | access_denied when the parent declined. No code is present — do not attempt the token exchange. |
Exchange the code for an access token
Trade the Real response (200):
code for a bearer token at token_url:On the
scope value. Discovery advertises the requested scope as child_profiles.read
(the canonical name), while the issued token echoes the short form scope: "profiles". Both
name the same grant — read-only access to the approved children. Key your logic off the token
you were issued, not off a hard-coded string, and treat the two as equivalent.Fields & errors — POST /oauth/token
Fields & errors — POST /oauth/token
Request body (
Response fields (200)
Errors
application/json)| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | yes | Always authorization_code for this flow. |
code | string | yes | The authorization code from the callback. |
client_id | string | yes | The provider DID — must match the one you sent to authorize. |
redirect_uri | string | yes | Must match the redirect_uri used at authorize. |
| Field | Type | Description |
|---|---|---|
access_token | string | Bearer token (sbxtok_…) for GET /oauth/profiles. |
expires_in | integer | Seconds until the token expires (3600 = 1 hour). |
scope | string | The granted scope, returned as profiles — equivalent to the child_profiles.read scope from discovery (see note above). |
token_type | string | Always Bearer. Send it as Authorization: Bearer <access_token>. |
| Status | error | When it happens |
|---|---|---|
400 | unsupported_grant_type | grant_type is missing or not authorization_code. |
400 | invalid_request | The body is malformed or a required field is absent. |
Read the shared child profiles
Call Real response (200):The platform is now connected. Hold onto each
profiles_url with the token. You get back exactly the children the parent approved —
each with a stable subject_ref you use for policy and enforcement calls:subject_ref — that is the handle you pass to
the policy and enforcement endpoints in Set up a family & kids.Fields & errors — GET /oauth/profiles
Fields & errors — GET /oauth/profiles
Request header
Response fields (200) — an array, one object per approved child
Errors
| Header | Required | Description |
|---|---|---|
Authorization | yes | Bearer <access_token> from the token step. |
| Field | Type | Description |
|---|---|---|
id | string | Provider-local child id (e.g. mia). |
displayName | string | Child’s display name, safe to show a parent. |
subject_ref | string (UUID) | Stable cross-system handle. Pass this to the policy and enforcement endpoints — it does not change across reconnects. |
kind | string | Subject kind, child for these entries. |
| Status | error | When it happens |
|---|---|---|
401 | invalid_token | No Authorization header was sent. |
Which platform supports which rule?
Not every platform can enforce every rule. Discovery endpoints let you pick the right one before you ask a parent to connect:enforcement_mode: dns, device, and oauth2 platforms are
applied programmatically, while manual_attested platforms require the parent to complete a
guided step by hand. See Platforms & enforcement modes.
Production notes
In production, connecting an account-linked platform (one where you hold a per-family
credential rather than an OAuth grant) uses
POST /compliance with your phosra_live_… key —
see First-time setup. Those endpoints are family-scoped and require
an authenticated caller who is a member of the family, so they cannot be exercised
anonymously against the open sandbox. The OAuth ceremony above is the anonymous, fully
runnable path.Next steps
Set up a family & kids
Build a family, add children, and get an age-appropriate policy in one call.
Disconnect & reconnect
Handle the decline path, tear a link down, and re-approve cleanly.