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

# Branding and appearance

> Put your name, logo, colors, copy, and default locale on the Numeral-hosted address page.

The Numeral-hosted address page can carry your business name, logo, colors, copy, and default locale. All of it lives on the checkout configuration, so it is set with the same two calls used for every other policy: update the draft, then publish. Session creation does not change. The `config_id` you already send selects the look.

<Info>
  Branding is pinned per published version. Sessions created after a publish pick up the new version. Sessions already in flight keep the version they were created with.
</Info>

## Where each field renders

| Field                                                            | Hosted page | Embedded collector                                                                               |
| ---------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------ |
| `branding.display_name`, `branding.logo`, `branding.custom_text` | Rendered    | Not rendered. Your page owns its own chrome.                                                     |
| `appearance`, `locale_default`                                   | Applied     | Applied as the base layer under any appearance or locale set on the `<numeral-checkout>` element |

Every key is optional. Without `branding` the hosted page renders as it does today.

## Configuration fields

Add these keys to the configuration payload alongside the existing fields.

```json theme={null}
{
  "schema_version": 1,
  "mode": "payment",
  "...": "existing fields unchanged",

  "locale_default": "en-US",

  "appearance": {
    "variables": {
      "colorPrimary": "#0b57d0",
      "colorBackground": "#ffffff",
      "colorText": "#111827",
      "fontFamily": "Inter, system-ui, sans-serif",
      "borderRadius": "8px"
    },
    "rules": {
      ".PrimaryButton": { "fontWeight": "600" },
      ".Input:focus": { "borderColor": "#0b57d0" }
    }
  },

  "branding": {
    "display_name": "Acme Outdoor",
    "logo": "brast_3f1c9d2e-5b0a-4c6e-9d21-7a8f0b1c2d3e",
    "custom_text": {
      "intro": { "message": "We need your address to calculate sales tax before payment." },
      "return_link": { "message": "Back to Acme" }
    }
  }
}
```

| Field                                      | Bounds                                                                                                                                                                                                                                                                | Rendered where                                                                 |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `locale_default`                           | BCP 47 tag, 2 to 10 characters, must canonicalize                                                                                                                                                                                                                     | `<html lang>`, number and currency formatting                                  |
| `appearance.variables`                     | `colorPrimary`, `colorBackground`, `colorText`, `colorTextSecondary`, `colorDanger`, `colorBorder`, `fontFamily`, `fontSizeBase`, `lineHeight`, `borderRadius`, `spacingUnit`, `focusRing`                                                                            | Inputs, buttons, focus rings, and the hosted header, panel, intro, and summary |
| `appearance.rules`                         | Selectors `.Input`, `.Input:hover`, `.Input:focus`, `.Input--invalid`, `.Label`, `.HelpText`, `.Error`, `.Select`, `.Checkbox`, `.Radio`, `.FileInput`, `.PrimaryButton`, `.SecondaryButton`, `.TaxSummary`, `.Divider`. Max 64 selectors. Values max 256 characters. | The matching form controls                                                     |
| `appearance.theme`                         | String                                                                                                                                                                                                                                                                | Reserved. Accepted and inert.                                                  |
| `branding.display_name`                    | 1 to 40 characters, single line                                                                                                                                                                                                                                       | Hosted header wordmark, first letter as the brand mark, `<title>` prefix       |
| `branding.logo`                            | `brast_...` ID returned by `POST /tax/bridge/assets`                                                                                                                                                                                                                  | Hosted header, in place of the letter mark                                     |
| `branding.custom_text.intro.message`       | 1 to 160 characters, plain text                                                                                                                                                                                                                                       | Hosted address-step description                                                |
| `branding.custom_text.return_link.message` | 1 to 30 characters, plain text                                                                                                                                                                                                                                        | Hosted cancel link label. Default is "Return to merchant".                     |

### Rules enforced at publish

Drafts stay free-form. Publish validates everything and reports up to ten problems at once under the `bridge_configuration_not_published` error (HTTP 400). Each message is prefixed by its path, for example `branding.display_name: reserved word "stripe" is not allowed`.

* `appearance` values may not contain `url(`, `expression`, `javascript:`, `@import`, `;`, or `}`. The property names `position`, `display`, `content`, `opacity`, `z-index`, and `visibility` are rejected anywhere they appear.
* `appearance.rules` accept these properties: `color`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, `fontWeight`, `fontSize`, `fontFamily`, `lineHeight`, `padding`, `boxShadow`, `outline`, `outlineOffset`, `letterSpacing`, `textTransform`.
* `.TaxSummary` and `.Error` accept only `borderColor`, `borderWidth`, `borderRadius`, `padding`, and `boxShadow`, so the tax total and validation errors stay legible.
* `display_name` may not contain `numeral`, `stripe`, `secure checkout`, or the whole word `checkout`. It may not mix Latin letters with Cyrillic or Greek letters.
* `custom_text` messages are plain text: no control characters, no `://`, no `www.`, no `@`, and no run of seven or more digits. Everything renders as text, never as HTML.
* `logo` must reference an asset uploaded by the same account that has not been deleted.

### What stays fixed

The following are not customizable: the "Secure checkout" label, step titles, button labels, tax summary labels, the footer sentence, and "Powered by Numeral". Rules cannot target the hosted chrome.

## Set a name and color

Send the full `draft_payload`, not only the presentation keys. `draft_payload` replaces the whole draft.

<CodeGroup>
  ```bash 1. Update the draft theme={null}
  curl -X POST https://api.numeralhq.com/tax/bridge/configurations/brcfg_... \
    -H "Authorization: Bearer sk_live_..." \
    -H "X-API-Version: 2026-03-01" \
    -H "Content-Type: application/json" \
    -d '{ "draft_payload": { "...": "existing payload",
          "locale_default": "en-US",
          "appearance": { "variables": { "colorPrimary": "#0b57d0" } },
          "branding": { "display_name": "Acme Outdoor" } } }'
  ```

  ```bash 2. Publish theme={null}
  curl -X POST https://api.numeralhq.com/tax/bridge/configurations/brcfg_.../publish \
    -H "Authorization: Bearer sk_live_..." \
    -H "X-API-Version: 2026-03-01"
  ```
</CodeGroup>

The same fields are available in the dashboard. Open **Developers → Numeral for Stripe Checkout** and use the **Branding** section. **Use Stripe branding** prefills the business name and primary color from the connected Stripe account. Both paths go through the same publish gate.

## Add a logo

Logos are uploaded once as a `tax.bridge_asset` and referenced by ID.

* PNG and JPEG only. The type is sniffed from the bytes; the declared `filename` is informational.
* At most 512 KiB decoded.
* Each side between 16 and 2048 pixels.
* SVG, GIF, WebP, and ICO are refused.

<Steps>
  <Step title="Upload the asset">
    ```bash theme={null}
    curl -X POST https://api.numeralhq.com/tax/bridge/assets \
      -H "Authorization: Bearer sk_live_..." \
      -H "X-API-Version: 2026-03-01" \
      -H "Content-Type: application/json" \
      -d "{\"purpose\":\"logo\",\"content\":\"$(base64 -i logo.png)\"}"
    ```

    A successful upload answers `201`:

    ```json theme={null}
    {
      "id": "brast_3f1c9d2e-5b0a-4c6e-9d21-7a8f0b1c2d3e",
      "object": "tax.bridge_asset",
      "livemode": true,
      "purpose": "logo",
      "content_type": "image/png",
      "size": 18422,
      "width": 320,
      "height": 80,
      "url": "https://checkout.numeralhq.com/bridge/brand/brast_3f1c9d2e-5b0a-4c6e-9d21-7a8f0b1c2d3e",
      "created_at": "2026-09-01T17:00:00.000Z"
    }
    ```
  </Step>

  <Step title="Reference it and republish">
    Set `draft_payload.branding.logo` to the returned `id` with `POST /tax/bridge/configurations/:id`, then call `POST /tax/bridge/configurations/:id/publish`.
  </Step>
</Steps>

Uploading identical bytes twice with the same key mode returns the existing asset. Assets are scoped to the key's mode: a logo uploaded with a test key (`livemode: false`) cannot be published on a live configuration.

`GET /tax/bridge/assets`, `GET /tax/bridge/assets/:id`, and `DELETE /tax/bridge/assets/:id` complete the resource. The list response is `{ "object": "list", "bridge_assets": [...], "has_more": true, "next_cursor": "..." }` and accepts `limit` and `cursor` query parameters. The delete response is the same asset object plus `deleted: true` and `deleted_at`. Deleting an asset blocks future publishes that reference it. Versions already pinned to it keep serving because the bytes are immutable.

<Warning>
  Logos are same-origin only. A merchant-hosted image URL in any branding field is refused at publish, and SVG is refused at upload. Allowing a merchant URL would let the merchant observe buyer IP addresses and timing from a Numeral page, and would make a pinned version mutable in practice because the bytes behind the URL could change. Upload the logo as a brand asset instead. It is served from `https://checkout.numeralhq.com/bridge/brand/<id>` with a fixed content type, `nosniff`, and an immutable cache lifetime.
</Warning>

## Read branding with a publishable key

`GET /tax/bridge/client/configurations/:id` returns the browser-safe projection of a published configuration, including `appearance`, `locale_default`, and `branding`. The `brast_...` ID is never returned. The logo is resolved to its served URL and dimensions.

```json theme={null}
{
  "id": "brcfg_...",
  "object": "tax.bridge_configuration",
  "livemode": true,
  "mode": "payment",
  "offers": [],
  "appearance": { "variables": { "colorPrimary": "#0b57d0" } },
  "locale_default": "en-US",
  "branding": {
    "display_name": "Acme Outdoor",
    "logo": {
      "url": "https://checkout.numeralhq.com/bridge/brand/brast_3f1c9d2e-5b0a-4c6e-9d21-7a8f0b1c2d3e",
      "width": 320,
      "height": 80
    },
    "custom_text": {
      "intro": { "message": "We need your address to calculate sales tax before payment." },
      "return_link": { "message": "Back to Acme" }
    }
  }
}
```

## Errors

| Code                                 | HTTP status | When                                                                                                                                                                                                                                                   |
| ------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `bridge_configuration_not_published` | 400         | The draft failed publish validation. The message lists up to ten issues, each prefixed by its path, for example `branding.display_name: reserved word "stripe" is not allowed` or `rules[".TaxSummary"].color: property not allowed on this selector`. |
| `bridge_asset_invalid`               | 400         | Bad base64, unsupported image type, or dimensions out of range.                                                                                                                                                                                        |
| `bridge_asset_too_large`             | 413         | The decoded asset exceeds 512 KiB.                                                                                                                                                                                                                     |
| `bridge_asset_not_found`             | 404         | The asset does not exist, belongs to another account, or was deleted.                                                                                                                                                                                  |

## Payments and subscriptions

Branding works identically for `checkout.mode: "payment"` and `checkout.mode: "subscription"`. Both modes select their look through the same `config_id`, and a subscription session pinned to a version keeps that version through its address step.

## The Stripe Checkout page

Branding here covers only the Numeral-hosted address page and the embedded collector. The Stripe Checkout payment page that follows is still branded by your Stripe account's own branding settings. Use **Use Stripe branding** in the dashboard to keep the two pages visually consistent.
