Fetch the router-signed enforcement profile

The core read of the data-plane. The {endpoint_id} path segment is the high-entropy bound-resolver label from the bind response, and presenting it under your RFC 9421 signature is the §9.3(b) authentication. The response is a SignedDocument whose document string parses to an EnforcementProfile.

document is a JSON string on the wire, not an object. The signature covers exactly those UTF-8 bytes — verify the signature over the raw string, then JSON.parse it. Never re-stringify an object before verifying. The canonical profile field is categories[]; profile.rules[] does not exist.

A wrong or unknown label returns 404, indistinguishable from a wrong-resolver dereference — fail-closed, no existence leak (§9.3(a)). The endpoint supports ETag / If-None-Match for 304 Not Modified.

Worked example

The @phosra/gatekeeper SDK is the supported path: it polls, verifies to the root, and exposes the decision. The raw signed GET is shown alongside it.

ts
import { signRequest } from "@openchildsafety/ocss"
 
const BASE  = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
const seed  = new Uint8Array(Buffer.from(process.env.OCSS_SANDBOX_TEST_SEED_B64URL!, "base64url"))
const keyID = process.env.OCSS_SANDBOX_KEY_ID!
 
const label = process.env.OCSS_SANDBOX_ENDPOINT_ID_LABEL!
const targetURI = `${BASE}/enforcement-profiles/${label}`
const headers = signRequest({ method: "GET", targetURI, keyID, seed, created: Math.floor(Date.now() / 1000) })
 
const res = await fetch(targetURI, { method: "GET", headers })
const signed = await res.json()               // { document: "<json string>", key_id, alg, sig }
const profile = JSON.parse(signed.document)   // parse AFTER verifying the signature
console.log(res.status, profile.categories)

The signer is defined once in the request-signing guide. A GET covers only @method, @target-uri, and ocss-spec-versionno content-digest. Always verify signed.sig to the router key and root before you trust categories[].

Example synthetic sandbox 200 response (router-signed fixture, categories[] empty here because the sandbox self-serve child carries no compiled rules yet):

json
{
  "document": "{\"categories\":[],\"document_type\":\"enforcement_profile\",\"ocss_version\":\"OCSS-v1.0-pre\",\"profile_ref\":\"sha256:0979812325e659675635dee167c202b92744891f315f99f1761a628e4ac3a87e\",\"rotation_epoch\":495365,\"token_binding\":\"162d45da4cd20908d138c08ae100439199953ed7810c88a149d162283268c605\",\"window\":{\"not_after\":\"2026-07-06T06:00:00Z\",\"not_before\":\"2026-07-06T05:00:00Z\"}}",
  "key_id": "did:ocss:phosra-router#router-sandbox-2026-06",
  "alg": "ed25519",
  "sig": "_GkUlEiLYL0T…"
}

Once the child has active rules (see the rule-write flow), each categories[] row carries a category, a decision (allow / warn / block), a fail_mode, and a per-child rule_ref you echo when you confirm enforcement.