Skip to main content
POST
/
enforcement-endpoints
/
{id}
/
rotate
Signed curl (wire shape)
# Signed headers are per-request — compute with the SDK (Node tab). Body is {}.
curl -X POST https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints/c8ff82dd-4aa8-4a52-a235-ae13717eb4c8/rotate \
  -H "Content-Type: application/json" \
  -H "OCSS-Spec-Version: OCSS-v1.0-pre" \
  -H 'Signature-Input: ocss=("@method" "@target-uri" "ocss-spec-version" "content-digest");created=1783315514;keyid="did:ocss:loopline#2026-06";alg="ed25519"' \
  -H 'Signature: ocss=:<base64-ed25519-sig>:' \
  -H 'Content-Digest: sha-256=:<base64-sha256-of-body>:' \
  -d '{}'
{
  "endpoint_id_label": "a-bmtAOYzb-Yhhnw11lZtOLFPwD_ms0fnRNQnNgRe-Q"
}
Issues a fresh §9.3(b) label for the binding identified by {id} (the binding_id from the bind response). The prior label is invalidated immediately — a subsequent profile GET on the old label returns 404. Only endpoint_id_label is returned; the connect_secret is not rotated (it stays fixed per binding). Rotate on your window cadence to keep the bound-resolver label short-lived. Because rotation is atomic and the old label dies instantly, poll the new label before discarding the old one only if you cannot tolerate a single missed cycle.

Worked example

import { signRequest } from "@openchildsafety/ocss"

const BASE  = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
const seed  = new Uint8Array(Buffer.from("bG9vcGxpbmUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE", "base64url"))
const keyID = "did:ocss:loopline#2026-06"

const bindingId = "c8ff82dd-4aa8-4a52-a235-ae13717eb4c8" // from the bind response
const targetURI = `${BASE}/enforcement-endpoints/${bindingId}/rotate`
const headers = signRequest({ method: "POST", targetURI, body: new TextEncoder().encode("{}"), keyID, seed, created: Math.floor(Date.now() / 1000) })
headers["Content-Type"] = "application/json"

const res = await fetch(targetURI, { method: "POST", headers, body: "{}" })
console.log(res.status, await res.json())
The sign_request / SignRequest helper is defined once in the request-signing guide — its Python, Go, and curl+openssl recipes are verified against this sandbox. Even an empty-body POST covers content-digest (over the bytes {}).
Real 200 response (captured from the hosted sandbox):
{
  "endpoint_id_label": "cOxEaug6hEkbx8lYQOM8NiBtLknSSup5zqXvMjRfHa8"
}
The prior label (iGrFqzp4…) now returns 404 on GET /enforcement-profiles/{endpoint_id}.

Path Parameters

id
string<uuid>
required

Binding UUID from the mint response (binding_id).

Response

Rotated. New label replaces the prior one immediately.

Binding rotation result. Source: EndpointHandlers.Rotate response in internal/ocsshttp/handler_endpoints.go. Returns a fresh label only.

endpoint_id_label
string
required

Rotated high-entropy §9.3(b) label. Replaces the prior label immediately; the prior label returns 404 after rotation. Never log.