> ## 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.

# Self-register advisor (sandbox)

> Sandbox-only self-service onboarding. Inserts a `did:ocss:<slug>` entry at `provisional` tier from a raw Ed25519 public key, then recompiles the served Trust List so the DID appears immediately. Requires `SANDBOX_MODE=true` on the census — production returns `403 self_register_sandbox_only`. Cannot overwrite an existing entry (`409`) and never escalates above `provisional`. No auth is required (the sandbox guard is the gate).




## OpenAPI

````yaml POST /advisors/self-register
openapi: 3.1.0
info:
  title: Phosra Developer API — provider account, keys & federation
  description: >
    The Phosra developer/provider control plane: manage your developer
    organization, API keys, and usage, and register as an advisor in the OCSS
    federation. Phosra is an accredited provider that operates OCSS-conformant
    infrastructure.


    The signed OCSS protocol verbs (rule writes, alert ingest, enforcement,
    Trust List) are NOT REST methods here — they are RFC 9421-signed and
    consumed via the `@openchildsafety/ocss` library. This spec covers only the
    plain-REST management surface. The parental-control product API is
    documented separately in `openapi.yaml`.
  version: 1.0.0
  license:
    name: Proprietary
    url: https://phosra.com/terms
  contact:
    name: Phosra
servers:
  - url: https://phosra-api-sandbox-production.up.railway.app/api/v1
    description: >-
      Hosted sandbox — the canonical, stable, partner-facing testing endpoint
      (seeded demo family + phosra_test_ keys). Same base the CLI uses.
  - url: https://prodapi.phosra.com/api/v1
    description: Production
  - url: http://localhost:8080/api/v1
    description: Local development
security:
  - SessionAuth: []
paths:
  /advisors/self-register:
    post:
      tags:
        - Advisors
      summary: Self-register a DID onto the sandbox Trust List (sandbox only)
      description: >
        Sandbox-only self-service onboarding. Inserts a `did:ocss:<slug>` entry
        at `provisional` tier from a raw Ed25519 public key, then recompiles the
        served Trust List so the DID appears immediately. Requires
        `SANDBOX_MODE=true` on the census — production returns `403
        self_register_sandbox_only`. Cannot overwrite an existing entry (`409`)
        and never escalates above `provisional`. No auth is required (the
        sandbox guard is the gate).
      operationId: selfRegisterAdvisor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - did
                - public_key_b64url
              properties:
                did:
                  type: string
                  description: The DID to register, `did:ocss:<slug>`.
                  example: did:ocss:touchstone
                public_key_b64url:
                  type: string
                  description: >
                    Raw 32-byte Ed25519 public key, base64url **unpadded** (RFC
                    4648 §5, no `=`). The census PKIX-wraps it for the Trust
                    List.
                  example: CMHWy3vUAiEcYDdE_bDvkRuEqwxkklS0tV-TYHJTlWU
                roles:
                  type: array
                  items:
                    type: string
                  description: >-
                    Informational role tags, stored verbatim on the provisional
                    entry.
                entry_role:
                  type: string
                  enum:
                    - verifying-agency
                  description: >
                    Optional Trust-List role marker. Only `verifying-agency`
                    (§5.4 open assessor market) is accepted; any other value
                    returns `400`.
      responses:
        '200':
          description: DID registered at provisional tier and published to the Trust List.
          content:
            application/json:
              schema:
                type: object
                required:
                  - advisor_id
                  - did
                  - key_id
                  - kid
                  - published_key_x
                  - trust_tier
                properties:
                  advisor_id:
                    type: string
                    format: uuid
                    description: >-
                      UUID of the advisor row created for this DID (returned
                      first in the live wire body).
                  did:
                    type: string
                  key_id:
                    type: string
                    description: >
                      Your full signing key id, `did:ocss:<slug>#<kid>` — use
                      verbatim as SenderKey.keyID. The census now returns this
                      directly in the 200 body.
                    example: did:ocss:touchstone#2026-07
                  kid:
                    type: string
                    description: >
                      The bare kid the census bound — the current UTC month
                      (YYYY-MM) at registration time.
                    example: 2026-07
                  published_key_x:
                    type: string
                    description: >-
                      The base64url public key now published on the Trust List
                      entry.
                  trust_tier:
                    type: string
                    description: >-
                      Always `provisional` — self-service never elevates the
                      tier.
                  entry_role:
                    type: string
                    description: Echoed only when an `entry_role` was requested.
              example:
                advisor_id: 7e4c1a90-3b28-4d67-95f1-0a8c6b2e9d43
                did: did:ocss:touchstone
                key_id: did:ocss:touchstone#2026-07
                kid: 2026-07
                published_key_x: CMHWy3vUAiEcYDdE_bDvkRuEqwxkklS0tV-TYHJTlWU
                trust_tier: provisional
                entry_role: verifying-agency
        '400':
          description: >
            Missing/invalid `did`, invalid `public_key_b64url` (bad base64url or
            not 32 bytes), or an `entry_role` other than `verifying-agency`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Bad Request
                message: public_key_b64url is required
                code: 400
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Census is not in sandbox mode (`self_register_sandbox_only`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Forbidden
                message: >-
                  self_register_sandbox_only: self-service DID registration is
                  only permitted in sandbox environments (SANDBOX_MODE=true)
                code: 403
        '409':
          description: >-
            The DID/slug is already on the Trust List
            (`did_already_registered`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Conflict
                message: >-
                  did_already_registered: this DID is already on the trust list;
                  self-register cannot overwrite existing entries
                code: 409
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security: []
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: >
            curl -sS -X POST
            "https://phosra-api-sandbox-production.up.railway.app/api/v1/advisors/self-register"
            \
              -H "Content-Type: application/json" \
              -d '{
              "did": "did:ocss:acme-advisor",
              "public_key_b64url": "REPLACE_WITH_YOUR_ED25519_PUBLIC_KEY_B64URL",
              "roles": [
                "advising-agency"
              ],
              "entry_role": "verifying-agency"
            }'
        - lang: JavaScript
          label: Node.js
          source: >
            const BASE =
            "https://phosra-api-sandbox-production.up.railway.app/api/v1";

            const res = await fetch(`${BASE}/advisors/self-register`, {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({
                "did": "did:ocss:acme-advisor",
                "public_key_b64url": "REPLACE_WITH_YOUR_ED25519_PUBLIC_KEY_B64URL",
                "roles": [
                  "advising-agency"
                ],
                "entry_role": "verifying-agency"
              }),
            });

            console.log(res.status, await res.json());
        - lang: Python
          label: Python
          source: |
            import requests

            BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
            res = requests.post(
                f"{BASE}/advisors/self-register",
                json={
                    "did": "did:ocss:acme-advisor",
                    "public_key_b64url": "REPLACE_WITH_YOUR_ED25519_PUBLIC_KEY_B64URL",
                    "roles": [
                        "advising-agency"
                    ],
                    "entry_role": "verifying-agency"
                },
            )
            print(res.status_code, res.json())
        - lang: Go
          label: Go
          source: "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tbase := \"https://phosra-api-sandbox-production.up.railway.app/api/v1\"\n\tbody := bytes.NewBufferString(`{\n  \"did\": \"did:ocss:acme-advisor\",\n  \"public_key_b64url\": \"REPLACE_WITH_YOUR_ED25519_PUBLIC_KEY_B64URL\",\n  \"roles\": [\n    \"advising-agency\"\n  ],\n  \"entry_role\": \"verifying-agency\"\n}`)\n\treq, _ := http.NewRequest(\"POST\", base+\"/advisors/self-register\", body)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}\n"
components:
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: HTTP status text (e.g. "Bad Request").
        message:
          type: string
          description: >-
            Human-readable detail (the actual reason, e.g. "unknown_capability:
            foo").
        code:
          type: integer
          description: HTTP status code.
        class:
          type: string
          description: >
            Stable error subclass for programmatic branching. This
            management/control plane returns the PLAIN `{error, message, code}`
            envelope and does NOT set `class` — the `class` field is emitted
            only by the signed data-plane (census) surface documented in
            `openapi.data-plane.yaml`.
      description: >-
        Standard error envelope returned for every 4xx and 5xx management
        response.
      example:
        error: Not Found
        message: developer org not found
        code: 404
      additionalProperties: true
  responses:
    Unauthorized:
      description: >
        No valid session bearer token. Send the WorkOS AuthKit access token as
        `Authorization: Bearer <token>`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Unauthorized
            message: missing authorization header
            code: 401
    RateLimited:
      description: >-
        Too many requests. Back off and retry after the interval given in the
        `Retry-After` response header.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Too Many Requests
            message: rate limit exceeded
            code: 429
    ServerError:
      description: >-
        An unexpected error occurred inside Phosra. The request is safe to retry
        with exponential backoff; contact support if it persists.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Internal Server Error
            message: internal error
            code: 500
    BadGateway:
      description: >-
        A downstream provider returned an invalid response while Phosra was
        fulfilling the request. Retry with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Bad Gateway
            message: downstream provider error
            code: 502
    ServiceUnavailable:
      description: >-
        A downstream provider or dependency is temporarily unavailable. Retry
        with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Service Unavailable
            message: downstream provider unavailable
            code: 503
  securitySchemes:
    SessionAuth:
      type: http
      scheme: bearer
      description: >
        A logged-in user session bearer token (WorkOS AuthKit access token from
        signup/login).

````