Create Studio Template
Create a single studio template programmatically via API
Updated
/v1/studio/templates/createcurl -X POST "https://api.orshot.com/v1/studio/templates/create" \
-H "Authorization: Bearer <ORSHOT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "<NAME>",
"canvas_width": 1,
"canvas_height": 1,
"description": "<DESCRIPTION>"
}'const res = await fetch("https://api.orshot.com/v1/studio/templates/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
"name": "<NAME>",
"canvas_width": 1,
"canvas_height": 1,
"description": "<DESCRIPTION>"
}),
});
const data = await res.json();import requests
response = requests.post(
"https://api.orshot.com/v1/studio/templates/create",
headers={"Authorization": "Bearer <ORSHOT_API_KEY>"},
json={
"name": "<NAME>",
"canvas_width": 1,
"canvas_height": 1,
"description": "<DESCRIPTION>"
},
)
data = response.json()$ch = curl_init("https://api.orshot.com/v1/studio/templates/create");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer <ORSHOT_API_KEY>",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"name" => "<NAME>",
"canvas_width" => 1,
"canvas_height" => 1,
"description" => "<DESCRIPTION>"
]));
$data = json_decode(curl_exec($ch), true);
curl_close($ch);require "net/http"
require "json"
uri = URI("https://api.orshot.com/v1/studio/templates/create")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer <ORSHOT_API_KEY>"
req["Content-Type"] = "application/json"
req.body = {
"name": "<NAME>",
"canvas_width": 1,
"canvas_height": 1,
"description": "<DESCRIPTION>"
}.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.
Create a new studio template in your workspace programmatically. This endpoint allows you to define the template structure, dimensions, and optionally include pre-built pages with elements.
For detailed information about the template data structure, see Anatomy of a Template.
Endpoint#
https://api.orshot.com/v1/studio/templates/createQuery Parameters#
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
includeThumbnails | Boolean | No | false | When true, the endpoint renders a thumbnail for every page and waits for all uploads before responding. When false or absent, thumbnails are generated asynchronously after the response is sent. |
includeVideoThumbnails | Boolean | No | false | When true and at most 2 pages are animated, the response waits for their animated previews and returns them in template.pages_meta[].video_thumbnail_url. Otherwise they are generated in the background. |
Request#
// Create a blank template with specified dimensions
await fetch("https://api.orshot.com/v1/studio/templates/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
name: "Product Banner",
description: "E-commerce product showcase banner",
canvas_width: 1200,
canvas_height: 628,
embed_user_id: "your-external-user-id" // Optional
}),
});// Create a template with pre-built page structure
await fetch("https://api.orshot.com/v1/studio/templates/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
name: "Social Media Post",
description: "Instagram post template",
canvas_width: 1080,
canvas_height: 1080,
pages_data: [
{
id: "page-1",
name: "Page 1",
canvas: {
width: 1080,
height: 1080,
backgroundColor: "#ffffff"
},
elements: [
{
id: "headline-element",
name: "Headline",
type: "text",
position: { x: 100, y: 200 },
dimensions: { width: 880, height: 100 },
content: "Your Headline Here",
parameterId: "headline",
parameterizable: true
},
{
id: "image-element",
name: "Product Image",
type: "image",
position: { x: 100, y: 350 },
dimensions: { width: 880, height: 500 },
content: "https://example.com/placeholder.png",
parameterId: "product_image",
parameterizable: true
}
],
modifications: []
}
]
}),
});{
"success": true,
"template": {
"id": 12345,
"name": "Social Media Post",
"canvas_width": 1080,
"canvas_height": 1080,
"tags": [],
"pages_count": 1,
"modifications": [
{
"key": "headline",
"id": "headline",
"type": "text",
"description": "Headline — Text content",
"helpText": "Headline — Text content",
"help_text": "Headline — Text content",
"element_name": "Headline",
"example": "Your Headline Here",
"page_number": 1,
"page_id": "page-1"
},
{
"key": "product_image",
"id": "product_image",
"type": "imageUrl",
"description": "Product Image — Image URL",
"helpText": "Product Image — Image URL",
"help_text": "Product Image — Image URL",
"element_name": "Product Image",
"example": "https://example.com/placeholder.png",
"page_number": 1,
"page_id": "page-1"
}
],
"created_at": "2026-01-29T10:30:00.000Z"
}
}{
"success": true,
"template": {
"id": 12345,
"name": "Social Media Post",
"canvas_width": 1080,
"canvas_height": 1080,
"tags": [],
"pages_count": 1,
"modifications": [
{
"key": "headline",
"id": "headline",
"type": "text",
"description": "Headline — Text content",
"helpText": "Headline — Text content",
"help_text": "Headline — Text content",
"element_name": "Headline",
"example": "Your Headline Here",
"page_number": 1,
"page_id": "page-1"
}
],
"created_at": "2026-01-29T10:30:00.000Z",
"thumbnails": {
"success": true,
"pages": [
{
"page": 1,
"page_id": "page-1",
"thumbnail_url": "https://storage.orshot.com/cloud/w-50/renders/thumbnails/abc123.png",
"error": null
}
]
}
}
}Rate Limits#
| Limit | Value |
|---|---|
| Requests per minute | 30 |
The limit is 30 requests per minute per API key, shared by every /v1/studio template endpoint, including template reads. Renders are not counted.
Request Body Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Name of the template (max 255 characters) |
description | String | No | Description of the template |
canvas_width | Integer | Yes | Width of the canvas in pixels (1-5000) |
canvas_height | Integer | Yes | Height of the canvas in pixels (1-5000) |
pages_data | Array | No | Array of page objects with elements. If not provided, a blank page is created |
film | Object | No | Makes the template a film: the pages play as one video, joined by each page's transitionIn. { "v": 1, "background"?, "output"?: { "profile": "film", "fps"? }, "tracks"? }. Validated with the pages; problems fail with 400 |
tags | Array | No | Array of tag strings to categorize the template (e.g. ["social", "marketing"]) |
thumbnail_url | String | No | Ignored on create. Orshot renders a thumbnail for each page from your design |
embed_user_id | String | No | Assign the template to an embed user. Accepts your external user ID or an Orshot internal ID (prefixed with eui_). Needs an embed with Per-User Data turned on and a plan that includes per-user data. A new external ID creates the embed user and counts toward your plan's embed user limit. External IDs use letters, numbers, _, -, @ and ., up to 256 characters |
Pages Data Structure#
Each page in the pages_data array should follow this structure:
| Field | Type | Description |
|---|---|---|
id | String | Optional. The page's id, unique in the template, used by links and page operations. Generated when omitted; send it back unchanged when you update the template. A page id used twice gets a new one, with a warning in the response |
name | String | Name of the page (e.g., "Page 1") |
canvas | Object | Canvas settings { width, height, backgroundColor } |
elements | Array | Array of text, image, shape, video, waveform, and other elements |
motion | Object | Optional page-level motion: look (grain, vignette), camera moves and animated groups |
transitionIn | Object | Optional, films only: how this page starts from the previous one, e.g. { "type": "whip", "duration": 0.4 } or { "type": "grow", "from": "<layer id on the previous page>" }. See Film |
modifications | Array | Generated by Orshot from the elements. You don't need to send it |
thumbnail_url | String | Generated by Orshot. You don't need to send it |
audioTracks | Array | Existing audio, generated narration, and ambient tracks |
subtitle | Object | Burned-in subtitle configuration |
videoDuration | Number | Saved video duration for this page in seconds |
Elements may carry transitions (animations) and motion (cinematic behaviors) for video output. Motion params are validated on write: structural problems fail with a 400 listing each issue; unknown or misspelled params are accepted and reported in the response warnings array with a did-you-mean suggestion.
Use audioTracks[].tts for generated narration and audioTracks[].sfx for ambient audio. Add tts.parameterId when the spoken script should be dynamic. See Template Anatomy for the complete page, audio, subtitle, and waveform schemas.
Response Fields#
| Field | Type | Description |
|---|---|---|
success | Boolean | Whether the operation was successful |
template.id | Integer | Unique identifier for the created template |
template.name | String | Name of the template |
template.canvas_width | Integer | Canvas width in pixels |
template.canvas_height | Integer | Canvas height in pixels |
template.tags | Array | Tags assigned to the template |
template.pages_count | Integer | Number of pages in the template |
template.modifications | Array | List of parameterizable modifications extracted from elements |
warnings | Array | Non-blocking authoring warnings (e.g. an unknown motion param with a suggested fix). Present only when there is something to fix |
template.created_at | String | ISO timestamp of creation |
template.thumbnails | Object | Only present when ?includeThumbnails=true. See Thumbnails Object Structure below |
template.pages_meta | Array | Only present when ?includeVideoThumbnails=true rendered the animated previews in the request. Each page's preview is in video_thumbnail_url |
Modification Object Structure#
Each modification in template.modifications contains:
| Field | Type | Description |
|---|---|---|
key | String | Unique key for the modification. For multi-page templates this is prefixed, e.g. page1@headline |
id | String | Same as key |
type | String | Type of modification (text, voiceText, imageUrl, videoUrl, backgroundColor, fill, color, stroke) |
description | String | Human-readable description, e.g. "Headline — Text content" or "Image URL" when no element name is set |
help_text | String | Same as description |
helpText | String | Deprecated copy of help_text |
element_name | String | Layer name of the element in the editor (only present when the element has a name) |
example | String | Default/example value for the field |
page_number | Integer | 1-based page number this modification belongs to |
page_id | String | Stable id of the page this modification belongs to — use this to reliably map modifications to pages after reorder |
Error Responses#
| Status Code | Error | Description |
|---|---|---|
| 400 | Validation failed | Invalid request body parameters, including a film block or page transitionIn that fails film validation (details names each problem by path) |
| 400 | Invalid input | tags is not an array of strings, or embed_user_id, stock media or generated narration could not be resolved. The error names the problem |
| 401 | Unauthorized | The OAuth token is invalid or expired. Refresh it |
| 403 | Access Forbidden | Invalid or missing API key, your plan does not include this endpoint, or a payment is overdue (see code). Per-User Data errors for embed_user_id also return 403 |
| 429 | Rate limit exceeded | Too many requests (max 30/min) |
| 500 | Internal server error | Server-side error |
Thumbnails Object Structure#
When you pass ?includeThumbnails=true, the endpoint renders a PNG thumbnail for each page using the same scale convention as the Studio editor (capped at 1200×1200, never upscaled). The returned template.thumbnails object has this shape:
{
"success": true,
"pages": [
{ "page": 1, "page_id": "...", "thumbnail_url": "https://...", "error": null },
{ "page": 2, "page_id": "...", "thumbnail_url": "https://...", "error": null }
]
}| 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 uploaded thumbnail PNG. null if this specific page failed |
pages[].error | String|null | Error message if this page failed. null on success |
Asynchronous thumbnails#
When ?includeThumbnails=true is not set (the default), the endpoint responds immediately and generates thumbnails in the background. They appear on each page's thumbnail_url in Get Studio Template shortly after creation, usually within a few seconds per page.
Pass ?includeThumbnails=true only when your workflow needs the thumbnail URLs in the same response (e.g., you're about to render or display the template immediately). Sync mode adds ~1–2 seconds per page to the response time.
Notes#
- If
pages_datais not provided, a blank page with the specified dimensions is automatically created - Elements with
parameterizable: trueand aparameterIdwill be automatically extracted as modifications - The
modificationsarray in the response shows which dynamic parameters are available for rendering - When
embed_user_idis provided, the template is assigned to that embed user. Your own external user ID is resolved to an internal Orshot ID using your workspace's embed. If that user does not exist yet, it is created, which needs Per-User Data turned on for the embed and counts toward your plan's embed user limit. You can also pass an Orshot internal ID directly (format:eui_followed by 16 alphanumeric characters). An internal ID must already exist.
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