Endpoint
multipart/form-data (for file uploads) or application/json (for URL-based jobs). Returns 202 Accepted immediately — processing is asynchronous.
Parameters
language — required
The source language of the audio. Must be the exact full name as returned by GET /api/v1/languages. Values are case-sensitive.
file — required if audio_url absent
Audio file, submitted as multipart/form-data. Accepted formats: mp3, wav, flac, m4a, aac, ogg, webm.
audio_url — required if file absent
An HTTPS URL pointing to the audio file. Must use https:// — HTTP URLs are rejected.
- Supported up to 100 MB. We probe the URL with a
HEADbefore creating the job and reject anything larger with413 VAL_002. The probe is best-effort: if your host refusesHEADor omitsContent-Length, the job is created anyway and an oversized file fails later during transcription instead. Sizing the file yourself is more reliable than relying on the probe. - The URL must stay reachable until transcription has fetched it — in practice the first minute or two after submission. We then convert the audio and keep our own copy, so the URL does not need to outlive that: alignment, re-alignment and downloads all read our copy, and a job held at a review gate for days never touches your URL again.
- Strictly one-time-use URLs will not work. We make up to three requests: a
HEADto check the size when the job is submitted, then aHEADand aGETduring transcription. A retried step can add more. Time-limited presigned URLs are fine as long as the window covers transcription.
align — optional, default true
Controls whether time-alignment runs after transcription.
When
false, the downloads object is omitted from the job response and webhook payload.
word_align — optional, default true
Controls whether word-level timestamps are generated in addition to line-level LRC/SRT. Only takes effect when align=true.
When
true, words_original and words_transliterated are added to results.downloads on completion. When false, those keys are absent.
word_align=false is no longer meaningfully cheaper or faster. On accounts
using forced alignment, word timings come back from the same pass that produces
the line timings, so turning them off saves nothing. Choose false only if
per-word data would be noise in your pipeline — not to reduce cost or latency.true.
Line timings alone cannot drive per-word highlighting.
isrc — optional
The recording’s International Standard Recording Code. Optional, and it will stay optional — ISRCs are assigned at release, so unreleased material, demos and pre-release masters often have none, and a distributor getting lyrics approved before release may not have one yet.
Send it when you have it. It is the only identifier that means the same thing to us and to your catalogue system, which is what makes “have we already processed this recording?” answerable. external_id cannot do that job — it is opaque to us — and a file hash cannot either, because a delivery master and an artist’s earlier upload are different encodes of the same recording.
Hyphens and lower case are accepted and normalised, so the form you have in a delivery sheet works as-is:
400. That is deliberate: a malformed ISRC is worse than none, because it looks like a join key and silently fails to match.
It is echoed on GET /jobs/{id}, in list rows, and in webhook payloads, and can be used as a filter: GET /v1/jobs?isrc=USSKG2400001.
Retrying safely — Idempotency-Key
If a submission times out you cannot tell whether it arrived. Retrying risks a duplicate job and a duplicate charge; not retrying risks the song silently never being processed.
Send an Idempotency-Key header — any unique string, one per song — and reuse it on every retry of that same submission:
job_id, with an Idempotent-Replay: true header, without creating or charging for anything.
Keys are scoped to your organisation and last 24 hours. The header is optional and changes nothing if omitted — but for any automated integration it is strongly recommended, and it is the only thing that prevents being billed twice for one song.
POST /batch accepts the same header, covering the whole job list.
review — optional, default false
When true, the job stops for a human before it is delivered. A job.awaiting_review event carries a review_url; job.complete follows once someone approves.
You do not need a webhook_url. The review_url also appears on GET /jobs/{id} and GET /jobs, so a polling consumer reaches the gate perfectly well without receiving webhooks.
review_stage — optional, default "aligned"
Where the review gate sits. Requires review: true; sending it alone is a validation error.
Use
"transcript" when corrections should reach the aligner. An edit at the aligned gate changes the words but cannot move the timings that were already produced from the old ones; an edit at the transcript gate is aligned from scratch.
"aligned" is the default so existing integrations are unaffected.
A
review_stage: "transcript" job waits up to 8 days for approval. If nobody
approves, it is marked failed with stage review rather than sitting in
processing forever.review_delivery — optional, default "both"
Which ways a held job can be approved. Requires review: true.
"api" is for running the review inside your own product — see
Running Review In Your Own Product. It is a structural
guarantee rather than a convention: the link’s security is its token, and for
these jobs no token exists.
Leave it unset unless you need a door shut. "both" lets you fall back to
forwarding review_url for a song your own reviewer cannot handle.
end_user — optional
The customer of yours this song belongs to. Useful when the artist never touches
lyrcs.ai, so a job would otherwise carry no trace of whose song it is.
external_id is required within the object and is your identifier — send the
same one and you get the same record. email and name are optional; a later
submit fills a missing one but never overwrites an existing value, and records are
never merged on email.
Echoed on the submission response, GET /jobs/{id} and list rows, and filterable:
GET /v1/jobs?end_user_id=cust_8842.
In multipart/form-data, send it as a JSON-encoded string in an end_user field.
These are not lyrcs.ai accounts and cannot sign in anywhere. The record exists
so that “which of your customers is this job?” has an answer on both sides.
webhook_url — optional
An HTTPS URL where event payloads will be delivered. Accepted in both the JSON body and multipart/form-data.
Webhook events: job.complete, job.awaiting_review, job.degraded, job.failed. See the Webhooks guide for payload shapes and retry behaviour.
external_id — optional
An arbitrary string (max 255 characters) that you can use to map the job back to a track in your own system. Accepted in both the JSON body and multipart/form-data.
When provided, external_id is echoed back in:
- The
202submission response GET /api/v1/jobs/{job_id}- The
job.completewebhook payload
null in all responses.
Response — 202 Accepted
job_id to poll GET /api/v1/jobs/{job_id} for status and results.