---
title: "Bulk Rendering"
canonical_url: https://docs.zvid.io/docs/automation/bulk-rendering/
source: docs/automation/bulk-rendering.md
content_revision: 6338386a1dd87e4a
---

# 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](https://docs.zvid.io/docs/templates/template-basics/): personalized videos from a CRM
export, one video per product, per city, per employee.

## Submitting a batch

```bash
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](https://docs.zvid.io/docs/automation/webhooks/) 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:

```json
{
  "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](https://docs.zvid.io/docs/dashboard/templates-and-bulk/) 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](https://docs.zvid.io/docs/credits-and-plans/). 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](https://docs.zvid.io/docs/automation/webhooks/) 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](https://docs.zvid.io/docs/operations/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.

## Related

- [Template basics](https://docs.zvid.io/docs/templates/template-basics/)
- [Dynamic content](https://docs.zvid.io/docs/templates/dynamic-content/)
- [Bulk renders in the dashboard](https://docs.zvid.io/docs/dashboard/templates-and-bulk/)
