# Signature-Input/Signature/Content-Digest are per-request — compute with the SDK
# (see the Node tab); a static signature cannot be reused. Body shown for reference.
curl -X POST https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints \
-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 '{"audience_did":"did:ocss:loopline","child_ref":"child:5ba0d00c-0000-4000-8000-0000000000c1","window_seconds":3600}'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 body = { audience_did: "did:ocss:loopline", child_ref: "child:5ba0d00c-0000-4000-8000-0000000000c1", window_seconds: 3600 }
const t = BASE + "/enforcement-endpoints", b = JSON.stringify(body)
const h = signRequest({ method: "POST", targetURI: t, body: new TextEncoder().encode(b), keyID, seed, created: Math.floor(Date.now() / 1000) })
h["Content-Type"] = "application/json"
const res = await fetch(t, { method: "POST", headers: h, body: b })
console.log(res.status, await res.json())
import requests
url = "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints"
payload = {
"audience_did": "did:ocss:snaptr",
"child_ref": "child:11111111-1111-4111-8111-111111111111",
"window_seconds": 2,
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"statutory_fail_closed": ["<string>"],
"standing_ref": "<string>",
"enforceable_categories": ["<string>"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, 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-endpoints",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'audience_did' => 'did:ocss:snaptr',
'child_ref' => 'child:11111111-1111-4111-8111-111111111111',
'window_seconds' => 2,
'request_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'statutory_fail_closed' => [
'<string>'
],
'standing_ref' => '<string>',
'enforceable_categories' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints"
payload := strings.NewReader("{\n \"audience_did\": \"did:ocss:snaptr\",\n \"child_ref\": \"child:11111111-1111-4111-8111-111111111111\",\n \"window_seconds\": 2,\n \"request_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"statutory_fail_closed\": [\n \"<string>\"\n ],\n \"standing_ref\": \"<string>\",\n \"enforceable_categories\": [\n \"<string>\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"audience_did\": \"did:ocss:snaptr\",\n \"child_ref\": \"child:11111111-1111-4111-8111-111111111111\",\n \"window_seconds\": 2,\n \"request_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"statutory_fail_closed\": [\n \"<string>\"\n ],\n \"standing_ref\": \"<string>\",\n \"enforceable_categories\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"audience_did\": \"did:ocss:snaptr\",\n \"child_ref\": \"child:11111111-1111-4111-8111-111111111111\",\n \"window_seconds\": 2,\n \"request_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"statutory_fail_closed\": [\n \"<string>\"\n ],\n \"standing_ref\": \"<string>\",\n \"enforceable_categories\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"endpoint_id_label": "U49ctRQSAz5TEpBSuHZxWhVaW28J7qfEvI0grdOxaRE",
"binding_id": "e376e187-cecb-42ed-9d19-aa05de1043cd",
"connect_secret": "PyBm20VF2xm78D-HOgi0Jg3ldKSguAXy_H5DRIpNhnQ"
}{
"error": "Bad Request",
"message": "audience_did, child_ref, and window_seconds (>=1) are required",
"code": 400,
"class": "malformed"
}{
"error": "Unauthorized",
"message": "exactly one Signature-Input and one Signature header are required",
"code": 401,
"class": "signature_invalid"
}{
"error": "Forbidden",
"message": "no active consent_attestation backs this binding (§4.2.2 precondition)",
"code": 403,
"class": "standing_failure",
"failed_step": "authority_binding"
}{
"error": "Not Found",
"message": "unknown child",
"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": "census operation not yet available",
"code": 503
}Bind a child enforcement endpoint
Mints a §9.3(b) bound-resolver label and returns the label, binding UUID, and connect_secret (all three returned exactly once). Requires an active consent_attestation for (audience_did, child_ref) as a §4.2.2 precondition. Caller must be an accredited resolver with a valid RFC 9421 signature.
# Signature-Input/Signature/Content-Digest are per-request — compute with the SDK
# (see the Node tab); a static signature cannot be reused. Body shown for reference.
curl -X POST https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints \
-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 '{"audience_did":"did:ocss:loopline","child_ref":"child:5ba0d00c-0000-4000-8000-0000000000c1","window_seconds":3600}'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 body = { audience_did: "did:ocss:loopline", child_ref: "child:5ba0d00c-0000-4000-8000-0000000000c1", window_seconds: 3600 }
const t = BASE + "/enforcement-endpoints", b = JSON.stringify(body)
const h = signRequest({ method: "POST", targetURI: t, body: new TextEncoder().encode(b), keyID, seed, created: Math.floor(Date.now() / 1000) })
h["Content-Type"] = "application/json"
const res = await fetch(t, { method: "POST", headers: h, body: b })
console.log(res.status, await res.json())
import requests
url = "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints"
payload = {
"audience_did": "did:ocss:snaptr",
"child_ref": "child:11111111-1111-4111-8111-111111111111",
"window_seconds": 2,
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"statutory_fail_closed": ["<string>"],
"standing_ref": "<string>",
"enforceable_categories": ["<string>"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, 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-endpoints",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'audience_did' => 'did:ocss:snaptr',
'child_ref' => 'child:11111111-1111-4111-8111-111111111111',
'window_seconds' => 2,
'request_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'statutory_fail_closed' => [
'<string>'
],
'standing_ref' => '<string>',
'enforceable_categories' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints"
payload := strings.NewReader("{\n \"audience_did\": \"did:ocss:snaptr\",\n \"child_ref\": \"child:11111111-1111-4111-8111-111111111111\",\n \"window_seconds\": 2,\n \"request_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"statutory_fail_closed\": [\n \"<string>\"\n ],\n \"standing_ref\": \"<string>\",\n \"enforceable_categories\": [\n \"<string>\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"audience_did\": \"did:ocss:snaptr\",\n \"child_ref\": \"child:11111111-1111-4111-8111-111111111111\",\n \"window_seconds\": 2,\n \"request_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"statutory_fail_closed\": [\n \"<string>\"\n ],\n \"standing_ref\": \"<string>\",\n \"enforceable_categories\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"audience_did\": \"did:ocss:snaptr\",\n \"child_ref\": \"child:11111111-1111-4111-8111-111111111111\",\n \"window_seconds\": 2,\n \"request_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"statutory_fail_closed\": [\n \"<string>\"\n ],\n \"standing_ref\": \"<string>\",\n \"enforceable_categories\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"endpoint_id_label": "U49ctRQSAz5TEpBSuHZxWhVaW28J7qfEvI0grdOxaRE",
"binding_id": "e376e187-cecb-42ed-9d19-aa05de1043cd",
"connect_secret": "PyBm20VF2xm78D-HOgi0Jg3ldKSguAXy_H5DRIpNhnQ"
}{
"error": "Bad Request",
"message": "audience_did, child_ref, and window_seconds (>=1) are required",
"code": 400,
"class": "malformed"
}{
"error": "Unauthorized",
"message": "exactly one Signature-Input and one Signature header are required",
"code": 401,
"class": "signature_invalid"
}{
"error": "Forbidden",
"message": "no active consent_attestation backs this binding (§4.2.2 precondition)",
"code": 403,
"class": "standing_failure",
"failed_step": "authority_binding"
}{
"error": "Not Found",
"message": "unknown child",
"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": "census operation not yet available",
"code": 503
}binding_id, and a connect_secret — all three returned exactly once. The caller must
be an accredited resolver with a valid RFC 9421 signature,
and there must be an active consent_attestation for (audience_did, child_ref) — the
§4.2.2 precondition. No consent → 403.
endpoint_id_label and connect_secret are secret credentials returned once. Presenting
the label as the {endpoint_id} path segment on a profile GET is the authentication — store
both immediately and never log them.Worked example
In the sandbox you satisfy the consent precondition with a single self-serve call (mint test consent), then bind. This runs end-to-end against the hosted sandbox: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"
async function signedPost(path, body) {
const targetURI = BASE + path, bodyText = JSON.stringify(body)
const headers = signRequest({ method: "POST", targetURI, body: new TextEncoder().encode(bodyText), keyID, seed, created: Math.floor(Date.now() / 1000) })
headers["Content-Type"] = "application/json"
return fetch(targetURI, { method: "POST", headers, body: bodyText })
}
// 1. Satisfy the consent-first gate (sandbox self-serve) — returns target_ref.
const consent = await (await signedPost("/sandbox/consent-attestations", { band: "13_15", consent_scope: "collection_parental_authority" })).json()
// 2. Bind the endpoint for that consented child.
const res = await signedPost("/enforcement-endpoints", {
audience_did: "did:ocss:loopline",
child_ref: consent.target_ref,
window_seconds: 3600,
})
console.log(res.status, await res.json())
# sign_request(...) is the ~25-line signer from /concepts/signing-requests — paste it in.
import json, requests
BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
SEED = "bG9vcGxpbmUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE" # sandbox loopline test seed
KEYID = "did:ocss:loopline#2026-06"
def signed_post(path, body_obj):
url = BASE + path
body = json.dumps(body_obj).encode()
h = sign_request("POST", url, KEYID, SEED, body)
h["Content-Type"] = "application/json"
return requests.post(url, data=body, headers=h)
# 1. Satisfy the consent-first gate (sandbox self-serve) — returns target_ref.
consent = signed_post("/sandbox/consent-attestations",
{"band": "13_15", "consent_scope": "collection_parental_authority"}).json()
# 2. Bind the endpoint for that consented child.
res = signed_post("/enforcement-endpoints",
{"audience_did": "did:ocss:loopline",
"child_ref": consent["target_ref"], "window_seconds": 3600})
print(res.status_code, res.json()) # -> 201 { binding_id, connect_secret, endpoint_id_label }
// SignRequest(...) is the stdlib-only signer from /concepts/signing-requests.
package main
import (
"bytes"; "encoding/json"; "fmt"; "io"; "net/http"
)
const (
base = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
seed = "bG9vcGxpbmUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE" // sandbox loopline test seed
keyID = "did:ocss:loopline#2026-06"
)
func signedPost(path string, bodyObj any) map[string]any {
body, _ := json.Marshal(bodyObj)
url := base + path
h, _ := SignRequest("POST", url, keyID, seed, body)
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
for k, v := range h {
req.Header.Set(k, v)
}
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(b))
var out map[string]any
json.Unmarshal(b, &out)
return out
}
func main() {
// 1. Consent-first gate (sandbox self-serve). 2. Bind for that child.
consent := signedPost("/sandbox/consent-attestations",
map[string]string{"band": "13_15", "consent_scope": "collection_parental_authority"})
signedPost("/enforcement-endpoints", map[string]any{
"audience_did": "did:ocss:loopline", "child_ref": consent["target_ref"], "window_seconds": 3600,
}) // -> 201 { binding_id, connect_secret, endpoint_id_label }
}
# The runnable, fully-signed openssl script lives in the signing guide:
# /concepts/signing-requests (POST /enforcement-endpoints example, verified 201).
# The wire shape of the four headers for this call:
curl -X POST https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-endpoints \
-H "Content-Type: application/json" \
-H "OCSS-Spec-Version: OCSS-v1.0-pre" \
-H 'Content-Digest: sha-256=:<std-base64 sha256(body)>:' \
-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=:<std-base64 ed25519-sig>:' \
-d '{"audience_did":"did:ocss:loopline","child_ref":"child:5ba0d00c-0000-4000-8000-0000000000c1","window_seconds":3600}'
sign_request / SignRequest) is defined once in the
request-signing guide — copy it in, or use the
@openchildsafety/ocss SDK’s signRequest. That guide’s Python, Go, and curl+openssl
recipes were each run end-to-end against this sandbox and returned a real 201.201 response (captured from the hosted sandbox):
{
"binding_id": "c8ff82dd-4aa8-4a52-a235-ae13717eb4c8",
"connect_secret": "pRrwmJa5gDmlCoK7pga9TGXAQ-kdZG6XBJkmkBjJ8lE",
"endpoint_id_label": "iGrFqzp43O0S9YTNN2oAT6zMzcugEX_EwZbraWrE1AA"
}
endpoint_id_label, and rotate using the
binding_id.Body
Binding-leg mint request. Source: endpointMintBody in internal/ocsshttp/handler_endpoints.go. Required: audience_did, child_ref, window_seconds >= 1.
DID of the resolving platform (e.g. "did:ocss:snaptr").
"did:ocss:snaptr"
"child:" — the child the resolver is binding to.
"child:11111111-1111-4111-8111-111111111111"
Profile validity window in seconds. The census mints a window of this duration and increments rotation_epoch accordingly.
x >= 1Optional canonical lowercase non-nil UUID selecting the durable Phosra Link lane. A faithful retry reuses this ID with the exact same request authority and receives 200 + OCSS-Replay: original with the byte-identical first result. Reuse with changed authority returns 409. Omit only for the legacy cleartext-once lane.
Optional list of category slugs the deployment declares fail-closed (§9.1 floor 3 / §9.2). fail_mode="closed" appears on those CategoryEntry rows.
Consent standing backing the binding. Required by the durable Link lane and formatted as consent:attestation:.
Closed-registry rule categories the platform can enforce.
Response
Endpoint bound. Store endpoint_id_label and connect_secret immediately.
Binding mint result. Source: EndpointHandlers.Mint response in internal/ocsshttp/handler_endpoints.go. Legacy calls receive the three credential fields once. Durable request-ID calls also receive request_id and can recover the byte-identical original result on a faithful retry; the replay cache is AES-256-GCM encrypted and authority-bound at rest.
High-entropy §9.3(b) bound-resolver label. Presenting it as the {endpoint_id} path segment in GET /enforcement-profiles/{endpoint_id} IS the authentication credential — treat as a secret, never log.
UUID of the persisted binding row (for rotation).
HMAC-SHA256 secret for verifying X-Phosra-Signature on inbound connect-leg deliveries (feed into gk.config({ connectSecret })). Store it immediately. Legacy calls disclose it once; authenticated durable retries can recover the encrypted original result.
Echoed exactly when the request selected the durable lane.