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

# HTTP headers

> Every request and response header the Phosra API reads or sets — authentication, content negotiation, RFC 9421 signatures, rate limits, deprecation signals, and the correlation id to quote in a support ticket. One table each, every value captured live from the sandbox.

Every header the Phosra API reads on the way in or sets on the way out, in one
place. If you only skim one page before wiring up a client, make it this one —
then jump to the deep-dive linked from each row.

<Info>
  **Verified live.** Every header name and every sample value on this page was
  captured with `curl -D -` against the partner sandbox at
  `https://phosra-api-sandbox-production.up.railway.app` on **2026-07-06**. The
  reads used below (`/health`, `/api/v1/platforms`, `/.well-known/ocss/trust-list`)
  need no API key, so you can reproduce the exact bytes.
</Info>

## Request headers

Headers you send. Only `Authorization` (or `X-Api-Key`) is required for
authenticated routes; the RFC 9421 triplet is required only for signed census
writes.

| Header              | When to send it                                                                                                                                      | Example                                      | Reference                                                |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------- |
| `Authorization`     | **Every authenticated call.** Developer API key or WorkOS session JWT as a bearer token.                                                             | `Authorization: Bearer phosra_live_4a1b2c…`  | [Authentication](/authentication)                        |
| `X-Api-Key`         | Exact alias for `Authorization: Bearer`. Use **only** when a proxy or framework reserves `Authorization`. If both are present, `Authorization` wins. | `X-Api-Key: phosra_live_4a1b2c…`             | [Authentication](/authentication#the-one-correct-header) |
| `X-Device-Key`      | On-device enforcement agents (iOS FamilyControls) instead of a developer key.                                                                        | `X-Device-Key: phosra_dev_…`                 | [Authentication](/authentication)                        |
| `Content-Type`      | Any request with a body (`POST` / `PUT` / `PATCH`). Phosra speaks JSON.                                                                              | `Content-Type: application/json`             | —                                                        |
| `Accept`            | Optional. Responses are JSON regardless; send it to be explicit.                                                                                     | `Accept: application/json`                   | —                                                        |
| `Signature-Input`   | **Signed census writes only** (binding mint, rotate, enforcement-confirmations, bound-resolver profile GETs). The RFC 9421 covered-components line.  | see [Signed writes](#signed-writes-rfc-9421) | [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421.html)  |
| `Signature`         | Paired with `Signature-Input`. The Ed25519 signature bytes. Exactly **one** of each is required.                                                     | `Signature: ocss=:<base64-ed25519>:`         | [Errors · 401](/errors#401-missing-signature)            |
| `Content-Digest`    | Signed writes with a body. `SHA-256` of the exact body bytes; covered by the signature.                                                              | `Content-Digest: sha-256=:<base64-sha256>:`  | [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421.html)  |
| `OCSS-Spec-Version` | Signed census writes. The census-spec version your payload targets; it is a covered signature component.                                             | `OCSS-Spec-Version: 2026-06`                 | [Versioning](/versioning)                                |

<Warning>
  **There is no `Idempotency-Key` header.** Unlike Stripe, Phosra ignores an
  `Idempotency-Key` request header entirely. Idempotency is keyed off a field
  **in the request body**, and a faithful replay is signalled by the
  [`OCSS-Replay`](#idempotency-replay) response header. Sending
  `Idempotency-Key` does nothing — see [Idempotency](/idempotency#where-the-key-lives-there-is-no-idempotency-key-header).
</Warning>

## Response headers

Headers Phosra sets. The rate-limit trio rides every `/api/v1/*` response; the
correlation id rides everything.

| Header                  | On which responses                                                                         | Example                          | Reference                                         |
| ----------------------- | ------------------------------------------------------------------------------------------ | -------------------------------- | ------------------------------------------------- |
| `Content-Type`          | All                                                                                        | `application/json`               | —                                                 |
| `X-RateLimit-Limit`     | Every `/api/v1/*` response                                                                 | `100`                            | [Rate limits](/rate-limits)                       |
| `X-RateLimit-Remaining` | Every `/api/v1/*` response                                                                 | `87`                             | [Rate limits](/rate-limits)                       |
| `X-RateLimit-Reset`     | Every `/api/v1/*` response. **Unix epoch seconds** the window resets — not a duration.     | `1783344660`                     | [Rate limits](/rate-limits)                       |
| `Retry-After`           | `429 Too Many Requests` only. Seconds to wait.                                             | `60`                             | [Rate limits](/rate-limits#what-a-429-looks-like) |
| `Deprecation`           | An endpoint/field in a sunset window (RFC 9745).                                           | `@1788220800`                    | [Deprecation policy](/deprecation)                |
| `Sunset`                | An endpoint with a committed removal date (RFC 8594). HTTP-date.                           | `Tue, 01 Dec 2026 00:00:00 GMT`  | [Deprecation policy](/deprecation)                |
| `Link`                  | Deprecated surfaces — points to the migration guide, `rel="deprecation"` / `rel="sunset"`. | `<https://…>; rel="deprecation"` | [Deprecation policy](/deprecation)                |
| `ETag`                  | Cacheable census reads (e.g. the Trust List) for conditional `If-None-Match` fetches.      | `"32e5fb0d…"`                    | [Trust Framework](/ocss/trust-framework)          |
| `OCSS-Replay`           | A faithful idempotent replay: `original`. Its absence means a fresh apply.                 | `OCSS-Replay: original`          | [Idempotency](/idempotency)                       |
| `X-Railway-Request-Id`  | **Every response.** The correlation id to quote in a support ticket.                       | `2HO1Woj4RFGk9Z7VjVra_w`         | [The correlation id](#the-correlation-id)         |

***

## Authentication headers

A developer API key goes in `Authorization: Bearer`. This is the canonical form
and the one every SDK sends:

```bash theme={null}
curl https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms \
  -H "Authorization: Bearer $PHOSRA_API_KEY"
```

`X-Api-Key: phosra_…` is accepted as an exact equivalent — the API checks
`Authorization: Bearer` first and falls back to `X-Api-Key`. Reach for it only
when a proxy or framework reserves the `Authorization` header; otherwise prefer
the bearer form so API keys, WorkOS JWTs, and MCP tokens all share one header.
Full details — key formats, scoping, rotation — live in
[Authentication](/authentication).

## Content negotiation

The API speaks JSON in and JSON out. Send `Content-Type: application/json` on
any request with a body; `Accept` is optional because responses are JSON
regardless. Signed writes send their body as raw JSON bytes — the exact bytes
you signed the `Content-Digest` over, never a re-stringified object.

## Signed writes (RFC 9421)

Census writes — binding mint (`POST /enforcement-endpoints`), key rotation,
`POST /enforcement-confirmations`, and bound-resolver profile GETs — are
authenticated with an [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421.html)
HTTP Message Signature (Ed25519), not just a bearer key. You send exactly one
`Signature-Input` and one `Signature`, plus a `Content-Digest` covering the
body:

```http theme={null}
Signature-Input: ocss=("@method" "@target-uri" "ocss-spec-version" "content-digest");created=1783315514;keyid="did:ocss:loopline#2026-06";alg="ed25519"
Signature: ocss=:<base64-ed25519-sig>:
Content-Digest: sha-256=:<base64-sha256-of-body>:
OCSS-Spec-Version: 2026-06
```

The signing `keyid` must be a `did#kid` published on the [Trust List](/ocss/trust-framework).
Get the triplet wrong — missing, malformed, duplicated, or an unknown key — and
the census returns `401 signature_invalid` **before** any standing check:

```bash theme={null}
curl -sS -X POST \
  https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement-confirmations \
  -H "Content-Type: application/json" -d '{}'
```

```json theme={null}
{
  "error": "Unauthorized",
  "message": "exactly one Signature-Input and one Signature header are required",
  "code": 401,
  "class": "signature_invalid"
}
```

The SDK's `signRequest` helper builds all four headers for you — see
[Idempotency](/idempotency) for a full signed-write example and
[Errors](/errors#401-missing-signature) for the failure modes.

## Rate-limit headers

Every `/api/v1/*` response tells you exactly where you stand in your window, so
you never have to guess. Read the headers on the response you already have:

```bash theme={null}
curl -sS -D - -o /dev/null \
  https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms \
  | grep -i x-ratelimit
```

```http theme={null}
x-ratelimit-limit: 100
x-ratelimit-remaining: 87
x-ratelimit-reset: 1783344660
```

`X-RateLimit-Reset` is **Unix epoch seconds**, not a duration. When you exhaust
the window you get `429 Too Many Requests` with a `Retry-After` (seconds).
Branch on the **status code** and honour `Retry-After` — never parse a
human-readable error string. The full backoff loop, in four languages, is on
[Rate limits](/rate-limits).

<Note>
  Census safety paths — `/.well-known/ocss/*`, the Trust List, enforcement
  writes, consent ingest — are **never** rate-limited (fail-open is a P0
  invariant), so they carry no `X-RateLimit-*` headers. A `/.well-known/ocss/trust-list`
  read returns a cacheable `ETag` instead:

  ```http theme={null}
  cache-control: public, max-age=300
  etag: "32e5fb0d59af537f8b6bad4a72578a756c5312b089f13114fd70272c28af56a6"
  ```
</Note>

## Deprecation signals

Before anything in `v1` changes, it carries machine-readable notice on the wire
so a gateway can detect it without reading these docs. A surface in a sunset
window responds with:

```http theme={null}
deprecation: @1788220800
sunset: Tue, 01 Dec 2026 00:00:00 GMT
link: <https://phosra.com/docs/migrations/…>; rel="deprecation"
```

* `Deprecation` — [RFC 9745](https://www.rfc-editor.org/rfc/rfc9745.html), an `@`-prefixed Unix timestamp of when the deprecation took effect.
* `Sunset` — [RFC 8594](https://www.rfc-editor.org/rfc/rfc8594.html), an HTTP-date of the committed removal.
* `Link` — a pointer to the human migration guide.

Alert on the presence of a `Sunset` header in staging and you will never be
surprised by a removal. Windows and the full lifecycle are on the
[Deprecation & Sunset policy](/deprecation).

<h2 id="idempotency-replay">
  Idempotency replay
</h2>

A faithful retry — same body-borne key, same payload — returns the original
bytes with `200 OK` **and** the header `OCSS-Replay: original`. Its absence
means a fresh apply. Read the status and this header; never diff bodies. A
conflicting replay (same key, changed payload) is rejected with `409 replay` and
carries no `OCSS-Replay`. Full semantics on [Idempotency](/idempotency).

## The correlation id

Every response carries a correlation id you can quote in a support ticket so we
can pull the exact request from our logs. Grab it before you contact anyone:

```bash theme={null}
curl -sS -D - -o /dev/null \
  https://phosra-api-sandbox-production.up.railway.app/health \
  | grep -i x-railway-request-id
```

```http theme={null}
x-railway-request-id: 2HO1Woj4RFGk9Z7VjVra_w
```

<Note>
  **Straight talk about this header's name.** The correlation id is currently
  surfaced as `X-Railway-Request-Id`, assigned by the sandbox's hosting edge
  (the census runs on Railway). It is a real, stable-per-request id our support
  team can look up — it is **not** a Phosra-issued API field, and the name may
  change to a vendor-neutral alias in a future release. Quote whatever
  `x-railway-request-id` value the failing response actually returned; don't
  synthesize one, and don't send an inbound `X-Request-Id` expecting it to be
  echoed (it isn't).
</Note>

Include this id, the endpoint, and the timestamp when you email
[developers@phosra.com](mailto:developers@phosra.com) — see [Support](/support)
and [Troubleshooting](/troubleshooting) for the full checklist.

## See every header at once

`curl -D -` dumps the full response head. This is the fastest way to inspect
what Phosra actually set on any call:

```bash theme={null}
curl -sS -D - -o /dev/null \
  https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms
```

```http theme={null}
HTTP/2 200
content-type: application/json
x-ratelimit-limit: 100
x-ratelimit-remaining: 87
x-ratelimit-reset: 1783344660
x-railway-request-id: 2HO1Woj4RFGk9Z7VjVra_w
```

## Related

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/authentication">
    Key formats, the `Authorization` vs `X-Api-Key` fallback, scoping, rotation.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/rate-limits">
    The `X-RateLimit-*` trio, `Retry-After`, and a copy-paste backoff loop.
  </Card>

  <Card title="Idempotency" icon="rotate" href="/idempotency">
    Why there is no `Idempotency-Key` header, and what `OCSS-Replay` means.
  </Card>

  <Card title="Deprecation & Sunset" icon="calendar-xmark" href="/deprecation">
    The `Deprecation` / `Sunset` / `Link` signals and the notice windows.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/errors">
    Every status, class, and the `401 signature_invalid` header failures.
  </Card>

  <Card title="Support" icon="life-ring" href="/support">
    What to send with a ticket — starting with the correlation id above.
  </Card>
</CardGroup>
