Render from Studio Template
Render any template you've designed in Orshot Studio by making a POST request — as an image, PDF, or video, at any size, and optionally publish it.
Updated
/v1/studio/rendercurl -X POST "https://api.orshot.com/v1/studio/render" \
-H "Authorization: Bearer <ORSHOT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"templateId": 1,
"modifications": {},
"response": {},
"pdfOptions": {}
}'const res = await fetch("https://api.orshot.com/v1/studio/render", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
"templateId": 1,
"modifications": {},
"response": {},
"pdfOptions": {}
}),
});
const data = await res.json();import requests
response = requests.post(
"https://api.orshot.com/v1/studio/render",
headers={"Authorization": "Bearer <ORSHOT_API_KEY>"},
json={
"templateId": 1,
"modifications": {},
"response": {},
"pdfOptions": {}
},
)
data = response.json()$ch = curl_init("https://api.orshot.com/v1/studio/render");
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([
"templateId" => 1,
"modifications" => [],
"response" => [],
"pdfOptions" => []
]));
$data = json_decode(curl_exec($ch), true);
curl_close($ch);require "net/http"
require "json"
uri = URI("https://api.orshot.com/v1/studio/render")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer <ORSHOT_API_KEY>"
req["Content-Type"] = "application/json"
req.body = {
"templateId": 1,
"modifications": {},
"response": {},
"pdfOptions": {}
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)Templates you design in Orshot Studio are Studio Templates — custom layouts whose text, images, and styles you parameterize and then render on demand from the API.
POST https://api.orshot.com/v1/studio/renderQuick start#
The smallest request: a template ID, your dynamic values, and the output you want back.
const res = await fetch("https://api.orshot.com/v1/studio/render", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
templateId: <TEMPLATE_ID>,
modifications: {
title: "Custom Title",
imageUrl: "https://acme.com/cover.png",
},
response: { type: "url", format: "png" },
}),
});
const { data } = await res.json();
// data.content → the rendered image URLEverything else on this page is optional and layers on top of this: render at any size, as a PDF or video, or publish it to social.
Response#
data holds your render. Its shape depends on the template — expand the matching tab:
One page → data is an object. Read the render at data.content (a string).
{
"data": {
"content": "data:image/png;base64,iVBORw0.....", // URL or base64, per response.type
"format": "png",
"type": "base64",
"responseTime": 325.22
}
}The base64 type with a binary response isn't supported (a binary stream returns one file).
Multiple pages → data is an array, one entry per page.
{
"data": [
{ "page": 1, "pageId": "a1b2c3d4-…", "content": "https://storage.orshot.com/…/page_1.png" },
{ "page": 2, "pageId": "b2c3d4e5-…", "content": "https://storage.orshot.com/…/page_2.png" }
],
"format": "png",
"type": "url",
"responseTime": 3166.01,
"totalPages": 3,
"renderedPages": 3
}Each entry has page (1-based number) and pageId (stable UUID). Prefer pageId when referencing a specific page — page numbers shift when pages are reordered, while pageId is stable for the page's lifetime.
When flow text overflows onto extra pages, those pages carry flow metadata so you know where each came from.
{
"data": [
{ "page": 1, "pageId": "a1b2c3d4-…", "content": "https://…/page_1.png" },
{ "page": 2, "content": "https://…/page_2.png", "sourcePageId": "a1b2c3d4-…", "sourcePageNumber": 1, "flowPage": 1 },
{ "page": 3, "content": "https://…/page_3.png", "sourcePageId": "a1b2c3d4-…", "sourcePageNumber": 1, "flowPage": 2 }
],
"format": "png",
"type": "url",
"totalPages": 3,
"renderedPages": 3,
"flowTotalPages": 2
}| Field | Where | Meaning |
|---|---|---|
sourcePageId | overflow page | UUID of the source template page the overflow came from |
sourcePageNumber | overflow page | Which template page the overflow came from |
flowPage | overflow page | Overflow index (1 = first overflow page, 2 = second, …) |
flowTotalPages | top level | Total overflow pages generated |
See Flowing Content for setup.
Add a publish object and the response also includes a publish array with
each account's status — see Publish to social.
Request body#
| Field | Type | Required | Description |
|---|---|---|---|
templateId | number | Yes | Your template's ID — shown on the template's page and in the playground. |
modifications | object | No | Your dynamic values, keyed by parameter ID. Multi-page templates prefix keys with the page, e.g. page2@title. You can also set style parameters to change layer styles at render time. |
response | object | No | What to render and how to return it (format, type, size, …). See below. |
pdfOptions | object | No | PDF-only output controls (document title, margins, DPI, color mode, page range). See Render as a PDF. |
videoOptions | object | No | Video-only output controls (fps, trim, audio, subtitles, page transitions). See Render as a video. |
publish | object | No | Publish the render to connected social accounts. See Publish to social. |
The response object#
| Field | Type | Description |
|---|---|---|
format | string | Output format: png, webp, jpg, avif, pdf, mp4, webm, mov, mkv, gif. Defaults to png. |
type | string | How content is returned: url, base64, or binary. |
mode | string | sync (default) waits for the render; async returns a job immediately — built for video and long renders. See Start an Async Render. |
scale | number | Output scale multiplier. 1 = template size, 2 = double, etc. |
size | string | Replace the render size — a preset slug (e.g. "instagram-story") or "WIDTHxHEIGHT" (e.g. "1200x630"). See Smart Resize. |
width + height | number | Explicit pixel dimensions instead of size (both required together, 10–5000px). |
extraSizes | array | object | Add the same design at extra sizes in one call. See Render multiple sizes. |
includePages | number[] | Multi-page only — render just these pages, e.g. [1, 3]. |
fileName | string | Custom output file name (no extension). url/binary types only. Carousel files are suffixed -page-1, -page-2, …; url outputs also get a -[HASH] suffix to stay unique. For PDFs, it also becomes the document's Title metadata unless pdfOptions.title is set. |
Smart Resize#
Render a template at a different canvas size without redesigning it — design once, then generate platform-ready outputs for every channel from the same template and modifications. Want to set this up visually first? See Smart Resize in Studio.
The layout adapts deterministically (no AI) — the same template and size always produce the same output:
- Everything scales uniformly, so proportions and text wrapping are preserved.
- Backgrounds and full-bleed images stretch to cover the new canvas.
- Elements re-anchor to the edge/center they were designed against (a top-left logo stays top-left; a footer stays pinned to the bottom).
- Grouped elements (e.g. a heading + subheading) move together as one unit.
- A safety guard guarantees the adapted layout never introduces overlapping elements.
Render at a different size#
size replaces the render size; the response shape is unchanged.
response: {
type: "url",
format: "png",
size: "instagram-story", // preset slug, or "1080x1920", or use width + height
}Available presets — or pass any "WIDTHxHEIGHT" string, or width + height (10–5000px):
instagram-story · instagram-post · instagram-post-portrait · instagram-post-landscape · facebook-post · facebook-story · facebook-cover · twitter-post · twitter-header · linkedin-post · linkedin-banner · youtube-thumbnail · youtube-short · tiktok-video · pinterest-pin · whatsapp-status · og-image · blog-header · email-header · website-banner · presentation-16-9 · a4-document · us-letter · business-card · zoom-background · leaderboard-ad · medium-rectangle-ad
response.scalestill applies on top of the adapted size —size: "1080x1920"withscale: 2outputs 2160×3840.- Best with moderate aspect-ratio changes; extreme changes (e.g. square →
leaderboard-ad) stay safe but elements may look small relative to the canvas.
Render multiple sizes in one call#
Where size replaces the output, extraSizes is purely additive: your normal response is unchanged, and each output gains an extraSizes array nested right beside it.
Pass an array of sizes:
response: { type: "url", format: "png", extraSizes: ["1080x1920", "1080x1080", "1200x630"] }…or a named object, when you want a label to route on:
response: { type: "url", format: "png", extraSizes: { story: "1080x1920", square: "1080x1080" } }The extra sizes nest beside your render — single-page on the data object, multi-page on each page (so a size is always self-describing about which page it belongs to):
{
"data": {
"content": "https://…", // your normal render (native or `size`)
"extraSizes": [
{ "size": "1080x1920", "width": 1080, "height": 1920, "content": "https://…" },
{ "size": "1080x1080", "width": 1080, "height": 1080, "content": "https://…" }
]
}
}{
"data": [
{ "page": 1, "content": "https://…", "extraSizes": [ { "size": "1080x1920", "width": 1080, "height": 1920, "content": "https://…" } ] },
{ "page": 2, "content": "https://…", "extraSizes": [ { "size": "1080x1920", "width": 1080, "height": 1920, "content": "https://…" } ] }
]
}A named size adds a name field to each entry, e.g. { "name": "story", "size": "story", … }.
Combine with size — size sets the base render, extraSizes add extra copies beside it:
// base rendered at 1200x630, plus a 1080x1920 copy
response: { type: "url", format: "png", size: "1200x630", extraSizes: ["1080x1920"] }Leave size off and data stays your original render — so extraSizes: ["1080x1920"] gives you the original and the story size.
Supported & billed. Image formats only (png, jpg, webp, avif)
with response.type url or base64 — PDF and video are coming, and
extraSizes isn't supported on templates with flowing (auto-expanding)
elements. Up to 50 extra outputs per call (pages × sizes). Each extra
output is billed like a page (2 renders = 1 credit). A size that fails
comes back with an error field instead of content — it doesn't break the
rest of the response and isn't billed.
Reproduce studio-approved sizes#
For pixel-perfect output, open your template in the studio and add sizes from the Smart Resize panel — each gets an instant adaptive preview, can be recomposed with AI, and is saved with the template. A render that requests a saved size reproduces that approved preview exactly, every time; the deterministic adaptation above is only used for sizes you haven't saved.
- Works with every response format and with multi-page templates (every page is adapted).
- Saved sizes apply whether the request comes from your own backend or an embed — your customers resize in the embed, you render the saved sizes via the API.
Render as a PDF#
Set response.format to pdf and add an optional pdfOptions object for document metadata and print controls (title, margins, DPI, color mode, image compression, page range).
body: JSON.stringify({
templateId: <TEMPLATE_ID>,
modifications: { name: "Sarah Chen", course: "UX Design" },
response: { type: "url", format: "pdf" },
pdfOptions: { title: "UX Design Certificate", margin: "0px", dpi: 300, colorMode: "cmyk" },
}),pdfOptions.title sets the PDF's Title metadata (shown in browser tabs and PDF reader window titles); it falls back to response.fileName when omitted. For accessible PDFs, describe image layers with the .alt parameter, e.g. "hero_image.alt": "123 Main St exterior photo".
Photo-heavy pages can run to tens of MB because the renderer stores scaled or cropped images as raw pixels. Add "imageFormat": "jpeg" to pdfOptions to re-encode them, which typically cuts 80 to 90% of the file size.
See PDF Options for every field (title, margins incl. per-side overrides, DPI, RGB/CMYK, image compression, page ranges) and print recommendations.
Render as a video#
For templates with video elements, set response.format to mp4, webm, mov, mkv, or gif.
body: JSON.stringify({
templateId: <TEMPLATE_ID>,
modifications: {
promoVideo: "https://example.com/clip.mp4", // a video element's parameter ID
"promoVideo.trimStart": 0,
"promoVideo.trimEnd": 10,
},
response: { type: "url", format: "mp4" }, // or "webm", "mov", "mkv", "gif"
videoOptions: { fps: 30, muted: true, combinePages: true, pageTransition: "fade" },
}),- Per-element controls (custom URL,
.trimStart,.trimEnd,.muted,.loop) → see Video Elements. - Render-level
videoOptions(fps, quality, trim, audio overrides, subtitles + styling, multi-page combine & transitions) → see Video Options. - Page-level audio tracks and subtitles set in Studio are included automatically; see Audio & Subtitles.
For multi-page video, set videoOptions.combinePages: true to get one
combined video instead of one file per page (supported for mp4/webm; GIF
always returns per-page files).
Video renders can take minutes — longer than many HTTP client timeouts. Add
mode: "async" to the response object to get a job back immediately and
collect the result by polling or webhook: see
Start an Async Render.
Publish to social#
Add a publish object to post the render straight to connected social accounts — no extra calls. The response gains a publish array with each account's status.
body: JSON.stringify({
templateId: <TEMPLATE_ID>,
modifications: { title: "New post is live!" },
response: { type: "url", format: "png" },
publish: {
accounts: [1, 2], // social account IDs from your workspace
content: "Check out our latest design!",
},
}),See Publish from API for the full object — drafts (isDraft), scheduling (schedule.scheduledFor, timezone), per-account platformOptions, TikTok tiktokSettings, statuses, and error handling.
Rate limits#
This endpoint is rate limited based on your plan. Check the response headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute for your plan |
X-RateLimit-Remaining | Remaining requests in the current window |
Retry-After | Seconds to wait (only on 429 responses) |
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 50+ integrations
- White-label embed for your own app
- 30 free credits — no credit card required