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, optional) — fetch nested children up to this level

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 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.

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 }
}

Read Many Assets at Once

Listing rows are deliberately lean. When you need the full record — description, colors, source_url, custom fields, dimensions, comment counts — ask for up to 100 assets in a single request instead of fetching them 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, since a group's children are reachable through its first_displayable_child.

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 when listing boards to fetch nested child boards in one call.
  • 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.