API Conventions
Behaviors that hold across every Playbook API endpoint. If you are building an integration, or an AI agent, against Playbook, treat this page as ground truth.
Base URL
All REST endpoints are served under:
https://api.playbook.com/v1
Most endpoints are scoped to an organization (workspace) slug, for example /v1/{org}/assets.
Authentication
Every request authenticates with a Bearer token in the Authorization header:
Authorization: Bearer YOUR_TOKEN
Create and manage tokens yourself in the Playbook app, with no approval step. See Getting Started.
Scopes
Tokens carry scopes, enforced at the API layer:
readlists and fetches boards, assets, comments, and search results.writecreates, updates, moves, deletes, uploads, comments, shares, and publishes.
A write call made with a read-only token returns 403. Never ship a token in client-side code or paste it into a chat with an AI model. Configure it once as a request header.
Response envelope
Successful responses wrap the payload in a top-level data key:
{ "data": { "token": "abc123", "title": "Hero shot" } }
List endpoints return data as an array. Always read from data.
Status codes and errors
| Code | Meaning |
|---|---|
200 | Success. |
401 | Authentication failed. Check your Bearer token. |
403 | Permission denied. Check the token's read / write scopes. |
404 | Not found. Check the organization slug, board token, or asset token. |
406 | Invalid input (used by batch URL ingest validation). |
422 | Validation error. The response body explains which field failed. |
402 | Monthly request quota exhausted (see below). |
429 | Rate limited. Back off and retry (see below). |
5xx | Playbook API error. Retry with backoff. |
Error responses include a human-readable message. Tokens are never echoed back in error bodies.
Every error body uses the same envelope, with a machine-readable code:
{
"errors": [
{
"message": "API quota exhausted: 20000/20000 requests this period",
"extensions": { "code": "api_quota_exceeded" }
}
]
}
Rate limiting
Requests are throttled per token at 120 requests per minute, the same for every plan. Exceeding it returns 429 Too Many Requests. Use the response headers instead of guessing a delay:
| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before retrying. |
RateLimit-Limit | Requests allowed in the current short-term window. |
RateLimit-Remaining | Requests remaining; 0 on a throttled response. |
RateLimit-Reset | Seconds until the short-term window resets. |
The JSON body also carries extensions.code: "rate_limit_exceeded" and includes the retry delay in its message. Wait at least Retry-After seconds before retrying; use exponential backoff only as additional protection for repeated 429 or 5xx responses.
Rate limiting is short-term and independent of the monthly quota below. A 429 clears within a minute; a 402 does not.
Monthly request quota
Every request to an organization-scoped endpoint (any path carrying a {slug}) counts against that organization's monthly allowance. Requests that fail validation or authorization still count — a client burning its allowance on malformed requests is generating real load.
| Plan | Included requests / month | Ceiling with usage billing |
|---|---|---|
| Free | 1,000 | — |
| Pro | 20,000 | 200,000 |
| Team | 200,000 | 1,000,000 |
| Business | 5,000,000 | — |
| Enterprise | Custom | Custom |
See pricing for full plan details.
Quota headers
Every response from an organization-scoped endpoint carries the current state:
| Header | Meaning |
|---|---|
X-API-Quota-Limit | Requests included in the plan this period. |
X-API-Quota-Remaining | Requests left in the included allowance. Reaches 0 and stays there once usage crosses into the billed band. |
X-API-Quota-Reset | When the counter rolls over (ISO 8601). |
Read X-API-Quota-Remaining and slow down before you hit zero rather than reacting to the 402.
When the quota runs out
Requests return 402 with the code api_quota_exceeded and a Retry-After header holding the seconds until the counter resets.
402 rather than 429 is deliberate: an exhausted monthly quota stays exhausted for days, so a client that treats it as a 429 and retries with backoff would hammer the API for the rest of the period without ever succeeding. Do not retry a 402 — either wait for X-API-Quota-Reset or raise the ceiling.
Usage billing
On Pro and Team, a workspace admin can lift the wall from the included allowance to the ceiling by enabling usage billing on the Developers tab in the Playbook app. It requires a payment method on file. Requests beyond the included allowance are then billed per request on the next invoice, alongside seats and storage — there is no separate invoice.
Until it is enabled, the included allowance is the wall. Nothing is ever billed without that explicit opt-in.
When the counter resets
The window follows the workspace's monthly billing date, so it is monthly even on an annual plan. Workspaces without a paid subscription reset on the first of the calendar month. X-API-Quota-Reset always carries the authoritative timestamp — do not assume the 1st.
Pagination
List endpoints accept page and per_page query parameters:
GET /v1/{org}/assets?page=2&per_page=50
GET /v1/{org}/assets also supports stable keyset pagination for a complete workspace crawl:
GET /v1/{org}/assets?cursor=true&per_page=100
GET /v1/{org}/assets?cursor=true&per_page=100&after_cursor=OPAQUE_VALUE
Read pagy.has_next_page to decide whether to continue, and pass pagy.after_cursor back unchanged. Cursor mode uses the default descending created_at order and cannot be combined with page or ascending sort. It is a full crawl and reconcile mechanism, not a change feed: it does not report deletions or only the records changed since the last crawl.
Async ingest
Uploads are asynchronous. Creating an asset (from a URL, a batch of URLs, or a signed upload) returns immediately with a skeleton asset. The row exists, but the bytes are still being fetched and processed.
Poll GET /v1/{org}/assets/{token} until is_skeleton is false, then check:
media_typepopulated means the asset processed successfully.source_errornon-null means ingest failed (the message is the latest worker error).is_link: truemeans it was kept as a bare link (only whenas_link: truewas passed).
For batch URL ingest, pass a uuid per item to correlate each async result back to your request. See the Upload guide, Async Ingest Semantics for the full contract and recommended polling cadence.
Driving Playbook from an AI agent
Everything above is also available to AI assistants through the hosted MCP server, which exposes the same operations as tools, with the same scopes and the same async semantics, and can read these docs at runtime via read_documentation. See the MCP guide.