Skip to main content

Brand kits (REST)

Full CRUD-ish lifecycle for brand kits. All endpoints under /v1/brand-kits.

Endpoints

Default kit

The first brand kit created for a team becomes the default automatically. POST /v1/tasks with no brand_kit_id uses the default. At most one default per team. Promote another kit with PATCH /brand-kits/{id} and {"is_default": true} — it clears whichever kit held the default, in the same transaction as any field writes in the same body, so a correct-and-promote cannot half-land. is_default: false is a 400: demoting without naming a replacement would leave the team with no default at all, so promote the replacement instead. Omit the field to leave the flag alone.

List

default_theme_canvas_id is the prefixed cvs_… ID of the kit’s saved default slides theme canvas (null if none). Returned on reads and writable via PATCH (see below). Fresh-canvas design tasks auto-apply it when present, and so does a blank POST /v1/canvases with category: "slides" and this kit’s brand_kit_id — the create response reports the attached theme as theme_canvas_id (null when the kit has no usable theme; the create still succeeds). Cursor-paginated; iterate with ?cursor=. See pagination.md.

Create from URL

Server scrapes the URL via Firecrawl and extracts colors, fonts, logos, tone, values, aesthetic. The call is synchronous and blocks for 10–30 seconds. Returns the created brand-kit record (same shape as list items). URL accepts bare domain (stripe.com) or full URL (https://stripe.com). Cached — re-calling with the same URL returns the cached result quickly. If the URL can’t be scraped (blocked by robots, 404, login-required): 422 scraping_user_error.

Update (partial)

Pass only the fields to change. Omitted fields keep their current values. Array fields replace wholesale. Passing colors overwrites the entire array — not a merge. To add one color, fetch the existing array, append, and PATCH the full list:
Same rule for fonts, brand_values, brand_aesthetic, brand_tone_of_voice. Setting the default theme canvas. PATCH default_theme_canvas_id with a cvs_… ID (a bare UUID is also accepted) to point the kit at a slides theme canvas; send null to clear it:
The ID must reference a canvas with template_type='theme' on the same team — anything else returns 400 invalid_theme_canvas. Omitting the field leaves the current value unchanged. Promoting the team default. PATCH is_default: true to make this the team’s default kit, clearing whichever kit held it. It applies in the same transaction as any field writes in the same body, so correcting a value and promoting the kit is one call that cannot half-land:
Promote-only: is_default: false returns 400. Demoting without naming a replacement leaves the team with no default at all, so promote the replacement kit instead. Omitting the field leaves the flag unchanged. Returns the updated record.

Add images (logos / references / assets)

Requires an existing file_id from POST /v1/uploads. Attaches the uploaded file to the kit in the named role. Role here is about the kit (what this image represents in the brand) — different from the role field on task attachments (source / reference / asset / import). Returns the updated brand-kit record.

List and remove images

Returns the full list — not cursor-paginated. total always equals returned. Read-back roles are logo or reference only. logo rows come from the kit’s logos; reference rows come from the design-assets bucket, which is shared by the reference and asset add-time roles — so an image attached with role: "asset" reads back as reference. Filtering the list for role == "asset" always returns nothing.
image_id is the id field (bki_…) from GET /brand-kits/{id}/images — not file_id, and not anything in the add-image response, which returns the whole kit record without image IDs. Returns 204 No Content. A repeat DELETE on the same id returns 404 rather than erroring; a wrong-prefix id (e.g. a file_ one) fails path validation with 422 before the handler runs.

Delete

Returns 204 No Content. Soft delete — historical tasks that referenced this kit continue to show it in their audit record. New design tasks with brand_kit_id pointing at a deleted kit get 404 not_found. If the deleted kit was the team’s default, there is no new default automatically. The team operates brand-kit-less until another kit is promoted (PATCH /brand-kits/{id} with {"is_default": true}) or a new kit is created.

In-task usage

Pass brand_kit_id explicitly on POST /v1/tasks to override the default:
Or skip_brand_kit: true to apply no kit:
skip_brand_kit overrides brand_kit_id when both are present.

Common wrong guesses

  • Merging array updates client-side without reading first. You’ll overwrite the kit’s colors / fonts with just the new entries. Always read, append, PATCH.
  • Sending is_default: false to clear a default. 400. The field is promote-only — set is_default: true on the kit you want instead, which clears the incumbent for you.
  • Expecting POST /v1/brand-kits to return immediately. The call is synchronous: it returns the completed kit, but blocks for 10–30s — budget for that in your client timeout.
  • Passing brand_kit_id for a deleted kit. 404. Check the kit exists before submitting the task.
  • Filtering the images list for role: "asset". Reads only ever return logo or reference — asset and reference share one bucket, so asset attachments come back as reference.
  • Passing a file_ id to DELETE /brand-kits/{id}/images/{image_id}. The path wants the bki_ id from GET /brand-kits/{id}/images; a file_ id fails path validation with 422. 404 is the answer for a well-formed bki_ id that isn’t attached.
  • Mixing up the task-attachment role and the brand-kit-image role. Task role is source / reference / asset / import. Brand-kit image role is logo / reference / asset. Similar words, different values.
  • Pointing default_theme_canvas_id at a deck instead of a theme. It must be a template_type='theme' canvas on the same team, not a regular design/deck canvas. Anything else is 400 invalid_theme_canvas.

Upstream