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

POST
/v1/studio/render
curl -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": {}
  }'

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.

Endpoint
POST https://api.orshot.com/v1/studio/render

Quick start#

The smallest request: a template ID, your dynamic values, and the output you want back.

JavaScript
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 URL

Everything 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).

JavaScript
{
  "data": {
    "content": "data:image/png;base64,iVBORw0.....", // URL or base64, per response.type
    "format": "png",
    "type": "base64",
    "responseTime": 325.22
  }
}

With response.type: "binary" the response body is the file itself, not JSON.

Multiple pages → data is an array, one entry per page.

JavaScript
{
  "data": [
    { "page": 1, "pageId": "a1b2c3d4-…", "content": "https://storage.orshot.com/…/Xk3v9Qp1aBc.png" },
    { "page": 2, "pageId": "b2c3d4e5-…", "content": "https://storage.orshot.com/…/Lm7Tz2Wq8Rd.png" }
  ],
  "format": "png",
  "type": "url",
  "responseTime": 3166.01,
  "totalPages": 3,
  "renderedPages": 3
}

File names are random, so treat each URL as opaque. Each entry has page (1-based number) and pageId (stable id). 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.

JavaScript
{
  "data": [
    { "page": 1, "pageId": "a1b2c3d4-…", "content": "https://…/Xk3v9Qp1aBc.png" },
    { "page": 2, "pageId": null, "content": "https://…/Lm7Tz2Wq8Rd.png", "sourcePageId": "a1b2c3d4-…", "sourcePageNumber": 1, "flowPage": 1 },
    { "page": 3, "pageId": null, "content": "https://…/Pq4Hs6Vn1Ke.png", "sourcePageId": "a1b2c3d4-…", "sourcePageNumber": 1, "flowPage": 2 }
  ],
  "format": "png",
  "type": "url",
  "totalPages": 3,
  "renderedPages": 3,
  "flowTotalPages": 2
}
FieldWhereMeaning
sourcePageIdoverflow pageId of the source template page the overflow came from
sourcePageNumberoverflow pageWhich template page the overflow came from
flowPageoverflow pageOverflow index (1 = first overflow page, 2 = second, …)
flowTotalPagestop levelTotal overflow pages generated

See Flowing Content for setup.

If part of a request was skipped, such as an unknown parameter ID or a video scale your plan does not support, the render still succeeds and the response has a warnings array of { "title", "details" } objects. A binary response carries the same list in the X-Orshot-Warnings header.

Request body#

FieldTypeRequiredDescription
templateIdnumberYesYour template's ID — shown on the template's page and in the playground.
modificationsobjectNoYour 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.
responseobjectNoWhat to render and how to return it (format, type, size, …). See below.
pdfOptionsobjectNoPDF-only output controls (document title, margins, DPI, color mode, outlined text, page range). See Render as a PDF.
videoOptionsobjectNoVideo-only output controls (fps, trim, audio, subtitles, page transitions). See Render as a video.
publishobjectNoPublish the render to connected social accounts. See Publish to social.
connectionsarrayNoSend the render to connected storage, such as Dropbox, Google Drive or S3.
webhook_url, metadatastringNoAsync mode only. See Start an Async Render.
langstringNoLanguage of the template's text, as a language tag such as el, ja, zh-Hant or tr. Leave it out to use the template language set in the Studio, if any. See Set the text language.
translatestring | objectNoTranslate the template's text with AI before rendering, e.g. "el" or { "to": "es-MX", "context": "real estate listing" }. 1 AI credit per 100 words. See Translate.

The response object#

FieldTypeDescription
formatstringOutput format: png, webp, jpg, avif, pdf, mp4, webm, mov, mkv, gif. Defaults to png.
typestringHow content is returned: url, base64, or binary. Defaults to base64. Video and GIF formats need url. Multi-page templates support binary only for pdf.
modestringsync (default) waits for the render; async returns a job immediately — built for video and long renders. See Start an Async Render.
scalenumberOutput scale multiplier. 1 = template size, 2 = double, etc. For PDFs, pdfOptions.dpi sets the scale instead (DPI divided by 72). Videos and GIFs can render at 2 on the Scale plan (a 1080×1350 template renders at 2160×2700) and render at 1 on every other plan. A value above 2 is not supported for videos. A long video at 2 also falls back to 1: at 1080×1920 and 30fps, the limit is about 15 seconds per page. Every fallback renders anyway and adds a warning to the response saying what it rendered at.
sizestring | objectReplace the render size: a preset slug (e.g. "instagram-story"), "WIDTHxHEIGHT" (e.g. "1200x630"), or { "width": 1200, "height": 630 }. See Smart Resize.
width + heightnumberExplicit pixel dimensions instead of size (both required together, 10–5000px).
extraSizesarray | objectAdd the same design at extra sizes in one call. See Render multiple sizes.
includePagesnumber[]Multi-page only — render just these pages, e.g. [1, 3].
qualitynumber1–100. Compression for jpg and webp (default 90) and avif (default 50). png ignores it. On video formats it is used when videoOptions.quality is not set.
fileNamestringCustom 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.

Set the text language#

Most text renders correctly without this, including Greek, Cyrillic, Arabic and CJK. Send lang when the language changes how text is drawn:

langWhat changes
elUppercase Greek drops its accents (ΝΕΑ ΤΙΜΗ, not ΝΈΑ ΤΙΜΉ). Orshot already does this for uppercase Greek layers when lang is left out.
tr, azUppercase i becomes İ
ja, zh-Hans, zh-Hant, koLine breaks follow that language's rules, and shared Chinese characters use its glyph shapes where the font has them
JSON
{
  "templateId": <TEMPLATE_ID>,
  "lang": "ja",
  "modifications": { "headline": "直営店オープン" }
}

An invalid tag is ignored. Renders with different lang values are cached separately.

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.

JavaScript
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):

PresetSize (px)
instagram-story1080×1920
instagram-post1080×1080
instagram-post-portrait1080×1350
instagram-post-landscape1080×566
facebook-post1200×630
facebook-story1080×1920
facebook-cover851×315
twitter-post1200×675
twitter-header1500×500
linkedin-post1200×627
linkedin-banner1584×396
youtube-thumbnail1280×720
youtube-short1080×1920
tiktok-video1080×1920
pinterest-pin1000×1500
whatsapp-status1080×1920
og-image1200×630
blog-header1200×630
email-header600×200
website-banner1920×600
presentation-16-91920×1080
a4-document2480×3508
us-letter2550×3300
business-card1050×600
zoom-background1920×1080
leaderboard-ad728×90
medium-rectangle-ad300×250

Aliases: x-post = twitter-post, x-header = twitter-header, instagram-square = instagram-post, instagram-reel = instagram-story.

  • response.scale still applies on top of the adapted size — size: "1080x1920" with scale: 2 outputs 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:

JavaScript
response: { type: "url", format: "png", extraSizes: ["1080x1920", "1080x1080", "1200x630"] }

…or a named object, when you want a label to route on:

JavaScript
response: { type: "url", format: "png", extraSizes: { story: "1080x1920", square: "1080x1080" } }

An entry can also map pages to sizes, keyed by page number or pageId. Pages you leave out keep their size, and the entry's size label reads custom:

JavaScript
response: { type: "url", format: "png", extraSizes: [{ "1": "1080x1920", "2": "instagram-post" }] }

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):

JavaScript
{
  "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://…" }
    ]
  }
}
JavaScript
{
  "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:

JavaScript
// 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.

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, outlined text, image compression, page range).

JavaScript
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.

JavaScript
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.
  • Video rendering needs a plan that includes video. Other plans get a 403.
  • Video and GIF formats need response.type: "url". base64 and binary return a 400.

Render a film#

A template with a film block (Studio's Film mode, or film sent on create) is one video: its pages play as scenes joined by each page's transitionIn, with the film's music and cross-page layers mixed in. Rendering it as mp4 returns a single object in data, like a single-page template:

JavaScript
body: JSON.stringify({
  templateId: <TEMPLATE_ID>,
  modifications: { "page1@headline": "Launch day", "page3@cta": "Try it free" },
  response: { type: "url", format: "mp4", mode: "async" },
}),
JavaScript
{
  "data": {
    "content": "https://storage.orshot.com/.../film.mp4",
    "format": "mp4",
    "type": "url",
    "videoDuration": 20.9,
    "responseTime": 48211.4
  }
}
  • The film's length is the scenes added up minus the transitions (both pages play during a transition). It counts against your plan's maximum video length.
  • includePages renders the listed pages one by one, as normal page videos. webm, mov, mkv and gif also render page by page; image and PDF formats are unchanged.
  • videoOptions.muted, videoOptions.sound (false mutes layer sound effects), videoOptions.music (false leaves out library music) and videoOptions.fps apply. fps sets the frame rate, up to 30. Without it the film's output.fps is used. combinePages, pageTransition, duration, audioSource and the subtitle options do not apply to a film; captions are not burned into films yet, and the render says so in warnings.
  • Smart Resize works on films: size renders the whole film at another size and extraSizes adds up to 4 more sizes per call. Each size is a full film render and is billed as one. Per-page size maps are refused because a film has one canvas.
  • The film's schema, transitions and limits are on the Film page.

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.

JavaScript
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!",
  },
}),

accounts is required. Without it the render still goes through, nothing is posted, and publish is an object instead: { "status": "skipped", "code": "no_accounts", "error": "...", "message": "...", "action": { ... } }. To draft or schedule a post before an account is connected, use POST /v1/social/publish with platforms.

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:

HeaderDescription
X-RateLimit-LimitMaximum requests per minute for your plan
X-RateLimit-RemainingRemaining requests in the current window
X-RateLimit-ResetSeconds until the current window resets
Retry-AfterSeconds to wait (only on 429 responses)

The same values are also sent as RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset.

Was this page helpful?

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