consent_holder). The rest of the configuration (the
questionnaire theme overlay, the redirect allowlist, and your therapy and commercial references)
stays platform-managed; you confirm it through the same readback before you go live.
Your display identity
The display-identity block is what the platform-rendered surfaces — the patient portal, the hosted checkout door, and the sign-in screen — read to wear your brand instead of the default. It carries four things and nothing else:
The brand you are writing is resolved from your key context, never from the request body. You
present the brand in the
X-Brand-Id header, and the brand_id in the path must match it. A brand
your key does not own — foreign, unknown, or mismatched — answers 404, never 403: there is no
way to probe whether another tenant’s brand exists.
Set it with your key
Send the display block toPUT /v1/account/brands/{brand_id}/config. The body carries any of
display, legal_documents (see your terms and privacy links) and
consent_holder (see who holds your consent documents). Those
are the only fields read here.
- The version is server-minted. You never send
version; the platform mints a new monotone version on each write and returns it — so every write, including a repeat, advances the version. The stored write is durable before it takes effect, so a restart re-serves exactly what you last wrote. - The block is merged, not replaced wholesale. Your display block is merged onto the existing configuration, so a display write never disturbs the platform-managed redirect allowlist or theme.
- A stale write is refused, not merged. If the saved configuration changed since you loaded it,
the write is refused with
409version-mismatchand nothing is written: read it again and retry. A503means the outcome is unknown: read the configuration back before retrying.
Your terms and privacy links
Patients are shown your terms of service and your privacy policy before they consent, and, when you hold your consent documents, your telehealth informed consent. Set the links with the same call, in alegal_documents block:
- Which links you can send depends on who holds your consent documents. When you hold them
(
consent_holderclient),legal_documentstakesterms,privacyandtelehealth(your telehealth informed consent), so your brand can become ready for patients on its own. Otherwise it takestermsandprivacyonly, and the telehealth informed consent link stays platform-managed. If the same call sendsconsent_holder, the value it sends decides. Any other key is refused with422and the issuebrand.legal_documents.slot, and nothing is stored. - Absolute
httpsonly. Each link must be an absolutehttpsURL to the document as your patients should read it. A relative path, anhttp:URL, an empty string or a non-string value is refused with422and the issuebrand.legal_documents.url, and nothing is stored. - One link at a time is fine. A link you send replaces that link. A link you leave out keeps the one already stored, so you can update your privacy policy without resending your terms.
- Send it with your display block or on its own. A body with only
legal_documentsleaves your display identity untouched, and the reverse is true too.
Who holds your consent documents
Each brand records who holds its patients’ consent documents and identity: the platform, or you. There is no default. Until it is chosen, your brand is not ready for patients. Set it with the same call, inconsent_holder:
client: you hold them. Patients see your own registered links, so every document your consents link to must be registered by you.- The platform’s value (the other value of the
consent_holderenum in the API reference): the platform holds them. The platform’s own documents apply by default, and your own legal links are not needed or used. Until the platform’s legal pages are published, patients see a “not available” notice in place of those links, and your readiness report readsreadiness_unknown. - Any other value is refused with
422and the issuebrand.consent_holder.
Read it back
GET /v1/account/brands/{brand_id}/config returns the configuration as the platform holds it. It is key-scoped the same way: the path brand_id must match
your X-Brand-Id, and any other brand answers 404. A brand with no configuration set yet also
answers 404.
The readback carries your theme, your enabled therapies and your redirect allowlist. It does not
list your offerings: read those with
GET /v1/account/brands/{brand_id}/offerings, which serves each
offering’s name, sku_id, therapy, entry mode, checkout link, price and readiness.
Is your brand ready for patients?
Every intake resolve (GET /v1/instrument/resolve) carries a patient_readiness report for your
brand. It is a report, not a wall: the questionnaire is served either way. Read it while you set up,
and before your first patient arrives.
GET /v1/account/brands/{brand_id}/config,
beside data, whenever the platform’s readiness check is available.
statusispatient_readyornot_patient_ready.missinglists every required item still unset, and is empty exactly whenstatusispatient_ready.consent_holderis who holds your consent documents, as chosen, andnot_chosenuntil someone chooses.required_documentslists the documents your brand’s served consents link to, and only those: a document no served consent links to is never required. It is worked out from the questionnaires actually served for your brand, and it is empty when that cannot be told yet. The documents the platform can name today are listed in the API reference (for exampleterms,privacyandtelehealth); the list can grow.missing[].itemis one of:display_name: your display name is not set;consent_holder: nobody has chosen who holds your consent documents;legal_document: a required document is not set from the chosen holder’s source.documentnames which one, for exampleprivacy. When you hold them, set your own link. When the platform holds them, the platform’s link must be set up; ask your onboarding contact;readiness_unknown: the platform cannot tell yet. That happens when the documents your served questionnaires need can’t be worked out yet, when the platform holds your consent documents and its legal pages aren’t in hand yet, or when a page served for your brand links to a legal document the platform can’t fill in there (patients see a “not available” notice in its place). Every page served for your brand is checked, not only consents.
missing[].detailsays in plain words what is missing and how to set it.- When the platform can’t tell, the answer is
not_patient_readywithreadiness_unknown, neverpatient_ready. Try again shortly, or ask your onboarding contact.
Where it renders
You do not call anything to make the display identity show up. The platform-rendered surfaces resolve it per request from your brand context and wear it automatically: the patient portal, the hosted checkout door, and the sign-in screen. A brand that has no display block does not error — those surfaces fall back to the platform’s default look, and the fallback is always explicit: the default is never silently presented as your brand’s own identity. How each part renders on the patient portal and the hosted checkout:- When changes show. The surfaces read your display identity on every page load, with no cache. A write is visible on the next page load.
- Light and dark mode. The surfaces follow the patient’s device setting: a device set to dark
shows the dark mode, otherwise the light mode.
colors.lightandcolors.darktheme each mode. - Logos.
logo.lightshows in light mode andlogo.darkin dark mode. With nologo.dark, the light logo shows in both modes, unchanged, so supply a dark asset if your light logo is dark on transparent. The logo is drawn by height, 24 or 40 CSS pixels depending on the header, and its width follows the image’s own proportions. Use an image a browser displays directly, such as SVG or PNG. - Root-relative logo paths. A path such as
/brand/logo.svgloads from the address of the page showing it, which is your member portal address. Host the file there, or use an absolutehttpsURL. - Radius.
radiusis a base value, not a single literal used everywhere. Larger and smaller corners are derived from it: small is the base minus 4px, medium the base minus 2px, large the base itself, and extra-large the base plus 4px.
The token reference
tokens is a partial theme set. You supply any subset of the slots below; anything you omit
falls back to the platform default when the theme is assembled. Every leaf value is a string. An
unknown group, mode, or slot is refused (see the wording wall).
Your brand surface is colors, corner radius and your logo (set in logo, beside tokens).
Typography is not supported: platform pages load no custom fonts, so a font family named in
typography is not loaded. Leave typography out.
Within
colors.light and colors.dark, the slot names are the semantic color slots of the
platform design-system brand-token contract. They are the exact vocabulary the theme stylesheet is
generated from, so they can never drift from what the surfaces actually render. Grouped by the
surface each one themes:
Partial override. You may set as few as one slot in one mode. The write is merged onto your
existing configuration and, at render time, onto the platform default — so a single-slot write
re-themes only that slot and leaves the rest of your look intact.
Unknown slots are refused. A slot name outside the vocabulary above (a typo, or a slot the
platform does not define) fails the whole write with
422 and a slot-level issue — nothing is
stored. A color value must be a non-empty string; an empty or non-string value is refused the same
way.
The wording wall
The display block is theming only — name, logo, and tokens. It is not a copy channel and can never carry patient-facing wording. Acopy key inside display is refused outright with 422:
display_name,
logo, tokens, and support_email is refused the same way with brand.display.unknown_key.
The client write reads only display, legal_documents and consent_holder. Anything else you place in the body — a theme,
a redirect allowlist, a brand id, a charge setting — has no effect here: those stay
platform-managed and cannot be set through your key.
The operator override
The same display block can also be written from the platform’s operator surface, as an override or as a starting look seeded when your brand is first created. That is a platform-side path, not yours; it writes the one shared configuration your readback returns, and it runs through the identical validator and the same wording wall. From your side there is one display identity, whichever path last wrote it — and the readback always shows you exactly what is stored.The rest of your configuration
Everything else in your brand configuration is platform-managed — set through your onboarding contact and confirmed through the same readback. Each member has a fixed meaning:theme— the presentation overlay for the intake questionnaire: theme tokens the renderer applies to each already-sequenced question. It is a separate token system from your display identity. The displaytokensabove dress the platform-rendered surfaces (portal, checkout, sign-in);themedresses the questionnaire your own renderer draws. The two are disjoint — the same brand may set both, and neither overrides the other. See how intake serving works.copy— brand copy slots for your non-clinical surfaces, keyed by slot. A copy slot changes wording on those surfaces only; it can never supply or override a question’s headline, which comes from the reviewed questionnaire. (This is separate from the display block, which carries no copy at all.)enabled_therapies— references to the therapies your brand offers. References, never definitions: the therapy, its questionnaire, and its clinical rules live on the platform.commercial— identifiers tying the brand to its storefront and pricing. References only.redirect_allowlist— the absolutehttpsURL prefixes a patient may be returned to after completion. Matching is exact origin plus path prefix; an entry that matches nothing is dropped, and an empty list refuses every redirect. Fail-closed by design — an open redirect is unrepresentable.
The clinical wall
The whole brand configuration — display identity included — is clinically inert by construction. Two checks run on every write, before anything is stored:- A closed allowlist. A configuration, and the display block within it, may carry only their permitted keys. Any other key is rejected.
- A clinical-shape scan. The entire configuration is scanned at every depth for any key that could carry clinical meaning — facts, options, controls, rules, sequencing, visibility, and their relatives. One such key anywhere fails the whole write.