Enforcement
Enforcement is the process of pushing a child’s active policy rules to connected platforms. When you trigger enforcement, Phosra creates an enforcement job that fans out to each connected platform in parallel, translating universal rules into platform-specific configurations.
Flow
- Trigger — Call
POST /children/{childID}/enforce (optionally targeting specific platforms)
- Job created — Returns
202 Accepted with {"job_id": "...uuid..."}
- Fan-out — Phosra pushes rules to each connected platform in parallel
- Poll for results — there is no push callback on enforcement completion (see below)
- Job completes — overall status reflects the aggregate result
Verification contract — poll, do not expect a webhook
There is no per-platform push webhook fired on enforcement completion. Webhooks exist for other events (policy.updated, family.created, etc.) but not for the completion of a per-platform enforcement result. To verify enforcement, poll:GET /api/v1/enforcement/jobs/{jobId}/results
The developer surface is identical:
GET /api/v1/developer/enforcement/jobs/{jobId}/results
Both return []EnforcementResult:
[
{
"id": "...",
"enforcement_job_id": "...",
"platform_id": "nextdns",
"status": "completed",
"rules_applied": 5,
"rules_skipped": 2,
"rules_failed": 0,
"manual_steps": [],
"completed_at": "2026-06-28T12:00:00Z"
},
{
"id": "...",
"enforcement_job_id": "...",
"platform_id": "netflix",
"status": "partial",
"rules_applied": 0,
"rules_skipped": 0,
"rules_failed": 0,
"manual_steps": [
"Open Netflix → Profile → Parental Controls → set maturity level to PG-13",
"Enable PIN lock on adult profiles"
],
"completed_at": "2026-06-28T12:00:01Z"
}
]
Reading the result
| Field | Meaning |
|---|
rules_applied | Rules written to the platform’s API (dns, device, api_write modes only) |
rules_skipped | Rules the platform does not support |
rules_failed | Rules that errored during the API write |
manual_steps[] | Human-readable steps the parent must apply; non-empty = guided, not programmatically applied |
A non-empty manual_steps array means the platform is manual_attested — Phosra generated the steps but the parent is the actor, not the API. Do not count these as rules_applied. See Platforms & enforcement modes for the full breakdown.
EnforcementJob vs BrowserEnforcementJob
EnforcementJob (the GET /enforcement/jobs/{jobId} response shape) is deprecated. Use BrowserEnforcementJob for all new enforcement workflows — it carries richer state (screenshots, deployment_model, duration_ms) and is the type returned by all current handler code.
Both share the same polling pattern: poll results until status is completed, partial, or failed.
Consumer vs developer surface
The consumer and developer enforcement routes use the same handlers — the path prefix is the only difference:
| Operation | Consumer path (WorkOS JWT) | Developer path (API key) |
|---|
| Trigger | POST /api/v1/children/{id}/enforce | POST /api/v1/developer/children/{id}/enforce |
| Get job | GET /api/v1/enforcement/jobs/{jobId} | GET /api/v1/developer/enforcement/jobs/{jobId} |
| Get results | GET /api/v1/enforcement/jobs/{jobId}/results | GET /api/v1/developer/enforcement/jobs/{jobId}/results |
| List jobs | GET /api/v1/children/{id}/enforcement/jobs | GET /api/v1/developer/children/{id}/enforcement/jobs |
This split is deliberate, not drift — it is an auth-path decision so server integrators (developer keys) and end-user apps (WorkOS JWTs) each reach the same logic through their respective credential type. Response shapes are identical.