> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phosra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Test in the sandbox

> Everything you can build against the open Phosra sandbox without a key — health, the signed Trust List, self-registering a provisional DID, and the full seeded family.

The Phosra **sandbox** is a complete, always-on copy of the API, seeded with a demo family and
a set of accredited reference providers. It exists so you can build and verify a full
integration — connect a platform, set up a family, enforce a policy, sign a request — **before
you ever provision a key**. This guide is the map: what is seeded, what is open, and how to get
yourself onto the Trust List.

Every response below is **verbatim live output**, captured while writing this page.

## The base URL

```bash theme={null}
export SANDBOX="https://phosra-api-sandbox-production.up.railway.app"
```

<Info>
  Nothing you do in the sandbox touches a real family or production data. When you are ready for
  production, swap the base URL for `https://prodapi.phosra.com` and add a `phosra_live_…` key —
  the request shapes are identical.
</Info>

## What's already seeded

| Thing                             | Value                                                          | Use it for                                                                     |
| --------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Demo family                       | `Mia`, `Leo`, `Ava` (children)                                 | The [connect ceremony](/guides/connect-a-platform) shares these three profiles |
| Reference provider (configured)   | `did:ocss:loopline`                                            | Run the full OAuth connect flow end-to-end                                     |
| Reference provider (unconfigured) | `did:ocss:courier`                                             | Test the `404 provider connect config not available` branch                    |
| Trust List entries                | 130+ on the signed Trust List (grows as parties self-register) | Verify signatures and accreditation tiers                                      |
| Reference platforms               | 22 (`nextdns`, `controld`, `apple`, …)                         | Discovery by capability and category                                           |

<Steps>
  <Step title="Confirm the sandbox is up">
    The `/health` endpoint needs no key and is the fastest liveness check:

    ```bash theme={null}
    curl -s "$SANDBOX/health"
    # → {"status":"ok"}
    ```
  </Step>

  <Step title="Read the signed Trust List">
    The Trust List is the source of truth for who is accredited. It is served at a well-known path
    and the **entire document is Ed25519-signed** by the sandbox root key — verify the signature
    before you trust any entry inside it.

    ```bash theme={null}
    curl -s "$SANDBOX/.well-known/ocss/trust-list" \
      | jq '{alg, key_id, entries: (.document | fromjson | .entries | length)}'
    ```

    **Real response (200):**

    ```json theme={null}
    {
      "alg": "ed25519",
      "key_id": "root-sandbox-2026-06",
      "entries": 135
    }
    ```

    <Note>
      The `entries` count is a **live, growing number** — every self-registration (below) adds an entry,
      so expect a value at or above the one shown here. Verify the signature and each entry's tier
      rather than asserting on the total.
    </Note>

    The top-level object wraps a signed document: `{ "document": "…", "key_id": "…", "alg": "ed25519", "sig": "…" }`.
    Parse `document` (itself JSON) to read the `entries` array — each entry carries a `did`, an
    `entity` name, its published `jwks` signing keys, and an accreditation tier. The
    [`@ocss/ts`](/sdks/phosra-developer-sdk) SDK verifies the signature and resolves keys for you.

    <Accordion title="Fields & errors — GET /.well-known/ocss/trust-list">
      **No parameters, no auth.** Returns the signed Trust List document.

      **Top-level response fields (200)**

      | Field      | Type   | Description                                                                                                               |
      | ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------- |
      | `document` | string | The Trust List as a JSON **string** — parse it to get `{ version, entries: [...] }`. Signed as-is; verify before parsing. |
      | `key_id`   | string | The signing key id, `root-sandbox-2026-06` in the sandbox.                                                                |
      | `alg`      | string | Signature algorithm, `ed25519`.                                                                                           |
      | `sig`      | string | Base64url Ed25519 signature over `document`.                                                                              |

      **Fields on each entry (inside the parsed `document.entries`)**

      | Field           | Type              | Description                                                                             |
      | --------------- | ----------------- | --------------------------------------------------------------------------------------- |
      | `did`           | string            | The party's DID.                                                                        |
      | `entity`        | string            | Human-readable entity name.                                                             |
      | `jwks`          | object            | The party's published `signing_keys` (and `payload_keys`), for verifying its envelopes. |
      | `tier`          | string            | Accreditation tier: `provisional`, `verified`, or `accredited`.                         |
      | `status`        | string            | `active` while the entry is valid.                                                      |
      | `valid_through` | string (RFC 3339) | When the entry expires.                                                                 |
    </Accordion>
  </Step>

  <Step title="Self-register a provisional DID">
    You do not need anyone's approval to start testing signed flows. Post your DID and a raw
    Ed25519 public key (base64url, unpadded) to `POST /api/v1/advisors/self-register`, and the
    census adds you to the sandbox Trust List at the **`provisional`** tier.

    First, mint a keypair and extract the raw 32-byte public key:

    ```bash theme={null}
    # Generate an Ed25519 private key
    openssl genpkey -algorithm ed25519 -out sandbox-key.pem

    # Extract the raw public key as base64url (unpadded)
    PUB=$(openssl pkey -in sandbox-key.pem -pubout -outform DER \
      | tail -c 32 | base64 | tr '+/' '-_' | tr -d '=')
    echo "$PUB"
    ```

    Then register:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s -X POST "$SANDBOX/api/v1/advisors/self-register" \
        -H "Content-Type: application/json" \
        -d "{
          \"did\": \"did:ocss:dx-demo\",
          \"public_key_b64url\": \"$PUB\",
          \"roles\": [\"provider\"]
        }"
      ```

      ```typescript TypeScript theme={null}
      const res = await fetch(`${SANDBOX}/api/v1/advisors/self-register`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          did: "did:ocss:dx-demo",
          public_key_b64url: pub,     // raw Ed25519 public key, base64url unpadded
          roles: ["provider"],
        }),
      });
      const advisor = await res.json();
      // advisor.key_id → "did:ocss:dx-demo#2026-07", advisor.trust_tier → "provisional"
      ```

      ```python Python theme={null}
      advisor = requests.post(f"{SANDBOX}/api/v1/advisors/self-register", json={
          "did": "did:ocss:dx-demo",
          "public_key_b64url": pub,   # raw Ed25519 public key, base64url unpadded
          "roles": ["provider"],
      }, timeout=30).json()
      # advisor["key_id"], advisor["trust_tier"] == "provisional"
      ```

      ```go Go theme={null}
      body, _ := json.Marshal(map[string]any{
      	"did":               "did:ocss:dx-demo",
      	"public_key_b64url": pub, // raw Ed25519 public key, base64url unpadded
      	"roles":             []string{"provider"},
      })
      res, _ := http.Post(sandbox+"/api/v1/advisors/self-register",
      	"application/json", bytes.NewReader(body))
      defer res.Body.Close()
      // decode { AdvisorID, DID, KeyID, PublishedKeyX, TrustTier }
      ```
    </CodeGroup>

    **Real response (200):**

    ```json theme={null}
    {
      "advisor_id": "fa97e1eb-1348-44c0-8954-2dec1df08d5c",
      "did": "did:ocss:dx-demo",
      "key_id": "did:ocss:dx-demo#2026-07",
      "kid": "2026-07",
      "published_key_x": "1NDtvUgkYV6h6XzR3y-FgAdKmqQ-i7XiWen7joyJYgA",
      "trust_tier": "provisional"
    }
    ```

    You are now on the sandbox Trust List. The `key_id` (`did#kid`) is what you sign your requests
    with; the census publishes your public key so counterparties can verify you.

    <Note>
      `provisional` is enough to test the full signed flow in the sandbox. Moving to `verified` or
      `accredited` — the tiers that carry weight in production — is a governance step, not an API call.
      See [Production accreditation](/integration/production-accreditation).
    </Note>

    <Accordion title="Fields & errors — POST /api/v1/advisors/self-register">
      **Request body** (`application/json`)

      | Field               | Type      | Required | Description                                                          |
      | ------------------- | --------- | :------: | -------------------------------------------------------------------- |
      | `did`               | string    |    yes   | Your chosen DID, e.g. `did:ocss:dx-demo`.                            |
      | `public_key_b64url` | string    |    yes   | Raw 32-byte Ed25519 public key, base64url, **unpadded**.             |
      | `roles`             | string\[] |    no    | Roles to claim, e.g. `["provider"]`.                                 |
      | `key_id` / `kid`    | string    |    no    | Optional key id; defaults to the current `YYYY-MM` (e.g. `2026-07`). |

      **Response fields (200)**

      | Field             | Type          | Description                                              |
      | ----------------- | ------------- | -------------------------------------------------------- |
      | `advisor_id`      | string (UUID) | Your Trust List record id.                               |
      | `did`             | string        | Echoes your DID.                                         |
      | `key_id`          | string        | Fully-qualified signing key id, `did#kid`.               |
      | `kid`             | string        | The short key id, e.g. `2026-07`.                        |
      | `published_key_x` | string        | The public key the census published for you (base64url). |
      | `trust_tier`      | string        | `provisional` for a self-registration.                   |

      **Errors**

      | Status | `message`                                                   | When it happens                                                            |
      | :----: | ----------------------------------------------------------- | -------------------------------------------------------------------------- |
      |  `400` | `did is required`                                           | `did` is missing from the body.                                            |
      |  `400` | `public_key_b64url is required`                             | The key field is missing.                                                  |
      |  `400` | `invalid_public_key_b64url: must decode to 32 bytes, got N` | The key is not a valid 32-byte Ed25519 key (wrong length or bad encoding). |
    </Accordion>
  </Step>
</Steps>

## The sandbox host

Everything partner-facing points at one host — the same base URL used throughout these guides.
It's open, seeded, and requires no key:

| Host                                                   | Backs                                                                                |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------ |
| `https://phosra-api-sandbox-production.up.railway.app` | The live openchildsafety.com walkthrough — the base URL used throughout these guides |

## Now build something

<CardGroup cols={3}>
  <Card title="Connect a platform" icon="plug" href="/guides/connect-a-platform">
    Run the OAuth connect ceremony against the seeded reference provider.
  </Card>

  <Card title="Set up a family & kids" icon="users" href="/guides/family-and-kids">
    Build a family and an active policy in one call.
  </Card>

  <Card title="Quickstart" icon="bolt" href="/quickstart">
    Zero to an enforced policy in under five minutes.
  </Card>
</CardGroup>
