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

# Reference parent-consent authorize leg (sandbox)

> SANDBOX-ONLY reference OAuth provider hosted on the census host root (NOT /api/v1). With no `decision`, renders an HTML consent page seeded with the sandbox test family (Mia/Leo/Ava). MACHINE-USABLE CONTRACT: re-request with `decision=approve` and the census issues an authorization code and 302-redirects to `redirect_uri?code=…&state=…` — so CI can automate the approval with no HTML scraping. `decision=deny` 302s with `error=access_denied&state=…`. Custom mobile schemes (e.g. propagate://) are allowed as redirect_uri. Stateless (opaque `sbxauth_` code). Gated on the Restricted band — 404 on dev/staging/production. Source: internal/ocsshttp/handler_sandbox_oauth.go Authorize().


<Note>
  **Sandbox only.** The reference OAuth provider is hosted on the census **host root** (not
  `/api/v1`) and gated on `PHOSRA_ENV==sandbox` — `404` on dev, staging, and production.
</Note>

The first leg of the sandbox reference OAuth flow, seeded with the test family (Mia / Leo /
Ava). With **no `decision`** it renders an HTML consent page. Its machine-usable contract: add
`decision=approve` and the census issues an authorization code and `302`-redirects to
`redirect_uri?code=…&state=…` — so CI can automate approval with no HTML scraping.
`decision=deny` redirects with `error=access_denied&state=…`. Custom mobile schemes (e.g.
`propagate://`) are allowed as `redirect_uri`.

<Frame caption="The seeded sandbox consent page rendered by GET /oauth/authorize (no decision param).">
  <img src="https://mintcdn.com/phosra/o0ansqUg4hqAQihW/images/data-plane-oauth-consent.jpg?fit=max&auto=format&n=o0ansqUg4hqAQihW&q=85&s=8df4e961e3b93237eaa372073bd28b31" alt="Phosra sandbox reference-provider consent page: a card titled 'Connect your family to this app?' with a 'Sandbox reference provider' pill and Approve / Deny buttons." width="700" height="420" data-path="images/data-plane-oauth-consent.jpg" />
</Frame>

## Worked example

Fully runnable — this leg is unsigned:

<CodeGroup>
  ```bash Shell theme={null}
  # Render the consent page (200, text/html):
  curl "https://phosra-api-sandbox-production.up.railway.app/oauth/authorize?redirect_uri=https://example.com/cb&state=xyz123"

  # Machine path — approve and capture the 302 Location header:
  curl -sD - -o /dev/null \
    "https://phosra-api-sandbox-production.up.railway.app/oauth/authorize?redirect_uri=https://example.com/cb&state=xyz123&decision=approve" \
    | grep -i '^location:'
  ```

  ```ts Node theme={null}
  const BASE = "https://phosra-api-sandbox-production.up.railway.app"
  const res = await fetch(
    `${BASE}/oauth/authorize?redirect_uri=https://example.com/cb&state=xyz123&decision=approve`,
    { redirect: "manual" },
  )
  const location = res.headers.get("location")
  const code = new URL(location).searchParams.get("code")   // sbxauth_…
  console.log(res.status, code)                              // 302  sbxauth_QBmKueI14KzQj9PM-2M9naWGkh2OU6WL
  ```
</CodeGroup>

**Real `302` on approve** (captured from the hosted sandbox):

```http theme={null}
HTTP/2 302
location: https://example.com/cb?code=sbxauth_QBmKueI14KzQj9PM-2M9naWGkh2OU6WL&state=xyz123
```

Exchange the `sbxauth_…` code at [`POST /oauth/token`](/api-reference/data-plane/oauth-token).


## OpenAPI

````yaml GET /oauth/authorize
openapi: 3.1.0
info:
  title: Phosra OCSS Data Plane — enforcement endpoints, profiles & confirmations
  description: >
    The signed OCSS data-plane the @phosra/gatekeeper SDK drives. These are NOT
    plain REST: profile GETs are RFC 9421-signed by the resolver, binding POSTs
    require accredited caller signatures, and profile responses are
    root-verifiable signed documents consumed via @openchildsafety/ocss /
    @phosra/gatekeeper. This spec documents the wire shapes for client
    generation; it does not change census behavior. The control plane (provider
    org, keys, advisors) is in openapi.control-plane.yaml; the parental-control
    product API is in openapi.yaml.

    Canonical profile shape: `categories[]` (there is no `rules[]` field — it
    does not exist on the wire, in the Go model, or in @phosra/gatekeeper
    types).

    Wire shapes are authoritative from the Go census:
      - profile.Document / CategoryEntry: internal/ocss/profile/document.go
      - EndpointHandlers.Mint / Rotate: internal/ocsshttp/handler_endpoints.go
      - ProfileReads.ServeHTTP: internal/ocsshttp/handler_profiles.go
      - EnforcementHandlers.Confirm: internal/ocsshttp/handler_enforcement.go
      - receipt.Receipt / EnforcementResultBody: internal/ocss/receipt/types.go
  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 one canonical, stable, partner-facing testing
      endpoint
  - url: http://localhost:8080/api/v1
    description: Local development
security: []
paths:
  /oauth/authorize:
    servers:
      - url: https://phosra-api-sandbox-production.up.railway.app
        description: Sandbox census host ROOT — the /oauth/* surface is NOT under /api/v1.
    get:
      summary: Reference parent-consent authorize leg (sandbox only)
      description: >
        SANDBOX-ONLY reference OAuth provider hosted on the census host root
        (NOT /api/v1). With no `decision`, renders an HTML consent page seeded
        with the sandbox test family (Mia/Leo/Ava). MACHINE-USABLE CONTRACT:
        re-request with `decision=approve` and the census issues an
        authorization code and 302-redirects to `redirect_uri?code=…&state=…` —
        so CI can automate the approval with no HTML scraping. `decision=deny`
        302s with `error=access_denied&state=…`. Custom mobile schemes (e.g.
        propagate://) are allowed as redirect_uri. Stateless (opaque `sbxauth_`
        code). Gated on the Restricted band — 404 on dev/staging/production.
        Source: internal/ocsshttp/handler_sandbox_oauth.go Authorize().
      operationId: referenceParentConsentAuthorizeLegSandboxOnly
      parameters:
        - name: redirect_uri
          in: query
          required: true
          description: >-
            Where the 302 sends the code (https, http, or a custom mobile
            scheme).
          schema:
            type: string
          example: propagate://connect
        - name: state
          in: query
          required: false
          description: Opaque value echoed back verbatim in the redirect (CSRF binding).
          schema:
            type: string
        - name: decision
          in: query
          required: false
          description: >
            Omit to render the consent page. `approve` → 302 with ?code=&state=.
            `deny` → 302 with ?error=access_denied&state=.
          schema:
            type: string
            enum:
              - approve
              - deny
      responses:
        '200':
          description: Consent page (text/html) — shown when `decision` is omitted.
          content:
            text/html:
              schema:
                type: string
              example: >
                <!doctype html><meta charset="utf-8"><meta name="viewport"
                content="width=device-width,initial-scale=1">

                <title>Phosra Sandbox — Connect</title>

                <style>body{font:16px/1.5
                system-ui,sans-serif;max-width:30rem;margin:3rem auto;padding:0
                1.25rem;color:#0f172a}

                h1{font-size:1.25rem}.card{border:1px solid
                #e2e8f0;border-radius:12px;padding:1.25rem}

                ul{padding-left:1.1rem}.act{display:flex;gap:.75rem;margin-top:1.25rem}

                a.btn{flex:1;text-align:center;text-decoration:none;padding:.7rem
                1rem;border-radius:10px;font-weight:600}

                .ap{background:#4f46e5;color:#fff}.dn{background:#f1f5f9;color:#334155}

                .tag{display:inline-block;background:#eef2ff;color:#4338ca;border-radius:999px;padding:.1rem
                .6rem;font-size:.75rem}</style>

                <div class="card"><span class="tag">Sandbox reference
                provider</span>

                <h1>Connect your family to this app?</h1>

                <p>Approving shares these sandbox child profiles so the app can
                receive their safety policy:</p>

                <ul><li>Mia <small>(child)</small></li><li>Leo
                <small>(child)</small></li><li>Ava
                <small>(child)</small></li></ul>

                <div class="act"><a class="btn ap"
                href="/oauth/authorize?decision=approve&amp;redirect_uri=https%3A%2F%2Fexample.com%2Fcb&amp;state=xyz123">Approve</a>

                <a class="btn dn"
                href="/oauth/authorize?decision=deny&amp;redirect_uri=https%3A%2F%2Fexample.com%2Fcb&amp;state=xyz123">Deny</a></div></div>
        '302':
          description: >
            Approve → Location: {redirect_uri}?code={sbxauth_…}&state={state}.
            Deny → Location: {redirect_uri}?error=access_denied&state={state}.
          headers:
            Location:
              description: redirect_uri with code+state (approve) or error+state (deny).
              schema:
                type: string
        '400':
          description: Missing or malformed redirect_uri. Full census error envelope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Bad Request
                message: redirect_uri is required
                code: 400
        '404':
          description: >
            Not a sandbox census (gated). The /oauth/* surface is not mounted on
            dev / staging-internal / production, so the router answers with a
            plain-text chi 404, NOT a JSON envelope.
          content:
            text/plain:
              schema:
                type: string
              example: |
                404 page not found
        '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 (runnable — unsigned leg)
          source: >
            # Render the consent page (200, text/html):

            curl
            "https://phosra-api-sandbox-production.up.railway.app/oauth/authorize?redirect_uri=https://example.com/cb&state=xyz123"

            # Machine path — approve and capture the 302 Location:

            curl -sD - -o /dev/null
            "https://phosra-api-sandbox-production.up.railway.app/oauth/authorize?redirect_uri=https://example.com/cb&state=xyz123&decision=approve"
            | grep -i '^location:'
        - lang: JavaScript
          label: Node (runnable)
          source: >
            const BASE = "https://phosra-api-sandbox-production.up.railway.app"

            const res = await
            fetch(`${BASE}/oauth/authorize?redirect_uri=https://example.com/cb&state=xyz123&decision=approve`,
            { redirect: "manual" })

            const code = new
            URL(res.headers.get("location")).searchParams.get("code")

            console.log(res.status, code) // 302  sbxauth_…
components:
  schemas:
    Error:
      type: object
      description: Standard error envelope returned for every 4xx and 5xx response.
      properties:
        error:
          type: string
          description: >-
            Short, stable, machine-readable error class — matches the HTTP
            status text (e.g. `Not Found`).
        message:
          type: string
          description: >-
            Human-readable explanation of what went wrong and, where possible,
            how to fix it.
        code:
          type: integer
          description: Numeric HTTP status code, duplicated in the body for convenience.
        class:
          type: string
          enum:
            - signature_invalid
            - nonconformant
            - replay
            - standing_failure
            - scope_failure
            - malformed
            - not_found
          description: >
            Stable machine-readable error subclass for programmatic branching.
            Present on ALL seven published census (data-plane) error classes —
            400⇒`malformed`, 401⇒`signature_invalid`,
            403⇒`standing_failure`|`scope_failure`, 404⇒`not_found`,
            409⇒`replay`, 422⇒`nonconformant`. ABSENT on the plain house 500, on
            control-plane/management errors, and on the compact sandbox-lane
            `{error:"<code>"}` bodies (OAuth + sandbox consent).
        failed_step:
          type: string
          description: >
            Present only on a §8.3.1 standing failure (`class:standing_failure`)
            — names the §6.2 step that failed, e.g. `authority_binding`.
      example:
        error: Not Found
        message: unknown enforcement endpoint
        code: 404
        class: not_found
      additionalProperties: true
  responses:
    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

````