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

# The branding and theming console model

> How white-label branding works in Orbit — the three read planes (dashboard identity, widget and public surfaces, domain-affirmed page identity), the field catalog, org vs subaccount precedence and inheritance, the custom-domain CNAME hookup, safe defaults, and what branding deliberately does not cover.

# The branding and theming console model

Branding in Orbit is the set of fields an organization sets to white-label
the surfaces its users and customers see — logo, colours, dashboard name,
favicon, and the custom domain everything is served from. This page is the
model: which read planes consume a branding value, what each field controls,
how org-level and subaccount-level branding reconcile, and what renders when
nothing is set. Configure it in the dashboard under **Settings > Branding**
or through the branding API.

<Note>
  Branding is presentation only. Regulatory brand registration (10DLC,
  toll-free, RCS verified sender, branded calling) is a different system —
  see [Brand identity and trust score](/concepts/brand-identity-trust-score).
</Note>

## Section 1 — The three planes

A branding value has no meaning until one of three read planes consumes it.
Each plane is a distinct read surface, and most "the wrong logo shows" bugs
are a plane-level mixup, not a bad save.

| Plane                             | What it resolves                                                                                                                                                             | Who reads it                                                                                                          |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Operator dashboard identity**   | Logo, logomark, favicon, dashboard name, and primary/accent colours for the full dashboard shell — sidebar, sign-in page, browser tab.                                       | Your org's signed-in users, and anyone on the sign-in page of a white-label domain.                                   |
| **Widget and in-app surfaces**    | Widget theming values shared by embeddable surfaces, plus the public help-center theme (`help_logo_url`, `help_primary_color`, `help_secondary_color`, `help_header_links`). | End customers inside embedded widgets and on the public help center.                                                  |
| **Domain-affirmed page identity** | The custom domain the branded experience is served from. Hosts resolved through a registered, verified domain attach that org's branding to even pre-auth pages.             | Everyone, including signed-out visitors to your dashboard hostname, white-label short links, and hosted public pages. |

The dashboard plane resolves from the org's branding on every authenticated
render and on the pre-auth sign-in page. The widget plane carries the same
values into embedded surfaces and the public help center's deliberately
restricted shape — plain-text labels and https links only, hex colours only,
no raw HTML/CSS. The domain plane is a registration, not a colour; its
Section 4 hookup is below.

## Section 2 — The field catalog

Branding fields live in one JSONB payload on the organization record
(`organizations.branding`). Every field is nullable; explicit `null` clears
it. The catalog is taken from `PATCH` validation as shipped:

| Field                  | Values it accepts                              | Plane it drives                                  |
| ---------------------- | ---------------------------------------------- | ------------------------------------------------ |
| `logo_url`             | URL (SSRF-validated server-side)               | Dashboard sign-in and sidebar identity           |
| `logomark_url`         | URL                                            | Compact dashboard sidebar mark                   |
| `primary_color`        | 6-digit hex `#A1B2C3`                          | Dashboard accents across shell surfaces          |
| `accent_color`         | 6-digit hex                                    | Secondary dashboard accent                       |
| `dashboard_name`       | String, max 50 chars                           | Title/name in the dashboard shell and tab        |
| `favicon_url`          | URL                                            | Browser tab icon                                 |
| `support_email`        | Email (≤320 chars)                             | Contact surface on branded pages                 |
| `custom_domain`        | Bare hostname regex `[a-z0-9.-]+`, 3–253 chars | Domain-affirmed hookup (Section 4)               |
| `terms_url`            | URL                                            | Legal link rendered on the sign-in page          |
| `privacy_url`          | URL                                            | Legal link rendered on the sign-in page          |
| `help_logo_url`        | HTTPS URL only                                 | Public help-center header                        |
| `help_primary_color`   | Hex                                            | Public help-center theme                         |
| `help_secondary_color` | Hex                                            | Public help-center theme                         |
| `help_header_links`    | Plain-text label + https URL, up to 8 links    | Public help-center header links                  |
| `help_show_powered_by` | Boolean                                        | Toggles the "powered by" line on the help center |

The public help-center subset is deliberately narrower than the dashboard:
no raw HTML or CSS, so a help theme cannot inject markup into an
unauthenticated public surface. The API enforces it at the validation
boundary.

## Section 3 — Precedence: org vs subaccount inheritance

Branding composes per organization: each org row carries its own payload,
and a subaccount may inherit from its parent.

* **Org-level self (no inheritance).** `GET /branding` returns the org's own
  stored values — the values it saved, not the effective resolution. Use it
  to show "what did we save" on an edit form.
* **Inheritance toggle.** `PATCH /branding` accepts
  `inherit_from_parent: boolean`. When true, the effective read walks up the
  parent chain (recursive CTE, bounded to 8 hops, cycle-safe) and folds
  ancestor values in nearest-first order.
* **Per-field precedence.** An explicitly set child field always wins; a
  null child field falls through to the nearest ancestor with a non-null
  value. Inheritance is per field, not all-or-nothing.
* **Provenance reporting.** The effective payload reports
  `inherits_from_parent`, `inherited_from_organization_id`, and an
  `inherited_fields` map naming which ancestor provided which field — so a
  parent mixing its own defaults with a couple of child overrides can see
  exactly which channels inherited.

**The org shortcut.** `PUT /organization/branding` is the own-org path
so the frontend never special-cases its own org id. It is the *same*
resolver as `PUT /subaccounts/:id/branding` when `:id` is the caller's own
organization — validation, cache invalidation, and audit logging stay
identical, only the path differs. When a parent wants to set brand defaults
its whole family of subaccounts inherits from, it owns those defaults under
its own org payload, and each child toggles `inherit_from_parent: true`.

The reseller shortcut is not a shortcut in the data model: both paths write
the same `organizations.branding` JSONB and resolve through the same
nearest-first fold. `PUT /organization/branding` just avoids the caller
having to embed its own id.

## Section 4 — Custom domain CNAME hookup

`custom_domain` is the hookup between the branding payload and the
white-label domain serving it. Until the domain is registered, verified,
and active, the value is a plain string on the payload; once the domain
flips to `active`, any request against your hostname resolves to your org
and serves the branded sign-in page, sidebar, and public links.

The full DNS/SSL lifecycle — one CNAME, managed certificate, the
pending/verifying/active/failed status machine — is covered in
[Custom domains and managed SSL](/concepts/custom-domains-and-ssl). The only
piece of the domain hookup that lives in branding is the `custom_domain`
field itself; everything else is the domains service.

## Section 5 — Safe defaults

When nothing is set, the platform falls back to neutral Devotel chrome:

* The effective branding resolver degrades to an empty payload (all-null
  fields) rather than throwing, so a dashboard render never errors because
  branding is unset.
* The dashboard shell renders the platform lockup and default blue tokens;
  white-label colour and logo overrides apply only when explicitly saved
  or inherited.
* On the pre-auth sign-in page of a custom domain, the default favicon and
  branding stay authoritative until the org saves overrides — no wrongly
  branded page ever renders because a lookup failed.

There is also a `POST /branding/reset` for org-admins to clear back to
defaults (audit-logged as `organization.branding.reset`). That is the
"reset to Devotel chrome" escape hatch, distinct from turning inheritance
off.

## Section 6 — Worked example: re-skin a subaccount end to end

Suppose a reseller wants its `Acme Retail` subaccount to have its own logo
and colours across the whole tree:

1. **The parent org saves the defaults.** `PUT /organization/branding` on
   the parent's own session with the family-wide values (logo, colours,
   help-center theme). Every child toggles `inherit_from_parent: true`.
2. **The subaccount overrides selectively.** A customer with the
   subaccount's own admin role calls `PUT /subaccounts/:id/branding` with
   the few fields it wants to pin. The fold picks the child values over
   the parent's, and null fields still inherit.
3. **Register the domain.** `POST /organization/domains` (or the
   subaccount-scoped equivalent) with the bare hostname, publish the
   `cname_target`, then `POST /organization/domains/:id/verify`. Once
   `active`, signed-in and sign-out traffic resolves to the branded
   experience.
4. **Confirm precedence in a read.** `GET /branding/effective` returns the
   folded values plus `inherited_fields` and `inherits_from_parent`, so
   the subaccount's portal shows exactly which fields it pinned and which
   it still pulls from the parent.

The same loop covers the public help center if help fields were set — a
customer-only surface with the restricted shape above.

## Section 7 — What branding is NOT

Three things are deliberately outside the branding schema:

* **Brand identity and trust score.** Regulatory verification — 10DLC,
  toll-free, WhatsApp, RCS, branded calling, per-number KYC — is a
  cross-channel rollup read-only hub. Branding declares *how your surfaces
  render*; trust score reports *how verified your brand is with the
  carriers*. They resolve from different data (registration registries,
  not your organization's JSONB) and are not interchangeable.
  See [Brand identity and trust score](/concepts/brand-identity-trust-score).
* **Sender identity.** Sender IDs, from-email defaults, WABA display names,
  and RCS agent names are channel-registration concepts configured
  per-channel. Branding does not cover any of them; the senders surface in
  the audience module does. Where a legal page or public link is rendered,
  the channel-level sender still resolves through its own gate.
* **The widget that consumes the widget plane.** This page models the
  fields and precedence; the native-chat-widget configuration surface is a
  separate guide. Values that drive an embedded widget still resolve
  through the same org branding payload.

## Section 8 — See also

* [Brand identity and trust score](/concepts/brand-identity-trust-score) —
  the regulatory verification rollup, not the theming system
* [Custom domains and managed SSL](/concepts/custom-domains-and-ssl) — the
  DNS/SSL half of the white-label domain hookup
* [The subaccount organization model](/concepts/subaccount-organization-model) —
  the two-level hierarchy branding inheritance climbs
* [White-label subaccounts guide](/guides/subaccounts-reseller) — the
  operational counterpart to this concept
