Batch API
GA-validated · 27/27 PASSSubmit up to 500 audio files against a saved recipe, then let us call you back when the work is done. Every guarantee below passed the GA readiness suite, 27 out of 27.
The async model
Submit a batch and it returns immediately with 202 Accepted and a batch_id. The work runs asynchronously behind a per-tenant fair-concurrency queue, so one account's large batch can never starve another's. Poll the status, or receive a completion webhook, then read the per-item results. Identical audio you resubmit within your own account is analyzed once and reused: we never re-analyze or re-charge you for the same file twice within your account.
Five verbs
The batch surface is five endpoints: submit, check status, read results, cancel, and retry.
| Verb | Route | Behavior |
|---|---|---|
| Submit | POST /v1/batch | 202 + batch_id (UUID), status IN_QUEUE, meta.source cloudflare_workflow. |
| Status | GET /v1/batch/{id} | Counters + percent_complete; terminal completed / failed / cancelled. |
| Results | GET /v1/batch/{id}/results | Per-item raw_results (page size 50, has_more). |
| Cancel | DELETE /v1/batch/{id} | Cancels pending items; in-flight items finish. |
| Retry | POST /v1/batch/{id}/retry | Requeues failed items by lineage (retry_of, attempt+1). |
Submit a batch
/v1/batchQueue a batch of audio against a recipe. Returns 202 with a batch_id (UUID) and status IN_QUEUE.
curl -X POST https://api.scarleta.ai/v1/batch \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipe_id": "rec_0007_stem_split",
"audio_urls": [
"https://example.com/track-1.wav",
"https://example.com/track-2.wav"
],
"webhook_url": "https://your-app.com/hooks/scarleta"
}'Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_id | string | Required | The saved recipe to run over every item (format rec_NNNN_name). |
| audio_urls | string[] | Required | Up to 500 audio URLs to process. Duplicate URLs are de-duplicated. |
| webhook_url | string | Optional | Where we POST the batch_completed event when the batch is terminal. |
| max_retries | integer | Optional | Positive integer; per-item automatic retry ceiling. |
Response
| Field | Type | Description |
|---|---|---|
| batch_id | string (UUID) | Identifier for the batch. |
| status | string | IN_QUEUE on a fresh submit. |
| meta.source | string | cloudflare_workflow: the engine running your batch. |
Example response
{
"batch_id": "b1a2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
"status": "IN_QUEUE",
"meta": { "source": "cloudflare_workflow" }
}Check status
/v1/batch/{id}Read live counters and percent_complete. Terminal states are completed, failed, or cancelled.
curl https://api.scarleta.ai/v1/batch/$BATCH_ID \
-H "Authorization: Bearer $SCARLETA_API_KEY"Response
| Field | Type | Description |
|---|---|---|
| status | string | IN_QUEUE, or terminal completed / failed / cancelled. |
| total_items | integer | Total items in the batch. |
| completed_items | integer | Items that finished successfully. |
| failed_items | integer | Items that failed after retries. |
| cancelled_items | integer | Items cancelled before they ran. |
| percent_complete | integer | Progress from 0 to 100. |
Example response
{
"batch_id": "b1a2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
"status": "completed",
"total_items": 2,
"completed_items": 2,
"failed_items": 0,
"cancelled_items": 0,
"percent_complete": 100
}Read results
/v1/batch/{id}/resultsPage through per-item raw_results. Page size is 50; has_more tells you when to fetch the next page.
curl "https://api.scarleta.ai/v1/batch/$BATCH_ID/results?page_size=50" \
-H "Authorization: Bearer $SCARLETA_API_KEY"Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page_size | integer | Optional | Items per page. Maximum and default is 50. |
Response
| Field | Type | Description |
|---|---|---|
| items | object[] | Per-item status and raw_results. |
| page_size | integer | Items returned in this page (max 50). |
| has_more | boolean | True when another page is available. |
Example response
{
"batch_id": "b1a2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
"items": [
{
"audio_url": "https://example.com/track-1.wav",
"status": "completed",
"raw_results": { "…": "per-item output for each recipe step" }
}
],
"page_size": 50,
"has_more": false
}Cancel a batch
/v1/batch/{id}Cancel pending items. Items already in flight are allowed to finish; nothing is dropped.
curl -X DELETE https://api.scarleta.ai/v1/batch/$BATCH_ID \
-H "Authorization: Bearer $SCARLETA_API_KEY"Retry failed items
/v1/batch/{id}/retryRequeue the failed items of a batch by lineage: each requeued item tracks its retry_of parent and attempt count.
curl -X POST https://api.scarleta.ai/v1/batch/$BATCH_ID/retry \
-H "Authorization: Bearer $SCARLETA_API_KEY"Error model
Every error returns a documented, stable code and an HTTP status: no guessing, no opaque 500s. These nine codes cover every way a batch call can be rejected.
Errors
| Status | Code | Description |
|---|---|---|
| 400 | missing_audio_urls | The audio_urls array is empty or absent. |
| 400 | missing_recipe_id | No recipe_id was supplied. |
| 400 | invalid_json | The request body was not valid JSON. |
| 400 | invalid_max_retries | max_retries must be a positive integer. |
| 422 | batch_too_large | More than 500 items in a single batch. |
| 404 | batch_not_found | Unknown batch id, or a batch owned by another account. |
| 409 | batch_already_final | Tried to cancel a batch that has already reached a terminal state. |
| 403 | batch_paid_only | The batch API is paid-only; free-tier keys are rejected by design. |
| 500 | workflow_binding_missing | Server-side deploy-regression guard; the workflow binding was unavailable. |
Completion webhook
Don't poll. Pass a webhook_url and we POST a batch_completed event when the batch reaches a terminal state. The payload is exactly these nine keys, in this order:
| Key | Type | Description |
|---|---|---|
| event | string | Always batch_completed. |
| batch_id | string (UUID) | The batch that finished. |
| status | string | Terminal state: completed, failed, or cancelled. |
| total_items | integer | Total items in the batch. |
| completed_items | integer | Items that finished successfully. |
| failed_items | integer | Items that failed after retries. |
| cancelled_items | integer | Items cancelled before they ran. |
| actual_total_cost | integer | Ledger-accurate total tokens charged for the batch. |
| completed_at | string (ISO 8601) | When the batch reached its terminal state. |
Deliveries are unsigned (the only secret is whatever you embed in the webhook URL), SSRF-guarded so private and loopback targets are never called, retried up to three times with backoff, and idempotent, with no double-POST on replay.
Example response
{
"event": "batch_completed",
"batch_id": "b1a2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
"status": "completed",
"total_items": 2,
"completed_items": 2,
"failed_items": 0,
"cancelled_items": 0,
"actual_total_cost": 1234,
"completed_at": "2026-09-17T18:04:11.000Z"
}