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

# Branding console: logo, colors, and custom domain

> Walk through the Settings → Branding page: logo and favicon upload, primary and accent colors, dashboard name, support email, the org-level and per-subaccount write paths, and the custom-domain CNAME flow.

# Settings → Branding

The **Branding** page under **Settings** is the self-serve white-label console for your dashboard: upload a logo and favicon, pick primary and accent colors, set a dashboard name and support email, and point a custom domain at your Orbit dashboard. Everything on the page is cosmetic — it changes how your dashboard, sign-in screen, and branded surfaces look. It touches no sending, routing, or compliance posture.

This guide walks each control on the page, the two API write paths behind it (org-level shortcut vs. per-subaccount override), the custom-domain CNAME flow, and how branding resolves for each host at render time.

## What the console controls

Open **Settings → Branding**. The controls you need are an owner or admin role; members see the page read-blocked. The top card — **Brand identity** — carries:

| Control              | What it changes                                                                       | Field                      |
| -------------------- | ------------------------------------------------------------------------------------- | -------------------------- |
| Logo                 | Sidebar header and sign-in screen                                                     | `logo_url`                 |
| Favicon              | Browser tab icon                                                                      | `favicon_url`              |
| Primary color        | Buttons, focus rings, active sidebar item                                             | `primary_color`            |
| Accent color         | Highlights, hover states, chart accents                                               | `accent_color`             |
| Dashboard name       | Browser tab title and sidebar header (falls back to the organization name when blank) | `dashboard_name`           |
| Support email        | Address shown to your customers in white-label transactional emails                   | `support_email`            |
| Terms / privacy URLs | Links shown on your public consent form and sign-in surfaces                          | `terms_url`, `privacy_url` |

Logo and favicon accept PNG, JPEG, or WebP up to 2 MB — SVG and ICO are rejected (SVG can carry script markup, so the API refuses it at upload). Colors are 6-digit hex (`#4F46E5`); the form shows a live preview of the pair on the dashboard shell before you save, and a per-field **Revert** resets any single edited field to the last saved value. **Reset to defaults** at the bottom of the card clears all of it with a confirmation step.

The page also hosts the [help center theme](/guides/help-center-portal) card (a separate, public-safe subset of fields) and the [branded calling](/concepts/brand-identity-trust-score) card, which governs how your brand renders on carriers' incoming-call screens — both are out of scope here.

## Two write paths

Every change on this page persists through one of two endpoint pairs, depending on whose branding you're setting:

* **Your own organization** — `PUT /api/v1/organization/branding`. A shortcut for a tenant managing its own look; the dashboard console uses this. There's no org id in the path — the caller's organization is the target.
* **A subaccount you operate** — `PUT /api/v1/subaccounts/{id}/branding`. The reseller path: a parent org overrides branding for a child org it manages. A subaccount's own admin can also call this with its own id.

Both accept the same body — any subset of `logo_url`, `favicon_url`, `primary_color`, `accent_color` (6-digit hex), `dashboard_name` (≤ 50 characters), `support_email`, `terms_url`, `privacy_url` — and both record the change in the audit log. Send `null` for a field to clear it; fields you omit keep their stored value. `POST /api/v1/organization/branding/reset` wipes the whole set back to platform defaults in one call, same as the console's reset button.

Which one you reach for:

* You run one Orbit organization and want it in your brand → the **org-level shortcut** (`/organization/branding`), or simply the dashboard page.
* You resell Orbit and brand each customer's console differently → the **per-subaccount path** (`/subaccounts/{id}/branding`), usually from the [subaccount onboarding flow](/guides/subaccounts-reseller). A subaccount that flips on `inherit_branding_from_parent` takes your parent branding wholesale instead — the [subaccount–organization model](/concepts/subaccount-organization-model) covers how inheritance resolves.

```bash theme={null}
# Own org
curl -X PUT "https://api.orbit.devotel.io/api/v1/organization/branding" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "primary_color": "#0EA5E9", "dashboard_name": "Acme Console" }'

# Subaccount (reseller)
curl -X PUT "https://api.orbit.devotel.io/api/v1/subaccounts/org_b27d8e4f1c/branding" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "primary_color": "#0EA5E9", "dashboard_name": "Acme Support" }'
```

## Custom domain

The second card on the page puts the dashboard on your own hostname — `app.acme.example` instead of `orbit.devotel.io`. The flow is register → publish a CNAME → verify → automatic TLS:

1. **Register the hostname.** Enter it in **Custom domain** (or call the org-level shortcut `POST /api/v1/organization/domains`; resellers use `POST /api/v1/subaccounts/{id}/domains`). The response — and every later read of the domain — carries the `cname_target` your DNS record must point at. Read it from the API or the console rather than remembered instructions.
2. **Publish the CNAME** at your DNS provider, pointing the host at the `cname_target`. Flattened or chained CNAMEs are fine as long as the chain ends at that target.
3. **Verify.** Hit **Verify** on the domain row (or `POST /api/v1/organization/domains/{domainId}/verify`). Once resolution matches, the domain flips to `verifying` and certificate issuance starts; around 15 minutes later it reaches `active` and serves HTTPS with nothing to upload or renew.

Removing a domain (`DELETE .../domains/{domainId}`) reverts your team to `orbit.devotel.io`. The full lifecycle — statuses `pending → verifying → active / failed`, how managed certificates are issued, and how to read provisioning state from the API — is covered in [Custom domains and managed SSL](/concepts/custom-domains-and-ssl).

## How effective branding resolves

Every render of an authenticated dashboard page resolves branding for the requesting host: the host maps to an organization, that organization's stored values fill their fields, and anything unset falls through to the platform default theme. For a subaccount with parent inheritance on, the parent's values fill the gaps instead. The resolver is the same one `GET /api/v1/branding/effective` exposes — see the [Branding API reference](/api-reference/branding) for the merged `inherits_from_parent` / `inherited_from_organization_id` diagnostics and the `Cache-Control: private, max-age=60` client-caching contract.

Unauthenticated surfaces are narrower. A white-labelled sign-in page on your custom domain can resolve the branding of the tenant that owns that host before any session exists (an IP-rate-limited lookup by host), and the public help center reads **only** the `help_*` field subset — it never receives your dashboard logo, support email, or custom domain. The [help center portal guide](/guides/help-center-portal) documents that subset; the consent form reads only the terms and privacy URLs. If resolution ever fails, the render falls back to the neutral default theme rather than an error.

## Where branding shows up

Only what ships: **logo, favicon, colors, dashboard/brand name, support email, and the legal URLs.** Concretely:

* **Dashboard chrome** — sidebar header, active-item color, buttons, focus rings, browser tab title and icon.
* **Sign-in and public surfaces** — the login screen, and the [public consent form](/guides/public-consent-form) which links your `terms_url` / `privacy_url`.
* **White-label transactional emails** — brand name, logo, and the `support_email` address where a template exposes them.
* **Help center** — exclusively the separate `help_*` subset, never the dashboard fields.

Branding is cosmetic; it makes no claims about deliverability, sender reputation, or regulatory posture.

## Reset and caching

Saving applies to the dashboard shell immediately — colors go live across the chrome without a reload, because the page applies the freshly saved tokens and refreshes the server-rendered layout. Reads elsewhere cache resolved branding for up to 60 seconds (the `max-age=60` contract above), so a change can take up to a minute to appear on surfaces that poll the resolver — a new sign-in page load, an embedded widget.

**Reset to defaults** (or `POST /api/v1/organization/branding/reset`) clears logo, favicon, colors, dashboard name, support email, and legal URLs behind a confirmation dialog. The dashboard falls back to platform defaults — or to your parent organization's branding if `inherit_branding_from_parent` is on. Nothing is recoverable after a reset; re-upload assets to restore them.

## Troubleshooting

* **Logo saved but the old one still shows** — the uploaded image sits behind a cached URL; hard-refresh the page, and expect up to the 60-second resolver cache elsewhere. If the logo never appears after upload, the file was rejected: only PNG, JPEG, and WebP up to 2 MB are accepted (SVG and ICO fail at upload by design).
* **Color rejected on save** — the server stores 6-digit hex only (`#4F46E5`). The form accepts what you paste while typing, but the API rejects 3-digit hex, `rgb()`, and `hsl()` values on save; normalize to 6-digit hex and re-save.
* **A color saved but nothing changed** — confirm with `GET /api/v1/branding/effective` that the value persisted, and remember the \~60-second cache. Also check your role: writes need owner/admin.
* **Domain stuck in `pending`** — the CNAME hasn't resolved yet. Check the record points at the exact `cname_target` returned for that domain, wait out the TTL, then hit **Verify** again. An inconclusive DNS answer (resolver timeout, no answer) leaves the status at `pending` with no error recorded. Email-sending domains are a separate pipeline — for those, see [email DNS drift and recovery](/troubleshooting/email-dns-drift).
* **Domain stuck in `verifying`** — DNS matched and the certificate is provisioning (roughly 15 minutes). If it sits there longer, the row's message tells you whether HTTPS activation is pending platform review; no action on your side. Never remove the CNAME mid-provisioning — that's the common cause of a stall.
* **Domain in `failed`** — the issuer reported a terminal failure; the `error` field on the domain row names the cause. Fix it (usually the DNS record) and verify again — the platform keeps retrying until you delete the domain.
* **401/403 calling the branding endpoints** — your role or API key lacks admin. The unauthenticated host-lookup path (`X-Tenant-Host`) is rate-limited by IP at a lower ceiling; don't poll it in a loop.
