Render endpoints

The campaign-free path: one call in, a webhook or poll for the result.

Endpoints

MethodPathDescription
POST/rendersCreate 1-100 renders in one call
GET/rendersList; filter by batch_id, status, campaign_id, or on_demand=true (no campaign)
GET/renders/:idGet a single render
POST/renders/:id/cancelCancel a queued/processing render
POST/renders/:id/retryRe-run a finished render in place

Send the same video URL on every call — it's content-addressed, so a repeat costs a lookup, not a re-upload, and the voice clone is reused automatically. There is no synchronous mode: renders take 1-5 minutes, so the terminal render.* webhook (or a poll) is the completion signal.

The render object

Every endpoint on this page returns this shape under data.

FieldTypeDescription
idstring (uuid)Render id. Use it for GET, cancel and retry.
batchIdstring (uuid)The batch this render was created in. One POST /renders call = one batch.
campaignIdstring | nullSet only when you passed campaignId on create; null for a pure on-demand render.
statusstringqueued, processing, completed, degraded, failed or cancelled. The last four are terminal.
recipientobjectThe recipient snapshot as submitted — firstName plus whichever of company, title, language, pronunciation, url you sent.
metadataobjectYour passthrough object, echoed verbatim. {} when you sent none.
degradationReasonstring | nullWhy a degraded render dropped the screen recording. A degraded render is never billed.
videoUrlstring | nullDirect video file URL. null until the render finishes.
shareableUrlstring | nullThe Outvo play page link — the URL to put in an email.
thumbnailUrlstring | nullThe raw signed storage URL, which expires. Use it as a null check only; for email, build the durable proxy from shareableUrl + '/thumb.gif' — see Email embed.
createdAtstring (ISO 8601)When the render was created.
completedAtstring | nullWhen it reached a terminal status.

Reference

POST/renders

Create 1-100 renders from one recording. Nothing is enqueued or billed unless the whole batch validates.

Success: 202 Accepted — or 200 OK on an idempotent replay

Validation is collected across the whole batch: a 100-recipient call reports every bad row in one 400 rather than making you fix them one at a time, and no render is created or billed until all of them pass.

A blocked or timed-out screen recording fails the render on this path. If a recipient has a url and the capture produces nothing, you get render.failed — not a greeting-only video. That is deliberate and differs from the campaign path, which ships the greeting-only video flagged as degraded and unbilled: a person can look at one and judge it, an automation ships whatever it is handed. Fix the URL and retry. degraded is still reachable here for other reasons, such as a lip-sync fallback.

Headers

FieldTypeDescription
Idempotency-KeystringOptional, max 255 characters. A replay with the same key and the same body returns 200 with the original batch at its current status; a different body returns 422. See Idempotency.

Body

application/json or multipart/form-data. In multipart, send the recording as the file part and placeholderWord, campaignId as plain fields, with recipients and options as JSON-encoded strings. Uploads are capped at 200 MB.

FieldTypeDescription
videostring (url)The base recording, as an http/https URL. Required unless you send a videoChecksum that already resolves, or upload the file as multipart. Fetched once and content-addressed; a repeat URL costs a HEAD, not a download.
videoChecksumstringSHA-256 of the recording bytes (sha256sum your-video.mp4). Skips the fetch entirely when Outvo already holds those bytes. An unknown checksum with no video and no upload is a 400.
placeholderWordreqstringThe word in your recording that gets replaced with each name — e.g. there if you say "Hey there".
recipientsreqarray1 to 100 recipient objects (fields below).
optionsobjectPer-batch recording options (fields below). Omitted keys use the pipeline defaults.
campaignIdstring (uuid)Associate these renders with an existing campaign for reporting. Optional; the renders still run on this path, not the campaign lifecycle.

recipients[] fields

FieldTypeDescription
firstNamereqstringThe name spoken in the greeting.
companystringCarried on the render for your own reporting.
titlestringCarried on the render for your own reporting.
languagestringLanguage hint for the TTS greeting, e.g. "en", "pt".
pronunciationstringPhonetic respelling used when the name is not pronounced as written.
urlstring (url)The page to screen-record behind the greeting. Must be http/https, and is required unless options.pipLayout is none — a missing URL with PIP on would silently bill for a weaker video.
metadataobjectArbitrary JSON, max 16 KB per recipient. Echoed verbatim on GET and in the terminal webhook — the correlation key back to your own record.

options fields

FieldTypeDescription
pipLayoutstringWhere the screen recording sits against the talking head. none renders a greeting-only talking head and makes url optional.
Valuesbottom-rightbottom-leftside-panelnoneDefaultbottom-right
pipDurationSecondsnumberHow long the screen recording runs, in seconds. 0 lets the pipeline decide.
Default0
recordingModestringViewport used for the screen recording. Mobile records portrait.
ValuesdesktopmobileDefaultdesktop
scrollStartSecnumber | nullSeconds before scrolling begins. 0-30, or null for the pipeline default.
Values0-30nullDefaultnull
scrollDownSecnumber | nullSeconds spent scrolling down. 0-30, or null.
Values0-30nullDefaultnull
scrollPauseSecnumber | nullSeconds paused at the bottom. 0-30, or null.
Values0-30nullDefaultnull
scrollUpSecnumber | nullSeconds spent scrolling back up. 0-30, or null.
Values0-30nullDefaultnull
disableScrollboolean | nullHold the page still instead of scrolling it. Omitted means no scroll on this path; an explicit false scrolls.
Valuestruefalsenull
nameSpotlightboolean | nullEmphasize the name moment visually.
Valuestruefalsenull
liveRecordingOverlayboolean | nullDraw the live-recording chrome over the screen capture.
Valuestruefalsenull

Request

json
POST /renders
Authorization: Bearer pc_live_xxx
Content-Type: application/json
Idempotency-Key: batch-2026-08-13-001

{
  "video": "https://your-cdn.com/base-recording.mp4",
  "placeholderWord": "there",
  "recipients": [
    {
      "firstName": "Sarah",
      "company": "Acme",
      "title": "Head of Growth",
      "url": "https://acme.com",
      "metadata": { "row": 1, "crmId": "003xx" }
    }
  ],
  "options": { "pipLayout": "bottom-right", "recordingMode": "desktop" }
}

Response

json
202 Accepted

{
  "data": {
    "batchId": "6f5b1a2c-6d1e-4a71-9f0e-1c2f3a4b5c6d",
    "renders": [
      {
        "id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
        "batchId": "6f5b1a2c-6d1e-4a71-9f0e-1c2f3a4b5c6d",
        "campaignId": null,
        "status": "queued",
        "recipient": { "firstName": "Sarah", "company": "Acme", "url": "https://acme.com" },
        "metadata": { "row": 1, "crmId": "003xx" },
        "degradationReason": null,
        "videoUrl": null,
        "shareableUrl": null,
        "thumbnailUrl": null,
        "createdAt": "2026-08-13T12:00:00.000Z",
        "completedAt": null
      }
    ]
  }
}

Errors

CodeHTTPWhen
VALIDATION_ERROR400Any invalid recipient row, a missing placeholderWord, a bad option value, or an unresolvable videoChecksum. The message lists every bad row at once — nothing was rendered.
FILE_TOO_LARGE400A multipart upload over 200 MB.
PLAN_LIMIT_REACHED402The whole batch is reserved up front: 100 renders against 3 remaining fails here, before anything is queued.
IDEMPOTENCY_KEY_REUSED422Same Idempotency-Key, different body.
RATE_LIMITED429Over 100 requests/min. Retry after the Retry-After header.

GET/renders

List your renders. This is the poll path when you have no webhook endpoint.

Success: 200 OK

Query parameters

FieldTypeDescription
pagenumberPage number, minimum 1.
Default1
per_pagenumberItems per page, maximum 100.
Default20
statusstringFilter by exact status, e.g. queued, completed, failed.
batch_idstring (uuid)Collect one POST /renders call. Results come back in submission order rather than newest-first.
campaign_idstring (uuid)Only renders associated with that campaign.
on_demandbooleantrue returns only renders with no campaign at all. Wins over campaign_id if you send both.

Response

json
200 OK

{
  "data": [ /* render objects, newest first (or batch order with batch_id) */ ],
  "meta": { "page": 1, "perPage": 20, "total": 42, "totalPages": 3 }
}

Errors

CodeHTTPWhen
RATE_LIMITED429Over 100 requests/min.

GET/renders/:id

Fetch one render, including its output URLs once it has finished.

Success: 200 OK

Path parameters

FieldTypeDescription
idreqstring (uuid)Render id from the create response.

Response

json
200 OK

{
  "data": {
    "id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
    "batchId": "6f5b1a2c-6d1e-4a71-9f0e-1c2f3a4b5c6d",
    "campaignId": null,
    "status": "completed",
    "recipient": { "firstName": "Sarah", "company": "Acme", "url": "https://acme.com" },
    "metadata": { "row": 1 },
    "degradationReason": null,
    "videoUrl": "https://cdn.outvo.io/outputs/....mp4",
    "shareableUrl": "https://share.outvo.io/v/abc123",
    "thumbnailUrl": "https://...signed...",
    "createdAt": "2026-08-13T12:00:00.000Z",
    "completedAt": "2026-08-13T12:03:41.000Z"
  }
}

Errors

CodeHTTPWhen
NOT_FOUND404No render with that id on your account.

POST/renders/:id/cancel

Stop a render that has not finished. An in-flight job is aborted, not merely dequeued.

Success: 200 OK

Path parameters

FieldTypeDescription
idreqstring (uuid)Render id.

Response

json
200 OK

{ "data": { "id": "...", "status": "cancelled", "completedAt": "2026-08-13T12:01:10.000Z", ... } }

Errors

CodeHTTPWhen
NOT_FOUND404No render with that id on your account.
VALIDATION_ERROR409The render already reached a terminal status. Only queued or processing renders can be cancelled.

POST/renders/:id/retry

Re-run a finished render in place, against the same recording and recipient. Jumps the queue.

Success: 200 OK

The render is reset in place — the same id keeps its history, and degradationReason, completedAt are cleared. A retry is a fresh render: it is billed if it succeeds.

Path parameters

FieldTypeDescription
idreqstring (uuid)Render id.

Response

json
200 OK

{ "data": { "id": "...", "status": "queued", "degradationReason": null, "completedAt": null, ... } }

Errors

CodeHTTPWhen
NOT_FOUND404No render with that id on your account.
VALIDATION_ERROR409The render is still queued or processing; the original recording has been swept by retention (submit it again); or the status changed between the check and the write.
PLAN_LIMIT_REACHED402A retry counts as one render against your quota.