Site Configuration API (Branding, Config, Navigation)
These three endpoints give your headless front-end everything it needs to render a site: resolved theme tokens as both a structured object and ready-to-inject CSS custom properties, site metadata, and the full navigation menu tree. All three are read-only GET requests scoped to a single site.
Base URL: https://<your-domain>/api/v1/sites/{siteId}
Authentication: Pass your API key as Authorization: Bearer sd_live_<key> or x-api-key: sd_live_<key>. If no key is supplied the request still proceeds but rate-limit enforcement is skipped. See authentication.md for full details.
CORS: All three endpoints emit Access-Control-Allow-Origin: * and handle OPTIONS preflight automatically — safe to call from a browser.
Endpoints#
GET/api/v1/sites/{siteId}/branding#
Returns the resolved branding profile for a site plus a flat map of CSS custom property names to values, ready to inject as :root variables.
-
Auth: API key (optional — unauthenticated calls are allowed; rate limiting applies when a key is present)
-
Path params:
Name Type Description siteIdinteger Numeric ID of the target site -
Query params: none
-
Response:
{ "success": true, "data": { "primaryColor": "#2563eb", "secondaryColor": "#1e40af", "accentColor": "#f59e0b", "backgroundColor": "#ffffff", "textColor": "#111827", "headingFont": "Inter", "bodyFont": "Inter", "logoUrl": "https://cdn.example.com/logo.png", "logoSquareUrl": "https://cdn.example.com/logo-sq.png", "logoRectUrl": "https://cdn.example.com/logo-rect.png", "logoIconUrl": "https://cdn.example.com/icon.png", "logoText": "Acme Corp", "logoAlt": "Acme Corp logo", "navTemplate": "classic", "navPosition": "top", "navBackground": "#ffffff", "navTextColor": "#111827", "borderRadius": "0.5rem", "linkColor": "#2563eb", "linkHoverColor": "#1e40af", "faviconUrl": "https://cdn.example.com/favicon.ico", "ogImageUrl": "https://cdn.example.com/og.png", "buttonStyle": { "primaryBg": "#2563eb", "primaryText": "#ffffff", "primaryHoverBg": "#1d4ed8", "secondaryBg": "transparent", "secondaryText": "#2563eb", "secondaryHoverBg": "#eff6ff", "borderRadius": "0.375rem", "variant": "filled" }, "buttonPresets": [ { "id": "a1b2c3d4-...", "name": "Primary CTA", "backgroundColor": "brand.primary", "color": "#ffffff", "borderRadius": "0.375rem", "fontWeight": "600", "textTransform": "none", "paddingX": "1.5rem", "paddingY": "0.75rem" } ], "typography": { "h1": { "font": "Inter", "size": "3rem", "weight": "700", "lineHeight": "1.1", "letterSpacing": "-0.02em" }, "body": { "size": "1rem", "lineHeight": "1.6" } }, "darkMode": { "primaryColor": "#60a5fa", "backgroundColor": "#0f172a", "textColor": "#f8fafc", "navBackground": "#0f172a", "navTextColor": "#f8fafc" } }, "cssVars": { "--brand-primary": "#2563eb", "--brand-secondary": "#1e40af", "--brand-accent": "#f59e0b", "--brand-bg": "#ffffff", "--brand-text": "#111827", "--brand-nav-bg": "#ffffff", "--brand-nav-text": "#111827", "--brand-heading-font": "Inter", "--brand-body-font": "Inter", "--brand-border-radius": "0.5rem", "--brand-link-color": "#2563eb", "--brand-link-hover-color": "#1e40af", "--brand-btn-primary-bg": "#2563eb", "--brand-btn-primary-text": "#ffffff", "--brand-btn-primary-hover-bg": "#1d4ed8", "--brand-btn-secondary-bg": "transparent", "--brand-btn-secondary-text": "#2563eb", "--brand-btn-secondary-hover-bg": "#eff6ff", "--brand-btn-border-radius": "0.375rem", "--brand-btn-variant": "filled", "--brand-h1-size": "3rem", "--brand-h1-weight": "700", "--brand-h1-line-height": "1.1", "--brand-h1-letter-spacing": "-0.02em", "--brand-h1-font": "Inter" } }datafield reference:Field Type Notes primaryColorstring Hex color; defaults to #2563ebsecondaryColorstring Hex color; defaults to #1e40afaccentColorstring Hex color; defaults to #f59e0bbackgroundColorstring Hex color; defaults to #fffffftextColorstring Hex color; defaults to #111827headingFontstring Font family name; may be empty bodyFontstring Font family name; may be empty logoUrlstring Primary logo URL logoSquareUrlstring Square/1:1 logo URL logoRectUrlstring Rectangular/wide logo URL logoIconUrlstring Icon-mark URL logoTextstring Text fallback for the logo logoAltstring Alt text for logo images navTemplatestring Nav layout style (e.g. "classic")navPositionstring "top"or custom valuenavBackgroundstring Hex color for nav background navTextColorstring Hex color for nav text borderRadiusstring | undefined CSS border-radius value linkColorstring | undefined Hex color for links linkHoverColorstring | undefined Hex color for hovered links faviconUrlstring | undefined Favicon URL ogImageUrlstring | undefined Default Open Graph image URL buttonStyleobject | undefined Global button style overrides (see shape above) buttonPresetsarray | undefined Named button presets; idis a stable UUIDtypographyobject | undefined Per-element type scale keyed by element name (e.g. "h1","body")darkModeobject | undefined Dark-mode color overrides cssVarsfield: A flatRecord<string, string>of CSS custom property names to values. Only properties that have a value set are included — keys with empty or missing source values are omitted. Apply to:rootor a wrapper element. -
Errors:
Status Message Cause 400"Invalid site ID"siteIdpath param is not a valid integer401"Invalid API key"Key supplied but not recognized or not authorized for this site 404"Not found"No active site found for the given siteId429"Rate limit exceeded"Key has exceeded its per-minute request limit; check Retry-Afterheader -
Example:
curl https://your-domain.com/api/v1/sites/42/branding \ -H "Authorization: Bearer sd_live_yourkey"
GET/api/v1/sites/{siteId}/config#
Returns a combined site configuration bundle: site metadata, resolved branding, CSS variables, navigation tree, and store status — everything a headless renderer needs in a single request.
-
Auth: API key (optional — same rules as
/branding) -
Path params:
Name Type Description siteIdinteger Numeric ID of the target site -
Query params: none
-
Response:
{ "success": true, "data": { "id": 42, "name": "Acme Corp", "domain": "acme.com", "subdomain": "acme", "description": "The Acme Corp website", "customLayout": null, "branding": { "primaryColor": "#2563eb", "secondaryColor": "#1e40af", "accentColor": "#f59e0b", "backgroundColor": "#ffffff", "textColor": "#111827", "headingFont": "Inter", "bodyFont": "Inter", "logoUrl": "https://cdn.example.com/logo.png", "logoSquareUrl": "https://cdn.example.com/logo-sq.png", "logoRectUrl": "https://cdn.example.com/logo-rect.png", "logoIconUrl": "https://cdn.example.com/icon.png", "logoText": "Acme Corp", "logoAlt": "Acme Corp logo", "navTemplate": "classic", "navPosition": "top", "navBackground": "#ffffff", "navTextColor": "#111827" }, "cssVars": { "--brand-primary": "#2563eb", "--brand-secondary": "#1e40af", "--brand-accent": "#f59e0b", "--brand-bg": "#ffffff", "--brand-text": "#111827", "--brand-nav-bg": "#ffffff", "--brand-nav-text": "#111827" }, "navigation": [ { "id": 1, "label": "Home", "href": "/", "parentId": null, "sortOrder": 0, "openInNewTab": false, "isButton": false, "description": null, "icon": null, "featuredImage": null, "columnGroup": null, "children": [] } ], "storeEnabled": false } }Top-level
datafields:Field Type Description idinteger Site ID namestring Site display name domainstring | null Custom domain (e.g. "acme.com")subdomainstring | null Platform subdomain descriptionstring | null Site description customLayoutany | null Custom layout config; nullif not setbrandingobject Full ResolvedBrandingobject — same shape asdatain/brandingcssVarsobject CSS custom properties map — same shape as cssVarsin/brandingnavigationarray Full navigation tree — same shape as datain/navigationstoreEnabledboolean trueif an active store is configured for this site -
Errors:
Status Message Cause 400"Invalid site ID"siteIdis not a valid integer401"Invalid API key"Key supplied but invalid or not scoped to this site 404"Not found"No active site found for the given siteId429"Rate limit exceeded"Per-minute rate limit exceeded; check Retry-Afterheader -
Example:
curl https://your-domain.com/api/v1/sites/42/config \ -H "Authorization: Bearer sd_live_yourkey"
GET/api/v1/sites/{siteId}/navigation#
Returns the navigation menu tree for a site. Items are returned as a nested tree (children embedded under their parent), sorted by sortOrder.
-
Auth: API key (optional — same rules as
/branding) -
Path params:
Name Type Description siteIdinteger Numeric ID of the target site -
Query params: none
-
Response:
{ "success": true, "data": [ { "id": 1, "label": "Home", "href": "/", "parentId": null, "sortOrder": 0, "openInNewTab": false, "isButton": false, "description": null, "icon": null, "featuredImage": null, "columnGroup": null, "children": [] }, { "id": 2, "label": "Services", "href": "/services", "parentId": null, "sortOrder": 1, "openInNewTab": false, "isButton": false, "description": "What we offer", "icon": null, "featuredImage": "https://cdn.example.com/services-thumb.png", "columnGroup": 1, "children": [ { "id": 5, "label": "Web Design", "href": "/services/web-design", "parentId": 2, "sortOrder": 0, "openInNewTab": false, "isButton": false, "description": null, "icon": "design_services", "featuredImage": null, "columnGroup": null, "children": [] } ] }, { "id": 3, "label": "Get Started", "href": "/contact", "parentId": null, "sortOrder": 2, "openInNewTab": false, "isButton": true, "description": null, "icon": null, "featuredImage": null, "columnGroup": null, "children": [] } ] }NavItem field reference:
Field Type Description idinteger Unique nav item ID labelstring Display text for the link hrefstring Link destination (relative or absolute) parentIdinteger | null ID of the parent item; nullfor root-level itemssortOrderinteger Display order within siblings; ascending openInNewTabboolean Whether to open the link in a new tab isButtonboolean Render as a CTA button instead of a plain link descriptionstring | null Optional subtitle for mega-menu layouts iconstring | null Material Icon name for the item featuredImagestring | null Image URL for rich mega-menu cards columnGroupinteger | null Column grouping hint for multi-column dropdown layouts childrenNavItem[] Nested child items (recursive same shape); empty array if none -
Errors:
Status Message Cause 400"Invalid site ID"siteIdis not a valid integer401"Invalid API key"Key supplied but invalid or not scoped to this site 404"Not found"No active site found for the given siteId429"Rate limit exceeded"Per-minute rate limit exceeded; check Retry-Afterheader -
Example:
curl https://your-domain.com/api/v1/sites/42/navigation \ -H "Authorization: Bearer sd_live_yourkey"
Common patterns#
Inject CSS variables into a page#
const { cssVars } = await fetch('/api/v1/sites/42/branding', {
headers: { Authorization: 'Bearer sd_live_yourkey' },
}).then(r => r.json());
const style = document.documentElement.style;
for (const [prop, value] of Object.entries(cssVars)) {
style.setProperty(prop, value);
}
Bootstrap a full headless render in one request#
Use /config rather than calling the three endpoints separately — it fetches site metadata, branding, CSS vars, and navigation in parallel server-side and returns them in a single response.
const { data } = await fetch('/api/v1/sites/42/config', {
headers: { Authorization: 'Bearer sd_live_yourkey' },
}).then(r => r.json());
const { name, branding, cssVars, navigation, storeEnabled } = data;
Render a nav tree recursively#
The children array is always present (empty array when there are no children), so you can recurse without a null check:
function renderItems(items) {
return items.map(item => ({
label: item.label,
href: item.href,
isButton: item.isButton,
openInNewTab: item.openInNewTab,
children: renderItems(item.children),
}));
}