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

# Go-live checklist

> The engineering pre-flight before you flip an integration from the sandbox to production — rotate keys, lock scopes, verify webhook signatures, arm deprecation and health monitoring, and pin every version. Each line links to its authoritative page.

Run this the day you flip from the sandbox to production. It is a short, mechanical
pre-flight — eight checks, each with a copy-paste command and a link to the page that
owns the detail. Nothing here is aspirational: every command below was run live against
the sandbox census.

<Note>
  **This is the engineering pre-flight — not accreditation.** Getting your entry onto the
  production Trust List is a separate governance gate with its own vetting, SLA, and
  eligibility bar. Do that on
  [Production Accreditation](/integration/production-accreditation). Come back here for the
  code-side switch-over once you are cleared.
</Note>

## The checklist

* [ ] **1. Rotate test → live keys** — mint `phosra_live_` keys; retire every `phosra_test_` key from prod config
* [ ] **2. Lock scopes to least privilege** — each key holds only the scopes its service calls
* [ ] **3. Verify webhook signatures in prod** — reject any delivery whose HMAC does not recompute
* [ ] **4. Arm Deprecation / Sunset monitoring** — alert on any `Deprecation` or `Sunset` response header
* [ ] **5. Diff sandbox → prod config** — base URL, trust root, and org id all move together
* [ ] **6. Confirm rate-limit headroom** — read `X-RateLimit-Remaining`; back off on `429`, never hammer
* [ ] **7. Pin exact SDK versions** — no caret ranges on a pre-1.0 line
* [ ] **8. Wire health / status monitoring** — poll `/health` and the trust-list `ETag`

<Steps>
  <Step title="Rotate test → live keys">
    A `phosra_test_` key **only** works against the sandbox base URL; a `phosra_live_` key
    only works against production. Mint live keys, load them into your prod secret manager,
    and make sure no `phosra_test_` value survives in production config.

    ```bash Mint a live key theme={null}
    curl -X POST https://prodapi.phosra.com/api/v1/developers/orgs/{orgId}/keys \
      -H "Authorization: Bearer <workos_access_token>" \
      -H "Content-Type: application/json" \
      -d '{"name":"orders-service","environment":"live","scopes":["read:policies","write:enforcement"]}'
    ```

    The raw key is in the response `key` field and is shown **exactly once** — only a
    SHA-256 hash is stored. Prefer [zero-downtime rotation](/authentication#rotation) (mint
    the new key, deploy, then revoke the old) so nothing breaks mid-cutover.

    → [Authentication · test vs live keys](/authentication#test-keys-vs-live-keys)
  </Step>

  <Step title="Lock scopes to least privilege">
    Every key carries an explicit scope list; a call to a route your key lacks returns
    `403`, never silent success. Grant the fewest scopes each service needs — a read-only
    reporting job should never hold `write:enforcement`. If one key leaks, the blast radius
    is only that key's scopes on that one environment.

    ```bash Read the scopes bound to a key theme={null}
    curl https://prodapi.phosra.com/api/v1/developers/orgs/{orgId}/keys/{keyId} \
      -H "Authorization: Bearer <workos_access_token>" \
      | python3 -c "import sys,json;print(json.load(sys.stdin)['scopes'])"
    ```

    → [Authentication · scoping](/authentication#scoping)
  </Step>

  <Step title="Verify webhook signatures in prod">
    Every delivery is signed `t=<unix>,v1=<hex>` in the `Phosra-Signature` header. Recompute
    `HMAC-SHA256(secret, "<t>." + rawBody)` over the **raw** body and reject on any mismatch —
    otherwise anyone who learns your endpoint URL can forge policy-change events. Use the
    per-environment signing secret returned when you register the production webhook, and
    compare in constant time.

    ```bash Recompute and compare (bash) theme={null}
    # $SIG = the v1=... value from the Phosra-Signature header; $T = the t=... value
    EXPECTED=$(printf '%s.%s' "$T" "$(cat body.json)" \
      | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary | xxd -p -c256)
    [ "$EXPECTED" = "$SIG" ] && echo "verified" || echo "REJECT"
    ```

    → [Webhooks · verifying signatures](/guides/webhook-events#verifying-signatures)
    (copy-paste verifiers in TypeScript, Python, and Go)
  </Step>

  <Step title="Arm Deprecation / Sunset monitoring">
    Before any v1 endpoint or field changes, the census sends
    [RFC 9745](https://www.rfc-editor.org/rfc/rfc9745) `Deprecation` and
    [RFC 8594](https://www.rfc-editor.org/rfc/rfc8594) `Sunset` response headers through the
    entire advance-notice window. Log or alert on their presence so a migration deadline
    never reaches you as a surprise `410`. Today the sandbox sends none — a clean scan is the
    healthy baseline you are monitoring for change against.

    ```bash Scan for lifecycle headers (empty today = healthy) theme={null}
    curl -s -D - -o /dev/null https://prodapi.phosra.com/api/v1/platforms \
      | grep -iE '^(deprecation|sunset|link):' || echo "none in effect"
    ```

    → [Deprecation & Sunset Policy · on the wire](/deprecation#on-the-wire-deprecation-and-sunset-headers)
  </Step>

  <Step title="Diff sandbox → prod config">
    Three values change together when you leave the sandbox. Miss one and a
    `phosra_live_` key will hit the sandbox census (or vice-versa) and fail closed.

    | Setting                       | Sandbox                                                | Production                                 |
    | ----------------------------- | ------------------------------------------------------ | ------------------------------------------ |
    | API / census base URL         | `https://phosra-api-sandbox-production.up.railway.app` | `https://prodapi.phosra.com/api/v1`        |
    | `LinkConfig.trustRootXB64Url` | sandbox root X                                         | **distinct** prod root X (Phosra provides) |
    | `LinkConfig.developerOrgId`   | —                                                      | your `org_…` (billing attribution)         |
    | API key                       | `phosra_test_…`                                        | `phosra_live_…`                            |

    Do not copy the sandbox trust root into production — the production root is a different
    Ed25519 identity (`root-prod-2026-06`), and pinning the wrong one makes every signature
    verification fail. Before you pin, confirm the census you are about to trust really is the
    production one by reading the root key id it signs its Trust List with:

    ```bash Confirm the production root (run live) theme={null}
    curl -s https://prodapi.phosra.com/.well-known/ocss/trust-list \
      | python3 -c "import sys,json;print('root key_id:', json.load(sys.stdin)['key_id'])"
    ```

    ```text Output theme={null}
    root key_id: root-prod-2026-06
    ```

    For an end-to-end round-trip, `phosra doctor` drives all three values at once. It reads
    the census from the environment (there is no `--env` flag) — override `PHOSRA_CENSUS_URL`
    and `PHOSRA_TRUST_ROOT_X` to point it at any census without touching your config file:

    ```bash Drive the doctor against a chosen census theme={null}
    PHOSRA_CENSUS_URL=https://phosra-api-sandbox-production.up.railway.app \
      PHOSRA_TRUST_ROOT_X=CMHWy3vUAiEcYDdE_bDvkRuEqwxkklS0tV-TYHJTlWU phosra doctor
    ```

    → [CLI · phosra doctor](/integration/cli#step-2-phosra-doctor) ·
    [Production Accreditation · after you are accredited](/integration/production-accreditation#after-you-are-accredited)
  </Step>

  <Step title="Confirm rate-limit headroom">
    Every metered response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and
    `X-RateLimit-Reset` (unix epoch seconds). Read them instead of hard-coding a quota, and
    on a `429` sleep until the reset — never retry immediately. Check your standing before a
    launch that spikes traffic:

    ```bash Read your current window theme={null}
    curl -s -D - -o /dev/null https://prodapi.phosra.com/api/v1/platforms \
      | grep -i x-ratelimit
    ```

    ```text Response headers theme={null}
    x-ratelimit-limit: 100
    x-ratelimit-remaining: 99
    x-ratelimit-reset: 1783332360
    ```

    → [Rate limits · read your window](/rate-limits#read-your-window-off-every-response)
  </Step>

  <Step title="Pin exact SDK versions">
    The published SDKs are pre-1.0, so a `0.x` **minor** bump may carry breaking changes. Pin
    the exact version you tested against — not a caret range — and bump on purpose after
    reading the [changelog](/changelog). Every version below is live on the npm registry.

    ```json package.json theme={null}
    {
      "dependencies": {
        "@phosra/sdk": "0.1.0",
        "@phosra/link": "0.1.2",
        "@phosra/mcp": "0.4.0",
        "@openchildsafety/ocss": "0.1.3"
      }
    }
    ```

    ```bash Install pinned theme={null}
    npm install @phosra/sdk@0.1.0 @openchildsafety/ocss@0.1.3
    ```

    → [Versioning · pin exact](/versioning) ·
    [Forward compatibility](/integration/forward-compatibility)
  </Step>

  <Step title="Wire health / status monitoring">
    Poll `/health` for liveness, and watch the trust-list `ETag` so you notice when the
    signed census document (rules, accredited entries, spec version) actually moves — a
    `304 Not Modified` means nothing changed; a `200` means re-read and re-verify.

    ```bash Liveness + change detection theme={null}
    curl -s -o /dev/null -w "health: %{http_code}\n" \
      https://prodapi.phosra.com/health
    # Capture the ETag, then send it back as If-None-Match to get 304 until it moves:
    curl -s -D - -o /dev/null https://prodapi.phosra.com/.well-known/ocss/trust-list \
      | grep -i etag
    ```

    → [Versioning · conditional reads](/versioning#dated-artifacts-annex-b-editions) ·
    [Deprecation · detect programmatically](/deprecation#detect-deprecations-programmatically)
  </Step>
</Steps>

## One-command pre-flight

Drop this in your CI or run it by hand right before the switch. It confirms liveness,
prints your rate-limit headroom, flags any active deprecation, and echoes the spec version
the census is pinned to. Point `PHOSRA_API` at production once your live keys are loaded.

```bash preflight.sh theme={null}
#!/usr/bin/env bash
set -euo pipefail
BASE="${PHOSRA_API:-https://phosra-api-sandbox-production.up.railway.app}"

echo "1. Health ......... $(curl -s -o /dev/null -w '%{http_code}' "$BASE/health")"

HDRS=$(curl -s -D - -o /dev/null "$BASE/api/v1/platforms")
LIMIT=$(echo  "$HDRS" | tr -d '\r' | awk 'tolower($1)=="x-ratelimit-limit:"{print $2}')
REMAIN=$(echo "$HDRS" | tr -d '\r' | awk 'tolower($1)=="x-ratelimit-remaining:"{print $2}')
echo "2. Rate headroom .. ${REMAIN:-?}/${LIMIT:-?} remaining"

if echo "$HDRS" | grep -qiE '^(deprecation|sunset):'; then
  echo "3. Deprecation .... ACTIVE — read the Deprecation/Sunset headers"
else
  echo "3. Deprecation .... none in effect"
fi

echo "4. OCSS version ... $(curl -s "$BASE/.well-known/ocss/trust-list" \
  | python3 -c "import sys,json;print(json.loads(json.load(sys.stdin)['document'])['ocss_version'])")"
```

```text Output (run live against the sandbox census) theme={null}
1. Health ......... 200
2. Rate headroom .. 99/100 remaining
3. Deprecation .... none in effect
4. OCSS version ... OCSS-v1.0-pre
```

<Check>
  All four green, keys rotated, scopes locked, webhook verification live, and monitoring
  armed? You are clear to flip `PHOSRA_API` and your keys to production. The request shapes
  are identical between sandbox and prod — only the base URL, trust root, and key change.
</Check>

## Next steps

<CardGroup cols={2}>
  <Card title="Production Accreditation" icon="badge-check" href="/integration/production-accreditation">
    The governance gate: get your entry onto the production Trust List.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    Test vs live keys, scoping, and zero-downtime rotation.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/guides/webhook-events">
    Signed event catalog and signature verification in four languages.
  </Card>

  <Card title="Deprecation & Sunset" icon="clock" href="/deprecation">
    The advance-notice window and the headers that carry it.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/rate-limits">
    The window headers and a copy-paste backoff loop.
  </Card>

  <Card title="Versioning" icon="code-branch" href="/versioning">
    Pin the API path, spec version, dated editions, and SDK semver.
  </Card>
</CardGroup>
