> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phosra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Enforcement

> How Phosra pushes policies to connected platforms — verification contract, polling, and the consumer vs developer surface

# 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

<Warning>
  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`
</Warning>

The developer surface is identical:
`GET /api/v1/developer/enforcement/jobs/{jobId}/results`

Both return `[]EnforcementResult`:

```json theme={null}
[
  {
    "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** |

<Note>
  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](/concepts/platforms) for the full breakdown.
</Note>

## 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.

## Related API Endpoints

* [Trigger enforcement](/developers/api-reference/enforcement/post-children-enforce) — Push rules to all or selected platforms
* [Get job status](/developers/api-reference/enforcement/get-enforcement-jobs) — Poll for completion
* [Get results](/developers/api-reference/enforcement/get-enforcement-jobs-results) — Per-platform breakdown
* [Retry failed job](/developers/api-reference/enforcement/post-enforcement-jobs-retry) — Re-attempt a failed enforcement
