Batch processing lets you submit multiple audio files in a single API call. All jobs process concurrently. A single batch.complete webhook fires when every job is done — regardless of whether individual jobs succeeded or failed.
When to use batch vs individual jobs
| Use case | Recommendation |
|---|
| Releasing a multi-track album | Batch — one request, one webhook |
| Processing a catalog of 100+ songs | Batch in groups of 20 |
| Single track with a webhook | Individual job |
| Real-time single track (need result ASAP) | Individual job |
| Metadata-only for a set of tracks | Batch with align=false per job |
Submitting a batch
Batches are JSON-only — every job must use audio_url. File uploads are not supported.
POST /api/v1/batch
{
"webhook_url": "https://api.example.com/webhooks/lyrcs",
"jobs": [
{ "audio_url": "https://cdn.example.com/track-1.mp3", "language": "Hindi" },
{ "audio_url": "https://cdn.example.com/track-2.mp3", "language": "Tamil" },
{ "audio_url": "https://cdn.example.com/track-3.mp3", "language": "Telugu", "align": false }
]
}
- Minimum 1 job, maximum 20 jobs per request
- Per-job fields:
audio_url (required), language (required), align (optional, default true), review (optional, default false)
Rate limit pre-flight
The rate limit check is performed before any jobs are created. If submitting N jobs would push any window over the limit, the entire batch is rejected with 429 and no jobs are queued.
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: minute limit would be exceeded by this batch",
"code": "RATE_001",
"retry_after": 60,
"jobs_requested": 15
}
Check the X-RateLimit-Remaining-* headers on any previous response to know how much headroom you have before submitting a large batch.
Polling GET /batch/[id]
The batch status progresses through:
queued → in_progress → complete (or partial)
Poll every 15–30 seconds. For a 20-job batch, expect 2–3 minutes total. The batch finalizes as soon as the last job finishes — it does not wait the full polling budget.
async function waitForBatch(batchId, apiKey, { intervalMs = 15000, timeoutMs = 600000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`https://lyrcs.ai/api/v1/batch/${batchId}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const batch = await res.json();
if (batch.status === "complete" || batch.status === "partial") return batch;
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error("Batch timed out");
}
Handling partial failures
A batch.complete webhook with status: "partial" means at least one job failed. The failed field tells you how many. Individual job entries in jobs[] have their own status field.
// From a batch.complete webhook payload
const failed = payload.jobs.filter((j) => j.status === "failed");
for (const job of failed) {
console.error(`Job ${job.job_id} (${job.language}) failed — resubmit individually`);
// Resubmit as an individual job via POST /transcribe
}
const completed = payload.jobs.filter((j) => j.status === "complete");
for (const job of completed) {
const lrcUrl = job.downloads?.lrc_original;
if (lrcUrl) {
// Download and deliver
}
}
Failed jobs should be resubmitted individually via POST /transcribe. The batch endpoint requires at least 1 job and you may only have 1 to retry.
For catalog workflows where you need lyrics text but not LRC files, set align=false per job. Processing is faster (~60s per job) and the batch completes sooner.
{
"webhook_url": "https://api.example.com/webhooks/lyrcs",
"jobs": [
{ "audio_url": "https://cdn.example.com/track-1.mp3", "language": "Hindi", "align": false },
{ "audio_url": "https://cdn.example.com/track-2.mp3", "language": "Marathi", "align": false }
]
}
Jobs submitted with align=false will not include downloads in the batch.complete payload or in GET /batch/{id} responses.
Processing large catalogs
The rate limit is 1,000 jobs per day. For a catalog larger than 1,000 tracks, spread submissions across multiple days or contact support@lyrcs.ai to discuss higher limits.
For 1,000 tracks:
- 50 batches of 20 jobs each
- All within the daily limit
- Each batch submitted when the previous
batch.complete fires (or use a queue)