# Update Layers

> Add, duplicate, replace, edit, delete or restack layers on a template's pages, including saved animation timing

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

---

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 individual layers of a template without re-sending the design: add a layer, copy one, replace one, edit any of its fields (including saved animation timing and motion, which [Update Template Modifications](https://orshot.com/docs/api-reference/enterprise-update-modifications) can only apply per render), delete it, or change its stacking order. 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](https://orshot.com/docs/api-reference/enterprise-update-pages) |
| Layers on a page: add, replace, edit (incl. saved animation timing), delete, restack | **Update Layers** (this page) |
| 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-layers
```

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

**Save animation timing**
```js
// Store new entrance timing on a layer (by element id or parameter id)
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-layers", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <ORSHOT_API_KEY>",
  },
  body: JSON.stringify({
    operations: [
      {
        op: "update",
        parameter_id: "headline",
        page_id: "a1b2c3d4-5e6f-7890-abcd-ef1234567890", // or page: 1
        set: {
          transitions: { enter: { type: "slideInUp", duration: 0.6 }, showAt: 1.2 },
          style: { color: "#FFFFFF" }
        }
      }
    ]
  }),
});
```

**Add and restack**
```js
// Add a logo on page 2 above the background, move a badge to the top, delete a sticker
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-layers", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <ORSHOT_API_KEY>",
  },
  body: JSON.stringify({
    operations: [
      {
        op: "add",
        page: 2, // or page_id: "b2c3d4e5-6f78-9012-bcde-f23456789012"
        element: { type: "image", content: "https://cdn.example.com/logo.png", x: 40, y: 40, width: 160, height: 60 },
        position: { above: "bg-shape" }
      },
      { op: "move", element_id: "badge", position: "top" },
      { op: "delete", element_id: "sticker-3" }
    ]
  }),
});
```

**Response**
```json
{
  "success": true,
  "changed": true,
  "template": {
    "id": 12345,
    "name": "Spring Sale Carousel",
    "updated_at": "2026-10-02T09:20:11.042Z",
    "pages": [
      { "page": 1, "page_id": "a1b2c3d4-...", "name": "Cover" },
      { "page": 2, "page_id": "b2c3d4e5-...", "name": "Offer" }
    ]
  },
  "results": [
    { "index": 0, "op": "add", "status": "applied", "page": 2, "page_id": "b2c3d4e5-...", "element_id": "7f6e5d4c-..." },
    { "index": 1, "op": "move", "status": "applied", "page": 2, "page_id": "b2c3d4e5-...", "element_id": "badge" },
    { "index": 2, "op": "delete", "status": "applied", "page": 2, "page_id": "b2c3d4e5-...", "element_id": "sticker-3" }
  ]
}
```

**Error**
```json
{
  "error": "operations[0] (update): 2 layers have parameterId \"cta\" (on pages 2, 3). Add \"page_id\" or \"page\" to pick one, or target it by element_id. Nothing was saved.",
  "code": "ambiguous_layer",
  "details": [
    { "index": 0, "op": "update", "code": "ambiguous_layer", "error": "2 layers have parameterId \"cta\" (…)" }
  ]
}
```

## 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 layer operations, applied in order. See [Operations](#operations). |
| `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 on an existing layer name it with **`parameter_id`** (a dynamic layer, as listed in the `modifications` of [Get Studio Template](https://orshot.com/docs/api-reference/studio-template-get)) or **`element_id`** (any layer: returned in `results` by your own `add` and `duplicate` operations, and readable in the full design by first-party clients such as the Orshot MCP server). Add `page_id` or `page` to search one page only; it is required when the same id appears on several pages, and for `add`.

### 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_id` or `page`, `element` (layer object), `position` (optional) | Adds a layer, on top by default. A missing id is generated. |
| `duplicate` | layer target, `position` (optional, default just above the original) | Copies the layer with a new id. |
| `replace` | layer target, `element` | Replaces the whole layer. It keeps its id. |
| `update` | layer target, `set` (object) | Changes any layer fields: `content`, `style`, `position`, `dimensions` (these three merge, so `{ "style": { "color": "#F00" } }` keeps other styles), `transitions`, `motion`, `parameterId`, `opacity`, … A `null` value removes the field. Flat `x`, `y`, `width`, `height` are accepted and only change what you send. |
| `delete` | layer target | Deletes the layer. |
| `move` | layer target, `position` | Changes the stacking order. |

**`position`** is `"top"`, `"bottom"`, `{ "above": "<layer id>" }` or `{ "below": "<layer id>" }`, relative to layers on the same page. Only the moved layer changes; other layers keep their `zIndex`.

Layer objects use the same shape as the elements in `pages_data` (see [Anatomy of a Template](https://orshot.com/docs/definitions/template-anatomy)); `type` is one of `text`, `image`, `element`, `shape`, `video`, `waveform`. Layer operations never change page numbers.

## Response Fields

| Field | Type | Description |
| --- | --- | --- |
| `success` | Boolean | `true` when the request was valid |
| `changed` | Boolean | `false` when the operations changed nothing. 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: `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`, `page_id`, `element_id`, `parameter_id` when the layer has one, and `source_element_id` for `duplicate` |
| `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`, `use_element_field` | An operation isn't an object with a known `op`, or sends the layer as `layer` instead of `element` |
| 400 | `layer_target_required`, `layer_not_found`, `ambiguous_layer`, `layer_target_mismatch` | The target layer is missing, doesn't exist, matches several layers, or `element_id` and `parameter_id` disagree |
| 400 | `page_target_required`, `page_not_found`, `invalid_page`, `invalid_page_id`, `page_target_mismatch` | The page given doesn't exist, or `add` has no page |
| 400 | `invalid_layer_object`, `duplicate_layer_id`, `layer_id_mismatch` | The layer object is malformed, its id is taken on that page, or it doesn't match the layer it replaces |
| 400 | `set_required`, `field_not_settable` | `update` has no `set`, or tries to change `id` |
| 400 | `position_required`, `invalid_position` | `move` without a valid `position`, or a position that names a layer on another page |
| 400 | `allow_reindex_not_applicable` | `allow_reindex` was sent; it only applies to Update Pages |
| 400 | `invalid_motion` | The operations introduce a structural motion error |
| 400 | `template_too_large`, `no_pages` | The design would exceed 10 MB, or the template has no pages yet |
| 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) |