Bulk Rendering
Bulk rendering submits one template + many variable sets in a single request — Zvid fans it out into individual render jobs, each with its own credits, status, and output. It's the scale half of templates: personalized videos from a CRM export, one video per product, per city, per employee.
Submitting a batch
curl -X POST https://api.zvid.io/api/render/bulk/api-key \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "tpl_xxxxxxxxxxxxxxxxxxxx",
"name": "spring-campaign",
"items": [
{ "variables": { "firstName": "Amira", "city": "Cairo" }, "name": "spring-amira" },
{ "variables": { "firstName": "Omar", "city": "Alexandria" } },
{ "variables": { "firstName": "Lina", "city": "Giza" } }
],
"webhookUrl": "https://example.com/hooks/bulk-done"
}'
Envelope fields:
| Field | Required | Notes |
|---|---|---|
template or payload | one of them | A stored tpl_… id, or a full inline project (with variables defaults). |
items | yes | 1–500 entries; each is { variables, name? }. Your plan's maxBulkItems may be lower — validation errors report it. |
variables | no | Batch-level values merged under every item's variables. |
overrides | no | Output knobs applied to every job (name, dimensions, format, …). |
name | no | Batch name shown in the dashboard. |
webhookUrl | no | Unsigned per-request callback for each child's completion or failure; not one batch event. |
Best-effort validation
Items are validated individually. Valid items become jobs immediately;
invalid ones are returned in errors — each entry carries the item's original
request index as item (0-based) plus field-level details — and the batch is
only rejected outright when the envelope is malformed or every item fails:
{
"bulkId": "blk_…",
"queued": true,
"totalJobs": 2,
"rejectedItems": 1,
"errors": [
{
"item": 1,
"error": "Validation failed",
"details": [
{
"field": "payload.duration",
"message": "Duration must be at least 0.1 seconds"
}
]
}
],
"failedToQueue": 0,
"creditsReserved": 12,
"clientRoom": "api:42:7",
"queueAhead": 0,
"jobs": [
{
"jobId": "550e8400-…",
"index": 0,
"name": "spring-amira",
"creditsReserved": 6
},
{ "jobId": "6ba7b810-…", "index": 2, "name": null, "creditsReserved": 6 }
]
}
jobs[].index is each queued job's original position in items, so you can
line results back up with your source rows even when some items were rejected.
Fix the failed rows and resubmit just those — the dashboard's bulk page has a fix-and-resubmit flow and CSV export for exactly this.
Credits
Video jobs reserve credits individually before they are queued, then refund failed work. Image batches reserve one credit per accepted image at the batch level and reconcile failed child jobs when the batch finishes. See Credits, Plans & Limits. Neither validation nor template preview reserves credits.
Before submitting a batch, validate representative datasets with
POST /api/render/validate/api-key. This checks one resolved request at a
time; it is not a bulk dry-run endpoint. Split larger imports into batches
within your account's maxBulkItems and the 500-item hard limit.
Tracking a batch
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/render/bulk | JWT or API key | List your bulk batches |
GET /api/render/bulk/{id} | JWT or API key | Batch status with per-item job states and results |
Individual jobs are also visible through the normal
GET /api/jobs/{id} endpoint, and each fires
webhook events on completion.
Keep bulkId and every returned jobs[].jobId. Separate rejected items from
accepted jobs that later fail. A timeout after submission does not establish
that nothing was queued: inspect the saved batch/job identifiers before
retrying. See Errors and retries.
Bulk images
POST /api/render/image/bulk (and /image/bulk/api-key) is the same contract
with type: "image" enforced — useful for thumbnail sets and social-card
batches. Images cost 1 credit per image: 25 images cost 25 credits.
Credits are reserved once per batch, with 1 credit refunded for each failed
image after the batch finishes.