Skip to content

Set a brand's logo or icon

The brands.images.set operation: PUT /api/v1/brands/{id}/images/{variant} in the REST API and the brands_images_set tool in the MCP server.

PUT/api/v1/brands/{id}/images/{variant}

Uploads a brand's logo or icon. The icon is the square mark checkout shows next to the brand's name, and the brand's image in the console; the logo is the wide mark the customer portal shows. Send the image base64-encoded in content: PNG, JPEG or WebP of at most 512 KB and 1024 pixels a side, the icon square and the logo between square and four times as wide as it is tall. The type is read from the bytes; SVG and GIF are refused.

An uploaded image wins over the typed branding.logoUrl / iconUrl (brands.update), which keep working when nothing is uploaded. Brand images are public: customers see them on checkout and the portal, so they are served to anyone at branding.logoFile.url / iconFile.url once the brand is in test or live mode. Uploading the same image again changes nothing. Needs brands:write. Dry-run it to check an image without storing it.

At a glance

  • REST: PUT /api/v1/brands/{id}/images/{variant}
  • MCP tool: brands_images_set
  • Operation ID: brands.images.set
  • Scope: Organization, or one of its legal entities or brands
  • Permission: brands:write
  • Risk tier: write
  • Dry run: Supported (send Ownrecord-Dry-Run: true)
  • Idempotency: Idempotency-Key optional
  • Modes: Test and live
  • Authentication: API key with the brands:write scope
  • Since: M13

Request

Path parameters

FieldTypeDescription
idbrd_… ID
variantone of logo, iconlogo: the wide mark on the portal (and later invoices and emails); icon: the square mark on checkout.

Body

A JSON object (Content-Type: application/json):

FieldTypeRequiredDescription
contentstringYesThe image's bytes, base64-encoded: PNG, JPEG or WebP of at most 512 KB and 1024 pixels a side. An icon is square (256 × 256 from the console); a logo is between square and four times as wide as it is tall (within 600 × 200 from the console).

Response

200 OK with a JSON object:

FieldTypeDescription
idbrd_… ID
object"brand"
orgIdorg_… ID
entityIdent_… ID
slugstring
namestring
statusone of draft, test_only, live, suspended
isPlatformBrandboolean
brandingobject
branding.logoUrlstring or null
branding.iconUrlstring or null
branding.logoFileobject or null
branding.iconFileobject or null
branding.primaryColorstring or null
branding.accentColorstring or null
branding.supportEmailstring or null
branding.supportUrlstring or null
branding.termsUrlstring or null
branding.privacyUrlstring or null
statementDescriptorstring
statementDescriptorSuffixstring or null
invoiceSeriesobject
invoiceSeries.invoicestring
invoiceSeries.creditNotestring
billingSettingsobject
billingSettings.dunningobject
billingSettings.dunning.retryOffsetsDaysarray of integer
billingSettings.dunning.finalActionone of unpaid, canceled
billingSettings.entitlementGraceDaysinteger
billingSettings.sendInvoiceobject
billingSettings.sendInvoice.graceDaysinteger
billingSettings.cardExpiryRemindersboolean
billingSettings.pausedEntitlementsboolean
billingSettings.paymentBehaviorone of allow_incomplete, pending_if_incomplete
policiesobject
policies.breakageRecognitionone of at_expiry, manualat_expiry (the default) or manual (held as expired credits until recognised).
policies.supportRefundLimitsarray of objectThe support role's refund limit per currency (M8); a currency not listed has the default of 100 of it.
policies.supportRefundLimits[].amountstringInteger micros as a string.
policies.supportRefundLimits[].currencystring
policies.disputeWalletFreezebooleanFreeze the customer's wallets when a dispute opens (default true; §19.2, M8).
policies.taxIdUnavailableone of charge_as_b2c, blockWhat a sale does when a tax ID couldn't be checked (§9.1 step 1; M13): charge_as_b2c (the default) or block.
portalSettingsobject
portalSettings.allowPlanChangesbooleanCustomers may switch plans along planChangeMatrix (no row: no switch).
portalSettings.planChangeMatrixarray of objectOne row per price: a subscription item on fromPriceId may switch to any of toPriceIds (same mode and currency).
portalSettings.planChangeMatrix[].fromPriceIdprice_… ID
portalSettings.planChangeMatrix[].toPriceIdsarray of price_… ID
portalSettings.allowCancelbooleanCustomers may cancel their subscriptions.
portalSettings.cancelBehaviorone of period_end, immediateA portal cancellation ends the subscription at the period end (default) or at once.
portalSettings.collectCancelReasonbooleanThe portal asks why the customer cancels.
portalSettings.allowQuantityChangesbooleanCustomers may change the quantity (seats) of licensed items.
checkoutSettingsobject
checkoutSettings.localesarray of stringThe brand's languages, its default first. A default, not a restriction: a customer's own language comes first.
checkoutSettings.collectPhoneboolean
checkoutSettings.requireBusinessDetailsone of auto, always, never
createdAtdate-time
updatedAtdate-time

Examples

Upload the brand's icon for checkout

HTTP
PUT /api/v1/brands/brd_01m4tx5nj8emwbyf7y9vcf47x4/images/icon
Authorization: Bearer or_test_…
Content-Type: application/json

{
  "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
}

Response (200):

JSON
{
  "id": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "object": "brand",
  "orgId": "org_01m4tx5nj7ekd958x53w4bntcg",
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "slug": "acme-app",
  "name": "Acme App",
  "status": "test_only",
  "isPlatformBrand": false,
  "branding": {
    "logoUrl": null,
    "iconUrl": null,
    "logoFile": null,
    "iconFile": {
      "fileId": "file_01m4tx5nj8emwbyf7y9vcf47yc",
      "url": "/media/file_01m4tx5nj8emwbyf7y9vcf47yc/63ef318d96b5d0d0ceba6e04a4e622b1158335cdc67c49e27839132c6f655058.png"
    },
    "primaryColor": null,
    "accentColor": null,
    "supportEmail": null,
    "supportUrl": null,
    "termsUrl": null,
    "privacyUrl": null
  },
  "statementDescriptor": "ACME APP",
  "statementDescriptorSuffix": null,
  "invoiceSeries": {
    "invoice": "ACM",
    "creditNote": "ACM-CN"
  },
  "billingSettings": {
    "dunning": {
      "retryOffsetsDays": [1, 3, 5, 7],
      "finalAction": "unpaid"
    },
    "entitlementGraceDays": 7,
    "sendInvoice": {
      "graceDays": 14
    },
    "cardExpiryReminders": true,
    "pausedEntitlements": false,
    "paymentBehavior": "allow_incomplete"
  },
  "policies": {
    "breakageRecognition": "at_expiry",
    "supportRefundLimits": [],
    "disputeWalletFreeze": true,
    "taxIdUnavailable": "charge_as_b2c"
  },
  "portalSettings": {
    "allowPlanChanges": true,
    "planChangeMatrix": [],
    "allowCancel": true,
    "cancelBehavior": "period_end",
    "collectCancelReason": true,
    "allowQuantityChanges": false
  },
  "checkoutSettings": {
    "locales": ["en", "hr"],
    "collectPhone": false,
    "requireBusinessDetails": "auto"
  },
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z"
}

Errors

CodeHTTP statusWhenWhat to do
validation_failed400content isn't base64, or is far larger than an image may be (param names the field).Fix the fields listed in details.issues and send the request again.
image_type_unsupported400The file isn't a still PNG, JPEG or WebP image (read from its bytes): SVG, GIF and animations are refused.Export the image as PNG, JPEG or WebP (a still image, not an animation) and upload it again. The type is read from the file's bytes, not from its name.
image_too_large400The image is larger than 512 KB.Resize or compress the image to at most 512 KB. Avatars and icons are shown at 256 × 256 pixels and logos within 600 × 200, so a small file loses nothing.
image_dimensions400The image is wider or taller than 1024 pixels, or not square (avatars and icons), or a logo outside 1:1 to 4:1.Use a square image of at most 1024 × 1024 pixels for avatars and icons, and a logo between square and four times as wide as it is tall, at most 1024 pixels a side.
upstream_timeout502The file store didn't answer in time; nothing was recorded. Retry.Retry the request with the same Idempotency-Key after a short wait.
resource_not_found404No brand with this ID is visible to the request.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
version_conflict409The brand changed at the same time; retry.Fetch the object again and retry the change.
permission_denied403You can't change this brand's settings.Ask an organization owner or admin for a role or scope that allows this action.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_reused, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.