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
Read each edge as "one ... has many ...":
| Relationship | Cardinality | Foreign key | Meaning |
|---|---|---|---|
| Family → Child | 1 — ∗ | child.family_id | A household holds one profile per kid. |
| Family → FamilyMember | 1 — ∗ | member.family_id | The guardians/parents on the account. |
| Child → ChildPolicy | 1 — ∗ | policy.child_id | A kid can have several policies (e.g. School Day, Weekend); one is active. |
| ChildPolicy → PolicyRule | 1 — ∗ | rule.policy_id | A policy is a set of rules drawn from the 123 OCSS categories. |
| ChildPolicy → EnforcementJob | 1 — ∗ | job.policy_id, job.child_id | Each push of a policy to platforms is a job. |
| EnforcementJob → EnforcementResult | 1 — ∗ | result.enforcement_job_id | One result row per connected platform the job fanned out to. |
| PolicyRule → Verdict | 1 — 1 (derived) | verdict.rule_ref | Each active rule compiles to one per-category decision in the signed profile. |
| Verdict → EnforcementReceipt | 1 — 1 (on confirm) | receipt.rule_ref | Confirming 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.
| Field | Type | Notes |
|---|---|---|
id | uuid | Primary key. |
name | string | Display name of the household. |
created_at | date-time | RFC 3339. |
{
"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
- 1draft
Created but not enforcing. Build up rules here.
POST /policiesstarts a policy indraftunless you activate it. - 2active
The only status that enforces.
POST /policies/{id}/activate. When a child has multiple active policies,priority(higher wins) breaks the tie. - 3paused
Temporarily off without deleting.
POST /policies/{id}/pause. Re-activate any time.
EnforcementJob — pending → running → completed | failed | partial
- 1pending → running
The job is created (
202 Accepted) and begins fanning out to platforms. - 2completed
Every
EnforcementResultsucceeded. - 3partial
Some platforms applied, some didn't — inspect
rules_skipped/rules_failedper result. Common when a platform can't express a category (manual_attested; see Platforms). - 4failed
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
# → 401The 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\":[ ...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
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.