Skip to main content

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

  1. Trigger — Call POST /children/{childID}/enforce (optionally targeting specific platforms)
  2. Job created — Returns 202 Accepted with {"job_id": "...uuid..."}
  3. Fan-out — Phosra pushes rules to each connected platform in parallel
  4. Poll for results — there is no push callback on enforcement completion (see below)
  5. 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

FieldMeaning
rules_appliedRules written to the platform’s API (dns, device, api_write modes only)
rules_skippedRules the platform does not support
rules_failedRules 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:
OperationConsumer path (WorkOS JWT)Developer path (API key)
TriggerPOST /api/v1/children/{id}/enforcePOST /api/v1/developer/children/{id}/enforce
Get jobGET /api/v1/enforcement/jobs/{jobId}GET /api/v1/developer/enforcement/jobs/{jobId}
Get resultsGET /api/v1/enforcement/jobs/{jobId}/resultsGET /api/v1/developer/enforcement/jobs/{jobId}/results
List jobsGET /api/v1/children/{id}/enforcement/jobsGET /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.