Core objects & data model

Six objects carry every parental-controls integration, plus one more that lives on the enforcement path. This page is the map: how they nest, how many of each you get, what states they move through, and where each one's field-by-field reference lives. Read it once and the rest of the API reference falls into place.

Every shape below is taken directly from the OpenAPI spec — the same document that generates the SDKs and the API reference. Where a field is an enum, the allowed values are the real ones the census accepts.

The resource graph

Entity-relationship diagram: Family 1—many Child and 1—many FamilyMember; Child 1—many ChildPolicy; ChildPolicy 1—many PolicyRule and 1—many EnforcementJob; EnforcementJob 1—many EnforcementResult (one per platform); each PolicyRule compiles to a Verdict/CategoryEntry, which on confirm() produces an EnforcementReceipt.
The Phosra core object model. A Family owns Children and FamilyMembers; each Child owns ChildPolicies; each policy owns PolicyRules. Enforcing a policy creates an EnforcementJob that fans out to one EnforcementResult per platform. On the data plane, each rule compiles to a Verdict that the gatekeeper reads locally, and confirming it writes an EnforcementReceipt.

Read each edge as "one ... has many ...":

RelationshipCardinalityForeign keyMeaning
Family → Child1 — ∗child.family_idA household holds one profile per kid.
Family → FamilyMember1 — ∗member.family_idThe guardians/parents on the account.
Child → ChildPolicy1 — ∗policy.child_idA kid can have several policies (e.g. School Day, Weekend); one is active.
ChildPolicy → PolicyRule1 — ∗rule.policy_idA policy is a set of rules drawn from the 123 OCSS categories.
ChildPolicy → EnforcementJob1 — ∗job.policy_id, job.child_idEach push of a policy to platforms is a job.
EnforcementJob → EnforcementResult1 — ∗result.enforcement_job_idOne result row per connected platform the job fanned out to.
PolicyRule → Verdict1 — 1 (derived)verdict.rule_refEach active rule compiles to one per-category decision in the signed profile.
Verdict → EnforcementReceipt1 — 1 (on confirm)receipt.rule_refConfirming an applied verdict writes one signed receipt.

Two planes, one graph. The green objects (Family → PolicyRule) and the blue objects (EnforcementJob, EnforcementResult) live in the control/product plane — normal REST resources you read and write with a phosra_ key. The violet Verdict and EnforcementReceipt live on the data plane: they are compiled into a signed profile and evaluated locally, in-process by the gatekeeper — there is no hosted POST /check. The rule_ref field is the thread that stitches a product-plane PolicyRule to its data-plane Verdict and back to a receipt.

Objects

The top of the graph. Everything else hangs off a family, directly or transitively.

FieldTypeNotes
iduuidPrimary key.
namestringDisplay name of the household.
created_atdate-timeRFC 3339.
json
{
  "id": "d3b07384-d9a0-4c9b-8f2e-2f1a6b0c1a20",
  "name": "The Rivera Family",
  "created_at": "2026-07-06T14:03:11Z"
}

Reference: Create a family · List families · concept → Families

Lifecycle & state

Three objects in the graph are state machines. Knowing the transitions saves you a class of "why isn't this enforcing?" bugs.

ChildPolicy — draft → active → paused

  1. 1
    draft

    Created but not enforcing. Build up rules here. POST /policies starts a policy in draft unless you activate it.

  2. 2
    active

    The only status that enforces. POST /policies/{id}/activate. When a child has multiple active policies, priority (higher wins) breaks the tie.

  3. 3
    paused

    Temporarily off without deleting. POST /policies/{id}/pause. Re-activate any time.

EnforcementJob — pending → running → completed | failed | partial

  1. 1
    pending → running

    The job is created (202 Accepted) and begins fanning out to platforms.

  2. 2
    completed

    Every EnforcementResult succeeded.

  3. 3
    partial

    Some platforms applied, some didn't — inspect rules_skipped / rules_failed per result. Common when a platform can't express a category (manual_attested; see Platforms).

  4. 4
    failed

    The job as a whole failed. Retry it.

Verdict standing — allow · warn · block (fail-closed)

A verdict is not a stored row that transitions; it is recomputed locally on every isAllowed() call from the current signed profile. Its standing is one of allow, warn, or block. If the cached profile is missing or expired, a fail_mode: closed category resolves to block with rule_ref: null — enforcement never silently passes.

Do not call confirm() on a fail-closed verdict (rule_ref is null) — there is nothing to attest. The SDK throws RuleRefRequired. See Platform integration.

Prove the graph is live

The product-plane objects are served today. No key gives you a 401 (not a 404), which is the correct answer — the surface exists:

bash
# The control/product plane is live:
curl -fsS https://prodapi.phosra.com/health
# → 200 {"status":"ok"}
 
# Family (and every product object) requires a phosra_ key — 401 without one:
curl -s -o /dev/null -w "%{http_code}\n" https://prodapi.phosra.com/api/v1/families
# → 401

The data-plane objects (Verdict, EnforcementReceipt) are served by the census. The public sandbox census is live and serves the signed Trust List every profile verifies against:

bash
curl -fsS https://phosra-api-sandbox-production.up.railway.app/health
# → 200
 
curl -fsS https://phosra-api-sandbox-production.up.railway.app/.well-known/ocss/trust-list | head -c 80
# → {"document":"{\"document_type\":\"trust_list_issue\",\"entries\":[ ...

On production the product-plane resources above require a phosra_ key (mint one in the Developer Console); see Authentication. On the open sandbox the product plane is keyless — the Quickstart's POST /setup/quick creates your first family → child → policy → rule → enforcement job end to end with no credential at all (see Test in the sandbox).

Where to go next