# Signed GET — headers are per-request (compute with the SDK, Node tab). No body.
curl https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA \
-H "OCSS-Spec-Version: OCSS-v1.0-pre" \
-H 'Signature-Input: ocss=("@method" "@target-uri" "ocss-spec-version");created=1783315514;keyid="did:ocss:loopline#2026-06";alg="ed25519"' \
-H 'Signature: ocss=:<base64-ed25519-sig>:'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 label = "iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA"
const t = `${BASE}/enforcement-profiles/${label}`
const h = signRequest({ method: "GET", targetURI: t, keyID, seed, created: Math.floor(Date.now() / 1000) })
const res = await fetch(t, { method: "GET", headers: h })
const signed = await res.json()
const profile = JSON.parse(signed.document) // parse AFTER verifying signed.sig
console.log(res.status, profile.categories)
import requests
url = "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"document": "{\"categories\":[],\"document_type\":\"enforcement_profile\",\"ocss_version\":\"OCSS-v1.0-pre\",\"profile_ref\":\"sha256:898314e28cbd0e7bede49d8c48b6e4309ef019ff3189d03cae4215fe7b8d2b39\",\"rotation_epoch\":495369,\"token_binding\":\"9f4187aa0931973599d0178b0b6a219604206f4a48dcc6166edfb66468a21220\",\"window\":{\"not_after\":\"2026-07-06T10:00:00Z\",\"not_before\":\"2026-07-06T09:00:00Z\"}}",
"key_id": "did:ocss:phosra-router#router-sandbox-2026-06",
"alg": "ed25519",
"sig": "stpRC97FNDiMd5JlkwTn_VxGJ3OngJ9opWiABfEZu4cof6ocL2aqIrjTDGH1vVUFmdI2m2TAhnMsT_Snz416Aw"
}{
"error": "Bad Request",
"message": "id must be a uuid",
"code": 400,
"class": "malformed"
}{
"error": "Unauthorized",
"message": "exactly one Signature-Input and one Signature header are required",
"code": 401,
"class": "signature_invalid"
}{
"error": "Not Found",
"message": "unknown enforcement endpoint",
"code": 404,
"class": "not_found"
}{
"error": "Too Many Requests",
"message": "rate limit exceeded",
"code": 429
}{
"error": "Internal Server Error",
"message": "internal error",
"code": 500
}{
"error": "Bad Gateway",
"message": "downstream provider error",
"code": 502
}{
"error": "Service Unavailable",
"message": "enforcement-profile signing identity not provisioned",
"code": 503
}Fetch the router-signed enforcement profile
§9.3(b) bound-resolver authentication: the caller signs the request with its Trust-List-published key (RFC 9421); the path segment is the high-entropy bound-resolver label from the mint response. A wrong or unknown label returns 404 (indistinguishable from a wrong-resolver dereference — fail-closed, no existence leak, §9.3(a)). The response is a SignedDocument whose document string parses to an EnforcementProfile; verify the signature to the router key then to the root before trusting the profile.
Serving discipline (§8.3.6 cl.3): signatures are minted at compile time and cached per binding. Supports ETag / If-None-Match for 304 Not Modified.
# Signed GET — headers are per-request (compute with the SDK, Node tab). No body.
curl https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA \
-H "OCSS-Spec-Version: OCSS-v1.0-pre" \
-H 'Signature-Input: ocss=("@method" "@target-uri" "ocss-spec-version");created=1783315514;keyid="did:ocss:loopline#2026-06";alg="ed25519"' \
-H 'Signature: ocss=:<base64-ed25519-sig>:'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 label = "iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA"
const t = `${BASE}/enforcement-profiles/${label}`
const h = signRequest({ method: "GET", targetURI: t, keyID, seed, created: Math.floor(Date.now() / 1000) })
const res = await fetch(t, { method: "GET", headers: h })
const signed = await res.json()
const profile = JSON.parse(signed.document) // parse AFTER verifying signed.sig
console.log(res.status, profile.categories)
import requests
url = "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/{endpoint_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"document": "{\"categories\":[],\"document_type\":\"enforcement_profile\",\"ocss_version\":\"OCSS-v1.0-pre\",\"profile_ref\":\"sha256:898314e28cbd0e7bede49d8c48b6e4309ef019ff3189d03cae4215fe7b8d2b39\",\"rotation_epoch\":495369,\"token_binding\":\"9f4187aa0931973599d0178b0b6a219604206f4a48dcc6166edfb66468a21220\",\"window\":{\"not_after\":\"2026-07-06T10:00:00Z\",\"not_before\":\"2026-07-06T09:00:00Z\"}}",
"key_id": "did:ocss:phosra-router#router-sandbox-2026-06",
"alg": "ed25519",
"sig": "stpRC97FNDiMd5JlkwTn_VxGJ3OngJ9opWiABfEZu4cof6ocL2aqIrjTDGH1vVUFmdI2m2TAhnMsT_Snz416Aw"
}{
"error": "Bad Request",
"message": "id must be a uuid",
"code": 400,
"class": "malformed"
}{
"error": "Unauthorized",
"message": "exactly one Signature-Input and one Signature header are required",
"code": 401,
"class": "signature_invalid"
}{
"error": "Not Found",
"message": "unknown enforcement endpoint",
"code": 404,
"class": "not_found"
}{
"error": "Too Many Requests",
"message": "rate limit exceeded",
"code": 429
}{
"error": "Internal Server Error",
"message": "internal error",
"code": 500
}{
"error": "Bad Gateway",
"message": "downstream provider error",
"code": 502
}{
"error": "Service Unavailable",
"message": "enforcement-profile signing identity not provisioned",
"code": 503
}{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.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.
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 label = "iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA" // your 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)
import { createGatekeeper } from "@phosra/gatekeeper"
const gk = createGatekeeper({
platformDid: "did:ocss:loopline",
platformKeyId: "did:ocss:loopline#2026-06",
gatekeeperSigningKey: { seed: /* 32-byte Ed25519 seed */ new Uint8Array(32), keyID: "did:ocss:loopline#2026-06" },
censusBaseUrl: "https://phosra-api-sandbox-production.up.railway.app",
trustRootXB64Url: process.env.PHOSRA_TRUST_ROOT_X!, // pinned root pubkey X
endpointId: "iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA",
})
const verdict = gk.isAllowed({ category: "infinite_scroll_block" })
if (verdict.decision === "block") { /* block the content */ }
# sign_request(...) — the signer from /concepts/signing-requests. A GET has no body,
# so no Content-Digest is covered (components @method, @target-uri, ocss-spec-version only).
import json, requests
BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
SEED = "bG9vcGxpbmUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE"
KEYID = "did:ocss:loopline#2026-06"
label = "iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA" # your endpoint_id_label
url = f"{BASE}/enforcement-profiles/{label}"
res = requests.get(url, headers=sign_request("GET", url, KEYID, SEED)) # no body arg
signed = res.json() # { document: "<json string>", key_id, alg, sig }
profile = json.loads(signed["document"]) # parse AFTER verifying the signature
print(res.status_code, profile["categories"])
// SignRequest(...) — the stdlib signer from /concepts/signing-requests.
package main
import (
"encoding/json"; "fmt"; "io"; "net/http"
)
func main() {
const (
base = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
seed = "bG9vcGxpbmUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE"
keyID = "did:ocss:loopline#2026-06"
label = "iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA" // your endpoint_id_label
)
url := base + "/enforcement-profiles/" + label
h, _ := SignRequest("GET", url, keyID, seed, nil) // nil body -> no Content-Digest
req, _ := http.NewRequest("GET", url, nil)
for k, v := range h {
req.Header.Set(k, v)
}
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
var signed struct{ Document string `json:"document"` }
json.Unmarshal(b, &signed) // verify signed.sig before trusting; then parse signed.Document
fmt.Println(resp.StatusCode, signed.Document)
}
# Runnable signed openssl GET: /concepts/signing-requests (drop the body + Content-Digest,
# cover only the three components). Wire shape:
curl https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-profiles/iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA \
-H "OCSS-Spec-Version: OCSS-v1.0-pre" \
-H 'Signature-Input: ocss=("@method" "@target-uri" "ocss-spec-version");created=1783315514;keyid="did:ocss:loopline#2026-06";alg="ed25519"' \
-H 'Signature: ocss=:<std-base64 ed25519-sig>:'
GET covers only @method, @target-uri, and ocss-spec-version — no content-digest.
Always verify signed.sig to the router key and root before you trust categories[].200 response (captured from the hosted sandbox — router-signed, categories[]
empty here because the sandbox self-serve child carries no compiled rules yet):
{
"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…"
}
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.Path Parameters
The high-entropy §9.3(b) bound-resolver label returned by the mint endpoint. Treat as a secret credential — never log.
Response
Router-signed enforcement profile. Verify to root before trusting. categories[] is the canonical profile field.
Router-signed wrapper. document is a JSON STRING on the wire (not an object) whose UTF-8 contents are the canonical JCS bytes the router key signed. Verify with the resolved signing key over exactly those bytes — never by re-stringifying an object. Matches the Signed.MarshalJSON contract in internal/ocss/profile/document.go.
Canonical JSON string of the inner EnforcementProfile document. The signature covers exactly these UTF-8 bytes. Parse as JSON to obtain the EnforcementProfile; do not re-marshal before verifying.
D-15 key id in "did:ocss:#" form.
"did:ocss:phosra-router#router-sandbox-2026-06"
One of: ed25519.
ed25519 base64url-raw (-_ alphabet, no padding) Ed25519 detached signature over the UTF-8 bytes of document. Matches base64.RawURLEncoding in internal/ocss/profile/document.go Sign().