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

# Onboard a multi-child household

> Chain five endpoints to onboard one family with two kids of different ages, generate a distinct age-appropriate policy for each, activate them, and confirm each policy matches the child’s rating ceiling. Every call runs live against the open sandbox.

Real households are not one child. This recipe onboards **one family with two kids of different
ages** and gives each an age-appropriate policy that Phosra generates from their birth date — a
9-year-old and a 16-year-old end up with genuinely different rules, screen-time, and rating
ceilings, from the same three lines of code.

You will chain five endpoints:

```mermaid theme={null}
flowchart LR
  A["POST /setup/quick<br/>family + Leo (9) + policy"] --> B["POST /families/{id}/children<br/>add Ava (16)"]
  B --> C["POST /children/{id}/policies<br/>draft policy for Ava"]
  C --> D["POST /policies/{id}/generate-from-age<br/>fill rules from age"]
  D --> E["POST /policies/{id}/activate<br/>go live"]
  E --> F["GET /children/{id}/age-ratings<br/>confirm the ceiling"]
```

Every request and response below is **verbatim live output**, captured by running these exact calls
against `https://phosra-api-sandbox-production.up.railway.app`. No API key, nothing to install.

<Info>
  **Sandbox-first.** These calls run against the open sandbox with no credential — nothing you create
  touches a real family. The shapes are identical in production: swap the base URL for
  `https://prodapi.phosra.com` and authenticate with a `phosra_live_…` key.
</Info>

## Before you start

Set the base URL once so every step is copy-paste:

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

<Steps>
  <Step title="Onboard the family with the first child">
    `POST /setup/quick` onboards a family, the first child, and an age-appropriate starter policy in one
    call. Give it the younger child — **Leo, age 9**.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s -X POST "$PHOSRA_BASE/api/v1/setup/quick" \
        -H "Content-Type: application/json" \
        -d '{
          "child_name": "Leo",
          "birth_date": "2016-09-03",
          "family_name": "The Okafor Family",
          "strictness": "recommended"
        }'
      ```

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

      const res = await fetch(`${BASE}/api/v1/setup/quick`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          child_name: "Leo",
          birth_date: "2016-09-03",
          family_name: "The Okafor Family",
          strictness: "recommended",
        }),
      });
      const setup = await res.json();
      const familyId: string = setup.family.id; // carry into every step below
      const leoId: string = setup.child.id;
      ```

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

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

      setup = requests.post(
          f"{BASE}/api/v1/setup/quick",
          json={
              "child_name": "Leo",
              "birth_date": "2016-09-03",
              "family_name": "The Okafor Family",
              "strictness": "recommended",
          },
          timeout=30,
      ).json()
      family_id = setup["family"]["id"]  # carry into every step below
      leo_id = setup["child"]["id"]
      ```
    </CodeGroup>

    The response returns the family, the child, a fully-seeded policy, and a `rule_summary` — Leo, at 9,
    lands in the **`child`** age group with a **90-minute** daily limit and a **PG** rating ceiling.
    Truncated to what you carry forward:

    ```json theme={null}
    {
      "family": {
        "id": "af03190e-b31c-40c9-9cc4-f282a532326e",
        "name": "The Okafor Family",
        "created_at": "2026-07-06T08:45:32.502290561Z"
      },
      "child": {
        "id": "f87273d3-bbb7-43b7-9929-f4a0f2d59373",
        "name": "Leo",
        "birth_date": "2016-09-03T00:00:00Z"
      },
      "policy": { "id": "6d81fd69-2395-48f5-aac9-447e3cc118bb", "status": "active" },
      "age_group": "child",
      "max_ratings": { "csm": "7+", "esrb": "E", "mpaa": "PG", "pegi": "7", "tvpg": "TV-Y7" },
      "rule_summary": {
        "screen_time_minutes": 90,
        "bedtime_hour": 20,
        "web_filter_level": "strict",
        "content_rating": "PG",
        "total_rules_enabled": 20
      }
    }
    ```

    Keep `family.id` (the next child is added to it) and `child.id` (Leo, for the ratings check at the
    end):

    ```bash theme={null}
    export FAMILY_ID="af03190e-b31c-40c9-9cc4-f282a532326e"
    export LEO_ID="f87273d3-bbb7-43b7-9929-f4a0f2d59373"
    ```

    <Card title="Reference: quickstart · setup/quick" icon="book" href="/quickstart">
      The one-call onboarding path, with the full request and response documented.
    </Card>
  </Step>

  <Step title="Add the second child">
    The teenager is added to the **same family** with [`POST /families/{familyID}/children`](/api-reference/children/add-child).
    Only a name and birth date are required — **Ava, age 16**.

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

      ```typescript TypeScript theme={null}
      const ava = await (
        await fetch(`${BASE}/api/v1/families/${familyId}/children`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ name: "Ava", birth_date: "2010-02-20" }),
        })
      ).json();
      const avaId: string = ava.id;
      ```

      ```python Python theme={null}
      ava = requests.post(
          f"{BASE}/api/v1/families/{family_id}/children",
          json={"name": "Ava", "birth_date": "2010-02-20"},
          timeout=30,
      ).json()
      ava_id = ava["id"]
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "id": "acf4ce5d-d452-47b8-a78b-3b4ca86f50be",
      "family_id": "af03190e-b31c-40c9-9cc4-f282a532326e",
      "name": "Ava",
      "birth_date": "2010-02-20T00:00:00Z",
      "created_at": "2026-07-06T08:45:32.832408747Z"
    }
    ```

    ```bash theme={null}
    export AVA_ID="acf4ce5d-d452-47b8-a78b-3b4ca86f50be"
    ```

    <Note>
      Adding a child does **not** create a policy — unlike `setup/quick`, which bundles one. That is the
      next two steps: create a draft policy, then fill it from Ava's age.
    </Note>
  </Step>

  <Step title="Create a draft policy for the teen">
    [`POST /children/{childID}/policies`](/api-reference/policies/create-policy) creates an empty
    **`draft`** policy. It has no rules yet — you fill it in the next step.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s -X POST "$PHOSRA_BASE/api/v1/children/$AVA_ID/policies" \
        -H "Content-Type: application/json" \
        -d '{ "name": "Ava'\''s Policy" }'
      ```

      ```typescript TypeScript theme={null}
      const avaPolicy = await (
        await fetch(`${BASE}/api/v1/children/${avaId}/policies`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ name: "Ava's Policy" }),
        })
      ).json();
      const avaPolicyId: string = avaPolicy.id;
      ```

      ```python Python theme={null}
      ava_policy = requests.post(
          f"{BASE}/api/v1/children/{ava_id}/policies",
          json={"name": "Ava's Policy"},
          timeout=30,
      ).json()
      ava_policy_id = ava_policy["id"]
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "id": "a3147895-b752-4d03-846c-35fd8fb4e77c",
      "child_id": "acf4ce5d-d452-47b8-a78b-3b4ca86f50be",
      "name": "Ava's Policy",
      "status": "draft",
      "version": 0,
      "created_at": "2026-07-06T08:45:33.024493168Z"
    }
    ```

    ```bash theme={null}
    export AVA_POLICY_ID="a3147895-b752-4d03-846c-35fd8fb4e77c"
    ```
  </Step>

  <Step title="Generate rules from Ava's age">
    This is the load-bearing call. [`POST /policies/{policyID}/generate-from-age`](/api-reference/policies/generate-policy-from-age)
    looks up the child on the policy, computes their age, and fills the policy with an age-appropriate
    rule set at the `strictness` you pass. We use **`strict`** for the teen.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s -X POST "$PHOSRA_BASE/api/v1/policies/$AVA_POLICY_ID/generate-from-age" \
        -H "Content-Type: application/json" \
        -d '{ "strictness": "strict" }'
      ```

      ```typescript TypeScript theme={null}
      const avaRules = await (
        await fetch(`${BASE}/api/v1/policies/${avaPolicyId}/generate-from-age`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ strictness: "strict" }),
        })
      ).json();
      // avaRules.length === 18 → the age-tuned rule set for a 16-year-old
      ```

      ```python Python theme={null}
      ava_rules = requests.post(
          f"{BASE}/api/v1/policies/{ava_policy_id}/generate-from-age",
          json={"strictness": "strict"},
          timeout=30,
      ).json()
      # len(ava_rules) == 18 → the age-tuned rule set for a 16-year-old
      ```
    </CodeGroup>

    The call returns the generated rules. Ava's set is **tuned to 16, not 9** — a longer daily limit, a
    PG-13 ceiling, a lighter web filter, contacts-only DMs, a later curfew, and no age gate:

    ```json theme={null}
    [
      { "category": "time_daily_limit",  "config": { "daily_minutes": 180 } },
      { "category": "content_rating",    "config": { "max_ratings": {
          "csm": "13+", "esrb": "T", "mpaa": "PG-13", "pegi": "12", "tvpg": "TV-14" } } },
      { "category": "web_filter_level",  "config": { "level": "light" } },
      { "category": "dm_restriction",    "config": { "mode": "contacts_only" } },
      { "category": "notification_curfew", "config": { "start": "22:00", "end": "06:00" } },
      { "category": "age_gate",          "enabled": false }
    ]
    ```

    Compare with Leo's seeded policy from step 1 — **same family, same code, different child**:

    | Rule                   | Leo (age 9, `recommended`) | Ava (age 16, `strict`) |
    | ---------------------- | -------------------------- | ---------------------- |
    | Daily screen time      | 90 min                     | 180 min                |
    | Content-rating ceiling | PG / CSM 7+                | PG-13 / CSM 13+        |
    | Web filter             | `strict`                   | `light`                |
    | DMs                    | `friends_only`             | `contacts_only`        |
    | Curfew                 | 20:00–07:00                | 22:00–06:00            |
    | Age gate               | on (min 13)                | off                    |
    | Total rules enabled    | 20                         | 18                     |

    <Accordion title="Fields & errors">
      | Field        | Type | Notes                                                                                                                                   |
      | ------------ | ---- | --------------------------------------------------------------------------------------------------------------------------------------- |
      | `strictness` | enum | `recommended` (default), `strict`, or `relaxed`. Shifts limits and thresholds; the **rating ceiling is driven by age**, not strictness. |

      `generate-from-age` **replaces** the policy's rules — it is a regenerate, not a merge. A policy with
      no resolvable child returns `400`. To hand-edit an individual rule afterward, see
      [Change a rule and enforce it](/guides/recipes/policy-change-and-enforce).
    </Accordion>
  </Step>

  <Step title="Activate the policy">
    A generated policy is still a `draft`. [`POST /policies/{policyID}/activate`](/api-reference/policies/activate-policy)
    makes it the child's live policy and stamps a version.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s -X POST "$PHOSRA_BASE/api/v1/policies/$AVA_POLICY_ID/activate" \
        -H "Content-Type: application/json" -d '{}'
      ```

      ```typescript TypeScript theme={null}
      const activated = await (
        await fetch(`${BASE}/api/v1/policies/${avaPolicyId}/activate`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: "{}",
        })
      ).json();
      // activated.status === "active"
      ```

      ```python Python theme={null}
      activated = requests.post(
          f"{BASE}/api/v1/policies/{ava_policy_id}/activate",
          json={},
          timeout=30,
      ).json()
      # activated["status"] == "active"
      ```
    </CodeGroup>

    ```json theme={null}
    { "id": "a3147895-b752-4d03-846c-35fd8fb4e77c", "status": "active", "version": 20 }
    ```

    Both children now have an active, age-tuned policy. Leo's went live at step 1; Ava's is live now.
  </Step>

  <Step title="Confirm each child's rating ceiling">
    Isolation across children is not just "two policies exist" — it is "each policy matches that child's
    age." [`GET /children/{childID}/age-ratings`](/api-reference/children/get-child-age-ratings) returns
    the rating ceiling Phosra computes from a child's birth date. Run it for both and the numbers line up
    with the policies you just generated.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s "$PHOSRA_BASE/api/v1/children/$LEO_ID/age-ratings"
      curl -s "$PHOSRA_BASE/api/v1/children/$AVA_ID/age-ratings"
      ```

      ```typescript TypeScript theme={null}
      const leoRatings = await (
        await fetch(`${BASE}/api/v1/children/${leoId}/age-ratings`)
      ).json();
      const avaRatings = await (
        await fetch(`${BASE}/api/v1/children/${avaId}/age-ratings`)
      ).json();
      // leoRatings.age === 9, avaRatings.age === 16
      ```

      ```python Python theme={null}
      leo_ratings = requests.get(f"{BASE}/api/v1/children/{leo_id}/age-ratings", timeout=30).json()
      ava_ratings = requests.get(f"{BASE}/api/v1/children/{ava_id}/age-ratings", timeout=30).json()
      # leo_ratings["age"] == 9, ava_ratings["age"] == 16
      ```
    </CodeGroup>

    ```json theme={null}
    // Leo
    { "age": 9,  "ratings": { "csm": { "code": "7+" },  "mpaa": { "code": "PG" } } }
    // Ava
    { "age": 16, "ratings": { "csm": { "code": "13+" }, "mpaa": { "code": "PG-13" } } }
    ```

    Leo's ceiling is **PG / CSM 7+**; Ava's is **PG-13 / CSM 13+** — exactly the `content_rating` rule
    each policy carries. One family, two children, two correct ceilings.
  </Step>
</Steps>

## The whole flow at a glance

Every row is one call you just ran, in order:

| # | Step                 | Call                                    | Live result                                    |
| - | -------------------- | --------------------------------------- | ---------------------------------------------- |
| 1 | Onboard family + Leo | `POST /setup/quick`                     | family `af03190e…`, Leo (9), 20-rule PG policy |
| 2 | Add Ava              | `POST /families/{id}/children`          | child `acf4ce5d…`, age 16                      |
| 3 | Draft Ava's policy   | `POST /children/{id}/policies`          | policy `a3147895…`, `draft`                    |
| 4 | Generate from age    | `POST /policies/{id}/generate-from-age` | 18 rules, PG-13, 180-min limit                 |
| 5 | Activate             | `POST /policies/{id}/activate`          | `active`, version 20                           |
| 6 | Confirm ceilings     | `GET /children/{id}/age-ratings`        | Leo PG · Ava PG-13                             |

## Next steps

<CardGroup cols={2}>
  <Card title="Change a rule and enforce it" icon="bolt" href="/guides/recipes/policy-change-and-enforce">
    Tighten one of these rules and push it to a connected platform — then read back what landed.
  </Card>

  <Card title="End-to-end walkthrough" icon="route" href="/guides/end-to-end-walkthrough">
    Link two platforms to this family, unlink one, and prove per-service isolation.
  </Card>

  <Card title="Families & kids" icon="users" href="/guides/family-and-kids">
    The full family, child, and policy resource model behind these calls.
  </Card>

  <Card title="Strictness levels" icon="sliders" href="/concepts/strictness-levels">
    How `recommended`, `strict`, and `relaxed` shift limits — and why the rating ceiling follows age.
  </Card>
</CardGroup>
