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

# Set up a family & kids

> Create a family, add a child, and get an active age-appropriate policy in a single call — run live against the open sandbox with real requests and responses.

Everything Phosra enforces hangs off a **child**. A child belongs to a **family**, and carries
a **policy** — the set of rules that get pushed to every connected platform. This guide builds
all three in one call, then enforces the result, entirely against the public sandbox.

Every response below is **verbatim live output**, captured while writing this page. No API key.

<Info>
  **Sandbox-first.** These requests run against
  `https://phosra-api-sandbox-production.up.railway.app` — open, seeded, and safe. In production,
  change the base URL to `https://prodapi.phosra.com` and add your `phosra_live_…` key; the
  request shapes are identical.
</Info>

## Before you start

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

<Steps>
  <Step title="Create a family, a child, and an active policy — in one call">
    `POST /setup/quick` is the fastest path to a working policy. Give it a child's name, birth
    date, and a strictness level (`recommended`, `strict`, or `light`). Phosra derives the age
    group from the birth date and returns a family, a child, an **active** policy, and a full set
    of age-appropriate rules — with no follow-up calls.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s -X POST "$PHOSRA_BASE/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 setup = 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",
        }),
      }).then((r) => r.json());

      setup.child.id;                    // keep this — it is the child handle
      setup.policy.status;               // "active"
      setup.rule_summary.total_rules_enabled; // 20
      ```

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

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

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

      child_id = setup["child"]["id"]    # keep this — it is the child handle
      setup["policy"]["status"]          # "active"
      ```

      ```go Go theme={null}
      body, _ := json.Marshal(map[string]string{
      	"child_name":  "Emma",
      	"birth_date":  "2016-03-15",
      	"strictness":  "recommended",
      })
      res, _ := http.Post(base+"/setup/quick", "application/json", bytes.NewReader(body))
      defer res.Body.Close()
      // decode { Family, Child, Policy, AgeGroup, MaxRatings, RuleSummary, Rules }
      ```
    </CodeGroup>

    **Real response (200), trimmed to the fields you will use:**

    ```json theme={null}
    {
      "family": { "id": "8581b059-4d30-4f98-88a5-c9667610f5ca", "name": "Emma's Family" },
      "child":  { "id": "1a787ff2-bcd5-4b2d-a5fd-2235ba4e6b9a", "name": "Emma",
                  "birth_date": "2016-03-15T00:00:00Z" },
      "policy": { "id": "7949ee63-d113-4405-b791-d400e6e1f97d", "status": "active" },
      "age_group": "preteen",
      "max_ratings": { "mpaa": "PG", "esrb": "E10+", "pegi": "7", "csm": "10+", "tvpg": "TV-PG" },
      "rule_summary": {
        "screen_time_minutes": 120,
        "bedtime_hour": 21,
        "web_filter_level": "moderate",
        "content_rating": "PG",
        "total_rules_enabled": 20
      }
    }
    ```

    Phosra picked `age_group: "preteen"` from the 2016 birth date and mapped it to concrete rating
    ceilings across five rating systems. Store `family.id`, `child.id`, and `policy.id`.

    <Tip>
      Change one field to see the age model work. A `2012-09-01` birth date with `strictness: "strict"`
      returns `age_group: "teen"`, `max_ratings.mpaa: "PG-13"`, and a lighter web filter — the
      policy adapts to the child, not to a fixed template.
    </Tip>

    <Accordion title="Fields & errors — POST /setup/quick">
      **Request body** (`application/json`)

      | Field        | Type                  | Required | Description                                                                                                                               |
      | ------------ | --------------------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------- |
      | `child_name` | string                |    yes   | The child's display name; becomes `child.name` and seeds `family.name` (`"Emma's Family"`).                                               |
      | `birth_date` | string (`YYYY-MM-DD`) |    yes   | Drives the derived `age_group` and the rating ceilings.                                                                                   |
      | `strictness` | string                |    no    | One of `recommended`, `strict`, `light`. Defaults to `recommended`; an unrecognised value falls back to the default rather than erroring. |

      **Response fields (200)**

      | Field                              | Type          | Description                                                                          |
      | ---------------------------------- | ------------- | ------------------------------------------------------------------------------------ |
      | `family.id`                        | string (UUID) | The created family. Store it.                                                        |
      | `child.id`                         | string (UUID) | **The child handle** — pass it to `/children/{id}/enforce` and the policy endpoints. |
      | `policy.id`                        | string (UUID) | The generated policy.                                                                |
      | `policy.status`                    | string        | `active` — the policy is live immediately, no activation call.                       |
      | `age_group`                        | string        | Derived bucket, e.g. `preteen`, `teen`.                                              |
      | `max_ratings`                      | object        | Rating ceilings across `mpaa`, `esrb`, `pegi`, `csm`, `tvpg`.                        |
      | `rule_summary.total_rules_enabled` | integer       | Count of enabled rule categories (`20` for `preteen` / `recommended`).               |
      | `rules`                            | array         | One object per enabled rule category with its `config` (see next step).              |

      **Errors**

      | Status | `message`                | When it happens                                                                                                                                                  |
      | :----: | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      |  `400` | `child_name is required` | `child_name` is missing from the body.                                                                                                                           |
      |  `400` | `birth_date is required` | `birth_date` is missing from the body.                                                                                                                           |
      |  `500` | `internal server error`  | `birth_date` is present but not a parseable `YYYY-MM-DD` date. Validate the format client-side — the sandbox surfaces this as a `500` rather than a clean `400`. |
    </Accordion>
  </Step>

  <Step title="Inspect the rules that were generated">
    The full response includes a `rules` array — one entry per enabled rule category, each with its
    own config. For example, the `addictive_design_control` rule comes back pre-tuned:

    ```json theme={null}
    {
      "id": "6106fd52-48fa-425b-ba4d-df54fcde2704",
      "policy_id": "7949ee63-d113-4405-b791-d400e6e1f97d",
      "category": "addictive_design_control",
      "enabled": true,
      "config": {
        "disable_streaks": true,
        "disable_autoplay": true,
        "disable_like_counts": true,
        "disable_daily_rewards": true,
        "disable_infinite_scroll": true
      }
    }
    ```

    Every category comes from the canonical [rule reference](/ocss/rule-reference). You can override
    any of them later with the [policy rules API](/api-reference/rules/update-rule).

    <Accordion title="Fields — the rule object">
      Each element of the `rules` array:

      | Field       | Type          | Description                                                                                           |
      | ----------- | ------------- | ----------------------------------------------------------------------------------------------------- |
      | `id`        | string (UUID) | The rule's id, for targeted updates.                                                                  |
      | `policy_id` | string (UUID) | The policy this rule belongs to — matches `policy.id`.                                                |
      | `category`  | string        | Rule category slug (e.g. `addictive_design_control`) from the [rule reference](/ocss/rule-reference). |
      | `enabled`   | boolean       | Whether the rule is active in this policy.                                                            |
      | `config`    | object        | Per-category settings. Shape depends on `category`.                                                   |
    </Accordion>
  </Step>

  <Step title="Enforce the policy across connected platforms">
    Push the active policy to every platform the family has connected. Enforcement is asynchronous —
    you get a job back immediately (`202 Accepted`):

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s -X POST "$PHOSRA_BASE/children/1a787ff2-bcd5-4b2d-a5fd-2235ba4e6b9a/enforce"
      ```

      ```typescript TypeScript theme={null}
      const job = await fetch(`${BASE}/children/${child_id}/enforce`, {
        method: "POST",
      }).then((r) => r.json());
      // job.id, job.status === "running"
      ```

      ```python Python theme={null}
      job = requests.post(f"{BASE}/children/{child_id}/enforce", timeout=30).json()
      # job["id"], job["status"] == "running"
      ```

      ```go Go theme={null}
      req, _ := http.NewRequest("POST", base+"/children/"+childID+"/enforce", nil)
      res, _ := http.DefaultClient.Do(req)
      defer res.Body.Close()
      // decode { ID, Status, TriggerType }
      ```
    </CodeGroup>

    **Real response (202):**

    ```json theme={null}
    {
      "id": "56072389-470e-4f7c-9d42-1f6cea38d43a",
      "child_id": "1a787ff2-bcd5-4b2d-a5fd-2235ba4e6b9a",
      "policy_id": "7949ee63-d113-4405-b791-d400e6e1f97d",
      "trigger_type": "manual",
      "status": "running"
    }
    ```

    <Accordion title="Fields & errors — POST /children/{childID}/enforce">
      **Path parameter**

      | Field     | Type          | Required | Description                         |
      | --------- | ------------- | :------: | ----------------------------------- |
      | `childID` | string (UUID) |    yes   | The `child.id` from `/setup/quick`. |

      **Response fields (202)**

      | Field          | Type          | Description                                            |
      | -------------- | ------------- | ------------------------------------------------------ |
      | `id`           | string (UUID) | **The enforcement job id** — poll it in the next step. |
      | `child_id`     | string (UUID) | Echoes the child being enforced.                       |
      | `policy_id`    | string (UUID) | The active policy being pushed.                        |
      | `trigger_type` | string        | `manual` for a direct call.                            |
      | `status`       | string        | `running` at accept time.                              |

      **Errors**

      | Status | `message`          | When it happens                                     |
      | :----: | ------------------ | --------------------------------------------------- |
      |  `400` | `invalid child ID` | `childID` is not a well-formed UUID.                |
      |  `404` | `child not found`  | `childID` is a valid UUID but no such child exists. |
    </Accordion>
  </Step>

  <Step title="Confirm the job completed">
    Poll the job by id until `status` is `completed`. Enforcement does **not** fire a webhook on
    completion — poll the job, don't wait for a callback.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s "$PHOSRA_BASE/enforcement/jobs/56072389-470e-4f7c-9d42-1f6cea38d43a"
      ```

      ```typescript TypeScript theme={null}
      const done = await fetch(`${BASE}/enforcement/jobs/${job.id}`).then((r) => r.json());
      // done.status === "completed", done.completed_at
      ```

      ```python Python theme={null}
      done = requests.get(f"{BASE}/enforcement/jobs/{job['id']}", timeout=30).json()
      # done["status"] == "completed"
      ```

      ```go Go theme={null}
      res, _ := http.Get(base + "/enforcement/jobs/" + job.ID)
      defer res.Body.Close()
      // decode { Status, CompletedAt }
      ```
    </CodeGroup>

    **Real response (200):**

    ```json theme={null}
    {
      "id": "56072389-470e-4f7c-9d42-1f6cea38d43a",
      "status": "completed",
      "completed_at": "2026-07-06T03:02:11.029Z"
    }
    ```

    That is the full loop: **create → enforce → confirm**. For results per platform, read
    `GET /enforcement/jobs/{id}/results` — a non-empty `manual_steps` array means that platform is
    parent-guided rather than programmatically applied.

    <Accordion title="Fields & errors — GET /enforcement/jobs/{jobID}">
      **Path parameter**

      | Field   | Type          | Required | Description                      |
      | ------- | ------------- | :------: | -------------------------------- |
      | `jobID` | string (UUID) |    yes   | The `id` returned by `/enforce`. |

      **Response fields (200)**

      | Field          | Type              | Description                                                            |
      | -------------- | ----------------- | ---------------------------------------------------------------------- |
      | `id`           | string (UUID)     | The job id.                                                            |
      | `status`       | string            | `running` → `completed` (or `failed`). Poll until it leaves `running`. |
      | `completed_at` | string (RFC 3339) | When the job finished; absent while still `running`.                   |

      **Notes & errors**

      | Behaviour              | Detail                                                                                                                                                            |
      | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | Unknown `jobID`        | Returns `200` with a `null` body rather than a `404`. Treat `null` as "no such job" in your client.                                                               |
      | No connected platforms | `GET /enforcement/jobs/{id}/results` returns `null` — there was nowhere to push. Connect a platform first (see [Connect a platform](/guides/connect-a-platform)). |
    </Accordion>
  </Step>
</Steps>

## Adding more children

Call `POST /setup/quick` once per child (each returns its own child in the same family when you
pass the same family context in production), or use the granular endpoints — `POST /families`,
`POST /families/{familyID}/children`, `POST /children/{childID}/policies/generate` — when you
need finer control. See the [Families](/api-reference/families/create-family) and
[Children](/api-reference/children/add-child) reference.

## Next steps

<CardGroup cols={2}>
  <Card title="Connect a platform" icon="plug" href="/guides/connect-a-platform">
    Link a family to a platform so enforcement has somewhere to land.
  </Card>

  <Card title="Test in the sandbox" icon="flask" href="/guides/test-in-sandbox">
    Understand the open sandbox, the signed Trust List, and self-registration.
  </Card>
</CardGroup>
