Skip to main content

Two credential classes

This platform issues two kinds of key, and they are not interchangeable. Which one an operation accepts is decided by what the operation does, not by where you call it from.
The prefix is not the convention you may expect. Here, pk_ marks the secret key. Some payment tools use pk_ for their browser-safe key and sk_ for their secret — this platform does not, and it issues no sk_ key at all. If a value beginning pk_ reaches browser code, a public repository, or a prompt pasted into a code-generation tool, treat it as disclosed and rotate it.

The API key

Presented as a bearer token:
  • Keys are client-scoped: one key resolves to exactly one client (tenant). Tenancy is never read from request bodies.
  • Keys on this site are test-scoped — they can only reach the test-mode environment and can never touch live rails.
  • Treat keys as secrets: server-side only, never in browser code or public repositories. Issue and rotation are handled by your onboarding contact — there is no self-serve key generation. See your credentials.
  • Revocation takes up to 30 seconds. Credential resolutions are cached for that long, and the window is the upper bound on how long a revoked or rotated key can still be accepted. A key you have just replaced is not dead the instant you replace it.

The publishable brand key

A read-only credential designed to live in a public page bundle. It is minted for one brand, and no header can widen that. It reaches exactly two operations: Two different refusals guard it, and they are not interchangeable:
  • Outside its capability — any other operation, and any write: 403, problem type https://purple.md/problems/publishable-credential-read-only. The credential is real; what you asked it to do is not something it may do.
  • Outside its brand — a brand that is not the one the key was minted for: 404, never 403, byte-identical to the answer for a brand that does not exist. A foreign brand, a brand belonging to another client and an unknown brand are one answer, so nothing about another tenant’s brands can be learned by asking.
Send it exactly as you send the API key, with the brand header alongside:
X-Brand-Id is required — omit it or malform it and the call is 400 https://purple.md/problems/malformed-brand before anything is dispatched. With a publishable key it must also equal the brand the key was minted for; any other value is the 404 above. Those two headers are also exactly what the cross-origin policy permits, so nothing else you add will survive a browser preflight. Three conditions before a browser read works at all:
  1. Your brand has a publishable key issued.
  2. You send X-Brand-Id and it matches that key’s brand.
  3. The exact origin your page is served from is registered on your brand’s browser-origin allowlist. Without that, the browser refuses the response regardless of the credential. See replace the browser origins and login URLs and domains.
Registering the origins is itself an API-key operation — it is account configuration, not a browser call. Replacing a publishable key revokes the brand’s previous ones, and — like the API key — that lands within one 30-second cache window, not instantly. A key you have pulled out of a page bundle can still be accepted until the window closes, so rotating it starts the clock rather than stopping it.

What needs no key

Three operations are anonymous by design, because they are browser redirects that begin and end a patient’s sign-in:
  • GET /v1/auth/login
  • GET /v1/auth/callback
  • GET /v1/auth/logout
Send the patient’s browser through them. They answer with a redirect rather than a 401, and there is nothing for your server to present. Every other operation on this reference is reached with a key.

Brand scoping

Most endpoints also require the brand context header:
The header is checked in two steps, and they answer differently:
  • Missing, or not a well-formed brd_ id400, type https://purple.md/problems/malformed-brand. This is checked before your request is dispatched, and it is a separate wall from the 401 above.
  • Well-formed, but not one of your brands404, never a 403. Existence is never revealed across accounts, so a brand belonging to another client and a brand that does not exist give the same answer.
Some operations additionally show X-Client-Id and X-Client-Brand-Ids in their reference entry. Those are resolved from your key and attached for you; you do not send them.

Errors

Authentication failures return 401 with an RFC 7807 application/problem+json body. The body is deliberately opaque — it never reveals whether a key exists, whether it was revoked, or whether the path you called is real.

Playground authentication

The interactive playground reads its auth schemes from the API specification. Enter your test key in the playground’s auth field once; it rides every try-it request from your browser to the test environment. The two brand reads above declare both schemes, so you can drive them with either credential. What the playground cannot reproduce is the browser-origin allowlist — it is not your page’s origin — so a publishable key that works there still needs your origin registered before it works from your own site.