Skip to main content
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.
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

Family — the household root

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.
Family
{
  "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
The people who can manage a family. Distinct from Child.
FieldTypeNotes
iduuidPrimary key.
family_iduuidForeign key → Family.
user_iduuidThe underlying user.
roleenumowner · parent · guardian.
joined_atdate-timeRFC 3339.
FamilyMember
{
  "id": "b8c9e1a2-3f45-4d67-89ab-cdef01234567",
  "family_id": "d3b07384-d9a0-4c9b-8f2e-2f1a6b0c1a20",
  "user_id": "6a1f0e2d-7c8b-4a9e-b0c1-d2e3f4a5b6c7",
  "role": "owner",
  "joined_at": "2026-07-06T14:03:11Z"
}
Reference: Add a member · List members
A profile within a family, keyed by birth date. Phosra derives the age group (and thus the age-appropriate rule defaults) from birth_date.
FieldTypeNotes
iduuidPrimary key.
family_iduuidForeign key → Family.
namestringDisplay name.
birth_datedateYYYY-MM-DD. Drives age-group defaults and the ≥13 write-notice branch.
avatar_urlstringOptional profile image.
created_atdate-timeRFC 3339.
Child
{
  "id": "a11ce0fa-0000-4000-8000-0000000000a1",
  "family_id": "d3b07384-d9a0-4c9b-8f2e-2f1a6b0c1a20",
  "name": "Mia",
  "birth_date": "2013-01-15",
  "avatar_url": null,
  "created_at": "2026-07-06T14:03:11Z"
}
Reference: Add a child · Get a child · Age ratings · concept → Children & Age Groups
The container a child’s rules live in. A child may own several; policies carry a lifecycle status and a priority for ordering.
FieldTypeNotes
iduuidPrimary key.
child_iduuidForeign key → Child.
namestringDisplay name (e.g. School Day).
statusenumdraft · active · paused. Only active policies enforce.
priorityintegerOrdering weight when multiple policies apply; higher wins.
versionintegerBumped on every change.
created_atdate-timeRFC 3339.
ChildPolicy
{
  "id": "b011c1e5-0000-4000-8000-000000000b01",
  "child_id": "a11ce0fa-0000-4000-8000-0000000000a1",
  "name": "School Day",
  "status": "active",
  "priority": 10,
  "version": 3,
  "created_at": "2026-07-06T14:03:11Z"
}
Reference: Create a policy · Generate rules from age · Activate · Pause · concept → Policies & Rules
A single rule inside a policy: which OCSS category, whether it’s on, and its category-specific config. There are 123 categories — see the Rule Categories reference.
FieldTypeNotes
iduuidPrimary key.
policy_iduuidForeign key → ChildPolicy.
categoryenumOne of the 123 OCSS rule categories.
enabledbooleanWhether this rule is active.
configobjectCategory-specific configuration.
created_atdate-timeRFC 3339.
PolicyRule
{
  "id": "c4f1e2d3-0000-4000-8000-0000000000c1",
  "policy_id": "b011c1e5-0000-4000-8000-000000000b01",
  "category": "content_rating",
  "enabled": true,
  "config": { "max_ratings": { "mpaa": "PG-13", "tv": "TV-Y7", "esrb": "E" } },
  "created_at": "2026-07-06T14:03:11Z"
}
Reference: List policy rules · Bulk upsert rules · Update a rule
Created when you enforce a policy. It fans out to every connected platform in parallel.
FieldTypeNotes
iduuidPrimary key.
child_iduuidForeign key → Child.
policy_iduuidForeign key → ChildPolicy.
trigger_typeenummanual · auto · webhook.
statusenumpending · running · completed · failed · partial.
started_atdate-time | nullSet when the job starts running.
completed_atdate-time | nullSet when it finishes.
created_atdate-timeRFC 3339.
EnforcementJob
{
  "id": "e5a2b3c4-0000-4000-8000-0000000000e1",
  "child_id": "a11ce0fa-0000-4000-8000-0000000000a1",
  "policy_id": "b011c1e5-0000-4000-8000-000000000b01",
  "trigger_type": "manual",
  "status": "completed",
  "started_at": "2026-07-06T14:05:02Z",
  "completed_at": "2026-07-06T14:05:04Z",
  "created_at": "2026-07-06T14:05:02Z"
}
Reference: Trigger enforcement · List jobs · Job results · concept → Enforcement
One row per connected platform in a job. This is what you poll to verify enforcement — there is no push webhook on job completion.
FieldTypeNotes
iduuidPrimary key.
enforcement_job_iduuidForeign key → EnforcementJob.
compliance_link_iduuidForeign key → the compliance link this result is attributed to.
platform_idstringThe target platform (e.g. nextdns).
statusenumpending · running · completed · failed · partial.
rules_appliedintegerRules successfully applied.
rules_skippedintegerRules the platform can’t express (see Platforms).
rules_failedintegerRules that errored.
error_messagestring | nullDetail on failure; null on success.
detailsobjectFree-form provider response payload captured for debugging.
started_atdate-time | nullRFC 3339 timestamp of when the result started running.
completed_atdate-time | nullRFC 3339 timestamp of when it finished.
EnforcementResult
{
  "id": "f6b3c4d5-0000-4000-8000-0000000000f1",
  "enforcement_job_id": "e5a2b3c4-0000-4000-8000-0000000000e1",
  "compliance_link_id": "c7d8e9f0-0000-4000-8000-0000000000c1",
  "platform_id": "nextdns",
  "status": "completed",
  "rules_applied": 5,
  "rules_skipped": 2,
  "rules_failed": 0,
  "error_message": null,
  "details": { "provider_status": 200 },
  "started_at": "2026-07-06T14:05:02Z",
  "completed_at": "2026-07-06T14:05:04Z"
}
Reference: Get job results · Retry a job
On the data plane, each active rule compiles into a CategoryEntry inside the signed §8.3.6 enforcement profile. The @phosra/gatekeeper SDK reads it with gk.isAllowed({ category })entirely locally, zero network — and returns a Verdict.
FieldTypeNotes
categoryslugOCSS rule category.
decisionenumallow · warn · block.
fail_modeenumopen · closed. closed = must block if the profile is missing/expired.
rule_refstringPer-child opaque handle; echo it back to confirm(). null when fail-closed.
rule_slugstringRegistry slug, for logging.
Verdict (CategoryEntry)
{
  "category": "addictive_pattern_block",
  "decision": "block",
  "fail_mode": "closed",
  "rule_ref": "rr_9f3c...opaque",
  "rule_slug": "addictive_pattern_block"
}
One field, two spellings. The wire/profile names this fail_mode with values open/closed; the gatekeeper SDK surfaces the same signal as failMode with allow/block. fail_mode:"closed"failMode:"block". See Platform integration.
Reference: Fetch profile · Platform integration · glossary → Verdict
After enforcing (or refusing) a verdict, the platform calls verdict.confirm(...), which signs and POSTs a §8.3.8 enforcement_result receipt. It is identity-free by construction — no child ref, device id, or user id. The inner body is exactly five fields.
FieldTypeNotes
envelope_refstringIdempotency key of the carried envelope.
rule_refstringThe same rule_ref from the Verdict — ties the receipt to the rule.
enforcement_stateenumapplied · degraded · refused.
applied_atdate-timeRFC 3339.
method_classstringMechanism of enforcement (e.g. dns_block, platform_gate). Non-empty when applied.
EnforcementResultBody (signed inner body)
{
  "envelope_ref": "env_01JT...",
  "rule_ref": "rr_9f3c...opaque",
  "enforcement_state": "applied",
  "applied_at": "2026-07-06T14:05:04Z",
  "method_class": "dns_block"
}
Reference: Submit confirmation · glossary → Receipt

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

draft

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

active

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

paused

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

EnforcementJob — pending → running → completed | failed | partial

1

pending → running

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

completed

Every EnforcementResult succeeded.
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

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:
# 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:
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\":[ ...
The product-plane resources above require a phosra_ key (mint one in the Developer Console); see Authentication and the Quickstart to create your first family → child → policy → rule → enforcement job end to end. There is no public unauthenticated sandbox for the product plane.

Where to go next

Quickstart →

Create the whole graph in one POST /setup/quick call, then enforce it.

Architecture →

Why the product plane and the enforcement data plane are kept separate.

Policies & Rules →

How the 123 OCSS categories become a policy.

Enforcement →

Jobs, per-platform results, and the poll-don’t-webhook contract.