# Published Page Design

A published board is a public web page. Its **design** — which elements sit in which region of the page, their content, and the page's background colour and typography — can be created and edited over the API, the same way the Publish editor does it.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

* **`write` scope** and permission to modify the board.
* **Page layout API writes enabled for your workspace.** Reads work everywhere; every write below answers `403 page_layout_api_disabled` until Playbook support enables it.

## 1. Discover the vocabulary[​](#1-discover-the-vocabulary "Direct link to 1. Discover the vocabulary")

```
curl "https://api.playbook.com/v1/page_layout_elements" \

  -H "Authorization: Bearer YOUR_TOKEN"
```

One global response (no workspace slug) describing everything you can write:

| Key                                 | What it holds                                                                                                                                                                                      |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `templates`                         | Valid template ids — the keys — each with `display_name`, default background colour and typography, and the `slots` (page regions) it mounts itself.                                               |
| `elements`                          | Every element type (`note`, `button`, `banner`, `section`, …) and the camelCase `props` it accepts. Flags mark props that are HTML, URLs, asset tokens, or `dead` (stored but no longer rendered). |
| `background_colors`, `typographies` | The values page settings accept.                                                                                                                                                                   |
| `operations`                        | The argument map for batch edits.                                                                                                                                                                  |
| `limits`                            | Hard payload caps, e.g. `max_batch_operations`.                                                                                                                                                    |

## 2. Publish with a template[​](#2-publish-with-a-template "Direct link to 2. Publish with a template")

```
curl -X POST "https://api.playbook.com/v1/my-org/boards/portfolio/publish" \

  -H "Authorization: Bearer YOUR_TOKEN" \

  -H "Content-Type: application/json" \

  -d '{"template": "standard"}'
```

A publish **with** a template creates the page design that the layout endpoints read and edit. A board published without one, and never saved in the Publish editor, is still public, but has no design: the layout endpoints answer `404 page_layout_not_found` until you publish it with a template.

| Response                                            | Meaning                                                                                                                                                                                                                                                                                                                                                    |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `422 unknown_template`                              | The id is not a template; the message lists the valid ids.                                                                                                                                                                                                                                                                                                 |
| `409 page_design_exists`                            | The page has stored element edits. They are never overwritten — publish without `template` to keep them, and edit the design instead. This does **not** protect a design that is only page settings (background colour, typography): a template publish replaces those. A page still following its template, never edited, simply switches to the new one. |
| `409 publication_conflict` / `page_layout_conflict` | Someone else changed the publication at the same moment. `retryable: true` — send the request again.                                                                                                                                                                                                                                                       |
| `503 template_catalogue_unavailable`                | Templates are temporarily unavailable. Retry later, or publish without a template.                                                                                                                                                                                                                                                                         |

Every board response (`GET /boards/{token}`, lists, children) carries the page state:

```
"published_page": { "active": true, "template": "standard", "url": "https://playbook.com/s/my-org/portfolio" }
```

`url` is `null` unless the page is live, and `template` can be set on a page that is not.

## 3. Read the page layout[​](#3-read-the-page-layout "Direct link to 3. Read the page layout")

```
curl "https://api.playbook.com/v1/my-org/boards/portfolio/page_layout" \

  -H "Authorization: Bearer YOUR_TOKEN"
```

```
{

  "data": {

    "uuid": "<uuid>",

    "template": "standard",

    "background_color": null,

    "typography": null,

    "slots": null,

    "subboards_settings": [],

    "version_token": "<opaque version token>"

  }

}
```

* **`slots: null` is normal.** A page nobody has edited stores no document and renders its template's defaults. Address such a page by slot **name** (`"slot_id": "description"`, from `templates[...].slots`): the first write saves the defaults and its response contains real ids for everything.
* `slots` and `subboards_settings` are returned verbatim. Their ids and camelCase keys are exactly what the write endpoints take back.
* `background_color` / `typography` `null` means the page inherits them.
* `subboards_settings` holds one full second document per nested board shown on the page. Edit one by sending its `collectionToken` as `subboard_token` on any write.

## 4. Edit elements[​](#4-edit-elements "Direct link to 4. Edit elements")

All paths below are under `/v1/{slug}/boards/{token}/page_layout`.

| Operation | Request                                                                                                                                                       |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Add       | `POST /elements` — `slot_id`, `at_index`, `element`; optional `parent_element_id` + `column_id`/`column_index`/`create_new_column` to insert into a `section` |
| Replace   | `PATCH /elements/{element_id}` — `slot_id`, `element`                                                                                                         |
| Move      | `POST /elements/{element_id}/move` — `from_slot_id`, `to_slot_id`, `at_index`                                                                                 |
| Delete    | `DELETE /elements/{element_id}?slot_id=…` — addressing in the **query string**                                                                                |
| Reorder   | `POST /slots/{slot_id}/reorder` — `ordered_ids`                                                                                                               |
| Batch     | `POST /operations` — up to 50 of the above, atomically                                                                                                        |

```
curl -X POST "https://api.playbook.com/v1/my-org/boards/portfolio/page_layout/elements" \

  -H "Authorization: Bearer YOUR_TOKEN" \

  -H "Content-Type: application/json" \

  -d '{

    "slot_id": "description",

    "at_index": 0,

    "element": { "id": "intro-note", "elementType": "note", "props": { "text": "<p>Spring collection</p>" } },

    "expected_version_token": "<version_token from the read>"

  }'
```

Rules that matter:

* **Supply your own `element.id`** on an add. Re-sending an identical add then succeeds without inserting twice; the same id with different content is `409 element_exists`.
* **`PATCH` replaces the element, not a field.** Every top-level key you send replaces the stored one wholesale, so `{"props": {"text": "…"}}` drops every other prop. Read the element, change it, send the whole `props` back.
* **Reorder takes the exact set.** `ordered_ids` must be the slot's current top-level ids — none missing, repeated or extra — or `422 reorder_id_mismatch` names the offenders.
* **Batches are all-or-nothing.** The first failing operation rolls the batch back; the error carries `operation_index`. Ids the server mints inside a batch cannot be addressed later in the same batch, so give new elements your own ids. One batch edits one document (the page, or one `subboard_token`).
* Props flagged as HTML or URLs must be strings. HTML is sanitized and unsafe URL schemes are dropped on write. An asset-token prop takes an asset token from your workspace; Playbook copies the asset into the page's library.

## 5. Page settings[​](#5-page-settings "Direct link to 5. Page settings")

```
curl -X PATCH "https://api.playbook.com/v1/my-org/boards/portfolio/page_layout" \

  -H "Authorization: Bearer YOUR_TOKEN" \

  -H "Content-Type: application/json" \

  -d '{"settings": {"backgroundColor": "dark", "typography": null}}'
```

A partial update: an omitted setting is untouched, `null` clears it so the page inherits again. Values outside `background_colors` / `typographies` are `422 invalid_page_setting`. A board following a workspace template stops following it after a real change, so a later template roll-out does not revert your edit.

## Working alongside people[​](#working-alongside-people "Direct link to Working alongside people")

* **Compare-and-swap.** Send the read's `version_token` as `expected_version_token` on any write. If the page changed in between you get `409 stale_document` and **nothing is applied** — read again, re-decide, re-send. Responses to a write that sent a token include the new `version_token`.
* **Did anything change?** `converged: true` means the goal already held (deleting an id that is not there, an identical add, a no-op move). `materialized: true` means the call saved the template defaults for a page that had no document. Both are **absent** when false. `PATCH` on an element never reports `converged`.
* **Freshness.** The API reflects a write immediately; the public page can serve the previous version for up to an hour.

## Errors[​](#errors "Direct link to Errors")

Errors use the standard envelope; key on `extensions.code`, and use `retryable` to decide whether to repeat the same request.

| Status | Codes                                                                                                                                                                                             |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 403    | `page_layout_api_disabled`, `board_not_authorized` (you cannot modify the board that governs this design), `board_read_only`                                                                      |
| 404    | `page_layout_not_found`, `slot_not_found`, `element_not_found`, `subboard_not_configured`                                                                                                         |
| 409    | `stale_document` (re-read), `element_exists` (new id or `PATCH`), `lock_wait_timeout` and `page_layout_library_locked` (retry as-is)                                                              |
| 422    | `invalid_parameter`, `invalid_prop_type`, `invalid_element_shape`, `ambiguous_slot_name`, `reorder_id_mismatch`, `invalid_page_setting`, `forbidden_prop`, `asset_not_found`, `element_too_large` |

## Over MCP[​](#over-mcp "Direct link to Over MCP")

The [MCP server](/docs/guides/mcp.md) exposes the same surface: `publish_board` (with `template`), `get_page_elements_catalog`, `get_page_layout`, `add_page_element`, `update_page_element`, `move_page_element`, `delete_page_element`, `reorder_page_slot`, `apply_page_operations` and `update_page_settings`.

One difference: `publish_board` called **without** `template` on a board that is **not live** and has no page design yet applies `standard` (or your workspace's own version of it), so the page is editable immediately. A live page and a board that already has a design are republished unchanged. The tool response's `template_applied` names the template applied, if any.

## Related API Endpoints[​](#related-api-endpoints "Direct link to Related API Endpoints")

* [Publish Board](/docs/api/publish-organization-collection.md)
* [Published Page Element Catalog](/docs/api/page-layout-elements.md)
* [Read Published Page Layout](/docs/api/page-layout.md)
* [Update Published Page Settings](/docs/api/update-page-layout-settings.md)
* [Add](/docs/api/add-page-element.md), [Update](/docs/api/update-page-element.md), [Move](/docs/api/move-page-element.md), [Delete](/docs/api/delete-page-element.md) Page Element
* [Reorder Page Slot](/docs/api/reorder-page-slot.md), [Apply Page Layout Operations](/docs/api/apply-page-layout-operations.md)
