> ## 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 token leg — code → access token (sandbox)

> SANDBOX-ONLY. Exchanges an authorization code (any value carrying the `sbxauth_` prefix) for an opaque bearer access token. Accepts application/x-www-form-urlencoded or JSON; PKCE code_verifier is accepted and ignored. Stateless. Gated on the Restricted band. Source: internal/ocsshttp/handler_sandbox_oauth.go Token().


<Note>
  **Sandbox only.** Hosted on the census **host root** (not `/api/v1`), gated on
  `PHOSRA_ENV==sandbox` — `404` elsewhere.
</Note>

Exchanges an authorization code (any value carrying the `sbxauth_` prefix, from
[`GET /oauth/authorize`](/api-reference/data-plane/oauth-authorize)) for an opaque bearer
access token. Accepts `application/x-www-form-urlencoded` or JSON. A PKCE `code_verifier` is
accepted and ignored. Stateless — tokens live one hour.

## Worked example

Fully runnable — this leg is unsigned:

<CodeGroup>
  ```bash Shell theme={null}
  curl -X POST https://phosra-api-sandbox-production.up.railway.app/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=authorization_code&code=sbxauth_QBmKueI14KzQj9PM-2M9naWGkh2OU6WL"
  ```

  ```ts Node theme={null}
  const BASE = "https://phosra-api-sandbox-production.up.railway.app"
  const res = await fetch(`${BASE}/oauth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({ grant_type: "authorization_code", code: "sbxauth_QBmKueI14KzQj9PM-2M9naWGkh2OU6WL" }),
  })
  const { access_token } = await res.json()
  console.log(res.status, access_token)   // 200  sbxtok_…
  ```
</CodeGroup>

**Real `200` response** (captured from the hosted sandbox):

```json theme={null}
{
  "access_token": "sbxtok_9xRWtUBLsk1omBYjWWe-20KbCxTO1Noh",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "profiles"
}
```

An unsupported `grant_type` returns `400 unsupported_grant_type`; a code without the
`sbxauth_` prefix returns `400 invalid_grant`. Present the `sbxtok_…` token to
[`GET /oauth/profiles`](/api-reference/data-plane/oauth-profiles).


## OpenAPI

````yaml POST /oauth/token
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/token:
    servers:
      - url: https://phosra-api-sandbox-production.up.railway.app
        description: Sandbox census host ROOT — the /oauth/* surface is NOT under /api/v1.
    post:
      summary: Reference token leg — authorization_code → access_token (sandbox only)
      description: >
        SANDBOX-ONLY. Exchanges an authorization code (any value carrying the
        `sbxauth_` prefix) for an opaque bearer access token. Accepts
        application/x-www-form-urlencoded or JSON; PKCE code_verifier is
        accepted and ignored. Stateless. Gated on the Restricted band. Source:
        internal/ocsshttp/handler_sandbox_oauth.go Token().
      operationId: referenceTokenLegAuthorizationCodeAccessTokenSandboxOnly
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - grant_type
                - code
              properties:
                grant_type:
                  type: string
                  enum:
                    - authorization_code
                code:
                  type: string
                  example: sbxauth_…
          application/json:
            schema:
              type: object
              required:
                - grant_type
                - code
              properties:
                grant_type:
                  type: string
                  enum:
                    - authorization_code
                code:
                  type: string
                  example: sbxauth_…
      responses:
        '200':
          description: Access token issued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxOAuthToken'
              example:
                access_token: sbxtok_8zc0PBmNAjyIs3p8B3Y2oxQW2csHHoy1
                token_type: Bearer
                expires_in: 3600
                scope: profiles
        '400':
          description: >
            `unsupported_grant_type` (grant_type != authorization_code) or
            `invalid_grant` (code lacks the `sbxauth_` prefix). Compact `{error:
            "<code>"}` OAuth-style body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: unsupported_grant_type
        '404':
          description: >
            Not a sandbox census (gated). The route is not mounted off the
            sandbox, 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: >
            curl -X POST
            https://phosra-api-sandbox-production.up.railway.app/oauth/token \
              -H "Content-Type: application/x-www-form-urlencoded" \
              -d "grant_type=authorization_code&code=sbxauth_QBmKueI14KzQj9PM-2M9naWGkh2OU6WL"
        - lang: JavaScript
          label: Node (runnable)
          source: |
            const BASE = "https://phosra-api-sandbox-production.up.railway.app"
            const res = await fetch(`${BASE}/oauth/token`, {
              method: "POST",
              headers: { "Content-Type": "application/x-www-form-urlencoded" },
              body: new URLSearchParams({ grant_type: "authorization_code", code: "sbxauth_QBmKueI14KzQj9PM-2M9naWGkh2OU6WL" }),
            })
            const { access_token } = await res.json()
            console.log(res.status, access_token) // 200  sbxtok_…
components:
  schemas:
    SandboxOAuthToken:
      type: object
      description: >
        SANDBOX-ONLY access token from POST /oauth/token. Source:
        internal/ocsshttp/handler_sandbox_oauth.go Token().
      required:
        - access_token
        - token_type
        - expires_in
        - scope
      properties:
        access_token:
          type: string
          description: Opaque bearer ("sbxtok_…"). Present it to GET /oauth/profiles.
        token_type:
          type: string
          enum:
            - Bearer
          description: 'One of: Bearer.'
        expires_in:
          type: integer
          example: 3600
          description: Access-token lifetime in seconds (sandbox tokens live one hour).
        scope:
          type: string
          example: profiles
          description: >-
            Granted scope; the reference provider issues the single `profiles`
            scope.
      additionalProperties: true
    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

````