# Signed headers are per-request — compute with the SDK (Node tab).
curl -X POST https://phosra-api-sandbox-production.up.railway.app/api/v1/sandbox/consent-attestations \
-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 '{"band":"13_15","consent_scope":"collection_parental_authority"}'
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 = { band: "13_15", consent_scope: "collection_parental_authority" }
const t = BASE + "/sandbox/consent-attestations", 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 })
const consent = await res.json()
console.log(res.status, consent.target_ref) // pass to POST /enforcement-endpoints
import requests
url = "https://phosra-api-sandbox-production.up.railway.app/api/v1/sandbox/consent-attestations"
payload = {
"child_ref": "child:5ba0d00c-0000-4000-8000-0000000000c1",
"band": "13_15",
"consent_scope": "collection_parental_authority"
}
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/sandbox/consent-attestations",
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([
'child_ref' => 'child:5ba0d00c-0000-4000-8000-0000000000c1',
'band' => '13_15',
'consent_scope' => 'collection_parental_authority'
]),
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/sandbox/consent-attestations"
payload := strings.NewReader("{\n \"child_ref\": \"child:5ba0d00c-0000-4000-8000-0000000000c1\",\n \"band\": \"13_15\",\n \"consent_scope\": \"collection_parental_authority\"\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/sandbox/consent-attestations")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"child_ref\": \"child:5ba0d00c-0000-4000-8000-0000000000c1\",\n \"band\": \"13_15\",\n \"consent_scope\": \"collection_parental_authority\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://phosra-api-sandbox-production.up.railway.app/api/v1/sandbox/consent-attestations")
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 \"child_ref\": \"child:5ba0d00c-0000-4000-8000-0000000000c1\",\n \"band\": \"13_15\",\n \"consent_scope\": \"collection_parental_authority\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"app_ref": "did:ocss:dx24c6fcc23",
"target_ref": "child:c22fd864-a783-5354-bfbb-d7d066b17592",
"standing_ref": "consent:attestation:sbx-consent:did:ocss:dx24c6fcc23:child:c22fd864-a783-5354-bfbb-d7d066b17592",
"idempotency_key": "sbx-consent:did:ocss:dx24c6fcc23:child:c22fd864-a783-5354-bfbb-d7d066b17592",
"band": "13_15",
"consent_scope": "collection_parental_authority",
"expiry": "2027-07-06T09:01:54Z",
"note": "sandbox-only test consent; pass audience_did=<this DID> and child_ref=<target_ref> to POST /api/v1/enforcement-endpoints to complete the consent-first mint"
}{
"error": "invalid_band"
}{
"error": "Unauthorized",
"message": "exactly one Signature-Input and one Signature header are required",
"code": 401,
"class": "signature_invalid"
}404 page not found
{
"error": "Too Many Requests",
"message": "rate limit exceeded",
"code": 429
}{
"error": "Internal Server Error",
"message": "internal error",
"code": 500
}{
"error": "census_store_vacant"
}Mint a test consent attestation (sandbox)
SANDBOX-ONLY self-serve consent. Mints a §8.3.2-shaped TEST consent row with app_ref = the RFC 9421 caller DID, so a cold self-registered platform can satisfy the consent-first gate on POST /api/v1/enforcement-endpoints with no roster edit and no live counterparty. With no child_ref, the census auto-provisions the sandbox self-serve test child and targets it; a supplied child_ref must already exist (the door never fabricates a caller-named child). Production §8.3.2 semantics unchanged. Gated on the Restricted band (PHOSRA_ENV==sandbox) — returns 404 on dev/staging/production. Source: internal/ocsshttp/handler_sandbox_consent.go.
# Signed headers are per-request — compute with the SDK (Node tab).
curl -X POST https://phosra-api-sandbox-production.up.railway.app/api/v1/sandbox/consent-attestations \
-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 '{"band":"13_15","consent_scope":"collection_parental_authority"}'
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 = { band: "13_15", consent_scope: "collection_parental_authority" }
const t = BASE + "/sandbox/consent-attestations", 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 })
const consent = await res.json()
console.log(res.status, consent.target_ref) // pass to POST /enforcement-endpoints
import requests
url = "https://phosra-api-sandbox-production.up.railway.app/api/v1/sandbox/consent-attestations"
payload = {
"child_ref": "child:5ba0d00c-0000-4000-8000-0000000000c1",
"band": "13_15",
"consent_scope": "collection_parental_authority"
}
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/sandbox/consent-attestations",
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([
'child_ref' => 'child:5ba0d00c-0000-4000-8000-0000000000c1',
'band' => '13_15',
'consent_scope' => 'collection_parental_authority'
]),
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/sandbox/consent-attestations"
payload := strings.NewReader("{\n \"child_ref\": \"child:5ba0d00c-0000-4000-8000-0000000000c1\",\n \"band\": \"13_15\",\n \"consent_scope\": \"collection_parental_authority\"\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/sandbox/consent-attestations")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"child_ref\": \"child:5ba0d00c-0000-4000-8000-0000000000c1\",\n \"band\": \"13_15\",\n \"consent_scope\": \"collection_parental_authority\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://phosra-api-sandbox-production.up.railway.app/api/v1/sandbox/consent-attestations")
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 \"child_ref\": \"child:5ba0d00c-0000-4000-8000-0000000000c1\",\n \"band\": \"13_15\",\n \"consent_scope\": \"collection_parental_authority\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"app_ref": "did:ocss:dx24c6fcc23",
"target_ref": "child:c22fd864-a783-5354-bfbb-d7d066b17592",
"standing_ref": "consent:attestation:sbx-consent:did:ocss:dx24c6fcc23:child:c22fd864-a783-5354-bfbb-d7d066b17592",
"idempotency_key": "sbx-consent:did:ocss:dx24c6fcc23:child:c22fd864-a783-5354-bfbb-d7d066b17592",
"band": "13_15",
"consent_scope": "collection_parental_authority",
"expiry": "2027-07-06T09:01:54Z",
"note": "sandbox-only test consent; pass audience_did=<this DID> and child_ref=<target_ref> to POST /api/v1/enforcement-endpoints to complete the consent-first mint"
}{
"error": "invalid_band"
}{
"error": "Unauthorized",
"message": "exactly one Signature-Input and one Signature header are required",
"code": 401,
"class": "signature_invalid"
}404 page not found
{
"error": "Too Many Requests",
"message": "rate limit exceeded",
"code": 429
}{
"error": "Internal Server Error",
"message": "internal error",
"code": 500
}{
"error": "census_store_vacant"
}PHOSRA_ENV==sandbox — returns 404 on dev, staging, and
production. Production §8.3.2 consent semantics are unchanged.app_ref = your
RFC 9421 caller DID, so a
cold self-registered platform can satisfy the consent-first gate on
POST /enforcement-endpoints with no
OCSS_CONSENT_ATTESTATION_APPS roster edit and no live counterparty.
With no child_ref, the census auto-provisions the sandbox self-serve test child and targets
it. A supplied child_ref must already exist — the door never fabricates a caller-named child.
The response gives you the target_ref to pass straight into the bind call.
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 targetURI = BASE + "/sandbox/consent-attestations"
const body = { band: "13_15", consent_scope: "collection_parental_authority" } // both optional; these are the defaults
const 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"
const res = await fetch(targetURI, { method: "POST", headers, body: bodyText })
const consent = await res.json()
console.log(res.status, consent.target_ref) // pass target_ref to POST /enforcement-endpoints
curl -X POST https://phosra-api-sandbox-production.up.railway.app/api/v1/sandbox/consent-attestations \
-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 '{"band":"13_15","consent_scope":"collection_parental_authority"}'
201 response (captured from the hosted sandbox):
{
"ok": true,
"app_ref": "did:ocss:loopline",
"target_ref": "child:5ba0d00c-0000-4000-8000-0000000000c1",
"standing_ref": "consent:attestation:sbx-consent:did:ocss:loopline:child:5ba0d00c-0000-4000-8000-0000000000c1",
"idempotency_key": "sbx-consent:did:ocss:loopline:child:5ba0d00c-0000-4000-8000-0000000000c1",
"band": "13_15",
"consent_scope": "collection_parental_authority",
"expiry": "2027-07-06T05:26:40Z",
"note": "sandbox-only test consent; pass audience_did=<this DID> and child_ref=<target_ref> to POST /api/v1/enforcement-endpoints to complete the consent-first mint"
}
(app_ref, target_ref) — re-minting returns the same
standing_ref. Next: bind the endpoint.
standing_ref satisfies the consent-first precondition for POST /enforcement-endpoints
(endpoint binding) — that is its only job. It does not satisfy a signed rule write
(POST /policies/{id}/rules): the sandbox mint stores no signed consent envelope, so the census’s
writer-tier resolver fails closed and the rule write returns 403 standing_failure. For a rule
write, obtain a standing via the §8.3.2 consent-ingest lane (a real signed consent_attestation),
not this sandbox mint. The two consent surfaces are not interchangeable.Body
Optional child: target. Empty ⇒ the auto-provisioned sandbox self-serve test child.
"child:5ba0d00c-0000-4000-8000-0000000000c1"
under_13, 13_15, 16_17, adult collection_parental_authority, third_party_disclosure Response
Test consent minted. Pass audience_did= and child_ref=<target_ref> to POST /api/v1/enforcement-endpoints to complete the consent-first mint.
true
"did:ocss:playnest"
"child:5ba0d00c-0000-4000-8000-0000000000c1"
"consent:attestation:sbx-consent-playnest-5ba0d00c"
"13_15"
"collection_parental_authority"