Run the connect ceremony to your own DID (sandbox)

Sandbox only. Gated on PHOSRA_ENV==sandbox — returns 404 on dev, staging, and production.

Runs the provider side of the EXT-04 §3.2 connect ceremony against your own platform DID + webhook, so a cold self-registered platform dev receives a real signed binding with no live counterparty and no roster edit. It mints a §9.3 binding for a seeded test child scoped to your platform_did and delivers the signed §3.6 callback to your webhook_url. The call is RFC 9421-signed as you.

Pick which seeded child with child_ref (optional). Omit it and the binding is minted for Mia; pass a seeded child ref to bind a different one. This is how you exercise the multi-profile model — one call per child yields a distinct endpoint_id_label per child, which is exactly what a service with several kids' profiles must map and enforce separately (see Bind a connection to the right child):

Seeded childchild_ref
Mia (default)child:a11ce0fa-0000-4000-8000-0000000000a1
Leochild:a11ce0fa-0000-4000-8000-0000000000a2
Avachild:a11ce0fa-0000-4000-8000-0000000000a3

The binding is minted and profile_url is observable even if the webhook delivery leg failsdelivered=false with a note explaining why, but you can still poll the profile.

Worked example

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 targetURI = BASE + "/sandbox/test-connect"
const body = {
  platform_did:   process.env.OCSS_SANDBOX_DID!,
  webhook_url:    "https://your-gatekeeper.example.com",   // POST /api/ocss/connect receiver base
  connect_secret: process.env.OCSS_SANDBOX_CONNECT_SECRET!,
  child_ref:      "child:a11ce0fa-0000-4000-8000-0000000000a2", // OPTIONAL — bind Leo; omit → Mia (see table above)
}
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 })
console.log(res.status, await res.json())

Example 200 sandbox response (example.com returns 405 to the callback, so delivered=false; the binding is minted regardless):

json
{
  "binding_id": "145c2779-1759-4378-abf3-c49a49408ede",
  "resolver_did": "did:ocss:loopline",
  "endpoint_id_label": "REDACTED_SANDBOX_ENDPOINT_ID_LABEL",
  "profile_url": "/api/v1/enforcement-profiles/REDACTED_SANDBOX_ENDPOINT_ID_LABEL",
  "connect_receiver": "https://example.com/api/ocss/connect",
  "delivered": false,
  "http_status": 405,
  "state": "sandbox-test-connect:145c2779-1759-4378-abf3-c49a49408ede",
  "note": "callback delivery did not receive a 2xx — check webhook_url reachability and that connect_secret matches your gk.config; the binding is minted and profile_url is observable regardless"
}

Poll profile_url with your platform key (fetch profile) to see the router-signed profile land, and rotate with the returned binding_id.