Fetching Data from the Playbook API
Retrieve key resources — organizations, boards, and assets—using simple GET calls.
Prerequisites
- Access token: a valid Playbook API token with read permissions, sent in the
Authorization: Bearerheader. - Organization Slug (
slug): Your organization's unique identifier. - Board Token (
board_token): Token of a specific board (for assets endpoint).
List Your Organizations
Endpoint
GET /organizations
Authentication
Send Authorization: Bearer YOUR_TOKEN with the request.
Query Parameters
page(integer, optional, default: 1)per_page(integer, optional, default: 20)
Example Request
curl "https://api.playbook.com/v1/organizations?page=1&per_page=20" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
Example Response
{
"data": [
{ "id": 1, "slug": "coolclient-ltd", "name": "CoolClient Ltd" },
{ "id": 2, "slug": "acme-corp", "name": "Acme Corp" }
],
"pagy": { "current_page": 1, "page_items": 20, "total_pages": 1, "total_count": 2 }
}
List Boards in an Organization
Endpoint
GET /{slug}/boards
Authentication
Send Authorization: Bearer YOUR_TOKEN with the request.
Query Parameters
page(integer, optional)per_page(integer, optional)depth(integer orall, optional, default1) —1returns only top-level boards; a number returns boards down to that level;allreturns every board in the workspacequery(string, optional) — filter by board titledisplayable_assets(boolean, optional, defaulttrue) — each board carries up to three preview assets indisplayable_assets. Building them is most of what this request costs, so passfalseif you do not show previews; the field then comes backnull.GET /{slug}/boards/{board_token},/childrenand/descendantsaccept it too.
Example Request
curl "https://api.playbook.com/v1/coolclient-ltd/boards?page=1&per_page=20" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
Example Response
{
"data": [
{ "id": 123, "token": "boardToken111", "title": "Homepage Assets" },
{ "id": 124, "token": "boardToken222", "title": "Blog Banners" }
],
"pagy": { "current_page": 1, "page_items": 2, "total_pages": 1, "total_count": 2 }
}
List Every Board Under a Board
To get a board's whole subtree — children, their children, and so on — in one paginated call instead of walking it level by level:
GET /{slug}/boards/{board_token}/descendants
The board itself is not included. Each row carries tree_depth and parent_id (the parent's id),
which is enough to rebuild the tree. GET /{slug}/boards/{board_token}/children returns only the direct children.
curl "https://api.playbook.com/v1/coolclient-ltd/boards/boardToken111/descendants?per_page=100" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
List Assets in a Board
Endpoint
GET /{slug}/boards/{board_token}/assets
Authentication
Send Authorization: Bearer YOUR_TOKEN with the request.
Query Parameters
page(integer, optional)per_page(integer, optional)nested_assets(boolean, optional) — include assets from the board's entire subtree, not just the ones sitting directly in it. Requires a board (either this path or?collection_token=). Reach for it whenever the token might be a parent board: a board's reportedasset_countcounts the whole subtree, so without this flag a parent whose assets all live in sub-boards reports assets and returns none.get_ai_payload(boolean, optional) — include each asset'sai_agent_payload. Defaults tofalse, and the key is then absent from the row rather thannull, so "I didn't ask for it" stays distinguishable from "this asset has none". Fetching a single asset by token always returns it. See Agent Context.created_after(ISO 8601 timestamp or date, optional) — only assets uploaded strictly after this instant. A bare date (2026-09-22) means midnight UTC; an empty value means no filter, and an unparseable one is a422. See What's New Since My Last Run.
Example Request
curl "https://api.playbook.com/v1/coolclient-ltd/boards/boardToken1234/assets?page=1&per_page=20" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
Example Response
{
"data": [
{
"id": 987,
"token": "assetToken111",
"title": "hero.jpg",
"display_url": "https://cdn.playbook.com/hero.jpg"
},
{
"id": 988,
"token": "assetToken222",
"title": "logo_dark.png",
"display_url": "https://cdn.playbook.com/logo_dark.png"
}
],
"pagy": { "current_page": 1, "page_items": 2, "total_pages": 1, "total_count": 2 }
}
What's New Since My Last Run
For a nightly sync, ask for the new uploads in a folder and everything under it, passing the time your previous run started:
curl "https://api.playbook.com/v1/coolclient-ltd/assets?collection_token=boardToken111&nested_assets=true&created_after=2026-09-22T02:00:00Z" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
created_after sees new uploads only. An asset that was edited, retagged, or moved into the
board from elsewhere keeps its original upload time and is not returned, and deletions are not
reported. The listing returns groups rather than their members, so an upload that auto-grouping
stacked into a group that already existed (the third or later file with the same name in the same
board) is not returned either. For those, pair it with an occasional full crawl (cursor=true), or
subscribe to the asset_added / asset_updated / asset_deleted webhooks —
asset_added fires for every upload, grouped or not.
An empty created_after= means no filter; an unparseable value is a 422.
Read Many Assets at Once
A listing row already carries description, colors, source_url, custom fields, dimensions
and comment counts, so a listing needs no second read. When you instead hold a
set of tokens (from search, a webhook, your own store), read up to 100 of them in a single request
rather than one at a time.
Endpoint
POST /{slug}/assets/batch_show
It is a POST only because the token list travels in the body; nothing is modified.
Body
asset_tokens(array of strings, required) — 1 to 100 tokens.
Query Parameters
get_ai_payload(boolean, optional) — include each asset'sai_agent_payload. Off by default here, unlike fetching a single asset by token, because 100 payloads of up to 4 KB each is a large response.
Example Request
curl -X POST "https://api.playbook.com/v1/coolclient-ltd/assets/batch_show" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"asset_tokens": ["assetToken111", "assetToken222", "goneToken"]}' | jq
Example Response
{
"data": {
"assets": [
{
"token": "assetToken111",
"title": "hero.jpg",
"size": 284913,
"source_url": "https://supplier.example/hero.jpg",
"display_url": "https://cdn.playbook.com/hero.jpg"
},
{
"token": "assetToken222",
"title": "logo_dark.png",
"size": 18220,
"source_url": null,
"display_url": "https://cdn.playbook.com/logo_dark.png"
}
],
"not_found": ["goneToken"]
}
}
Always check not_found. One unreadable token does not fail the call, so a token that did not
resolve is otherwise indistinguishable from one you forgot to send. A token lands there when it is
unknown, deleted, or not visible to you — the three are not distinguished on purpose.
Unlike the assets listing, this addresses assets by token, so a grouped child asset is returned
when you ask for it. The listing returns only top-level rows: a group's first_displayable_child
gives its first member, and GET /{slug}/assets/{token}/children lists them all (see
Retrieving Group Children).
About source_url
source_url is where an asset came from: the URL it was ingested from, for a saved link and for a
file Playbook fetched from a URL alike (the browser extension does the latter). It is null for
direct uploads, notes and colour swatches, which have no origin.
It is not a URL you can display or download from — use thumbnails, display_url or
permalink for that.
Playing a video: stream_url
Video assets carry stream_url, a signed HLS manifest (master.m3u8) you can hand straight to any
HLS player — hls.js, Safari, VLC, ffplay. The manifest is the whole contract: the quality playlists
and segments underneath it are authorized for you already, so there is nothing else to sign.
stream_url is a sibling of display_url, not a replacement. For a video display_url stays
the poster image, which is what you want for a grid or a <video poster=…>. Both are signed for
roughly 24 hours, exactly like thumbnails.
It is null for anything that is not a video, and for a video whose encode has not finished. What
you do about a null depends on the workspace's plan:
| Plan | What happens |
|---|---|
| Team, Business, Enterprise, and Pro or Team trials | The encode starts when the video is uploaded. Read the asset again in a few minutes and stream_url is there. |
| Free, Pro, Creative | Nothing is encoded up front. Reading the asset is what starts the encode — so the first GET /v1/{slug}/assets/{token} returns null and also queues the work. Read it again a few minutes later. |
Either way the only call you need is the ordinary asset read, and polling it is how you wait.
If stream_url never fills in, something went wrong — the encode failed, or on a plan that encodes
at upload it was never started. A null on its own does not tell you which, so the way to ask is:
curl -X POST "https://api.playbook.com/v1/$ORG/assets/$ASSET/retry_video_processing" \
-H "Authorization: Bearer $TOKEN"
A success means an encode has now been queued — go back to polling the asset. A 422 means there is
nothing to do: the encode already succeeded, or the workspace's plan does not include video
streaming.
Call it once, not in a loop. It cannot tell a running encode from a dead one, so calling it again while one is under way queues a second job for the same video. Poll the asset read, never this endpoint.
Error Handling
401 Unauthorized: invalid or missing token.404 Not Found: invalidslugorboard_token.406 Not Acceptable:asset_tokensmissing, empty, or not an array.422 Unprocessable Entity: invalid query parameters, or more than 100asset_tokens.
Tips & Next Steps
- Use
depth=all, or/descendantson a board, to get nested boards in one call. - Add
displayable_assets=falseto board reads when you only need the tree: large pages return much faster without the previews. - Use
created_afterwithnested_assets=truefor incremental syncs of new uploads. - Use
nested_assets=truewhen the board token might be a parent — otherwise you get only what sits directly in it. - List first, then
batch_showthe tokens you care about: two calls for a whole board, rather than one per asset. - Combine these endpoints to build dashboards or synced local caches of Playbook content.