# Update Pages

> Add, duplicate, replace, edit, delete or reorder pages of a template without re-sending the whole design

- **URL**: https://orshot.com/docs/api-reference/enterprise-update-pages

---

This endpoint is available only to first party clients like the
[Orshot MCP server](https://orshot.com/agents) and workspaces on the
Enterprise plan. Checkout the [Enterprise
Pricing](https://orshot.com/pricing) to get access.

Change one or more pages of a template in a single request: add a page, duplicate one, replace one, change its settings (name, canvas, background, audio, subtitles, duration), delete it or move it, without re-sending the whole design to [Update Template](https://orshot.com/docs/api-reference/enterprise-template-update). Only pages whose content changed get a new thumbnail and animated preview; every other page keeps the one that already matches it.

## Which update endpoint?

| You want to change | Use |
| --- | --- |
| Name, tags, canvas size, or the entire design at once | [Update Template](https://orshot.com/docs/api-reference/enterprise-template-update) |
| Pages: add, duplicate, replace, edit settings, delete, reorder | **Update Pages** (this page) |
| Layers on a page: add, replace, edit (incl. saved animation timing), delete, restack | [Update Layers](https://orshot.com/docs/api-reference/enterprise-update-layers) |
| Values of existing dynamic layers: text, images, colors, style, position | [Update Template Modifications](https://orshot.com/docs/api-reference/enterprise-update-modifications) |

## Endpoint

```markdown tab="Endpoint"
https://api.orshot.com/v1/studio/templates/:templateId/update-pages
```

## Query Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `includeThumbnails` | Boolean | No | `false` | When `true`, the response waits for the new thumbnails of the changed pages. Otherwise they are generated in the background. |
| `includeVideoThumbnails` | Boolean | No | `false` | When `true` and at most 2 changed pages are animated, the response waits for their animated previews. Otherwise they are generated in the background. |

## Request Examples

**Add a page**
```js
// Append a page (no existing page changes number)
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-pages", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <ORSHOT_API_KEY>",
  },
  body: JSON.stringify({
    base_updated_at: "2026-10-02T09:12:44.120Z", // from GET /v1/studio/templates/12345
    operations: [
      {
        op: "add",
        page_data: {
          name: "Pricing",
          canvas: { width: 1080, height: 1080, backgroundColor: "#0F172A" },
          elements: [
            { type: "text", content: "Plans from $9", x: 80, y: 120, width: 920, height: 120, parameterizable: true, parameterId: "price_line" }
          ]
        }
      }
    ]
  }),
});
```

**Edit page settings**
```js
// Rename page 2 and change its background; canvas merges, so width/height stay
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-pages", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <ORSHOT_API_KEY>",
  },
  body: JSON.stringify({
    operations: [
      // By page id (stays valid after pages are reordered). By number: { op: "update", page: 2, set: { ... } }
      { op: "update", page_id: "b2c3d4e5-6f78-9012-bcde-f23456789012", set: { name: "Offer", canvas: { backgroundColor: "#FFFFFF" } } }
    ]
  }),
});
```

**Duplicate and reorder**
```js
// Copy the cover to the end, then move it to the front.
// Moving changes existing page numbers, so allow_reindex is required.
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-pages", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <ORSHOT_API_KEY>",
  },
  body: JSON.stringify({
    allow_reindex: true,
    operations: [
      { op: "duplicate", page: 1 }, // or page_id: "a1b2c3d4-..."
      { op: "move", page: 4, position: 1 } // the copy is page 4 at this point; its page_id is in the response
    ]
  }),
});
```

**Response**
```json
{
  "success": true,
  "changed": true,
  "template": {
    "id": 12345,
    "name": "Spring Sale Carousel",
    "updated_at": "2026-10-02T09:14:03.511Z",
    "pages": [
      { "page": 1, "page_id": "f0e1d2c3-...", "name": "Cover" },
      { "page": 2, "page_id": "a1b2c3d4-...", "name": "Cover" },
      { "page": 3, "page_id": "b2c3d4e5-...", "name": "Offer" },
      { "page": 4, "page_id": "c3d4e5f6-...", "name": "Back" }
    ]
  },
  "results": [
    { "index": 0, "op": "duplicate", "status": "applied", "page_id": "f0e1d2c3-...", "source_page_id": "a1b2c3d4-...", "page": 1 },
    { "index": 1, "op": "move", "status": "applied", "page_id": "f0e1d2c3-...", "page": 1 }
  ],
  "reindexed": [
    { "page_id": "a1b2c3d4-...", "from": 1, "to": 2 },
    { "page_id": "b2c3d4e5-...", "from": 2, "to": 3 },
    { "page_id": "c3d4e5f6-...", "from": 3, "to": 4 }
  ]
}
```

**Error**
```json
{
  "error": "These operations change the numbers of existing pages (page 1 → 2, page 2 → 3, page 3 → 4). Render calls that address pages by number (\"page2@headline\", includePages) would then hit different pages. Send \"allow_reindex\": true to confirm, or add/duplicate pages at the end instead. Nothing was saved.",
  "code": "reindex_not_allowed",
  "details": [
    { "index": null, "code": "reindex_not_allowed", "error": "These operations change the numbers of existing pages (…)" }
  ]
}
```

## Rate Limits

This endpoint shares the Enterprise limit of **30 requests per minute** per API key with the other template endpoints.

## URL Parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `templateId` | Integer | Yes | The ID of the template to update |

## Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `operations` | Array | Yes | 1 to 50 page operations, applied in order. See [Operations](#operations). |
| `allow_reindex` | Boolean | No | Required (`true`) when the operations change the number of any existing page: deleting a page that isn't the last, inserting or duplicating somewhere other than the end, or moving. |
| `base_updated_at` | String | No | The template's `updated_at` from your last read. If the template changed since, nothing is written and you get `409`. Without it, operations apply to the latest version. |
| `embed_user_id` | String | No | Assign the template to an embed user, same as [Update Template Modifications](https://orshot.com/docs/api-reference/enterprise-update-modifications). |

## Operations

Every operation has an `op`. Operations that act on an existing page name it with **`page_id`** (stable, from [Get Studio Template](https://orshot.com/docs/api-reference/studio-template-get)) or **`page`** (1-based number). A page number means the number at that point in the request, after earlier operations; `page_id` never changes, so it is the safer target.

### Page number or page id

Anywhere an operation names a page, send either one:

| Field | Type | What it is |
| --- | --- | --- |
| `page_id` | String | The page's id. It never changes, so it keeps pointing at the same page after pages are added, deleted or reordered. Read it from [Get Studio Template](https://orshot.com/docs/api-reference/studio-template-get) or from `template.pages` in any response of this endpoint. |
| `page` | Integer | The page number, starting at 1. Simple for a one-off edit, but it changes when pages are added, deleted or moved. |

If you send both, they must point at the same page, otherwise the request is refused with `page_target_mismatch`.

| `op` | Fields | What it does |
| --- | --- | --- |
| `add` | `page_data` (page object), `position` (optional page number, default after the last page) | Adds a new page. Missing page and layer ids are generated. |
| `duplicate` | `page_id` or `page`, `position` (optional, default after the last page) | Copies a page with new page and layer ids. |
| `replace` | `page_id` or `page`, `page_data` | Replaces the page's whole content. The page keeps its id. |
| `update` | `page_id` or `page`, `set` (object) | Changes page settings: `name`, `canvas` (merged, so `{ "backgroundColor": "#000" }` keeps the size), `audioTracks`, `subtitle`, `videoDuration`, `motion`. A `null` value removes the field. Layers can't be changed here; use [Update Layers](https://orshot.com/docs/api-reference/enterprise-update-layers). |
| `delete` | `page_id` or `page` | Deletes the page. A template always keeps at least one page. |
| `move` | `page_id` or `page`, `position` | Moves the page to that page number. |

Page objects use the same shape as `pages_data` in [Create Studio Template](https://orshot.com/docs/api-reference/enterprise-template-create). Layers may use the flat `x`, `y`, `width`, `height` and `src` fields.

### Page numbers and `allow_reindex`

Render calls address pages by number: `"page2@headline"` modification keys, `includePages`, and the `page` field of Update Template Modifications. If page 2 becomes page 3, those calls silently hit a different page. So any operation list that changes the number of an existing page is refused unless you send `"allow_reindex": true`, and the response's `reindexed` array lists every page that moved. Adding or duplicating at the end, and deleting the last page, never renumber anything.

## Response Fields

| Field | Type | Description |
| --- | --- | --- |
| `success` | Boolean | `true` when the request was valid |
| `changed` | Boolean | `false` when the operations changed nothing (for example setting a name to its current value). Nothing is written and the version does not move. |
| `template.id` | Integer | Template ID |
| `template.name` | String | Template name |
| `template.updated_at` | String | The new version. Send it as `base_updated_at` on your next write. |
| `template.pages` | Array | Every page after the operations: `page` (number), `page_id`, `name` |
| `template.thumbnails` | Object | Only with `?includeThumbnails=true`: the new thumbnails of the changed pages |
| `results` | Array | One entry per operation: `index`, `op`, `status` (`applied` or `unchanged`), `page` (final number), `page_id`, and `source_page_id` for `duplicate` |
| `reindexed` | Array | Only when page numbers changed: `{ page_id, from, to }` per existing page that moved |
| `warnings` | Array | Only when present: non-blocking notes, such as unknown motion keys |

## Errors

Every error explains what to change. A `400` means nothing was saved: operations are all-or-nothing.

| Status | `code` | When |
| --- | --- | --- |
| 400 | `operations_required`, `operations_empty`, `too_many_operations` | `operations` is missing, empty, or longer than 50 |
| 400 | `unknown_op`, `invalid_operation` | An operation isn't an object with a known `op` |
| 400 | `page_object_in_page` | A page object was sent as `page`; send it as `page_data` |
| 400 | `page_target_required`, `page_not_found`, `invalid_page`, `invalid_page_id`, `page_target_mismatch` | The target page is missing, doesn't exist, or `page` and `page_id` disagree. The message lists the pages that exist. |
| 400 | `invalid_page_object`, `duplicate_page_id`, `page_id_mismatch` | The page object is malformed, its id is taken, or it doesn't match the page it replaces |
| 400 | `set_required`, `field_not_settable`, `invalid_canvas` | `update` has no `set`, tries to change `elements`, `id` or generated fields, or has an invalid canvas |
| 400 | `position_required`, `invalid_position` | `move` without a valid `position` |
| 400 | `last_page`, `too_many_pages`, `template_too_large` | Deleting the only page, going past 200 pages, or leaving a design larger than 10 MB |
| 400 | `no_pages` | The template has no pages yet; send the full design with Update Template first |
| 400 | `reindex_not_allowed`, `invalid_allow_reindex` | Existing pages would be renumbered without `"allow_reindex": true` |
| 400 | `invalid_motion` | The operations introduce a structural motion error |
| 403 | | The API key, plan or trial access doesn't include this endpoint |
| 404 | | The template isn't in your workspace |
| 409 | | `base_updated_at` is stale: fetch the template again and redo the operations on that version |
| 423 | | An approval flow locks the template; see [Approvals](https://orshot.com/docs/api-reference/approvals-overview) |