Skip to content

Voice API

Native Voice routes plus OpenAI-compatible speech and transcription. Voice credits packs top up any key.

On this page · 4 sections

All routes authenticate with Authorization: Bearer $SCALEMAX_API_KEY. Voice spends Voice credits on the key you already use. API-credit dollar balances are not used. Auto-only keys are refused at the scope gate. Match the billing surface: a token key uses https://api.scalemax.pro/token/v1; a credit key uses https://api.scalemax.pro/v1. Do not infer the URL from the key name. Errors use the OpenAI envelope plus x-request-id. Native generation is async (202 + task). OpenAI-compatible speech and transcription wait and return the finished body.

Overview and pricing: Voice.

Task object

Every native create/list/get/retry response uses this customer-visible shape, except POST /v1/voice/voices/cloned, which returns a voice.clone object (with task_id). File URLs are ScaleMax paths; they require the same key. Failed tasks report usage.credits = 0 and usage.units = 0. usage.credits is the Voice-credit debit; usage.units is the same number. usage.cost_usd is always "0.0000"when present — Voice never charges API-credit dollars.

voice.task.json
{
  "id": "<uuid>",
  "object": "voice.task",
  "kind": "tts",
  "engine": "elevenlabs",
  "model": "eleven_multilingual_v2",
  "voice": "<voice_id>",
  "status": "completed",
  "progress": 100,
  "title": "Hello world",
  "group_id": null,
  "group_index": null,
  "input": {
    "text": "…",
    "language": "en",
    "voice_settings": {},
    "export_transcript": false
  },
  "result": {
    "audio_url": "https://api.scalemax.pro/v1/voice/files/<id>/audio",
    "srt_url": null,
    "source_url": null,
    "characters_used": 12,
    "duration_seconds": null,
    "transcript": null
  },
  "usage": { "credits": 12, "units": 12, "cost_usd": "0.0000" },
  "error": null,
  "created_at": "…",
  "updated_at": "…"
}

status is one of pending, processing, completed, failed, cancelled. kind is tts, dialogue, voice_changer, dubbing, stt, or clone. engine is elevenlabs, minimax, or capcut. List responses for stt omit the full result.transcript (it can be megabytes) and send result.transcript_preview instead: { text, words, language, speakers, duration_seconds }. GET /v1/voice/tasks/{id} returns the full transcript.

Native routes

Prefix /v1/voice. Base URL https://api.scalemax.pro/v1.

POST /v1/voice/tts

Submit text-to-speech. Returns 202 and a task. engine is inferred from model when omitted. text must fit the model max_chars.

FieldTypeRequiredNotes
enginestringnoelevenlabs | minimax | capcut. Inferred from model when omitted.
modelstringyesCatalogue id, e.g. eleven_multilingual_v2.
voicestringyesVoice id from GET /voices, or a cloned-voice id we issued.
textstringyesPlain text. Length ≤ model max_chars.
languagestringyesISO code (en, vi) for ElevenLabs/CapCut; full name (English) for MiniMax.
voice_settingsobjectnoEngine-specific (stability, similarity, style, speaker boost, speed, pitch, vol).
export_transcriptbooleannoIf true, estimate includes +15% and result may include srt_url.
group_iduuidnoSplit-scene / SRT batch id.
group_indexintegernoOrder inside the group.
titlestringnoCustomer-visible name.
tts.sh
curl -X POST "https://api.scalemax.pro/v1/voice/tts" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"eleven_multilingual_v2","voice":"<voice_id>","text":"Hello","language":"en"}'

POST /v1/voice/dialogue

Multi-speaker script. At least two speakers. Text lines must be Name: line. Estimate is total characters × the highest speaker multiplier.

FieldTypeRequiredNotes
textstringyesScript in Name: line form.
speakersobjectyesMap of speaker name → {engine?, model, voice, language, voice_settings?}.
pause_between_turnsnumbernoSeconds between turns, 0–3.
titlestringnoCustomer-visible name.
dialogue.sh
curl -X POST "https://api.scalemax.pro/v1/voice/dialogue" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Ada: Hello.\nBen: Hi there.",
    "speakers": {
      "Ada": {"model":"eleven_multilingual_v2","voice":"<id_a>","language":"en"},
      "Ben": {"model":"eleven_multilingual_v2","voice":"<id_b>","language":"en"}
    }
  }'

POST /v1/voice/voice-changer

Speech-to-speech. Multipart. Audio ≤ 50 MB / 5 minutes. Estimate = ceil(duration/60)×1000 credits.

Temporarily unavailable. Every call currently returns 503 voice_changer_unavailable before the upload is read. Nothing is reserved or charged, and speech-to-speech models are left out of GET /v1/voice/models until it is back.

FieldTypeRequiredNotes
audiofileyesmp3/wav/m4a/ogg/webm/aac/flac. ≤50 MB.
voicestringyesTarget voice id.
modelstringnoSTS catalogue models only.
remove_background_noisebooleannoNoise removal flag.
voice_settingsstring (JSON)noJSON object as a string field.
duration_secondsnumberyes1–300. Used for the reservation ceiling.
voice-changer.sh
curl -X POST "https://api.scalemax.pro/v1/voice/voice-changer" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -F "[email protected]" \
  -F "voice=<voice_id>" \
  -F "duration_seconds=12"

POST /v1/voice/dubbing/uploads

Start a chunked dubbing upload. Returns { upload_id, chunk_size }. The upload id is ours; it expires after two hours.

dubbing-init.sh
curl -X POST "https://api.scalemax.pro/v1/voice/dubbing/uploads" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY"

POST /v1/voice/dubbing/uploads/{upload_id}/chunks

Upload one chunk. Multipart. Chunk ≤ 100 MB.

FieldTypeRequiredNotes
indexintegeryesZero-based chunk index.
chunkfileyes≤100 MB. At most 512 chunks per upload.

POST /v1/voice/dubbing/uploads/{upload_id}/complete

Finish the upload and start dubbing. Returns 202 + task. Estimate = ceil(duration_seconds) × 50 credits (3000/min). Source language is required; there is no auto-detect.

FieldTypeRequiredNotes
total_chunksintegeryesMust match the chunks already sent.
filenamestringnoOriginal filename.
content_typestringnoSource media type.
source_languagestringyesISO 639-1. Required; no auto.
target_languagestringyesISO 639-1.
enginestringnoDefault minimax.
voicestringyesTarget voice id.
duration_secondsnumberyesUsed for the reservation ceiling.

POST /v1/voice/transcriptions

Speech-to-text. Multipart. File ≤ 90 MB / 90 minutes. Returns 202 and a task. Poll GET /v1/voice/tasks/{id}?wait=8 until completed or failed. Estimate = ceil(duration_seconds × 0.35) credits (21 per minute). On a completed GET-by-id, result.transcript is the structured transcript. List responses carry result.transcript_preview instead of the full transcript. Retry returns 400 retry_not_supported.

FieldTypeRequiredNotes
filefileyesAudio ≤90 MB. MP3, WAV, M4A, AAC, OGG, Opus, FLAC, WebM, AIFF.
languagestringyesISO 639-1/3, 2–3 letters (en, eng).
duration_secondsnumberyes1–5400. Used for the reservation ceiling.
timestamps_granularitystringnonone | word | character.
diarizebooleannoLabel speakers.
num_speakersintegerno1–32. Hint for diarization.
diarization_thresholdnumberno0–1.5. Only when num_speakers is omitted.
tag_audio_eventsbooleannoTag non-speech events such as (laughter).
no_verbatimbooleannoRemove filler words / false starts.
stt.sh
curl -X POST "https://api.scalemax.pro/v1/voice/transcriptions" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -F "[email protected]" \
  -F "language=en" \
  -F "duration_seconds=56" \
  -F "timestamps_granularity=word" \
  -F "diarize=true" \
  -F "tag_audio_events=true" \
  -F "no_verbatim=false"
stt-poll.sh
curl "https://api.scalemax.pro/v1/voice/tasks/$TASK_ID?wait=8" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY"

Completed result.transcript shape (minus upstream transcription_id):

transcript.json
{
  "language_code": "eng",
  "language_probability": 1.0,
  "text": "Hello from the recording.",
  "audio_duration_secs": 56.1,
  "words": [
    {
      "text": "Hello",
      "start": 0.12,
      "end": 0.48,
      "type": "word",
      "speaker_id": "speaker_0",
      "logprob": -0.12
    }
  ]
}

words[].type is word, spacing, or audio_event. List preview: result.transcript_preview = { text, words, language, speakers, duration_seconds } where text is the first ~240 characters.

GET /v1/voice/tasks

List this key's tasks only. { object: 'list', data: task[], has_more }. For kind=stt, each row carries result.transcript_preview instead of the full transcript.

FieldTypeRequiredNotes
kindstringnoFilter by task kind.
group_iduuidnoFilter by split-scene / SRT group.
pageintegerno1-based page.
page_sizeintegernoPage size.

GET /v1/voice/tasks/{id}

Fetch one task. A poll of a non-terminal task is the state machine: ScaleMax refreshes upstream (throttled to once per 2 s per task) and settles or releases on transition. A background sweeper covers abandoned tasks. Pass wait (integer seconds, 0–20, default 0) to long-poll: the request waits until the task finishes or the limit, then returns the same task body it would on a normal GET.

FieldTypeRequiredNotes
waitintegernoLong-poll seconds, clamped 0–20. Returns early when the task reaches a terminal status.
poll.sh
curl "https://api.scalemax.pro/v1/voice/tasks/$TASK_ID" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY"

DELETE /v1/voice/tasks/{id}

Soft-delete a terminal task and free stored audio. 409 task_not_terminal while the task is still open. Releases no money — settlement already happened.

POST /v1/voice/tasks/{id}/retry

New task row and a new reservation, reusing the stored input. The new row links retry_of. Speech-to-text returns 400 retry_not_supported.

retry.sh
curl -X POST "https://api.scalemax.pro/v1/voice/tasks/$TASK_ID/retry" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY"

GET /v1/voice/files/{id}/{audio|srt|source}

Stream the result file. Auth required. Files are cached server-side for replay. Correct content-type and Content-Disposition; HTTP Range is honoured (including on a cache hit).

download.sh
curl "https://api.scalemax.pro/v1/voice/files/$TASK_ID/audio" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -o out.mp3

POST /v1/voice/estimate

Price a request without spending. Body matches /tts or { kind, duration_seconds? } for changer/dubbing/stt/clone. Returns credits, chars, max_chars, billing, units_remaining, and insufficient_units.billing is voice_units or voice_pack. There is no cost_usd on this response; if an older gateway still sends it, ignore it. A job the remaining credits cannot cover is refused on submit with HTTP 402 insufficient_voice_credits.

estimate.sh
curl -X POST "https://api.scalemax.pro/v1/voice/estimate" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"eleven_multilingual_v2","text":"Hello","language":"en","voice":"<voice_id>"}'

GET /v1/voice/models

Enabled catalogue rows. { object: 'list', data }. Query engine= to filter.

GET /v1/voice/languages

Languages for an engine. { object: 'list', data }. Query engine=. Cached about an hour.

GET /v1/voice/voices

List voices. Response is { object: 'list', data, has_more }. engine=elevenlabs&library=default|shared, engine=minimax&library=system, or engine=capcut&library=system. Filters: page, page_size, search, gender, age, accent, language, category, sort, use_cases, emotion. Preview URLs are rewritten to /v1/voice/voices/{engine}/{voice_id}/preview.

voices.sh
curl "https://api.scalemax.pro/v1/voice/voices?engine=elevenlabs&library=default" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY"

GET /v1/voice/voices/cloned

Cloned voices this key owns. { object: 'list', data }. Ids are ours; TTS with voice=<our clone id> resolves server-side.

POST /v1/voice/voices/cloned

Start a MiniMax clone. Multipart. Returns 202 with a voice.clone object (not a task; task_id points at the 1000-credit clone task). Sample ≤ 20 MB.

FieldTypeRequiredNotes
filefileyesVoice sample ≤20 MB.
namestringyesDisplay name.
language_tagstringnoLanguage of the sample.
genderstringnoPresented gender tag.
need_noise_reductionbooleannoClean the sample first.
preview_textstringno≤200 characters.

DELETE /v1/voice/voices/cloned/{id}

Delete a cloned voice we issued.

POST /v1/voice/voices/cloned/{id}/retry

Retry a failed clone.

OpenAI-compatible routes

POST /v1/audio/speech

Synchronous TTS: submit, poll up to 120 s, stream audio/mpeg. Creates the same Voice task as native TTS.model is optional and defaults to eleven_multilingual_v2.response_format other than mp3 is 400 — upstream audio is mp3 only.

FieldTypeRequiredNotes
modelstringnoOptional. Default eleven_multilingual_v2. A TTS catalogue id, or an OpenAI id: tts-1 and tts-1-hd map to eleven_multilingual_v2, gpt-4o-mini-tts to eleven_flash_v2_5.
inputstringyesText to speak.
voicestringyesVoice id.
response_formatstringnoDefault mp3. Anything else → 400.
speednumbernoForwarded into voice_settings when the model supports speed.
languagestringnoDefault en.
enginestringnoInferred from model when omitted.
audio-speech.sh
curl -X POST "https://api.scalemax.pro/v1/audio/speech" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -o hello.mp3 \
  -d '{
    "model": "eleven_multilingual_v2",
    "voice": "<voice_id>",
    "input": "Hello",
    "language": "en"
  }'

POST /v1/audio/transcriptions

The OpenAI speech-to-text API, so the OpenAI SDKs, Open WebUI, LibreChat and n8n work with only the base URL and key changed. Multipart upload, file ≤ 25 MB. The call waits for the transcript and returns it in the requested format. It runs the same job as /v1/voice/transcriptions, so a transcript can also be fetched later from GET /v1/voice/tasks/{id} (the id is in the x-scalemax-voice-task header).

Billing. Voice credits by audio length — about 21 per audio minute (at most 0.35 credits per second), priced on the length the transcriber measured, the same as /v1/voice/transcriptions. OpenAI clients do not send a duration, so the call first holds an upper bound worked out from the file (the exact length for WAV; otherwise the file size, up to 90 minutes). The final charge is never more than that hold, and the rest is released. A failed job is not charged. The key needs enough free Voice credits for the hold, which for a compressed file is usually several times the final charge.

Waiting. Short jobs answer normally with x-scalemax-voice-task, x-scalemax-credits (credits charged) and x-request-id. For json and verbose_json, a job still running after about 20 seconds gets a 200 straight away with a space sent every 15 seconds to keep the connection open, then the JSON (leading spaces are valid JSON). Such a response carries no x-scalemax-credits header, and a job that then fails or runs past 10 minutes is reported inside that body as an { "error": … } object. text, srt and vtt cannot be padded: after 90 seconds they return 504 upstream_timeout naming the task, and the job keeps running. Fetch it from GET /v1/voice/tasks/{id}; it is charged once.

FieldTypeRequiredNotes
filefileyesAudio ≤ 25 MB: flac, m4a, mp3, mp4, mpeg, mpga, oga, ogg, opus, wav, webm. Larger files → 413 too_large.
modelstringyesOne of whisper-1, gpt-4o-transcribe, gpt-4o-mini-transcribe, gpt-4o-transcribe-diarize, scribe_v1, scribe_v2. All run on Scribe v2; gpt-4o-transcribe-diarize also labels speakers. Anything else → 400 with param model.
languagestringnoISO 639-1 (en, fr). Default en: the transcriber needs a language, so set it for non-English audio.
response_formatstringnojson (default) → { text }; text; srt; vtt; verbose_json → { task, language, duration, text, words?, segments? }.
timestamp_granularities[]string[]noverbose_json only. word adds words [{ word, start, end }]; segment (the default) adds segments [{ id, start, end, text, … }].
temperaturenumberno0–2. Forwarded to the transcriber.
promptstringnoAccepted for compatibility and ignored.
audio-transcriptions.sh
curl -X POST "https://api.scalemax.pro/v1/audio/transcriptions" \
  -H "Authorization: Bearer $SCALEMAX_API_KEY" \
  -F "[email protected]" \
  -F "model=whisper-1" \
  -F "language=en" \
  -F "response_format=srt"
transcriptions.py
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_SCALEMAX_KEY",
    base_url="https://api.scalemax.pro/v1",  # a token key uses https://api.scalemax.pro/token/v1
)

with open("meeting.mp3", "rb") as audio:
    transcript = client.audio.transcriptions.create(
        model="whisper-1",
        file=audio,
        language="en",
        response_format="verbose_json",
        timestamp_granularities=["word", "segment"],
    )

print(transcript.text)
for word in transcript.words or []:
    print(word.start, word.end, word.word)

Error codes

HTTPCodeWhen
400invalid_requestSchema, missing fields, unsupported response_format, script/speaker validation, text over the model max_chars, or duration_seconds outside the kind's cap.
402insufficient_voice_creditsNot enough Voice credits on this key. Buy a Voice pack in Voice → Credits.
403key_disabledAuto-only key, or Voice is not enabled for this key.
404not_foundUnknown task, file, clone, or model id for this key.
409task_not_terminalDELETE of a still-open task.
413request_too_largeJSON body or OpenAI-compatible speech payload over the request cap.
413too_largeMultipart media over the route's byte cap (changer 50 MB, STT 90 MB, OpenAI transcription 25 MB, clone 20 MB, dubbing chunk 100 MB).
429rate_limit_exceededUpstream submit semaphore full (message mentions busy; Retry-After: 2) or the key's request/concurrency cap.
503provider_unavailableRuntime switch voice_enabled is off, or Voice is otherwise unavailable.
503voice_changer_unavailableThe Voice Changer is temporarily switched off. The upload is not read and nothing is charged.
504upstream_timeoutA synchronous OpenAI-compatible call ran out of wait time. The job keeps running; the message names the task to fetch from GET /v1/voice/tasks/{id}.