Start an Async Render
Add response.mode "async" to any render to run it in the background, then collect the result by polling or webhook. Built for video, big PDFs, and batch pipelines that outlive HTTP timeouts.
Updated
A synchronous render holds the HTTP connection for the whole render. That is fine for images, but video renders can take minutes, longer than most client timeouts (Zapier ~30s, Make ~60s, many AI agents 120s). Async mode returns immediately with a job, the render runs server-side, and you collect the result by polling or via a webhook.
POST https://api.orshot.com/v1/studio/render (with response.mode: "async")The related endpoints for working with the job it creates:
- Get a Render Job — poll one job for its result
- List Render Jobs — your workspace's jobs, newest first
- Cancel a Render Job — cancel a job that has not started
Start an async render#
Add mode: "async" to the response object of a normal render request. Everything else (modifications, videoOptions, Smart Resize, publish) works exactly the same. Async works for any output format: video, PDF, or image.
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: "Launch day!" },
response: {
mode: "async", // the one field that makes this a background job
type: "url",
format: "mp4", // or "pdf", "png", …
},
videoOptions: { duration: 30, fps: 30 },
}),
});
const job = await res.json(); // HTTP 202{
"id": 1204,
"status": "queued",
"finished": false,
"created_at": "2026-08-25T10:30:00.000Z",
"self": "https://api.orshot.com/v1/studio/render-jobs/1204"
}Async mode requires response.type: "url" (the default). Results are delivered as hosted URLs, not base64 or binary.
The job object#
Every async endpoint returns this shape. Poll Get a Render Job until finished is true.
| Field | Type | Description |
|---|---|---|
id | number | The job's id. Use it to poll, list, or cancel. |
status | string | queued, processing, succeeded, failed, or canceled. |
finished | boolean | false while the job is queued or processing, true once it reaches a terminal status. Poll until this is true rather than comparing statuses, so new statuses can't break your integration. |
result | object | Only on succeeded. The exact same response body a synchronous render returns (data, format, publish, …), so your handling code is mode-agnostic. |
error | string | Only on failed. A human-readable, actionable message. |
error_code | string | Only on failed. A stable machine code, e.g. video-duration-exceeds-plan, invalid-video-duration, render-timeout. |
metadata | string | Your metadata string, echoed back verbatim (max 1024 chars). |
created_at / started_at / completed_at | string | ISO timestamps for each phase. |
expires_at | string | ISO timestamp when the job record is removed (7 days after creation). The rendered asset is unaffected. |
self | string | The job's poll URL. |
Webhook (optional)#
Pass a webhook_url in the render request and Orshot POSTs the finished job to it, no polling needed.
body: JSON.stringify({
templateId: <TEMPLATE_ID>,
response: { mode: "async", format: "mp4" },
webhook_url: "https://acme.com/hooks/orshot",
metadata: "order-8412", // echoed back so you can correlate
}),The delivery payload:
{
"event": "render_job.finished",
"job": { "id": 1204, "status": "succeeded", "finished": true, "result": { ... }, "metadata": "order-8412" }
}- Delivery is attempted 3 times (immediately, +30s, +2m) with a 10-second timeout each; respond with any 2xx to acknowledge.
- The URL must be publicly reachable over http(s). Private and loopback addresses are rejected at create time.
- Webhooks are a convenience; polling remains the source of truth. If delivery fails, the job is still there to poll.
- Jobs you cancel do not fire a webhook (you already know the outcome). Every other terminal status does.
Statuses & guarantees#
| Status | Meaning |
|---|---|
queued | Accepted; the render has not started yet. |
processing | Rendering now. |
succeeded | Done. result holds the render response. |
failed | Did not complete. error says why and what to change; error_code is stable. |
canceled | You canceled it before it started. Nothing was billed. |
- Renders have a 15-minute ceiling. A job that exceeds it fails with
error_code: "render-timeout"(reduce duration/fps/quality or split pages into separate renders). A render that keeps running and completes just after the ceiling may still be billed, even though the job readsfailed. - Credits are charged exactly as in sync mode, on render completion. A job that never renders (validation failure, or canceled while queued) bills nothing; a render that actually ran is billed even if the job later reads
failed. - Jobs are scoped to your workspace; a job id from another workspace answers
404.
When to use async vs sync#
| Use sync (default) | Use async |
|---|---|
| Images and PDFs (typically < 5s) | Any video render |
| You want the file in one request | Your platform enforces short HTTP timeouts (Zapier, Make, AI agents/MCP) |
| Simplest possible integration | Long or multi-page renders, or fire-many-collect-later pipelines |
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