Branding
Branding controls your tenant application screens, recipient claim and signing pages, and application notification emails. These surfaces render from a single tenant theme: a brand name, a logo, a color palette, copy overrides, and a verified from-address. This page covers reading and editing that theme, the logo upload flow, verifying a custom sending domain, and the pre-auth public read that lets a hosted page paint your brand before the member has authenticated.
What Branding Controls
A tenant has one application branding theme. It carries the values used by its member-facing screens and notices: the brand and legal names, the from, reply-to, and support email addresses, a logo, a light and dark color palette, email-specific colors, page copy, and links to your terms and privacy pages. The console Settings and Branding page edits the same theme this API exposes.
Reads return two views of the theme so you can tell what you set apart from what actually renders:
- Resolved. The values that render after layering. Any field you leave unset falls back
to the platform default (the
XKOVAbrand,no-reply@xkova.com, no logo). Every resolved field is present. - Theme. The raw tenant-wide row exactly as you set it. A
nullfield here means you have not set it and it inherits from the platform default.
Resolution runs most-specific first: a surface-specific override, then your tenant-wide theme, then the
platform default. Editing sets your tenant-wide theme; anything you leave null keeps inheriting.
Customer API calls always read and write the authenticated tenant's theme.
Read and Edit the Theme
GET /v1/branding returns the resolved bundle plus the raw theme. Any tenant-scoped caller can
read it. PUT /v1/branding upserts the tenant-wide theme and requires the
branding.manage permission (an admin or owner). The write is a patch: it updates the fields you
send and leaves every other field untouched, so a partial body is the normal case. The logo is set through
the separate upload flow below, not through this call.
The editable theme fields:
| Field | Shape | Renders as |
|---|---|---|
brand_name, legal_name | Text, up to 120 and 160 chars | The display name and legal entity name across pages and email. |
from_name, from_email, reply_to_email,
support_email | Name text plus valid email addresses | The sender identity and support contact on transactional email. |
primary_color, secondary_color, accent_color | Strict
#RRGGBB hex | The light-mode page palette. |
primary_color_dark, secondary_color_dark,
accent_color_dark | Strict #RRGGBB hex | The dark-mode palette pages
use under prefers-color-scheme: dark; null inherits the light value. |
email_button_color, email_bg_color,
email_text_color | Strict #RRGGBB hex | Email-specific colors. |
app_title, footer_text | Text | The page title and footer copy. |
terms_url, privacy_url | https URLs | Links surfaced on member pages. |
subject_override, intro_override | Text | Override the email subject line and intro copy. |
display_term_preset | One of member, customer,
user, client | The noun used for the end user across member-facing copy. |
copy_overrides | Object with singular, plural,
possessive | Override the end-user noun forms directly instead of picking a preset. |
#RRGGBB hex; named,
three-digit, and eight-digit forms are rejected. Free-text fields reject newlines and angle brackets, and
terms_url and privacy_url must be https. A rejected field returns
validation_failed. The write is idempotent under an Idempotency-Key; see
Idempotency.Upload a Logo
A logo is set in three steps so the image bytes never pass through the API request body.
- Request a target.
POST /v1/branding/logo/upload-urlwith thecontent_type(one ofimage/png,image/jpeg,image/webp,image/gif) and thecontent_lengthin bytes (up to 5 MiB, 5,242,880 bytes). It returns anupload_url, anobject_key, and anexpires_at. - Upload the bytes.
PUTthe image directly to theupload_urlbefore it expires, using the requested content type. Uploading alone does not change your saved logo. - Confirm.
POST /v1/branding/logoas the same tenant, using the exactobject_keyreturned for this upload. Treat that key as opaque. The API checks the stored size and fully decodes the image, including every animation frame; the client-declared content type is not trusted. Invalid, incomplete, oversized or foreign uploads returnvalidation_failed. On success a separate saved image is associated with the theme and the updated branding is returned. Later writes through the original upload URL cannot change that saved image. A saved logo URL or key cannot be confirmed as a new upload.
Read logo_upload_limits from GET /v1/branding for current upload rules.
New logos allow at most 4096 × 4096 pixels and 32 million decoded pixels across all frames,
including animated PNG, GIF and WEBP. Count image width × image height × frame count,
including any separate poster image. For example, twenty 1000 × 250 frames use 5 million pixels.
Saved originals remain unchanged. Complex images that
exceed validation processing limits are rejected; export a simpler image or shorter animation.
For a crisp logo, start with a transparent PNG around 1000 × 250 pixels for a wordmark or 512 × 512 for an icon. These are design recommendations. Leave clear space and check readability on light and dark backgrounds. The Console lets you preview the original image before choosing Upload Logo. Failed validation or publication leaves your saved branding unchanged.
has_logo_override tells you whether your tenant has its own tenant-wide logo.
DELETE /v1/branding/logo clears only that override, including a legacy URL.
Existing partner/platform inheritance applies, so a resolved logo can remain; surface-specific
overrides are unchanged. Stored originals are retained and previously issued image URLs remain usable.
All three logo writes require branding.manage.
Verify a Sending Domain
A branded from_email only delivers reliably from a domain you have verified. Registering a
custom sending domain returns the DNS records to publish, and verification checks that they have propagated.
GET /v1/branding/email-domainslists your registered domains, each with its DNS records and live status. The envelope also carriesprovider_configured, which isfalsewhen the platform's domain-capable email provider is not set up; domain management remains unavailable until XKOVA completes that configuration.POST /v1/branding/email-domainsregisters a domain (a fully-qualified name such asmail.example.com) and returns the record set to publish. Re-registering an existing domain returnsemail_domain_conflict; a domain the provider rejects returnsemail_domain_invalid.POST /v1/branding/email-domains/{id}/verifyasks the provider to re-check the published records and returns the refreshed status.DELETE /v1/branding/email-domains/{id}removes the domain.
Each domain response carries an id, the domain, its status, the
region, the dns_records to publish (each with a record, name,
value, type, ttl, and priority), a verified_at,
a last_synced_at, and a created_at. The status moves through five states:
| Status | Meaning |
|---|---|
not_started | Registered; the DNS records have not been published or checked yet. |
pending | Verification is in progress while the records propagate. |
verified | The records are live; the domain can send. verified_at
is set. |
failed | Verification did not succeed; re-check the published records. |
temporary_failure | A transient provider error; retry verification. |
The mutations require the branding.email_domain.manage permission. A verify or delete against
an id that does not exist for your tenant returns email_domain_not_found.
Public Capability Branding Read
Purpose-bound hosted pages such as claims need to paint your brand before the person has authenticated. GET
/v1/public/branding serves that read with no API key: it takes a claim_token query
parameter, and the token itself is the capability, exactly like the recipient
claim endpoints. It resolves the token to a tenant and returns that tenant's public
brand bundle, a subset with the brand name, support email, logo, palette (including the dark-mode colors), app
title, footer text, and terms and privacy links. Internal sender-domain fields are omitted.
An invalid, expired, unknown, or missing token returns the platform-default bundle with a 200,
never a 404, so token validity cannot be fingerprinted by status. The endpoint is rate-limited by
client IP. Use it only to render a hosted page whose claim token the page already holds.
XKOVA Hosted Auth does not ask customer code to call this endpoint or place a tenant selector in the URL. The reusable Hosted screens and Account Center resolve the brand from the application-bound ceremony. Cognito sign-in codes use the pool's configured sender and template; application notices use XKOVA's tenant-aware mail path. The former signed authentication-email hook is retired. Basic Cognito-managed login branding is configured separately from the tenant's application theme.
Authorization and Errors
Reads (GET /v1/branding and GET /v1/branding/email-domains) are open to any
tenant-scoped caller. Every mutation requires a permission and is recorded in the
audit log as a configuration change.
| Operation | Permission |
|---|---|
| Upsert theme, logo upload / confirm / delete | branding.manage |
| Add, verify, remove a sending domain | branding.email_domain.manage |
The error codes each surface most commonly returns:
| Surface | Codes |
|---|---|
| Theme upsert, logo | validation_failed, role_denied |
| Sending domains | email_domain_invalid, email_domain_conflict,
email_domain_not_found |
Provider-configuration and provider-runtime diagnostics are private operational errors and are not part of the customer error catalog.
See Authorization for how permissions resolve from roles and Errors and Correlation IDs for the full catalog.
Related
- Settings and Branding: editing the same theme from the operator console.
- Escrow and Claims: the claim page the public branding read paints.
- Tenants and Workspaces: the tenant a theme belongs to.

