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

# Sandbox test data

> The one reference for every fixed value in the Phosra sandbox — demo child profiles, reference providers and platforms, the POST /setup/quick shape, connect scopes, and reset/idempotency semantics. The Stripe-test-cards of Phosra.

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](/guides/test-in-sandbox); this page is the flat lookup table.

## Base URL

```bash theme={null}
export PHOSRA_SANDBOX="https://phosra-api-sandbox-production.up.railway.app"
```

<Info>
  **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.
</Info>

## The cheat sheet

Everything fixed in the sandbox, in one scan. Each row links to its section below.

| What                     | Value                                                  | Notes                                                                         |
| ------------------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------- |
| Base URL                 | `https://phosra-api-sandbox-production.up.railway.app` | No key required                                                               |
| Health check             | `GET /health` → `{"status":"ok"}`                      | Fastest liveness probe                                                        |
| Demo child — Mia         | `a11ce0fa-0000-4000-8000-0000000000a1`                 | [subject\_ref](#demo-child-profiles)                                          |
| Demo child — Leo         | `a11ce0fa-0000-4000-8000-0000000000a2`                 | subject\_ref                                                                  |
| Demo child — Ava         | `a11ce0fa-0000-4000-8000-0000000000a3`                 | subject\_ref                                                                  |
| Connectable provider     | `did:ocss:loopline`                                    | `GET /providers/{did}/connect` → `200`                                        |
| Unconfigured provider    | `did:ocss:courier`                                     | `GET /providers/{did}/connect` → `404` (the accredited-but-unconfigured case) |
| One-call setup           | `POST /setup/quick`                                    | [Family + child + active policy + 20 rules](#post-setup-quick)                |
| Reference platforms      | 22 ids across 4 categories                             | [`netflix`, `apple`, `nextdns`, …](#reference-platforms)                      |
| Session isolation header | `X-Sandbox-Session: <any-string>`                      | [Deterministic per-caller data](#reset-and-idempotency)                       |
| Connect scopes           | `child_profiles.read` (connect) / `profiles` (token)   | [Token mint](#connect-and-token-scopes)                                       |
| Trust List               | `GET /.well-known/ocss/trust-list`                     | Signed, `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](/guides/connect-a-platform)
shares, and their `subject_ref` values are stable across every sandbox reset, so you can hard-code
them in tests.

| Child | `subject_ref` (stable UUID)            | Kind  |
| ----- | -------------------------------------- | ----- |
| Mia   | `a11ce0fa-0000-4000-8000-0000000000a1` | child |
| Leo   | `a11ce0fa-0000-4000-8000-0000000000a2` | child |
| Ava   | `a11ce0fa-0000-4000-8000-0000000000a3` | child |

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[]`:

```bash theme={null}
# 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`):**

```json theme={null}
[
  { "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" }
]
```

<Note>
  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.
</Note>

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

| DID                 | Entity                            | `GET /providers/{did}/connect`                                         | Use it to test                                        |
| ------------------- | --------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------- |
| `did:ocss:loopline` | Loopline (OCSS reference emitter) | `200` — returns `authorize_url`, `token_url`, `profiles_url`, `scopes` | The happy path: run the OAuth connect flow end to end |
| `did:ocss:courier`  | Courier Messaging                 | `404 provider connect config not available`                            | The accredited-but-**unconfigured** branch            |

```bash theme={null}
curl -s "$PHOSRA_SANDBOX/api/v1/providers/did:ocss:loopline/connect"
```

**Live response (`200 OK`):**

```json theme={null}
{
  "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](/guides/test-in-sandbox#read-the-signed-trust-list) 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.

```bash theme={null}
curl -s "$PHOSRA_SANDBOX/api/v1/platforms"
```

| Category    | `platform_id`s                                                                                                       |
| ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `device`    | `fire_tablet`, `fire_tv`, `android`, `apple`, `apple_watch`, `microsoft`                                             |
| `dns`       | `cleanbrowsing`, `controld`, `nextdns`                                                                               |
| `gaming`    | `nintendo`, `playstation`, `xbox`                                                                                    |
| `streaming` | `prime_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.

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```typescript TypeScript theme={null}
  const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";

  const res = await fetch(`${BASE}/setup/quick`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      child_name: "Emma",
      birth_date: "2016-03-15",
      strictness: "recommended",
    }),
  });
  const setup = await res.json();

  console.log(setup.child.id);      // child UUID
  console.log(setup.policy.status); // "active"
  console.log(setup.rules.length);  // 20
  ```

  ```python Python theme={null}
  import requests

  BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"

  res = requests.post(f"{BASE}/setup/quick", json={
      "child_name": "Emma",
      "birth_date": "2016-03-15",
      "strictness": "recommended",
  }, timeout=30)
  res.raise_for_status()
  setup = res.json()

  print(setup["child"]["id"])       # child UUID
  print(setup["policy"]["status"])  # "active"
  print(len(setup["rules"]))        # 20
  ```
</CodeGroup>

### Request fields

| Field         | Type                  | Required | Description                                                                                                                                                                                                                                                                    |
| ------------- | --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `child_name`  | string                | yes      | The child's display name.                                                                                                                                                                                                                                                      |
| `birth_date`  | string (`YYYY-MM-DD`) | yes      | Drives 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. |
| `strictness`  | string                | no       | `relaxed`, `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_id`   | string (UUID)         | no       | Reuse an existing family instead of creating one. Also the key to [idempotent retries](#reset-and-idempotency).                                                                                                                                                                |
| `family_name` | string                | no       | Names 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:

```json theme={null}
{
  "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
  }
}
```

<Accordion title="The 20 generated rule categories">
  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](/ocss/rule-reference) — the
  age-based generator selects the subset relevant to a child's age band.
</Accordion>

## Connect and token scopes

The reference-provider OAuth flow uses two scope strings:

| Leg                            | Scope                 | Meaning                                                     |
| ------------------------------ | --------------------- | ----------------------------------------------------------- |
| `GET /providers/{did}/connect` | `child_profiles.read` | The scope the provider advertises for the connect ceremony. |
| `POST /oauth/token` response   | `profiles`            | The granted scope on the issued access token.               |

The token response is a standard bearer grant:

```json theme={null}
{ "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.

```bash theme={null}
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.

```bash theme={null}
# 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\"}"
```

<Warning>
  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.
</Warning>

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

<Steps>
  <Step title="Swap the base URL">
    `https://phosra-api-sandbox-production.up.railway.app` → `https://prodapi.phosra.com`
  </Step>

  <Step title="Add a live key">
    Attach an `Authorization: Bearer phosra_live_…` header. [Create one](/platform/create-account) in
    the dashboard.
  </Step>

  <Step title="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.
  </Step>
</Steps>
