BrandAttach docs
One verified token and done. Three ways to attach a brand: JavaScript, CSS, or REST.
Quickstart
- Create an account.
- Scan a domain to create a brand profile. Verify the domain to enable live tokens.
- Register your SaaS app in Apps and create an app token.
- In your app, accept a customer's BrandAttach brand token. Pass both tokens to BrandAttach.
For SaaS apps
Register your app once. Support every customer's brand token.
- Register your app in Apps.
- Add the origins where your app runs (e.g.
https://*.vendor.com). - Create a
ba_app_live_…token. Store it as a server secret. - Add a "BrandAttach token" field to your customer settings.
- When rendering, combine the customer's brand token with your app token. If the response includes
approval_required, prompt the customer to ask their brand owner to approve your app.
<script
src="https://brandattach.com/attach.js"
data-brand="ba_live_<customer_brand_token>"
data-app="ba_app_live_<your_app_token>">
</script>
<img data-ba-logo="navbar" alt="Company logo">
<img data-ba-logo="avatar" alt="">
<span data-ba-name></span>
<button class="ba-button">Continue</button>Backend (server-rendered) example:
GET https://brandattach.com/api/brand/{{ customer.brandAttachToken }}
Authorization: Bearer {{ appToken }}For brand owners
- Verify your domain in the dashboard so you can issue live tokens.
- Choose an access policy: Allow registered apps (recommended for most brands), Require approval, or Private.
- Review which apps are using your brand and revoke any of them at any time.
- Use /report to flag misuse.
JavaScript snippet
One line. Attaches CSS variables, replaces logos and brand names, exposes window.BrandAttach.
<script
src="https://brandattach.com/attach.js"
data-brand="ba_live_<customer_brand_token>"
data-app="ba_app_live_<your_app_token>">
</script>
<img data-ba-logo="navbar" alt="Company logo">
<img data-ba-logo="avatar" alt="">
<span data-ba-name></span>
<button class="ba-button">Continue</button>The script never stretches images: it sets object-fit: contain; max-width: 100%; height: auto;.
CSS-only integration
For server-rendered apps, link the theme stylesheet and use the CSS variables / utility classes.
<link rel="stylesheet" href="https://brandattach.com/api/brand/ba_live_xxx/theme.css">
<button class="ba-button">Continue</button>
<p style="color: var(--ba-color-primary)">Brand colour</p>Available variables:
--ba-brand-name--ba-color-primary,--ba-color-accent,--ba-color-background,--ba-color-surface,--ba-color-text,--ba-color-muted,--ba-color-border--ba-color-success,--ba-color-warning,--ba-color-error,--ba-color-info--ba-font-primary--ba-radius-sm,--ba-radius-md,--ba-radius-lg--ba-logo-primary,--ba-logo-login,--ba-logo-avatar,--ba-logo-footer
Helper classes:
.ba-button,.ba-text-primary,.ba-bg-primary.ba-logo-navbar,.ba-logo-footer,.ba-logo-login,.ba-logo-avatar
REST API
GET https://brandattach.com/api/brand/ba_live_xxx
Authorization: Bearer ba_app_live_yyy
200 OK
{
"brand": { "id": "brand_...", "name": "Acme", "domain": "acme.com", "version": 7 },
"logos": { "primary": { "url": "...", "type": "horizontal", "source": "owner" }, ... },
"logoPlacements": {
"navbar": { "url": "...", "width": 160, "height": 40, "fit": "contain" },
"avatar": { "url": "...", "width": 96, "height": 96, "fit": "contain" }
},
"colors": {
"primary": "#0057FF", "accent": "#FFB000", "background": "#FFFFFF",
"surface": "#F8FAFC", "text": "#101828", "muted": "#667085",
"border": "#EAECF0", "success": "#12B76A", "warning": "#F79009",
"error": "#F04438"
},
"fonts": { "primary": "Inter", "fallback": "system-ui, ...", "source": "owner" },
"radius": { "sm": "4px", "md": "8px", "lg": "16px" },
"rules": { "doNotStretchLogo": true, "minimumLogoWidth": 120, ... },
"trust": { "verified": true, "verifiedDomain": "acme.com" },
"permissions": { "appRegistered": true, "approvalStatus": "approved", ... },
"cache": { "ttl": 300, "staleWhileRevalidate": 600, "version": "brand_..-v7" }
}All endpoints accept the token as a path parameter and return CORS-friendly responses.
| Method | Path | What it does |
|---|---|---|
| GET | /api/brand/<token> | Complete brand JSON, with fallbacks, version, and cache headers |
| GET | /api/brand/<token>/theme.css | CSS variables + helpers |
| GET | /api/brand/<token>/logo?slot=… | Placement-aware logo (302 to processed asset, or inline initials SVG fallback) |
| GET | /attach.js | Drop-in JS snippet |
Logo placements
BrandAttach classifies every logo by shape and serves the right one for each UI slot — aspect ratio is always preserved.
| Slot | Ideal shape | Used in |
|---|---|---|
| navbar | horizontal | Site header, app top bar |
| footer | horizontal | Page footer |
| login | horizontal or square | Login / signup cards |
| avatar | icon / square | Profile chip, badge |
| favicon | icon | Browser tab |
| horizontal | Transactional email header | |
| app_icon | icon / square | PWA, mobile shortcut |
| card | horizontal or square | Marketing cards, hero |
<img data-ba-logo="navbar" alt="Company logo">
<img data-ba-logo="avatar" alt="Company icon">GET https://brandattach.com/api/brand/ba_live_xxx/logo?slot=navbar&theme=auto
→ 302 Redirect to the processed image URL (or original SVG / inline initials fallback)If no logo is available, BrandAttach returns a generated initials SVG so your UI never breaks.
BIMI Connect
BrandAttach uses BIMI as an additional trust signal and as a source of square logo assets. We do not issue VMC/CMC certificates, and BIMI alone does not guarantee inbox display in any mail client.
- Check your DNS for a BIMI record at
default._bimi.<domain>. - Validate DMARC and SPF readiness.
- Import your BIMI logo as a
logo_bimiBrandAttach asset for avatar/email slots (your primary logo is never overwritten without explicit confirmation). - Use a VMC (Verified Mark Certificate) or CMC (Common Mark Certificate) for verified-mark display in Gmail/Yahoo — BrandAttach reports the certificate type but does not issue them.
React example
// React example
import { useEffect, useState } from "react";
export function useBrand(brandToken: string, appToken: string) {
const [brand, setBrand] = useState<any>(null);
useEffect(() => {
fetch(`https://brandattach.com/api/brand/${brandToken}`, {
headers: { Authorization: `Bearer ${appToken}` }
}).then(r => r.json()).then(setBrand);
}, [brandToken, appToken]);
return brand;
}Security model
BrandAttach's rule is one verified token and done. The safety system sits behind the scenes so the integration stays a single script tag, but every guardrail below applies to the public APIs.
Test vs live tokens
ba_test_…— for development. Works against any brand, no origin restriction. Generated fallbacks are allowed.ba_live_…— for approved apps. Requires a verified brand, complete required fields (or approved fallbacks), and at least one allowed origin.ba_app_test_…/ba_app_live_…— identify the SaaS app making the request. Live production requests should pass both tokens.
Brand access policy
Brand owners choose how registered apps can use their brand:
- Allow registered apps — registered apps can use the brand by default; brand owners can revoke at any time.
- Require approval — every app must be explicitly approved.
- Private — only invited apps can use the brand.
High-risk use cases (login, payment, email, advertising) require approval by default even on permissive policies.
Domain verification
Verify ownership of the brand domain in the dashboard. Three methods:
- DNS TXT:
_brandattach.<domain>with valueba-verify=<code>. - Meta tag:
<meta name="brandattach-verify" content="ba-verify-…">. - Well-known file: text at
/.well-known/brandattach-verify.
Origin restrictions
Live tokens carry an allowlist of origins. Registered apps also carry their own list of origins. The public APIs read the request's Origin header (falling back to Referer) and reject anything outside both lists. Wildcard patterns are supported:
https://app.acme.com // exact origin
https://*.acme.com // any subdomain of acme.com
*.acme.com // protocol-agnostic wildcard
acme.com // bare host, https impliedRequests with neither Origin nor Referer are treated as server-to-server and allowed — the threat model is browser-based misuse.
Caching + TTL
- Brand JSON:
max-age=300, stale-while-revalidate=600, with ETag. - theme.css:
max-age=900, stale-while-revalidate=2400, with ETag. - Logo assets (processed/versioned):
max-age=31536000, immutable. - Logo fallbacks (generated SVG):
max-age=86400, stale-while-revalidate=604800. - Permission decisions: TTL 120s for allows; denied responses use
private, no-store. - Brand owners can recompile or force-rescan from the dashboard.
Revocation
- Revoked brand or app tokens return 403 (and the brand owner sees the denied attempt).
- Revoked approvals are honored within the permission TTL (default 120 s).
Usage logging
Every public API hit is logged with status (allowed / denied), endpoint, origin, app, and the resolved approval. Brand owners see this in their dashboard.
Misuse reports
Anyone can report a token being used to impersonate a brand at /report.
Domain scanning
- Scans are SSRF-safe: private IPs, loopback, link-local, and non-HTTP(S) URLs are blocked.
- Requests are bounded by timeout (≤9s), response size (≤2MB), and a small redirect cap.
- If a scan fails or detection is poor, fill in the brand manually — every field is editable.