Skip to navigation

Batch Processing

Process multiple lipsync generations efficiently

The Sync Labs Batch API processes up to 500 lip sync generations in a single request, sent as a JSONL file or as a JSON array of rows. This is ideal for bulk video processing workflows, such as personalized video campaigns, content localization, or any scenario requiring high-volume lip sync generation.

What is the batch file format?

Batch processing enables you to submit 20 to 500 generations without having to handle queueing or concurrency yourself, with a target turnaround time of 24 hours. Batch jobs bypass your normal generation concurrency limit, but the Batch API is only available on the Scale plan or higher. As of now, only generations with inputs up to 30 seconds are supported in batch processing.

Batch API requires the Scale plan or higher, including Enterprise

Batch concurrency limits

Batch jobs bypass your normal per-generation concurrency limit, but a separate limit caps how many batches you can run at once, based on your plan. If you submit a new batch while you are already at that limit, the API returns a 429 Too Many Requests response.

The 429 body is a JSON object describing the limit you hit:

{
"statusCode": 429,
"errorCode": "concurrency_limit_reached",
"message": "Batch concurrency limit reached. Please wait for an existing batch to complete or upgrade your plan.",
"activeBatches": 3,
"concurrencyLimit": 3,
"retryAfterSeconds": 20
}

The response also includes a Retry-After header (in seconds) and an X-Sync-Concurrency-Limit header. Rather than retrying immediately, wait retryAfterSeconds before submitting again, or wait for one of your in-flight batches to complete.

How do I create a batch job?

1

Prepare Your Input File

Create a JSON Lines (.jsonl) file with your generation requests. Each line should contain a unique request_id, the endpoint (must be "/v2/generate"), and a payload with the standard generation request format (same as the Generate API). The file must be in JSON Lines (.jsonl) format with a minimum of 20 records, maximum file size of 5MB, and up to 500 requests per batch.

input.jsonl
{"request_id": "request-1", "endpoint": "/v2/generate", "payload": {"model": "lipsync-2", "input": [{"type": "video", "url": "https://assets.sync.so/docs/example-video.mp4"}, {"type": "audio", "url": "https://assets.sync.so/docs/example-audio.wav"}]}}
{"request_id": "request-2", "endpoint": "/v2/generate", "payload": {"model": "lipsync-2", "input": [{"type": "video", "url": "https://assets.sync.so/docs/example-video.mp4"}, {"type": "audio", "url": "https://assets.sync.so/docs/example-audio.wav"}]}}
2

Create a Batch

from sync import Sync
sync = Sync()
batch = sync.batch.create(
input=open("input.jsonl", "rb")
)
print(f"Batch created with ID: {batch.id}")

Instead of uploading a file, you can send application/json with the records in rows. Each row is the same object as one JSONL line, and both forms are validated the same way: 20 to 500 rows by default, 5 MB or smaller when written as JSONL, and unique request_id values. When a record is refused, the error names its line. Dialogue edit refusals return it in a lineNumber field; other record errors, such as invalid JSON or a missing request_id, give it only in message (for example Record at line 4: payload is required). Lines count non-empty records, not physical lines, so blank lines in a JSONL file are skipped; with a JSON body, line N is rows[N-1]. The examples above show two records for brevity; a batch needs at least 20.

Optional parameters:

  • webhook_url: Receive notifications when the batch completes
  • dry_run: Set to true to validate your input without processing. A valid input returns {"status": "VALIDATED"} and creates no batch.

Batch creation is not covered by idempotent requests. If a create request times out, list your batches with GET /v2/batch before submitting again, so you do not run the same rows twice.

3

Check Batch Status

Monitor your batch progress by polling the status:

batch = sync.batch.get(batch_id)
print(f"Status: {batch.status}")
print(f"Progress: {batch.metrics}")

A batch can have one of the following status:

  1. PENDING: Batch created, waiting to start processing
  2. PROCESSING: Generations are being processed
  3. COMPLETED: All generations finished (successfully or with failures)
  4. FAILED: Batch processing failed entirely
4

Check Batch Results

When a batch completes, results are available as a JSON Lines file at the outputUrl of the get batch response. A GET to that URL returns a 302 redirect to a signed result file; clients that follow redirects automatically can read the JSONL response directly. Each line contains:

output.jsonl
{"request_id": "request-1", "endpoint": "/v2/generate", "payload": {...}, "status": "COMPLETED", "error": null, "response": {...}, "updated_at": "2024-01-15T10:35:00Z"}
{"request_id": "request-2", "endpoint": "/v2/generate", "payload": {...}, "status": "FAILED", "error": {"code": "INVALID_INPUT", "message": "..."}, "response": null, "updated_at": "2024-01-15T10:35:00Z"}

The response field contains the same data as individual Generate API responses. Field names in the results file are snake_case, so a completed row’s video is at response.output_url and its generation ID at response.id. Match each line to your input by request_id.

For a complete working example, see the batch processing example in our examples repository.

Webhook Notifications

When you provide a webhook_url, you’ll receive POST notifications when your batch completes:

Batch webhook delivery is retried until delivered. Your receiver should treat the batch ID as an idempotency key and safely ignore duplicate notifications.

webhook_payload.json
{
"id": "batch_abc123",
"createdAt": "2024-01-15T10:30:00Z",
"status": "COMPLETED",
"webhookUrl": "https://your-webhook-url.com/batch-webhook",
"metrics": {
"totalGenerations": 5,
"successCount": 4,
"failedCount": 1,
"pendingCount": 0
},
"options": {},
"outputUrl": "https://api.sync.so/v2/batches/batch_abc123/result"
}

The completion webhook can be delivered more than once for the same batch. Use the batch id to ignore notifications you have already processed.

Dialogue edit rows (early access)

A batch row can render a completed dialogue edit preview instead of pairing a video with new audio. Use this to turn one recorded video into many variants, such as one per recipient name. Personalized Video Messaging has a complete script.

Dialogue edit rows are in early access and are enabled per organization. Contact us to turn them on. Until then, rows that carry dialogueEdit are refused at submit.

Each row’s payload contains:

  • model, for example sync-3.
  • input with exactly one video: the same source video the preview was made from.
  • dialogueEdit: {"id": "..."}: the ID of a completed preview from POST /v2/dialogue-edits in your organization.

Do not add audio or text inputs, segments, or dubParams; the preview supplies the audio.

{
"rows": [
{
"request_id": "line-2",
"endpoint": "/v2/generate",
"payload": {
"model": "sync-3",
"input": [{"type": "video", "url": "https://assets.sync.so/docs/example-video.mp4"}],
"dialogueEdit": {"id": "7644754c-bdfc-4d51-a78f-e0cb0cbbd40f"}
}
}
],
"dry_run": true
}

The same row objects work as lines in a JSONL file upload.

At submit, each dialogue edit row is expanded into exactly the request POST /v2/generate builds for that preview, and it is charged the same as that generation. The payload in the results file shows the expanded request (the video, the preview audio, and the edited segments), not the row you sent. Send the batch with dry_run: true first: it runs every check below, including the expansion, without rendering anything.

  • All dialogue edit rows in one batch must use previews made from the same source video. For several videos, submit one batch per video.
  • The usual batch limits apply: 20 to 500 rows and 5 MB.
  • Most previews render as edited segments of the video. A preview that can only be rendered as whole-video lip sync is accepted in a batch only when that render is 30 seconds or shorter; render longer ones with POST /v2/generate.

Dialogue edit refusals

If any dialogue edit row cannot be rendered, the whole request is refused and no batch is created. The response carries the errorCode that POST /v2/generate would return for that preview, plus the lineNumber of the first failing row:

{
"statusCode": 422,
"errorCode": "dialogue_edit_not_found",
"lineNumber": 3,
"message": "Record at line 3: The dialogue edit was not found in this organization."
}
errorCodeStatusCauseFix
dialogue_edit_not_found422The ID is unknown or belongs to another organization.Use a preview your organization created.
dialogue_edit_not_completed422The preview is still PENDING or PROCESSING, or it FAILED.While it is pending or processing, poll GET /v2/dialogue-edits/{id} until it completes, then submit. A FAILED preview is terminal: create a new one.
dialogue_edit_source_mismatch422The row’s video is not the video the preview was made from.Send the preview’s original source video.
dialogue_edit_retime_required422The preview lacks the retiming it needs to render (segmentLipsyncEnabled or sectionExpansionEnabled is false on it).Create a new preview. If it still reports false, contact us.
dialogue_edit_removal_too_large422The edit removes more speech than the surrounding footage can absorb.Restore some words and create a new preview.
dialogue_edit_audio_conflict422The row also has an audio or text input, segments, or dubParams.Send only the video input and dialogueEdit.
dialogue_edit_unsupported422The API cannot render this preview, including a whole-video render longer than 30 seconds.Create a new whole-video preview with an API key, or render this one with POST /v2/generate.
dialogue_edit_batch_mixed_sources422The previews in the batch come from different source videos.Submit one batch per source video.
generation_input_validation_failed400dialogueEdit is not an object with a UUID id, or input does not hold exactly one video.Fix the row’s shape.

Only the first failing row is reported. Fix it and repeat the dry run until it returns {"status": "VALIDATED"}.

  • Rate Limits — understand API rate limiting to plan your batch sizes effectively
  • Webhooks Guide — set up webhook notifications for batch completion events
  • Error Handling — reference for batch-specific error codes and troubleshooting