Update Template Modifications
Update text, images, videos, styles and spoken narration in template layers
Updated
/v1/studio/templates/{templateId}/update-modificationscurl -X PATCH "https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-modifications" \
-H "Authorization: Bearer <ORSHOT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"data": [],
"embed_user_id": "<EMBED_USER_ID>",
"base_updated_at": "<BASE_UPDATED_AT>"
}'const res = await fetch("https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-modifications", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
"data": [],
"embed_user_id": "<EMBED_USER_ID>",
"base_updated_at": "<BASE_UPDATED_AT>"
}),
});
const data = await res.json();import requests
response = requests.patch(
"https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-modifications",
headers={"Authorization": "Bearer <ORSHOT_API_KEY>"},
json={
"data": [],
"embed_user_id": "<EMBED_USER_ID>",
"base_updated_at": "<BASE_UPDATED_AT>"
},
)
data = response.json()$ch = curl_init("https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-modifications");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer <ORSHOT_API_KEY>",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"data" => [],
"embed_user_id" => "<EMBED_USER_ID>",
"base_updated_at" => "<BASE_UPDATED_AT>"
]));
$data = json_decode(curl_exec($ch), true);
curl_close($ch);require "net/http"
require "json"
uri = URI("https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-modifications")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer <ORSHOT_API_KEY>"
req["Content-Type"] = "application/json"
req.body = {
"data": [],
"embed_user_id": "<EMBED_USER_ID>",
"base_updated_at": "<BASE_UPDATED_AT>"
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)Enterprise Only
This endpoint is available only to first party clients like the Orshot MCP server and workspaces on the Enterprise plan. Check out Enterprise pricing to get access.
Update parameterized content directly without re-sending the full template structure. You can update text, images, videos, styles, and dynamic voiceover scripts across multiple pages in one request.
For detailed information about the template data structure, see Anatomy of a Template.
Which update endpoint?#
| You want to change | Use |
|---|---|
| Name, tags, the default canvas size, or the entire design at once | Update Template |
| Pages: add, duplicate, replace, edit settings (including page size), delete, reorder | Update Pages |
| Layers on a page: add, replace, edit (incl. saved animation timing), delete, restack | Update Layers |
| Values of existing dynamic layers: text, images, colors, style, position | Update Template Modifications (this page) |
Endpoint#
https://api.orshot.com/v1/studio/templates/:templateId/update-modificationsQuery Parameters#
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
includeThumbnails | Boolean | No | false | When true, thumbnails are regenerated for the pages that had modifications applied, and the response waits for all renders to complete. When false or absent, thumbnails regenerate asynchronously after the response. |
Request Examples#
// Update text and image on a single page
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-modifications", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
data: [
{
page: 1,
modifications: {
headline: "Flash Sale: 24 Hours Only!",
subtitle: "Don't miss out on amazing deals",
product_image: "https://cdn.example.com/sale-banner.png",
price: "$49.99"
}
}
],
embed_user_id: "your-external-user-id" // Optional
}),
});// Update multiple pages in a carousel template
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-modifications", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
data: [
{
page: 1,
modifications: {
headline: "Welcome to Our Store",
hero_image: "https://cdn.example.com/hero.png"
}
},
{
page: 2,
modifications: {
headline: "Featured Products",
product_1: "https://cdn.example.com/product1.png",
product_2: "https://cdn.example.com/product2.png"
}
},
{
page: 3,
modifications: {
headline: "Contact Us",
email: "hello@example.com",
phone: "+1 234 567 8900"
}
}
]
}),
});// Name the page by its id instead of its number.
// The id keeps pointing at the same page after pages are added, deleted or reordered.
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-modifications", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
data: [
{
page_id: "b2c3d4e5-6f78-9012-bcde-f23456789012",
modifications: {
headline: "Ends Sunday"
}
}
]
}),
});// Update content and styles together
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-modifications", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
data: [
{
page: 1,
modifications: {
// Content updates
headline: "Black Friday Sale",
discount: "70% OFF",
// Style updates using dot notation
"headline.fontSize": 72,
"headline.color": "#ffffff",
"discount.backgroundColor": "#ff0000",
"discount.color": "#ffffff"
}
}
]
}),
});{
"success": true,
"template_id": "12345",
"applied": [
{ "page": 1, "page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890", "key": "headline", "type": "text", "content": "Black Friday Sale" },
{ "page": 1, "page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890", "key": "discount", "type": "text", "content": "70% OFF" },
{ "page": 1, "page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890", "key": "product_image", "type": "image", "content": "https://cdn.example.com/shoe.png" },
{ "page": 1, "page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890", "key": "headline.fontSize", "type": "style", "prop": "fontSize", "content": 72 },
{ "page": 2, "page_id": "b2c3d4e5-6f78-9012-bcde-f23456789012", "key": "headline", "type": "text", "content": "Ends Sunday" }
]
}{
"success": true,
"template_id": "12345",
"applied": [
{ "page": 1, "page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890", "key": "headline", "type": "text", "content": "Black Friday Sale" },
{ "page": 1, "page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890", "key": "product_image", "type": "image", "content": "https://cdn.example.com/shoe.png" }
],
"failed": [
{
"page": 1,
"page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"key": "nonexistent_param",
"error": "Element with parameterId 'nonexistent_param' not found",
"reason": "param_not_found"
},
{
"page": 5,
"page_id": null,
"error": "Page 5 does not exist. Template has 3 page(s)."
}
],
"skipped": [
{
"page": 1,
"page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"key": "product_image.prompt",
"reason": "render_only_prompt",
"message": "AI prompt parameters ('product_image.prompt') apply to a single render and are not stored on the template"
}
]
}{
"success": true,
"template_id": "12345",
"applied": [
{ "page": 1, "page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890", "key": "headline", "type": "text", "content": "Black Friday Sale" },
{ "page": 2, "page_id": "b2c3d4e5-6f78-9012-bcde-f23456789012", "key": "headline", "type": "text", "content": "Ends Sunday" }
],
"thumbnails": {
"success": true,
"pages": [
{
"page": 1,
"page_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"thumbnail_url": "https://storage.orshot.com/cloud/w-50/renders/thumbnails/xy7.png",
"error": null
},
{
"page": 2,
"page_id": "b2c3d4e5-6f78-9012-bcde-f23456789012",
"thumbnail_url": "https://storage.orshot.com/cloud/w-50/renders/thumbnails/xy8.png",
"error": null
}
]
}
}Rate Limits#
| Limit | Value |
|---|---|
| Requests per minute | 30 |
| Max modifications per request | 50 |
The limit is 30 requests per minute per API key, shared by every /v1/studio template endpoint, including template reads. Renders are not counted.
URL Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
templateId | Integer | Yes | The ID of the template to update |
Request Body Structure#
The request body uses a data array format, where each item names a page (by number with page, or by id with page_id) and the modifications to apply:
{
"data": [
{
"page": 1,
"modifications": {
"headline": "New Headline Text",
"product_image": "https://cdn.example.com/new-image.png",
"price": "$99.99"
}
}
]
}Request Body Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
data | Array | Yes | Array of page modification objects |
embed_user_id | String | No | Assign or update the template's embed user. Accepts either an external user ID (resolved using your workspace's embed configuration) or an Orshot internal ID (prefixed with eui_). Same rules as in Create Studio Template: a new external ID needs Per-User Data turned on and counts toward your plan's embed user limit. |
base_updated_at | String | No | The template's updated_at from your last read of Get Studio Template. If the template changed since, nothing is written and you get 409. Must be a timestamp string: null is refused with 400. Without it, the patch applies to the latest version. |
Data Object Structure#
Each object in the data array should include:
| Field | Type | Required | Description |
|---|---|---|---|
page | Integer | One of page or page_id | Page number (1-indexed) to apply modifications to. Page numbers change when pages are added, deleted or reordered. |
page_id | String | One of page or page_id | The page's id, from Get Studio Template or from page_id in any response of this endpoint. It never changes, so it keeps pointing at the same page after a reorder. If you send both, they must point at the same page. |
modifications | Object | Yes | Key-value pairs of parameterId to new value |
elementId | String | No | Target one layer by its element id instead of by parameterId, including layers that aren't dynamic. Use the key element for its content and element.<property> for its style, for example { "element": "New text", "element.fontSize": "64px", "element.color": "#FF0000" }. Fails with element_not_found or duplicate_element_id when the id matches no layer or several on that page. |
Supported Modification Types#
Text Content#
Update text element content by its parameterId:
{
"page": 1,
"modifications": {
"headline": "Summer Sale 2026!",
"subtitle": "Up to 50% off all items",
"cta_text": "Shop Now"
}
}Image URLs#
Update image sources by providing a URL:
{
"page": 1,
"modifications": {
"product_image": "https://cdn.example.com/product.png",
"logo": "https://cdn.example.com/logo.svg",
"background": "https://cdn.example.com/bg.jpg"
}
}Spoken Narration#
If a generated voiceover has a tts.parameterId, pass a new string under that ID to replace its spoken script:
{
"page": 1,
"modifications": {
"voiceover": "A new launch story for this customer."
}
}The new narration is generated when the template is saved and bills AI credits: 1 per started 10 seconds of speech, 2 for premium voices.
Style Properties#
Update style properties using dot notation:
{
"page": 1,
"modifications": {
"headline.fontSize": 48,
"headline.color": "#ff0000",
"cta_button.backgroundColor": "#007bff",
"subtitle.fontFamily": "Inter"
}
}Response Fields#
| Field | Type | Description |
|---|---|---|
success | Boolean | Whether the operation completed |
template_id | String | ID of the updated template, as sent in the URL |
applied | Array | List of successfully applied modifications |
failed | Array | List of failed modifications (only if any failed) |
skipped | Array | Valid render-only modifications that are not stored (only if any were skipped) |
thumbnails | Object | Only present when ?includeThumbnails=true. See Thumbnails Object Structure below |
Which array a key lands in#
| Array | Present when | Meaning |
|---|---|---|
applied | always | The value was written to the template |
failed | only when non-empty | The key did not land and you need to change something: unknown parameter, rejected value, ambiguous key |
skipped | only when non-empty | The key is valid for a render but has nothing to store on a template, so there is nothing to fix |
failed and skipped are omitted from the response when they are empty, so a
clean patch returns applied only. Check data.failed before treating a patch
as fully successful.
Applied/Failed Object Structure#
| Field | Type | Description |
|---|---|---|
page | Integer | Page number where the modification was applied/failed. A pageN@ key is reported against the page it addressed, not the page of the enclosing item |
page_id | String | Stable id of the page where the modification was applied/failed. null if the requested page number does not exist on the template. For an item that failed on its page_id, the id you sent |
key | String | The parameter ID or style path |
type | String | Type of modification: text, image, video, shape, container, canvas, element, or style |
paramType | String | Canonical parameter type, including voiceText for dynamic narration (when applicable) |
paramId | String | The parameter ID the key resolved to. null for a layer targeted by elementId that has no parameter ID (applied items only) |
op | String | What was written: content, style, position, alt, transform or tts.text (applied items only) |
elementId | String | The element id, when the item used elementId |
prop | String | The property that was written, for style items (fontSize, x, width, alt, …). Absent for content items |
content | String | The applied value (only for applied items) |
error | String | Error message (only for failed items) |
reason | String | Stable machine-readable code for the failure, for per-key failures and page_id failures (see below). Absent on other item-level failures |
Keys you need to act on come back in failed with a reason code:
| Reason | Meaning |
|---|---|
param_not_found | No element on the template carries that parameter ID |
element_not_found | elementId matches no layer on that page |
duplicate_element_id | elementId matches more than one layer on that page |
invalid_content_value | Value rejected by validation (error carries the specific message) |
invalid_voice_text | Dynamic narration must be a non-empty string |
non_string_value / invalid_value / invalid_crop_value | The value type does not fit the parameter or property |
render_only_inline | Inline text parameters (headline:name) apply per render; send the whole headline value instead |
bare_content_multi_page | Ambiguous key on a multi-page template; prefix it with pageN@ |
page_not_found | The pageN@ prefix or the page_id names a page the template does not have |
invalid_page_id | page_id is not a non-empty string |
invalid_page | page was sent next to page_id and is not a positive integer |
page_target_mismatch | page and page_id were both sent and point at different pages |
unsupported_key / unsupported_type / unsupported_element_type | The key or the element type has no stored home for that kind of value |
sanitized_placeholder | The value is a log stand-in for uploaded bytes ([base64 ...]), not real content |
Keys that are legitimate for a render but have no home on a stored template come
back in skipped with a reason and a human-readable message. They are
informational: send them to a render call instead.
| Reason | Meaning |
|---|---|
overridden_by_page_key | A pageN@ key for the same parameter was also sent and won for that page |
render_only_prompt | AI prompt keys (photo.prompt, .promptMode, .promptReferences) |
render_only_transition | Transition dynamics (.showAt, .hideAt, .enter*, .exit*) |
render_only_video | Video dynamics on a video element (.trimStart, .muted, .loop) |
not_persisted_href / not_persisted_contentType | .href and .contentType are read per render, never stored |
Thumbnails Object Structure#
When ?includeThumbnails=true is passed, a PNG thumbnail is regenerated for only the pages that had modifications applied (not all pages). The thumbnails object in the response has this shape:
| Field | Type | Description |
|---|---|---|
success | Boolean | true if at least one page rendered successfully |
pages[].page | Integer | 1-based page number |
pages[].page_id | String | Stable id of the page |
pages[].thumbnail_url | String|null | Public URL of the new thumbnail PNG. null if this page failed |
pages[].error | String|null | Error message if this page failed. null on success |
When ?includeThumbnails is not passed, thumbnails regenerate in the background for the modified pages and appear on the template shortly after the response.
Supported Style Properties#
When using dot notation for style updates, the following properties are commonly supported:
| Property | Type | Description |
|---|---|---|
fontSize | Number | Font size in pixels |
fontFamily | String | Font family name |
color | String | Text color (hex, rgb, rgba) |
backgroundColor | String | Background color |
opacity | Number | Opacity (0-1) |
fontWeight | String/Number | Font weight (normal, bold, 100-900) |
textAlign | String | Text alignment (left, center, right) |
letterSpacing | Number | Letter spacing in pixels |
lineHeight | Number | Line height multiplier |
Error Responses#
| Status Code | Error | Description |
|---|---|---|
| 400 | data required | Request body missing data array |
| 400 | data array empty | Empty data array |
| 400 | Maximum 50 total modifications per request | Too many modifications in one request |
| 400 | Invalid base_updated_at | base_updated_at is not the updated_at timestamp from Get Studio Template |
| 401 | Unauthorized | The OAuth token is invalid or expired. Refresh it |
| 403 | Access Forbidden | Invalid API key, your plan does not include this endpoint, or a payment is overdue (see code) |
| 404 | Template not found | The template isn't in your workspace |
| 409 | Template changed | base_updated_at is stale, or the template kept changing while the patch was applied. The body has conflict: "stale" and detail.updated_at. Fetch the template again and retry |
| 423 | stage_locked | An approval flow locks the template. See Approvals |
| 429 | Rate limit exceeded | Too many requests (max 30/min) |
| 500 | Internal server error | Server-side error |
Invalid per-item input (a non-positive page, an unknown page_id, or modifications that isn't an object) does not fail the request: the item is reported in the failed array and the response still returns 200.
Cache Invalidation#
When modifications are applied:
- Template render cache is automatically invalidated
- Dynamic URL cache for the template is cleared
- Subsequent render requests will use the updated content
Best Practices#
- Use Parameter IDs: Ensure your template has
parameterIdset on elements you want to update. A layer with aparameterIdcan be patched even if it is not marked dynamic - Batch Updates: Combine multiple page modifications in a single request to reduce API calls
- Validate URLs: Ensure image URLs are accessible and return valid images
- Error Handling: Always check the
failedarray in responses for partial failures. It is omitted when every key landed, so testdata.failedbefore reading its length. Keys inskippedneed no action - Style Consistency: When updating styles, ensure values are compatible with the element type
Ready to automate?
Start rendering images, PDFs and videos from your templates in under 2 minutes. Free plan, no credit card.
Get your API key- Image, PDF and video generation via API
- Visual editor with AI and smart layouts
- Zapier, Make, MCP and 40+ integrations
- White-label embed for your own app
- 100 free credits a month, no credit card required