Skip to main content
This is the single page to keep open while you build against the sandbox — the equivalent of Stripe’s test cards or Plaid’s sandbox credentials. Everything here is a fixed, seeded value you can hard-code in a test: the demo children, the connectable reference providers, the 22 reference platforms, the exact POST /setup/quick shape, the connect scopes, and the rules that govern resets and retries. Every value on this page was captured live from the sandbox while writing it. For a guided, step-by-step tour of the same sandbox (health, the signed Trust List, self-registering a DID), read Test in the sandbox; this page is the flat lookup table.

Base URL

export PHOSRA_SANDBOX="https://phosra-api-sandbox-production.up.railway.app"
One sandbox, one base URL. phosra-api-sandbox-production.up.railway.app is the open partner sandbox — no key, no signup, safe to hammer. Every curl on this page runs against it verbatim. (You may see a -staging host elsewhere in the census; that one is a Phosra-internal mirror and does not expose the reference-provider OAuth surface — use the -production host above.) When you go live, swap the base URL for https://prodapi.phosra.com and add a phosra_live_… key. The request and response shapes are identical.

The cheat sheet

Everything fixed in the sandbox, in one scan. Each row links to its section below.
WhatValueNotes
Base URLhttps://phosra-api-sandbox-production.up.railway.appNo key required
Health checkGET /health{"status":"ok"}Fastest liveness probe
Demo child — Miaa11ce0fa-0000-4000-8000-0000000000a1subject_ref
Demo child — Leoa11ce0fa-0000-4000-8000-0000000000a2subject_ref
Demo child — Avaa11ce0fa-0000-4000-8000-0000000000a3subject_ref
Connectable providerdid:ocss:looplineGET /providers/{did}/connect200
Unconfigured providerdid:ocss:courierGET /providers/{did}/connect404 (the accredited-but-unconfigured case)
One-call setupPOST /setup/quickFamily + child + active policy + 20 rules
Reference platforms22 ids across 4 categoriesnetflix, apple, nextdns, …
Session isolation headerX-Sandbox-Session: <any-string>Deterministic per-caller data
Connect scopeschild_profiles.read (connect) / profiles (token)Token mint
Trust ListGET /.well-known/ocss/trust-listSigned, key_id=root-sandbox-2026-06

Demo child profiles

The sandbox is seeded with one demo family — Mia, Leo, and Ava — each with an active policy already in place. These are the profiles the connect ceremony shares, and their subject_ref values are stable across every sandbox reset, so you can hard-code them in tests.
Childsubject_ref (stable UUID)Kind
Miaa11ce0fa-0000-4000-8000-0000000000a1child
Leoa11ce0fa-0000-4000-8000-0000000000a2child
Avaa11ce0fa-0000-4000-8000-0000000000a3child
You retrieve them by completing the reference-provider OAuth flow, exactly as a real Connect integration would. The /oauth/profiles leg returns them as a bare ChildProfile[]:
# 1. Approve the sandbox consent screen → get an authorization code
CODE=$(curl -s -D - -o /dev/null \
  "$PHOSRA_SANDBOX/oauth/authorize?redirect_uri=https%3A%2F%2Fexample.com%2Fcb&state=xyz&decision=approve" \
  | grep -i '^location' | sed -E 's/.*code=([^&]+).*/\1/' | tr -d '\r')

# 2. Exchange the code for an access token
TOKEN=$(curl -s -X POST "$PHOSRA_SANDBOX/oauth/token" \
  -H "Content-Type: application/json" \
  -d "{\"grant_type\":\"authorization_code\",\"code\":\"$CODE\"}" \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")

# 3. List the connected child profiles
curl -s "$PHOSRA_SANDBOX/oauth/profiles" -H "Authorization: Bearer $TOKEN"
Live response (200 OK):
[
  { "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" }
]
The consent code and access token are opaque, single-value strings (sbxauth_… and sbxtok_…). The sandbox flow is stateless — it does not persist codes or bind PKCE — so any freshly minted sbxauth_ code exchanges cleanly. This is a sandbox affordance, not a production IdP.

Reference providers

Providers live on the signed Trust List. Two are wired for the full connect ceremony and give you the two branches you need to test:
DIDEntityGET /providers/{did}/connectUse it to test
did:ocss:looplineLoopline (OCSS reference emitter)200 — returns authorize_url, token_url, profiles_url, scopesThe happy path: run the OAuth connect flow end to end
did:ocss:courierCourier Messaging404 provider connect config not availableThe accredited-but-unconfigured branch
curl -s "$PHOSRA_SANDBOX/api/v1/providers/did:ocss:loopline/connect"
Live response (200 OK):
{
  "authorize_url": "https://phosra-api-sandbox-production.up.railway.app/oauth/authorize",
  "token_url": "https://phosra-api-sandbox-production.up.railway.app/oauth/token",
  "profiles_url": "https://phosra-api-sandbox-production.up.railway.app/oauth/profiles",
  "scopes": ["child_profiles.read"],
  "name": "Loopline"
}
The Trust List holds many more verified reference entities you can discover and verify signatures against — did:ocss:aura, did:ocss:brightcanary, did:ocss:murmur, did:ocss:beacon, did:ocss:household-acme, and more — plus a growing set of provisional entries added by self-registration. Because self-registration is open, treat the total entry count as a live number: verify each entry’s signature and tier rather than asserting on the total. See Test in the sandbox for the signed-document shape and how to self-register your own DID.

Reference platforms

GET /api/v1/platforms returns the 22 enforcement targets the sandbox can fan a policy out to, grouped by category. Use any platform_id when connecting a platform or reading discovery results.
curl -s "$PHOSRA_SANDBOX/api/v1/platforms"
Categoryplatform_ids
devicefire_tablet, fire_tv, android, apple, apple_watch, microsoft
dnscleanbrowsing, controld, nextdns
gamingnintendo, playstation, xbox
streamingprime_video, disney_plus, hulu, max, netflix, paramount_plus, peacock, roku, youtube_tv, youtube

POST /setup/quick

The fastest path to a working policy. Hand it a child’s name, birth date, and a strictness level; it creates a family, a child, an active policy, and a full set of age-appropriate rules — derived from the birth date, in one call, no follow-ups.
curl -s -X POST "$PHOSRA_SANDBOX/api/v1/setup/quick" \
  -H "Content-Type: application/json" \
  -d '{
    "child_name": "Emma",
    "birth_date": "2016-03-15",
    "strictness": "recommended"
  }'

Request fields

FieldTypeRequiredDescription
child_namestringyesThe child’s display name.
birth_datestring (YYYY-MM-DD)yesDrives the age band and the generated rule set. Send it in exactly this format — a malformed value is not cleanly validated today: the sandbox currently returns 500 Internal Server Error rather than a 400, so treat YYYY-MM-DD as a hard client-side requirement.
strictnessstringnorelaxed, recommended (default), or strict. Adjusts rule configuration (screen-time minutes, filter level), not the rule count — you always get the full 20-rule set.
family_idstring (UUID)noReuse an existing family instead of creating one. Also the key to idempotent retries.
family_namestringnoNames a newly created family. Defaults to "<child_name>'s Family".

Response shape (201 Created)

Trimmed to the top-level shape — the live rules array carries all 20 entries:
{
  "family": { "id": "c479c437-…", "name": "Emma's Family" },
  "child":  { "id": "bb3821b0-…", "name": "Emma", "birth_date": "2016-03-15T00:00:00Z" },
  "policy": { "id": "5ccaad2f-…", "name": "Emma's Protection Policy", "status": "active" },
  "rules":  [ /* 20 age-appropriate rules */ ],
  "age_group": "preteen",
  "max_ratings": { "csm": "10+", "esrb": "E10+", "mpaa": "PG", "pegi": "7", "tvpg": "TV-PG" },
  "rule_summary": {
    "screen_time_minutes": 120,
    "bedtime_hour": 21,
    "web_filter_level": "moderate",
    "content_rating": "PG",
    "total_rules_enabled": 20
  }
}
Every POST /setup/quick returns these 20 rule categories (config values vary with the child’s age and the strictness level):addictive_design_control, age_gate, algo_feed_control, content_rating, data_deletion_request, dm_restriction, geolocation_opt_in, monitoring_activity, notification_curfew, privacy_account_creation, privacy_profile_visibility, purchase_approval, purchase_block_iap, social_chat_control, targeted_ad_block, time_daily_limit, time_scheduled_hours, usage_timer_notification, web_filter_level, web_safesearch.These are 20 of the 123 categories in the OCSS rule registry — the age-based generator selects the subset relevant to a child’s age band.

Connect and token scopes

The reference-provider OAuth flow uses two scope strings:
LegScopeMeaning
GET /providers/{did}/connectchild_profiles.readThe scope the provider advertises for the connect ceremony.
POST /oauth/token responseprofilesThe granted scope on the issued access token.
The token response is a standard bearer grant:
{ "access_token": "sbxtok_…", "token_type": "Bearer", "expires_in": 3600, "scope": "profiles" }

Reset and idempotency

The sandbox has no destructive “reset” button — instead it gives you two levers so your tests stay clean and repeatable: Deterministic per-caller data (X-Sandbox-Session). Pass an X-Sandbox-Session: <any-string> header and the sandbox keys all your data to a stable sandbox user for that session. Reuse the same value across a test run to accumulate against one caller; use a fresh value to start from a clean slate. Omit it and you share the default session.
curl -s -X POST "$PHOSRA_SANDBOX/api/v1/setup/quick" \
  -H "Content-Type: application/json" \
  -H "X-Sandbox-Session: my-test-run-42" \
  -d '{"child_name":"Nora","birth_date":"2015-05-01","strictness":"strict"}'
Idempotent retries (family_id). POST /setup/quick deduplicates on (family_id, child_name, birth_date). A repeat call that passes the family_id from a previous response — with the same child name and birth date — returns the same child and policy rather than minting a duplicate. This makes the endpoint safe to retry after a network failure, and it repairs a half-created child from an interrupted first attempt.
# First call — creates family + child; capture the family id
FID=$(curl -s -X POST "$PHOSRA_SANDBOX/api/v1/setup/quick" \
  -H "Content-Type: application/json" \
  -d '{"child_name":"Nora","birth_date":"2015-05-01","strictness":"strict"}' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['family']['id'])")

# Retry with family_id — returns the SAME child id, not a duplicate
curl -s -X POST "$PHOSRA_SANDBOX/api/v1/setup/quick" \
  -H "Content-Type: application/json" \
  -d "{\"family_id\":\"$FID\",\"child_name\":\"Nora\",\"birth_date\":\"2015-05-01\",\"strictness\":\"strict\"}"
Without a family_id, each anonymous POST /setup/quick creates a new family — two bare calls return two different families and children. Idempotency kicks in only once you thread the family_id back through. That is the intended design: it lets you build up independent test households without a shared caller identity.

Going to production

Nothing you do in the sandbox touches a real family. When you are ready, three changes flip you to production, and the request shapes are unchanged:
1

Swap the base URL

https://phosra-api-sandbox-production.up.railway.apphttps://prodapi.phosra.com
2

Add a live key

Attach an Authorization: Bearer phosra_live_… header. Create one in the dashboard.
3

Use real DIDs

The reference providers (did:ocss:loopline, …) and demo children are sandbox-only. In production you connect real accredited providers and real family data.