Notes and Colors
Not everything on a Playbook board is a file. Two kinds of asset carry their content inline instead of pointing at one:
- A note is a sticky-note asset holding prose — a brief, a caption, a to-do, context for the assets around it. It has a background colour and a card height.
- A colour swatch holds a single colour. Two or more swatches grouped together become a palette.
Both are ordinary assets: they live on a board, take tags and statuses, appear in search, and are
fetched, updated and deleted through the same endpoints as any other asset. What makes them
different is that there are no bytes to fetch, so they are created synchronously — the response
is the finished asset, with no is_skeleton polling step of the kind
uploading requires.
Prerequisites
- Access token — an API token with the
writescope, sent as a Bearer token. - Organization slug (
slug) — your workspace identifier, e.g.coolclient-ltd. - Board token (
collection_token) — optional. If omitted or unknown, the asset falls through to a default board rather than erroring, so validate it first if it matters. Fetch board tokens from the boards endpoint.
Create a note
Notes share the generic asset-create endpoint with URL ingest. Sending text instead of uri is
what makes the new asset a note.
Endpoint
POST /v1/{slug}/assets
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
Request body
All fields live under an asset key.
| Field | Type | Required | Description |
|---|---|---|---|
text | string | yes | The note's content. Sending this instead of uri is what creates a note. |
title | string | no | Title shown above the note. |
description | string | no | Longer description. |
color | string | no | Background colour as a hex code — use a note palette colour, see below. Renders white when omitted. |
height | integer | no | Card height in pixels. |
collection_token | string | no | Board to add the note to. |
group_token | string | no | An existing group asset to place the note inside. |
tags | array | no | Manual tags applied on creation. |
status | string | no | Status label applied on creation. |
color and height apply to notes only and are ignored when uri is present.
Sample request
curl -X POST https://api.playbook.com/v1/coolclient-ltd/assets \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"asset": {
"text": "Ship the palette by Friday",
"title": "Deadline",
"color": "#F4EFD9",
"height": 320,
"description": "from the kickoff call",
"collection_token": "boardToken123"
}
}'
Sample response
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": {
"token": "assetToken123",
"title": "Deadline",
"text": "Ship the palette by Friday",
"text_type": "note",
"average_color": "#F4EFD9",
"primary_height": 320,
"description": "from the kickoff call",
"collection_token": "boardToken123",
"is_link": false,
"is_skeleton": false
}
}
The note's prose comes back in text, and text_type is note. The background colour is reported
as average_color — the same field an image uses for its dominant colour.
Note background colours
color is stored verbatim and is not validated, but only the note palette has a dark-mode
counterpart. Playbook matches the stored string exactly, including case, against:
#ffffff · #F1F3F7 · #ECEAF2 · #F4EBE0 · #F4EFD9 · #E6EEE7 · #F1E5E5 · #ECEAE6
A note always switches to light text in dark mode, but its background is only darkened when the
value matches one of the colours above. Send a brand colour like #FFE8A3 — or a palette colour in
the wrong case, like #f4efd9 — and the note keeps its light background under light text. Stick to
the list, copied exactly, unless you only care about light mode.
To edit a note later, PATCH /v1/{slug}/assets/{token} accepts text alongside the usual title
and description.
Create colour swatches
Endpoint
POST /v1/{slug}/assets/create_colors
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
Request body
All fields live under a colors key.
| Field | Type | Required | Description |
|---|---|---|---|
color_codes | array | yes | 1–100 colour values. Each is a hex code, CMYK, a two-stop gradient, or a Pantone name — see below. |
titles | array | no | Titles positionally aligned with color_codes. A null or missing entry falls back to the code itself; an explicit "" is kept as an empty title. |
collection_token | string | no | Board to add the swatches to. board_token is accepted as an alias. |
parent_token | string | no | Merge the new swatches into this existing asset or group instead of forming a palette of their own. |
What counts as a colour value
Four forms are accepted — the same four the web app can render — and anything else is rejected with
406 before a single swatch is created:
- A hex code —
#RRGGBBor the three-digit shorthand#RGB, any casing.#FF0000and#f00both work;FF0000without the#does not. - CMYK —
CMYK(0%, 100%, 100%, 0%): four numbers, percent signs optional. - A two-stop gradient — two hex codes joined by the arrow
→(U+2192), e.g.#FF0000→#0000FF. The gradient angle is not part of the value; the web app keeps it in the asset's title as45°. - A Pantone name — written without the
PANTONEprefix and matched exactly, including case, against Playbook's Pantone list:185 C,185 CP,20-0001 TPM.
PANTONE prefix is the common mistake"PANTONE 185 C" — the way the name is normally written — is not a valid value. The correct
form is "185 C". This is enforced rather than stored, because a swatch whose value resolves to no
colour is created successfully, counts toward the board's asset total, and then renders as an empty
slot with nothing to show.
Values are stored verbatim — Playbook does not rewrite a hex code's casing or expand a Pantone name
into its hex on write. text reads back exactly what you sent.
Sample request
curl -X POST https://api.playbook.com/v1/coolclient-ltd/assets/create_colors \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"colors": {
"collection_token": "boardToken123",
"color_codes": ["#FF0000", "#00FF00", "#0000FF"],
"titles": ["Red", "Green", null]
}
}'
Sample response
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": {
"assets": [
{ "token": "aA1", "title": "Red", "text": "#FF0000", "text_type": "color", "is_group": false },
{ "token": "bB2", "title": "Green", "text": "#00FF00", "text_type": "color", "is_group": false },
{ "token": "cC3", "title": "#0000FF", "text": "#0000FF", "text_type": "color", "is_group": false }
],
"group": {
"token": "dD4",
"is_group": true,
"text": "#FF0000|#00FF00|#0000FF",
"text_type": "palette"
}
}
}
assets comes back in the order you sent the codes. The third swatch was given a null title, so it
fell back to its own code.
Palettes
Sending two or more codes merges them into a palette: a group asset whose text_type is
palette and whose text is the member codes joined by |. You do not ask for a palette — grouping
colour swatches produces one.
- One code, no
parent_token→ a single loose swatch, andgroupcomes backnull. - Two or more codes → a palette, returned as
group. - Any number of codes with
parent_token→ the swatches are merged into that asset's group. If the group ends up containing anything that is not a colour, it is a plain group rather than a palette, and itstext_typeisnull. Note that grouping moves the swatches onto the parent's board, whatevercollection_tokensaid.
To rearrange a palette after the fact — add more swatches to it, or break it apart — use the
grouping endpoints described in
Advanced Asset Management: POST /v1/{slug}/assets/group_assets and
POST /v1/{slug}/assets/ungroup_assets, exposed over MCP as group_assets and ungroup_assets.
One thing to know before ungrouping: a group left with fewer than two assets is dissolved. Its last asset is freed as well and the group asset itself is deleted, so ungrouping one swatch from a two-swatch palette leaves you with two loose swatches and no palette. The response lists only the assets you named, not every asset that changed — re-read the group if you need its state.
Errors
Errors use the standard envelope,
{ "errors": [{ "message": "...", "extensions": { "code": "..." } }] }.
| Status | Message | Cause |
|---|---|---|
| 406 | color_codes cannot be empty | color_codes was missing or an empty array. |
| 406 | color_codes must be non-blank strings | An entry was empty, whitespace, or not a string (a JSON number or boolean counts). |
| 422 | color_codes limit is 100 | More than 100 codes in one call. |
| 403 | Not authorized | The token lacks the write scope, or you cannot write to the board. |
| 404 | Not found | parent_token does not name a live asset in this workspace — a trashed asset counts as absent. |
The whole call runs in one transaction, so a rejected batch leaves no partial palette behind.
Swatches do not count against your workspace's asset ceiling: that limit counts stored files, and a swatch has no file. A workspace at its file limit can still create colours.
Reading notes and swatches back
Every asset endpoint reports text assets through the same three fields:
| Field | Meaning |
|---|---|
text_type | note, color, palette, or null for an ordinary file or link. |
text | The note's prose, the swatch's colour value, or a palette's member codes joined by |. |
average_color | A note's background colour. (For an image this is its dominant colour instead.) |
Text assets have no bytes, so display_url, thumbnails, size and media_type are empty for
them. Render a note or a swatch from text and average_color.
An image's own colours
Everything above is about colours somebody authored. An image also has colours of its own, and those are a separate field.
When Playbook renders an asset's preview it extracts the image's primary colours and stores them.
Every asset endpoint returns them as colors: an array of #RRGGBB strings, most dominant first,
up to six. These are the small swatches the web app draws underneath an asset.
{
"token": "3EiEw6UGeLArdBVVxo4XDqQr",
"media_type": "image/jpeg",
"average_color": "#888579",
"colors": ["#0B0B0B", "#2A3A2E", "#5A5648", "#A9A396", "#D8D5C6", "#8E8B7C"]
}
Three things worth knowing before you build on it:
colorsis always an array, and it is empty for anything without a rendered preview — notes, swatches, links, and file types that never produced one. An empty array means the image was never analysed, not that it has no colours. It never comes backnull.colorsandaverage_colorare not the same thing.average_coloris a single blended value, and on a note it carries the note's background colour instead.colorsis the palette.- You cannot set
colors. It is derived at preview time and read-only; to put a colour on a board, create a swatch withcreate_colorsas described above.
The two halves of this page compose: read colors from the images on a board, then post the codes
you like back to that same board with create_colors to save the palette as real swatches. No image
download and no colour analysis of your own is involved.
Over MCP
Both operations are available to AI assistants through the hosted MCP server, so you can create them in natural language instead of writing calls:
| Tool | What it does |
|---|---|
create_note | Add a note to a board, with optional background colour and card height. Needs write. |
create_colors | Add 1–100 swatches; two or more come back merged into a palette. Needs write. |
create_colors returns assets, group (null when no palette was formed) and count. get_asset
carries text, text_type and average_color, so an assistant can read back what it created. The
list and search tools return a leaner projection that keeps text_type — enough to tell a note from
a swatch from a file — but not the text itself; fetch that with get_asset.
get_asset also carries the colors array described above, which is what lets an assistant build a
palette from a board: list the board, read each image's colors, then hand the codes it picks to
create_colors. Those colours are on the detail response only — list and search rows omit them, so
ask for an asset by token.