Skip to main content
The review flow puts a human between transcription and delivery. You choose where that human sits with review_stage, and the choice changes what they can affect. There are two ways to reach that human, and they are two doors into the same held job rather than two modes: the artist reviews on our page via review_url (this guide), or inside your product via POST /jobs/{id}/approve. Both converge on the same approval, and you can choose per job with review_delivery. If the artist is already signed in to your app, read Running Review In Your Own Product instead.

Choosing a stage

review_stage: "transcript" — the job stops as soon as the lyrics exist, before any timing work. The reviewer checks words; alignment then runs on the text they approved. Use this when corrections should reach the aligner. It is the right default for distributor workflows where an artist confirms wording. review_stage: "aligned" (the default) — the job aligns first and stops with finished, time-synced lines. The reviewer can hear each line in place. Use this for QC on timing. Corrections are still accepted, and are re-timed against the audio on approval.
"aligned" is the default so that integrations built before review_stage existed are unaffected.

End-to-end: review before alignment

1

Submit with review_stage=transcript

Returns 202 { "job_id": "...", "status": "queued", "review_required": true, "review_stage": "transcript" }.webhook_url is optional — see Without webhooks below.
2

Transcription runs (~60 seconds)

Lyrics, transliteration and translation are produced. No alignment yet.
3

job.awaiting_review fires

4

Route review_url to the artist

Forward it by email, SMS, or your own platform. The artist does not need a lyrcs.ai account.
5

The artist reviews and approves

They see the lyrics without timestamps — timings do not exist yet — and can correct any line before approving.
6

Alignment runs on the approved text (~30 seconds)

This is the point of the transcript gate: the aligner receives the corrected words, not the original ones.
7

job.complete fires with full results

Transcript, transliteration, translation and download URLs for LRC, SRT and — when requested — word-level JSON.

End-to-end: review after alignment

Identical, with two differences: the gate fires after alignment (~90s rather than ~60s), the reviewer sees time-synced lines and can click any timestamp to hear it, and approval finalises the job rather than starting alignment. If they correct a line, approval re-runs alignment over the corrected text so the word-level timings continue to describe the words actually present. Where re-alignment is unavailable for your organisation, the word-level track is removed instead of being returned stale — the approval response says which happened via its alignment field.

What is held back, precisely

Only the job.complete webhook waits for approval. GET /api/v1/jobs/{id} reports the pipeline’s real state throughout, and output that exists is readable. On an aligned-stage job that means status: "complete" and working download URLs before anyone has approved.
Do not treat status: "complete" as sign-off. review_approved_at is the only field that means a human approved. If you need results to be genuinely unavailable before approval, use review_stage: "transcript" — alignment has not run, so there is nothing timed to fetch early.

Without webhooks

A webhook endpoint is convenient but not required. Every field the flow needs is on the job:
Poll until review_url is non-null to get the link, then until review_approved_at is non-null to learn approval happened. A transcript-stage job also reports stage: "awaiting_review" while it waits, so you can find everything pending in one sweep. At scale, poll GET /jobs with updated_since rather than each job individually. review_url is null until the job reaches its gate — after transcription for the transcript stage, after alignment for the aligned stage. Do not cache a null value.

Tokens and expiry

Review tokens are valid for 7 days and may be used more than once, so a reviewer can open the link, leave, and return. After review_expires_at the link stops working; contact support@lyrcs.ai for a new one. A review_stage: "transcript" job that is never approved is marked failed with stage review after 8 days, rather than sitting in processing forever.

Review in batch jobs

Batch jobs support both parameters per job:
Each job’s review_url appears in GET /api/v1/batch/{id} once that job reaches its gate.
batch.complete counts a review job as done when its pipeline work finishes, not when a human approves — so it can fire before every artist has signed off. Individual job.complete webhooks still fire per approval. Track approvals through review_approved_at, not through batch.complete.

What a reviewer can change

They can retype any line, and — where the timings can be produced afresh — add, delete and reorder lines. That matters because transcription gets line boundaries wrong as well as words: a line can be missed entirely, two sung lines merged into one, or one split across two. Adding and removing lines is offered when the job is held at the transcript gate, where alignment has not run yet, and at the aligned gate only where re-alignment is available. Where it is not, the wording of existing lines can still be corrected — but a new line could only be given a timestamp nobody measured, so the control is not offered and an attempt is refused with 409 REV_001. Line operations apply to both scripts together. Transliteration is position-mapped to the original, so inserting into one alone would misalign every pair below it.

Heavy edits are recorded

Every approval records how many lines changed on each script, and keeps a write-once snapshot of what the model originally produced. An edit that changes most of the lyric is additionally flagged as a replacement. Nothing is blocked. A genuinely poor transcript deserves a heavy correction, and refusing one would punish a reviewer for our error. The flag exists so that wholesale replacement is visible rather than invisible.

Other limitations

  • The job.complete webhook fired after approval is single-attempt and does not retry. If you miss it, poll the job.