Brands API
Manage the Brand catalogue. All routes under /api/admin/brands require platform-superadmin; the public reads under /api/public/brands/by-* are accessible without auth so the sign-in flow can resolve theme + IdP config before the user has a token.
List brands
GET /api/admin/brands
Returns every Brand in the catalogue, including suspended.
200 OK
{
"items": [
{
"id": "aroovo",
"slug": "aroovo",
"name": "Aroovo",
"status": "active",
"primaryDomain": "agents.aroovo.tech",
"hostnameVerifiedAt": "2026-05-24T11:02:00.000Z",
"createdAt": "2026-05-24T10:00:00.000Z"
}
]
}Get a brand
GET /api/admin/brands/:id
200 OK
{
"id": "acme-agency",
"slug": "acme-agency",
"name": "Acme Agency",
"status": "active",
"primaryDomain": null,
"themeJson": null,
"authProvider": null,
"createdAt": "2026-05-25T09:00:00.000Z"
}
404 Not Found
{ "error": "brand_not_found", "detail": "No Brand with id acme-agency" }Create a brand
POST /api/admin/brands
Request body
{
"slug": "acme-agency",
"name": "Acme Agency",
"primaryDomain": "agents.acme.com" // optional
}
200 OK
{ "id": "acme-agency", "slug": "acme-agency", ... }
400 Bad Request
{ "error": "invalid_slug", "detail": "slug must match /^[a-z0-9-]+$/" }
409 Conflict
{ "error": "slug_exists", "detail": "Brand slug acme-agency already exists" }
403 Forbidden
{ "error": "requires_platform_owner" }Update a brand
PATCH /api/admin/brands/:id
Partial patch — only supplied fields are updated.
Request body
{
"name": "Acme Agency (US)",
"themeJson": "{ \"--b-pale\": \"#74C3F0\" }"
}
200 OK
{ "id": "acme-agency", "name": "Acme Agency (US)", ... }
409 Conflict
{ "error": "aroovo_brand_locked",
"detail": "Cannot change status on the platform Brand" }Lifecycle transitions
POST /api/admin/brands/:id/suspend — flip to suspended.POST /api/admin/brands/:id/offboard — flip to offboarding (30-day soft delete window).POST /api/admin/brands/:id/activate — flip back to active.DELETE /api/admin/brands/:id — soft-delete (same as offboard).
200 OK
{ "id": "acme-agency", "status": "suspended" }
409 Conflict
{ "error": "aroovo_brand_locked" }Provider config (BYO LLM keys)
GET /api/admin/brands/:id/provider-config — returns status (which keys are set), never plaintext.POST /api/admin/brands/:id/provider-key — set a provider key. Body: { provider: "anthropic" | "openai" | "openrouter" | "openclaw", value: "<key>" }. The value is encrypted and stored in the Secret vault.DELETE /api/admin/brands/:id/provider-key — clear a provider key.PUT /api/admin/brands/:id/openclaw-base-url — set the OpenClaw gateway URL.
Auth provider (BYO identity)
POST /api/admin/brands/:id/auth-provider — pick and configure the Brand's IdP. Body:
{
"provider": "google", // entra | google | okta | auth0
"config": {
"clientId": "...",
"hostedDomain": "acme.com"
}
}Slack config
POST /api/admin/brands/:id/slack-config — paste Slack bot token + signing secret.DELETE /api/admin/brands/:id/slack-config — clear.PUT /api/admin/brands/:id/slack-config/channel-map — map a Slack channel to a Tenant slug.
Hostname verification
POST /api/admin/brands/:id/hostname-verify/start — returns the DNS-TXT challenge token.POST /api/admin/brands/:id/hostname-verify/check — the platform polls DNS and flips hostnameVerifiedAt on success.POST /api/admin/brands/:id/hostname-verify/clear — clear the verification (e.g. on domain change).
Brand-Owner invites
GET /api/admin/brands/:id/owners — list Owners.POST /api/admin/brands/:id/owner-invite — invite a new Brand-Owner by email; emails must validate against the configured IdP.
Tenants under a Brand
GET /api/admin/brands/:id/tenants — list all Tenants for this Brand. Useful when a Brand-Owner's console view needs the full tenant catalogue.
Brand export (offboarding)
GET /api/admin/brands/:id/export — bundle every audit row, tenant config, KB file, and secret reference (not the secret values) into a downloadable archive. Used during the 30-day offboarding window.
Public reads (no auth)
GET /api/public/brands/by-slug/:slug — resolve a slug to { brandId, name, themeJson, authProvider } for the sign-in flow.GET /api/public/brands/by-id/:id/theme — minimal theme blob for early CSS variable injection.GET /api/public/brands/by-host/:host — used by the edge middleware to attach x-brand-id.
Source: src/platform/brands-api.ts / ADR-003.