XKOVA Docs

Branding

Branding controls how your tenant appears to its members. Every hosted surface a member sees, the recipient claim page, the signing page, and the transactional emails you send, renders 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 branding theme. It carries the values that render across every member-facing surface: 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 XKOVA brand, no-reply@xkova.com, no logo). Every resolved field is present.
  • Theme. The raw tenant-wide row exactly as you set it. A null field 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. Partner scope is an internal platform scope above tenants that integrators do not use, so you always read and write your own 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:

FieldShapeRenders as
brand_name, legal_nameText, up to 120 and 160 charsThe display name and legal entity name across pages and email.
from_name, from_email, reply_to_email, support_emailName text plus valid email addressesThe sender identity and support contact on transactional email.
primary_color, secondary_color, accent_colorStrict #RRGGBB hexThe light-mode page palette.
primary_color_dark, secondary_color_dark, accent_color_darkStrict #RRGGBB hexThe dark-mode palette pages use under prefers-color-scheme: dark; null inherits the light value.
email_button_color, email_bg_color, email_text_colorStrict #RRGGBB hexEmail-specific colors.
app_title, footer_textTextThe page title and footer copy.
terms_url, privacy_urlhttps URLsLinks surfaced on member pages.
subject_override, intro_overrideTextOverride the email subject line and intro copy.
display_term_presetOne of member, customer, user, clientThe noun used for the end user across member-facing copy.
copy_overridesObject with singular, plural, possessiveOverride the end-user noun forms directly instead of picking a preset.
Validation is strict. Colors must be a full six-digit #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.

  1. Request a target. POST /v1/branding/logo/upload-url with the content_type (one of image/png, image/jpeg, image/webp, image/gif) and the content_length in bytes (up to 5 MB). It returns an upload_url, an object_key, and an expires_at.
  2. Upload the bytes. PUT the image directly to the upload_url before it expires.
  3. Confirm. POST /v1/branding/logo with the object_key. The API re-fetches the uploaded object and sniffs its magic bytes; the client-declared content type is not trusted, so a non-image is rejected with validation_failed. On success the logo is persisted on the theme and the updated branding is returned.

DELETE /v1/branding/logo clears the logo so surfaces fall back to the default. All three 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-domains lists your registered domains, each with its DNS records and live status. The envelope also carries provider_configured, which is false when the platform's domain-capable email provider is not set up; managing domains then returns email_domain_provider_unconfigured.
  • POST /v1/branding/email-domains registers a domain (a fully-qualified name such as mail.example.com) and returns the record set to publish. Re-registering an existing domain returns email_domain_conflict; a domain the provider rejects returns email_domain_invalid.
  • POST /v1/branding/email-domains/{id}/verify asks 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:

StatusMeaning
not_startedRegistered; the DNS records have not been published or checked yet.
pendingVerification is in progress while the records propagate.
verifiedThe records are live; the domain can send. verified_at is set.
failedVerification did not succeed; re-check the published records.
temporary_failureA 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 Pre-Auth Branding Read

Hosted member pages need to paint your brand before the member 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.

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.

OperationPermission
Upsert theme, logo upload / confirm / deletebranding.manage
Add, verify, remove a sending domainbranding.email_domain.manage

The error codes each surface most commonly returns:

SurfaceCodes
Theme upsert, logovalidation_failed, role_denied
Sending domainsemail_domain_invalid, email_domain_conflict, email_domain_not_found, email_domain_provider_unconfigured, email_domain_provider_error

See Authorization for how permissions resolve from roles and Errors and Correlation IDs for the full catalog.

Related

For the exact request and response shapes, see the operations list or try the calls live in the interactive reference.