> ## Documentation Index
> Fetch the complete documentation index at: https://docs.purplelabelmd.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Identifiers and error types

> Which identifier each call accepts, and the typed problem responses your integration meets.

An offering carries more than one identifier, and each call accepts a specific one. This page is the
reference for which id goes where, and for the typed error responses — RFC 7807 `application/problem+json` —
your integration meets along the way.

## Identifiers

Every offering has two machine identifiers. The catalog console shows both on an offering's detail
screen, each one copyable and labeled by the call that accepts it — so you never have to guess one or
compose it by hand.

| Identifier     | What it is                                                                                                                                                                        | Accepted by                                                                   |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `sku_id`       | The offering's stable machine id, shaped `sku_…`. It is derived from the offering code — never composed by hand.                                                                  | `POST /v1/prospects`, which opens an enrollment for a direct-buy start.       |
| `offering_ref` | The offering reference. In this version it is exactly the catalog offering code — the same human-readable code you see on the offering's detail screen, for example `SYN-TRZ-3M`. | `GET /v1/promotions/merchandising` and `POST /v1/payments/checkout-sessions`. |

The code you read on an offering screen (`SYN-TRZ-3M`) **is** the `offering_ref` — there is no
separate lookup, and no display-versus-machine split for it in this version. Pass that code wherever
an `offering_ref` is asked for. The `sku_id` is a different string (`sku_…`); it is the id the
prospect-capture call expects, not the merchandising or checkout call.

Of these calls, `POST /v1/payments/checkout-sessions` and `GET /v1/payments/{order_ref}/status`
appear on the API reference tab. The merchandising read and prospect capture are not in the published
reference: prospect capture (`POST /v1/prospects`) is made by the platform's own hosted checkout on
its server, so your storefront never calls it directly.

## The enrollment thread

A patient's enrollment carries a `journey_id` (shaped `jny_…`), minted the first time an intake
session resolves. On the hosted platform it travels in the `X-Journey-Id` request header — that
header is how the platform threads one patient's context across calls. Send it back on a later
`GET /v1/instrument/resolve` to pick an in-progress questionnaire up where the patient left off,
and it is the same id you see on webhook events and on the enrollment status read
(`GET /v1/journeys/{journey_id}/status`). The `X-Journey-Id` header is what the hosted platform
reads to resume; a `journey_id` query parameter is honored only by the standalone renderer used for
direct testing, so build against the header.

## Problem responses

Every refusal is an `application/problem+json` body: a stable `type`, a short `title`, and the HTTP
`status` repeated in the body. Switch on the `type` — it is the stable, machine-readable part.

### The API-key wall

The platform's key-protected reads — for example, your account's orders — sit behind your API key in
an `Authorization: Bearer` header, scoped to a brand you own with `X-Brand-Id`:

* A request with no key is refused `401` `https://purple.md/problems/unauthenticated`.
* A valid key paired with a brand that is not yours is refused `404`
  `https://purple.md/problems/brand-not-found` — never `403`, so a brand's existence never leaks
  across tenants.

### The typed responses you will meet

| `type`                                               | `status` | What it means                                                                                                                                                                                                                           |
| ---------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `commerce/merchandising/not-configured`              | `404`    | The brand-and-offering pair has no merchandising configured. Configure its pricing — the path and the offering are fine.                                                                                                                |
| `commerce/checkout/unknown-offering`                 | `404`    | The `offering_ref` does not resolve for this brand. Check the code, or the brand's catalog.                                                                                                                                             |
| `commerce/checkout/client-authored-charge-parameter` | `422`    | The checkout body carried a money field — a price, a fee, or a topology. The server re-resolves every money figure from your configured catalog; the only accepted body keys are `offering_ref`, `journey_id`, and an optional `promo`. |
| `commerce/checkout/card-data-unconstructible`        | `422`    | A card-shaped field was present. No call accepts a card number; card capture happens only on the hosted page.                                                                                                                           |
| `commerce/checkout/promo-validation-unavailable`     | `422`    | A `promo` token was sent but cannot be validated in this version, so it is refused rather than silently dropped.                                                                                                                        |

The checkout create can also return `commerce/checkout/offering-not-purchasable` (`422`) — the
offering resolved but has no usable price binding.

The checkout create requires an `Idempotency-Key` request header of at least eight characters, and
the only body keys it accepts are `offering_ref`, `journey_id`, and an optional `promo`.

One more typed response comes from the enrollment status read rather than checkout:

| `type`                                              | `status` | What it means                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| --------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `https://purple.md/problems/journey-status-pending` | `422`    | Returned by `GET /v1/journeys/{journey_id}/status` when the platform has no status to report for an enrollment that is yours. Branch on this `type`: it means "no status yet", not a missing enrollment and not a malformed call. The `detail` explains the situation at the time of the call; the `type` is the stable part. See [when a status is not yet reportable](/pages/integrate/the-patient-flow#when-a-status-is-not-yet-reportable). |

That read can also return `503`: the status source was unavailable, so the read did not succeed.
That tells you nothing about the enrollment itself — retry with backoff, and do not read a failed
call as a statement about the patient. Both sit alongside the `400`/`401`/`404` refusals above, not
in place of them.

### When a refusal is not namespaced

Not every refusal carries a namespaced `type`. Some the platform makes before it reaches the
checkout logic — a request whose body is not valid JSON, or one missing its `X-Brand-Id` — come
back with the RFC 7807 default `type` of `about:blank`, still carrying a `title`, the HTTP
`status`, and a `detail`. So switch on `type` when it is namespaced, and fall back to the HTTP
`status` when it is `about:blank`. The list above is the set of named types you will meet; treat it
as the ones worth branching on, not as an exhaustive catalog of every possible refusal.

### Reading a 404

Not every `404` means the same thing:

* A `404` whose `type` is **namespaced** (`commerce/…`) means your call reached the platform and the
  resource is mounted but **not configured** — fix your configuration or your pricing, not your URL.
* A `404` with the **generic** type `https://purple.md/problems/not-found` means the path itself is
  not a routable resource — check the URL and the `/v1` prefix.

One ordering detail on the key-authenticated calls: a request with no key is refused `401` before the
path is routed, so a wrong path sent without a key returns that `401` rather than the generic `404`.
