Tonael v1
On this page

Tonael API

Submit an MP3 audio file with pre-segmented lyrics and receive word-level timestamps for karaoke synchronization. Results are delivered asynchronously and retained per tier: 24 hours for hot (real-time) jobs, 72 hours for batch jobs.

Base URL: https://tonael.com

Authentication

All API requests require an API key sent via the X-API-Key header.

X-API-Key: ton_your_api_key_here

API keys are provided by the platform administrator. Keys prefixed with ton_ are Tonael keys.

Free Tier & Sign-up

Start free — 10 free minutes of tonael, no card required. Add a card and your allowance grows to 30 free minutes. The grant lands as credit on your balance, so you choose which model to spend it on — the minutes quoted are at the tonael hot rate, so tonael_ultra draws it down faster and batch stretches it further. Free-tier keys run on hot (real-time) processing with a small concurrency cap; making your first top-up lifts those limits and unlocks batch processing.

POST /api/v1/signup

Create an account from an email address. The API key is returned exactly once. Your free minutes unlock after you verify your email.

curl -X POST https://tonael.com/api/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'

GET /api/v1/verify

Redeem the verification token from your sign-up email. Verifying your email releases the free credit.

curl "https://tonael.com/api/v1/verify?token=your_verification_token"

Pricing & Metering

You are billed per second of audio, rounded up, with a 30-second minimum per job. Prices are in USD, exclusive of VAT/sales tax.

Model Hot (real-time) Batch (24h window, −37.5%)
tonael
word-level alignment
$0.045 / min $0.028 / min
tonael_ultra
vocal isolation + alignment
$0.095 / min $0.059 / min

A 3.5-minute song costs from $0.16 (tonael) or $0.33 (tonael_ultra) at hot rates. Batch has a 24-hour window: a batch job starts within 24 hours, or it is rejected and fully refunded, and its result stays available for 72 hours — see Batch processing. Processing more than 10,000 minutes/month? Talk to us.

Failed jobs are never charged. If a job fails or is cancelled, the reserved amount is automatically re-credited to your balance.

Credits

Credit is prepaid. Buy a pack — $15, $50, or $200 — or top up a custom amount. Minimum top-up: $15. Jobs draw down your balance as they are metered.

GET /api/v1/credits/balance

Return the current balance in dollars.

curl https://tonael.com/api/v1/credits/balance \
  -H "X-API-Key: ton_your_api_key_here"

{ "balance_usd": "42.50" }

POST /api/v1/credits/topup

Charge your registered card for a credit pack (pack_15, pack_25, pack_50, pack_100, pack_200) or a custom amount_usd (minimum 15). Provide exactly one of the two.

Before your first top-up, set your billing details once via POST /api/v1/credits/tax-identity (country, is_business, and for an EU business an optional vat_number). They determine the VAT on your invoice, so a top-up before they are set returns 422 TAX_IDENTITY_REQUIRED.

An Idempotency-Key header is required. Generate one value per purchase (a UUID is ideal) and reuse that same value if you retry a request that timed out or failed with a network error — that is what guarantees your card is charged only once. A repeat of a key that already went through returns 409 DUPLICATE_SUBMISSION instead of charging again.

curl -X POST https://tonael.com/api/v1/credits/topup \
  -H "X-API-Key: ton_your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f14e45f-ea3f-4d1b-9a2c-7b6c0d5e1a90" \
  -d '{"pack": "pack_50"}'

{ "status": "credited", "amount_usd": "$50.00", "balance_usd": "92.50" }

Credits are prepaid and non-refundable, and never expire. Failed or cancelled jobs are automatically re-credited to your balance; no cash refunds are issued.

POST /api/v1/sync

Submit an alignment job. Returns immediately with a job ID. Results are delivered asynchronously.

Request

Content-Type: multipart/form-data

Field Type Required Description
audio file One of Audio file — MP3, WAV, FLAC, M4A/AAC, OGG/Opus, AIFF, WMA, WebM and more (max 1 GB, max 3 hours). Provide this or audio_url.
audio_url string One of HTTPS URL we download the audio from instead of you uploading it. Same formats and limits. Recommended for large files or slow connections — a direct upload ties up the request for as long as your link takes, whereas a URL is fetched server-to-server in seconds. Signed / expiring URLs work. The host must be publicly reachable over HTTPS.
lyrics string Yes Lyrics text (max 240,000 chars). Auto-segmented if no line breaks provided.
language string Yes Language of the lyrics: one of the 28 supported codes, e.g. en, fr, zh.
model string No tonael (default) or tonael_ultra. Tonael Ultra delivers better results than Tonael, at a higher cost. Try Tonael first; switch to Tonael Ultra if the result is not good enough.
granularity string No char (default) or word. The default char returns word timestamps plus a nested chars array of per-letter timestamps on each word. Pass word for a lighter, word-only response. Same price either way — granularity does not change billing.
priority string No hot (default, real-time) or batch (lower cost, opportunistic processing with a 24-hour window: the job starts within 24 hours, or it is rejected and fully refunded — see batch_fallback to switch it to hot instead). See Batch processing. Batch requires a paid account.
batch_fallback string No Batch jobs only. What happens if the job has not started by the end of the 24-hour batch window: reject (default — the job is rejected and fully refunded) or hot (the job automatically switches to hot processing, billed at the hot rate). The value is always validated (an invalid value is a 400, even on hot submissions) but has no effect outside batch. See Batch processing.
callback_url string No Batch jobs only: HTTPS URL notified when the job completes or is rejected, signed with your API key's webhook secret — see Webhooks. Ignored on hot submissions; poll the status endpoint instead.

Example

curl -X POST https://tonael.com/api/v1/sync \
  -H "X-API-Key: ton_your_api_key_here" \
  -F "audio=@song.mp3" \
  -F "lyrics=Joyeux anniversaire
Cher Antoine
On te souhaite
Plein de bonheur" \
  -F "language=fr"

Response 202 Accepted

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "processing",
  "created_at": "2026-04-04T10:30:00Z"
}

GET /api/v1/sync/{job_id}

Retrieve the status and result of an alignment job. Results are available for 24 hours after completion for hot jobs, and 72 hours for batch jobs. After that window the endpoint returns 410 Gone.

Query Parameters

Param Default Description
format json Output format. Options: json, lrc, srt, vtt, ass, ttml, sbv, txt

Export Examples

# Get as Enhanced LRC (karaoke)
curl "https://tonael.com/api/v1/sync/{job_id}?format=lrc" -H "X-API-Key: ..."

# Get as SRT (subtitles)
curl "https://tonael.com/api/v1/sync/{job_id}?format=srt" -H "X-API-Key: ..."

# Get as WebVTT (web video)
curl "https://tonael.com/api/v1/sync/{job_id}?format=vtt" -H "X-API-Key: ..."

# Get as ASS (video karaoke with word highlighting)
curl "https://tonael.com/api/v1/sync/{job_id}?format=ass" -H "X-API-Key: ..."

Example

curl https://tonael.com/api/v1/sync/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "X-API-Key: ton_your_api_key_here"

Response 200 OK (completed)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "language": "fr",
  "model": "tonael",
  "product_type": "sync_tonael",
  "created_at": "2026-04-04T10:30:00Z",
  "completed_at": "2026-04-04T10:30:12Z",
  "expires_at": "2026-04-05T10:30:12Z",
  "result": {
    "processing": { "processing_time_ms": 3437 },
    "result": {
      "segments": [
        {
          "index": 0,
          "text": "Joyeux anniversaire",
          "start": 5.240,
          "end": 7.810,
          "words": [
            { "word": "Joyeux",       "start": 5.240, "end": 5.880, "confidence": 0.94 },
            { "word": "anniversaire", "start": 5.920, "end": 7.810, "confidence": 0.89 }
          ]
        }
      ],
      "statistics": {
        "total_segments": 4,
        "total_words": 12,
        "avg_confidence": 0.87,
        "low_confidence_words": 1,
        "unaligned_words": 0,
        "duration_s": 30.5
      }
    }
  }
}

Per-letter timestamps (default)

By default (no granularity param), each word carries both its own timestamps and a nested chars array of per-letter timestamps. Pass granularity=word for a lighter, word-only response with no chars array. Word-level fields are identical either way, and the price is the same.

{
  "word": "café",
  "start": 5.240,
  "end": 5.880,
  "confidence": 0.94,
  "chars": [
    { "char": "c", "start": 5.240, "end": 5.360, "confidence": 0.95 },
    { "char": "a", "start": 5.360, "end": 5.520, "confidence": 0.93 },
    { "char": "f", "start": 5.520, "end": 5.700, "confidence": 0.92 },
    { "char": "é", "start": 5.700, "end": 5.880, "confidence": 0.90 }
  ]
}

Notes: character timestamps are approximately 20 ms in resolution. Some characters — punctuation, and certain diacritics or accents — may not receive an independent timestamp, so a word's chars array can be shorter than its letter count. Characters are always returned in reading order with non-decreasing start times within a word.

Response 202 Accepted (still processing)

{
  "job_id": "a1b2c3d4-...",
  "status": "processing",
  "created_at": "2026-04-04T10:30:00Z"
}

Response 410 Gone (expired)

{
  "error": {
    "code": "RESULT_EXPIRED",
    "message": "Result has expired and is no longer available",
    "details": "Results are retained for 24 hours (72 hours for batch jobs)"
  }
}

DELETE /api/v1/sync/{job_id}

Cancel a job that is still in the queue — typically a batch job waiting for its window — and get its reserved amount back in full. Only jobs with status queued can be cancelled. The same call cancels a queued stem separation or karaoke video: DELETE /api/v1/stems/{job_id} and DELETE /api/v1/video/{job_id}.

A 409 NOT_CANCELLABLE means the job can no longer be cancelled: it is processing or finished, or it was already claimed for processing. A batch job can still read queued for a moment after it is claimed, so a 409 on a queued job means its processing has started.

Example

curl -X DELETE https://tonael.com/api/v1/sync/{job_id} \
  -H "X-API-Key: ton_your_api_key_here"

Response 200 OK

{ "job_id": "...", "status": "cancelled" }

Response 409 Conflict (not cancellable)

{ "error": { "code": "NOT_CANCELLABLE", "message": "Job is 'processing' — only 'queued' jobs can be cancelled" } }

Batch Processing

Not in a hurry? Submit with priority=batch for a lower rate. Batch has a 24-hour window: a batch job starts within 24 hours, or it is rejected and fully refunded — or switched to hot, if you ask for it. Its result stays available for 72 hours after completion. Batch is opportunistic and available on paid accounts; make your first top-up to unlock it.

Endpoint Batch price
POST /api/v1/sync $0.028 / min tonael, $0.059 / min tonael_ultra
POST /api/v1/stems $0.087 / min full, $0.047 / min vocal_instrumental
POST /api/v1/video
POST /api/v1/video/rerender
The batch base rate of $0.037 / min, times the render_mode factor, times the resolution factor (none for cdg) — the same factors as the hot price. Every option's price per minute is listed on the pricing page.
  • Same endpoints, same request shape — add priority=batch, and optionally batch_fallback and callback_url, with the same names and values on every endpoint above. A video render still requires callback_url; its webhook_secret is optional in batch and has no effect there.
  • The cost is held from your balance at submission (batch rate), exactly as for hot jobs — insufficient balance is rejected synchronously with INSUFFICIENT_CREDIT.
  • Batch is not guaranteed processing. If the job has not started when the 24-hour batch window ends, the default resolution is a rejection with a full refund: the job fails with error code BATCH_WINDOW_ELAPSED, nothing is charged, and you can resubmit hot (immediate) or batch again (same risk).
  • Prefer never being rejected? Pass batch_fallback=hot: if the 24-hour window ends before the job starts, the job automatically switches to the hot lane and is billed at the hot rate — for a video, the hot price of the same render_mode and resolution (the price difference is charged when the switch happens; if your balance cannot cover it, the job falls back to the default rejection + full refund). An escalated job is a hot job from that point on: hot rate, hot result retention (24 hours).
  • Batch results are retained 72 hours after completion (hot: 24 hours).
  • Provide a callback_url to be notified on completion or rejection — signed with your API key's webhook secret, see Webhooks — or poll the status endpoint. A batch job still waiting can be cancelled for a full refund: see Cancel.
curl -X POST https://tonael.com/api/v1/sync \
  -H "X-API-Key: ton_your_api_key_here" \
  -F "audio=@song.mp3" \
  -F "lyrics=..." \
  -F "language=fr" \
  -F "priority=batch" \
  -F "batch_fallback=hot" \
  -F "callback_url=https://your-app.example.com/hooks/tonael"

Stem separation

Split a track into isolated stems. A separate product from alignment — billed on its own, per minute of source audio. No lyrics needed.

POST /api/v1/stems

FieldValuesNotes
audioMP3 or WAVthe file to separate
stems_modefull, vocal_instrumentalfour tracks, or vocals + instrumental. Sets the price.
output_formatmp3, wavMP3 320 kbps, or PCM 16-bit
priorityhot, batchdefaults to hot. Batch takes the batch price and the 24-hour window.
batch_fallbackreject, hotbatch only: what happens if the job has not started when the window ends. Defaults to reject.
callback_urlHTTPS URLbatch only: notified on completion or rejection, signed with your API key's webhook secret

Separation runs at one quality — the best one. There is no model choice: tonael and tonael_ultra are accepted for compatibility and produce the same result at the same price.

GET /api/v1/stems/{job_id}

202 while processing; 200 with a download_url per stem once done; 410 once the retrieval window has passed.

GET /api/v1/stems/{job_id}/download/{stem_name}

Streams one stem. No API key required — the unguessable job id is the capability, so the URL works directly in a player or a browser. Stem names are vocals, drums, bass, other, or vocals and instrumental.

DELETE /api/v1/stems/{job_id}

Cancels a job still queued — typically a batch job waiting for its window — and releases its hold in full. A job already processing, or already claimed for processing, cannot be cancelled: that is a 409, as described under Cancel.

Karaoke video

Render an alignment you already have into a video. It takes the audio and the timings — not lyrics — so three formats of one song cost one alignment and three renders, not three of each.

POST /api/v1/video

FieldValuesNotes
audioaudio filethe track the video carries
alignmentJSONwhat GET /api/v1/sync/{job_id} returned. Either shape is accepted.
orientationlandscape, portrait, square16:9, 9:16 or 1:1
resolution720p, 1080p, 4kdefaults to 1080p. Sets the price.
render_modeburned, greenscreen, alpha, cdgMP4, MP4 on a key colour, ProRes MOV with transparency, or an MP3+G bundle. Sets the price.
templatea name from the list belowdefaults to classic
styleJSON objectoverride any individual look parameter
backgroundPNG, JPEG or WebPyour own still image behind the lyrics
duet_map{"a": [0,2], "b": [1,3]}which singer sings which line, by index. Free — it changes colours, not processing.
callback_urlHTTPS URLrequired. The render is delivered here, not polled.
priorityhot, batchdefaults to hot. Batch takes the batch price of the same render_mode and resolution, and the 24-hour window.
batch_fallbackreject, hotbatch only: what happens if the render has not started when the window ends. Defaults to reject; hot bills the hot price of the same options.
webhook_secret16+ charactersrequired for a hot render: it signs the delivery, same scheme as every other webhook — verify hot video deliveries with this value, not your key's webhook secret. Optional in batch and has no effect there: a batch render is signed with your key's webhook secret.

Answers 202 with a job_id. The alignment must carry a positive audio_duration_s — it is what the render is billed on — and every word timestamp must fall inside it.

GET /api/v1/video/{job_id}/download

Streams the finished file. No API key required — the unguessable job id is the capability, so the URL plays directly in a browser or a player. Available for 24 hours (72 hours for a batch render). A transparent render is a ProRes MOV, which browsers do not decode: download it and open it in your editor.

GET /api/v1/video/templates

The look library, as data you can build a picker from: name, label, description and whether it renders today. A template marked available: false carries available_when naming what it still needs — asking for one is a 400, never a silent fallback to a different look.

POST /api/v1/video/rerender

Renders from a completed /sync job by id, without paying for the alignment again — pass the audio plus job_id and the timings are fetched for you. Every parameter above applies, priority and batch_fallback included. Billed at the video rate only.

Webhooks

We POST a JSON body to your callback_url when a job reaches a terminal state. Two kinds of job are pushed this way:

  • Batch jobs (POST /api/v1/sync, /api/v1/stems, /api/v1/video and /api/v1/video/rerender with priority=batch), on completion or rejection — a job switched to hot by batch_fallback=hot included. Signed with your API key's webhook signing secret, video renders included.
  • Hot karaoke video (POST /api/v1/video and POST /api/v1/video/rerender without priority=batch). Signed with the webhook_secret you sent with the request.

A hot /api/v1/sync job and a hot stem separation are not pushed: poll their status endpoints.

Delivery guarantees

  • At-least-once delivery. A webhook may be delivered more than once. Deduplicate on job_id — treat a repeat of a job_id you have already processed as a no-op.
  • Failed deliveries are retried with exponential backoff. Respond with a 2xx to acknowledge.

Your webhook signing secret

Each API key has its own. Find it in the console: API keys, then Webhook secret on the key's row. Rotate there replaces it: webhooks queued after the rotation are signed with the new secret, while a delivery already queued keeps the secret it was signed with, retries included. Hot video renders are the exception: they are signed with the webhook_secret you sent with the request, not with your key's secret. A batch video render is signed with your key's secret, like every other batch job.

Verifying the signature

Every delivery carries an X-Webhook-Signature header: the HMAC-SHA256 hex digest of the raw request body, keyed with the signing secret above. Compute it over the exact bytes you received, before parsing the JSON. We send canonical JSON — keys sorted alphabetically, UTF-8 encoded — but a body you parse and serialize again need not match those bytes, so never verify a re-serialized copy. Compare in constant time, and reject the delivery if the signature does not match.

import hashlib
import hmac

def verify(raw_body: bytes, signing_secret: str, received_signature: str) -> bool:
    expected = hmac.new(
        signing_secret.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, received_signature)

# Flask: verify(request.get_data(), SECRET, request.headers.get("X-Webhook-Signature", ""))
const express = require("express");
const crypto = require("crypto");

const app = express();
const SIGNING_SECRET = process.env.TONAEL_WEBHOOK_SECRET;

// express.raw keeps req.body as the exact bytes received (a Buffer).
app.post("/hooks/tonael", express.raw({ type: "application/json" }), (req, res) => {
  const expected = Buffer.from(
    crypto.createHmac("sha256", SIGNING_SECRET).update(req.body).digest("hex"),
  );
  const received = Buffer.from(req.get("X-Webhook-Signature") || "");
  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body.toString("utf8"));
  // Deduplicate on event.job_id, then handle the event.
  res.sendStatus(200);
});

Batch payload (completed)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "service_tier": "batch",
  "result_url": "https://tonael.com/api/v1/sync/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "expires_at": "2026-04-05T10:30:12+00:00",
  "result": { "...": "the alignment GET /api/v1/sync/{job_id} returns" }
}

Batch payload (rejected or failed)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "failed",
  "error": {
    "code": "BATCH_WINDOW_ELAPSED",
    "message": "..."
  }
}

result_url is the status endpoint that serves the result. error.code is BATCH_WINDOW_ELAPSED when the 24-hour batch window elapsed (nothing is charged), or PROCESSING_FAILED. A video delivery carries a video object instead: video_url, the file's content_type, render_mode, dimensions and expires_at.

GET /api/v1/health

Health check endpoint. No authentication required. Returns the status of all system components.

Response 200 OK (all systems operational)

{
  "status": "healthy",
  "checks": {
    "api": "ok",
    "database": "ok",
    "processing": "ok"
  }
}

Response 503 Service Unavailable (degraded)

{
  "status": "degraded",
  "checks": {
    "api": "ok",
    "database": "ok",
    "processing": "down"
  }
}

Use this endpoint for uptime monitoring. Status healthy = all systems go. degraded = partial outage. unhealthy = critical failure.

Error Codes

HTTP Code Description
400INVALID_AUDIOUnsupported, empty or undecodable audio — or neither/both of audio and audio_url were provided
400AUDIO_TOO_LARGEFile exceeds 1 GB
400INVALID_AUDIO_URLaudio_url is not an HTTPS URL, or its host is not publicly reachable
400AUDIO_URL_UNREACHABLEWe could not download the audio at audio_url (timeout, non-200, or empty response)
400AUDIO_TOO_LONGAudio exceeds 3 hours
400INVALID_LYRICSLyrics exceed 240,000 chars
422LYRICS_REQUIREDNo lyrics provided — this endpoint aligns the lyrics you provide with your audio
400UNSUPPORTED_LANGUAGELanguage code not supported
401UNAUTHORIZEDMissing or invalid API key
402INSUFFICIENT_CREDITNot enough credit for this job — the message shows available, required, and the exact shortfall in dollars. Top up via POST /api/v1/credits/topup (minimum $15)
403KEY_PAUSEDAPI key is paused by administrator
403KEY_REVOKEDAPI key has been revoked
403BATCH_REQUIRES_PAIDBatch priority requires a paid account — add credit with your first top-up to unlock it (POST /api/v1/credits/topup)
403WALLET_REQUIREDThis API key has no wallet and cannot be billed
400INVALID_PRIORITYThe priority value is invalid — valid values are hot and batch
400INVALID_BATCH_FALLBACKThe batch_fallback value is invalid — valid values are reject and hot
400INVALID_WEBHOOK_SECRETA real-time video render (POST /api/v1/video or /api/v1/video/rerender without priority=batch) needs a webhook_secret of at least 16 characters — it is optional for a batch render
—BATCH_WINDOW_ELAPSEDTerminal job state (webhook / status polling, not an HTTP error): the batch job did not start within the 24-hour batch window and was fully refunded — resubmit with priority=hot, or use batch_fallback=hot to switch automatically next time
404JOB_NOT_FOUNDJob ID does not exist or belongs to another key
429CONCURRENCY_LIMITToo many jobs in flight for this key — retry once an in-flight job finishes
429BATCH_CONCURRENCY_LIMITThis key already has the maximum number of batch jobs waiting or in flight — submit more once some of them finish
409LEGACY_TOPUP_UNSUPPORTEDThis account is dollar-denominated — use POST /api/v1/credits/topup with a pack (pack_15, pack_25, pack_50, pack_100, pack_200) or a custom amount_usd (minimum $15)
409ACCOUNT_NOT_MIGRATEDThis account is not yet dollar-denominated — the /api/v1/credits endpoints become available after migration
400IDEMPOTENCY_KEY_REQUIREDA top-up was sent without an Idempotency-Key header — it is required on POST /api/v1/credits/topup so a retried request can never charge your card twice
409DUPLICATE_SUBMISSIONThis Idempotency-Key was already used for a top-up — the original purchase stands and nothing was charged again. Use a new key for a genuinely new purchase
422TAX_IDENTITY_REQUIREDSet your billing details first via POST /api/v1/credits/tax-identity (country + business capacity) — they determine the VAT on your invoice
410RESULT_EXPIREDResult deleted after its retention window (24 hours for hot jobs, 72 hours for batch)
503BATCH_UNAVAILABLEBatch processing is temporarily unavailable — resubmit with priority=hot to process in the real-time lane
500INTERNAL_ERRORUnexpected server error

All errors follow the format: {"error": {"code": "...", "message": "..."}}

Billing & Refunds

Failed jobs are never charged. When a job fails or is cancelled, the credit reserved for it is automatically returned to your balance — no action needed on your side. The recredit appears in GET /api/v1/billing/usage as a positive entry labeled “Job failed or cancelled (credit returned to balance)”.

Every job is billed individually. When you submit multiple jobs, each one is charged on its own: a failed job is recredited on its own, and never affects what you pay for the jobs that completed.

Credits are non-refundable. Prepaid credits are sold to professional customers and are not reimbursed in cash, whether consumed or not. Any commercial gesture is granted in credits. See the Terms of Sale for the full doctrine, including the statutory carve-out that applies if you purchase as a consumer.

Credits never expire. Your balance stays usable for as long as your account is open and in good standing. There is no validity period and no expiry sweep — on any account type, with or without a VAT ID.

Supported Languages

Send the language of the lyrics as language. These 28 codes are accepted by both models; any other code is rejected with UNSUPPORTED_LANGUAGE.

Bulgarianbg
Chinese (Mandarin)zh
Croatianhr
Czechcs
Danishda
Dutchnl
Englishen
Filipinotl
Finnishfi
Frenchfr
Germande
Greekel
Hindihi
Indonesianid
Italianit
Japaneseja
Koreanko
Malayms
Polishpl
Portuguesept
Romanianro
Russianru
Slovaksk
Spanishes
Swedishsv
Tamilta
Turkishtr
Ukrainianuk

Lyrics Guidelines

What you should do

Send the lyrics as-is: Just paste the raw lyrics text. The API handles everything else — formatting, cleaning, and segmentation are fully automatic.

Tip: For best results, include only what is actually sung or spoken. But even if you include metadata or headers, the API will do its best.

What the API handles automatically

AUTO Line segmentation: Long lines are automatically split at natural points (punctuation, pauses) for optimal karaoke display. You can send a single block of text — the API will segment it intelligently.

AUTO Bracket tags: Section markers like [Verse 1], [Chorus], [SFX], [Female] are automatically stripped.

AUTO Numbers: Digits are converted to words (10 → dix, 5 → cinq) based on the language parameter.

AUTO Accented characters: Characters like é, à, ç, ö, ü are normalized internally. Original text is preserved in the output.

AUTO Punctuation: Standalone punctuation (!, ?, :) is handled gracefully.

Limits

Max audio duration3 hours
Max file size1 GB
Accepted formatsMP3, WAV, FLAC, M4A/AAC, OGG/Opus, AIFF, WMA, WebM, AMR, CAF, AU, WavPack, APE, TTA
Max lyrics length240,000 characters
Result retention24 hours (hot) · 72 hours (batch)
Processing time~10-15 seconds (typical 3-4 min song)
Concurrent jobs2 before a card is registered, 5 after — counted across alignment, separation and video together. Over the limit answers 429 CONCURRENCY_LIMIT; wait for one to finish or ask us for more.
Server at capacity503 SERVER_BUSY with a Retry-After header, when more uploads arrive than we can hold at once. Not your request’s fault — retry it.