White-label
Per-organization branding, with measured contrast and rejected SVGs.
Fuente del diagrama (mermaid)
flowchart LR
W[Alta en WorkOS · MANUAL] --> M[Membresía con admin:write]
M --> L[Login]
L --> B[PUT /v1/org/branding]
B --> G[POST /v1/org/branding/logo]
G --> C[Consola brandeada]
Organization creation is manual and done by a person. Creating organizations by API means mutating the identity provider: a bug there puts someone in the wrong tenant — legitimately inside, with every later control waving them through — which is the worst class of leak in a multi-tenant product.
curl -sS "$SBOX/v1/org/branding" -X PUT \
-H "authorization: Bearer $TOKEN" \
-H "idempotency-key: branding-demo-1" \
-H "content-type: application/json" \
-d '{"displayName":"Aseguradora Demo","colors":{"primary":"#7c2d12","accent":"#065f46"}}'Contrast
Branding that does not reach WCAG AA is rejected with 422, the measured ratio, and a color that does pass. The client uploading their corporate light blue is not choosing badly on purpose: they are choosing without knowing their own team will not be able to read the console.
What gets measured: text over the brand color at 4.5:1 (where the text lives), and brand over background at 3:1 in both themes. A color is not required to pass against both backgrounds, because none does — the brand defines a hue and each theme uses its own step.
Logo
512 KiB maximum, image/svg+xml or image/png. An SVG with <script>, an
on* handler, javascript:, <foreignObject>, <iframe> or an external
entity is rejected, not cleaned: blacklist sanitizing is a race the
defender loses, and accepting one would be persistent XSS in the session of
the client's entire organization.
What breaks
403 if the credential lacks admin:write. The permission comes from the
canonical catalog — there is no org:branding:write, because a permission the
catalog does not know is not granted by any role template.
See also the full demo flow.