Skip to main content

Endpoint

Accepts 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.
The Vercel serverless body limit is ~4.5 MB. For larger files — most production audio — use audio_url instead, which is supported up to 100 MB.

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 HEAD before creating the job and reject anything larger with 413 VAL_002. The probe is best-effort: if your host refuses HEAD or omits Content-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 HEAD to check the size when the job is submitted, then a HEAD and a GET during 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.
If you are rendering karaoke-style or word-by-word lyric video, you want 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:
A value that is not a valid ISRC is rejected with 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:
The first request creates the job. Every repeat returns the same 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 202 submission response
  • GET /api/v1/jobs/{job_id}
  • The job.complete webhook payload
When omitted, the field is present but null in all responses.

Response — 202 Accepted

Use job_id to poll GET /api/v1/jobs/{job_id} for status and results.

Examples

Basic file upload

Transcript only — align=false

Use when you need metadata (lyrics text) but not time-synced files. Faster and sufficient for streaming store metadata delivery or liner notes.

With external_id for track mapping

With artist review gate — review=true

Use when results must be approved by the artist before delivery.