Skip to main content

Fetching Data from the Playbook API

Retrieve key resources — organizations, boards, and assets—using simple GET calls.

Prerequisites​

  1. Access token: a valid Playbook API token with read permissions, sent in the Authorization: Bearer header.
  2. Organization Slug (slug): Your organization's unique identifier.
  3. 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 or all, optional, default 1) — 1 returns only top-level boards; a number returns boards down to that level; all returns every board in the workspace
  • query (string, optional) — filter by board title
  • displayable_assets (boolean, optional, default true) — each board carries up to three preview assets in displayable_assets. Building them is most of what this request costs, so pass false if you do not show previews; the field then comes back null. GET /{slug}/boards/{board_token}, /children and /descendants accept 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 reported asset_count counts 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's ai_agent_payload. Defaults to false, and the key is then absent from the row rather than null, 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 a 422. 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's ai_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:

PlanWhat happens
Team, Business, Enterprise, and Pro or Team trialsThe encode starts when the video is uploaded. Read the asset again in a few minutes and stream_url is there.
Free, Pro, CreativeNothing 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: invalid slug or board_token.
  • 406 Not Acceptable: asset_tokens missing, empty, or not an array.
  • 422 Unprocessable Entity: invalid query parameters, or more than 100 asset_tokens.

Tips & Next Steps​

  • Use depth=all, or /descendants on a board, to get nested boards in one call.
  • Add displayable_assets=false to board reads when you only need the tree: large pages return much faster without the previews.
  • Use created_after with nested_assets=true for incremental syncs of new uploads.
  • Use nested_assets=true when the board token might be a parent — otherwise you get only what sits directly in it.
  • List first, then batch_show the 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.