Skip to content
API Reference · v1

Build with Audexum.

REST API. Bearer auth. JSON in, WAV/MP3/OGG/Opus out. Streaming and webhooks included.

Authentication

Generate an API key from Developer settings. Send it as a Bearer token on every request.

http
Authorization: Bearer sk_live_•••••••••••••••••••••••••••••••••

POST/api/synthesize

Generate audio from text. Returns audio/wav by default (44.1 kHz, 16-bit PCM, mono). Use format for MP3/OGG/Opus, or stream=true for low-latency chunked streaming.

curl
# The shortest call there is. No backend named, so you get
# V3 Expressive on the Atlas voice — our most expressive engine.
curl -X POST https://audexum.com/api/synthesize \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world"}' \
  --output hello.wav

# V2 Multilingual, where speed and steps apply and 32 languages are available.
curl -X POST https://audexum.com/api/synthesize \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Hello world",
    "backend": "v2",
    "voice": "M1",
    "lang": "en",
    "speed": 1.05,
    "steps": 8
  }' \
  --output hello.wav

Request body

FieldTypeDefaultNotes
textstringdefault requiredUp to ~5,000 characters per call.
voiceper tier (see Voice tiers)default AtlasVoice ids are tier-specific. The default follows backend.
langen, bg, ko, ja, ar, de, …default en33 supported. See /api/voices.
speed0.5–2.0default 1.05Playback rate.
steps4–16default 8Diffusion steps. More = higher quality, slower.
formatwav | mp3 | ogg | opusdefault wavOutput format. Quota is identical across formats.
streambooleandefault falseStream chunked WAV. Only format=wav, and V1/V2 only. V3 Expressive returns the whole file and ignores stream.
backendv3 | v2 | v1default v3Voice tier. Omit it and you get V3 Expressive. See Voice tiers below.
instructstringdefault ""V3 only. Natural-language style/emotion, e.g. "warm and slow". Ignored by V1/V2.

Voice tiers

Pick a tier with backend. Each tier has its own voices and a per-character credit multiplier. Sending an unknown voice for a tier returns 400.

Omit backend and the tier is inferred: a V2 voice (F1..F5, M1..M5) keeps you on V2 Multilingual, a V3 voice selects V3 Expressive, and naming neither gives you V3 Expressive onAtlas. Existing integrations that send only avoice are unaffected, and keep billing at their tier's multiplier.

backendtiercostvoices
v1V1 Instant: fast, English-first1×See /api/voices
v2V2 Multilingual: 32 languages, incl. Bulgarian1.5×F1..F5, M1..M5
v3V3 Expressive: most expressive, emotion & style2×Atlas, Reid, Sage, Kai, Cole, Ivy, Mira, Elle, Nova
curl
# V3 Expressive — most expressive tier, with an optional style prompt
curl -X POST https://audexum.com/api/synthesize \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "text": "We shipped it. Finally.",
    "backend": "v3",
    "voice": "Atlas",
    "instruct": "warm, relieved, unhurried"
  }' \
  --output ultra.wav

Response headers

FieldTypeDefaultNotes
Content-Typestringdefault —audio/wav · audio/mpeg · audio/ogg · audio/opus
X-Gen-Time-Msintdefault —Wall-clock generation time, ms.
X-Audio-Duration-Sfloatdefault —Output audio duration, seconds.
X-RTFfloatdefault —Audio / generation ratio. >1 = faster than real-time.
X-Formatstringdefault —Echo of requested format.
X-Stream1default —Present when stream=true; body is concatenated WAV chunks.

Output formats (OGG / Opus / MP3)

Add "format": "ogg" (or mp3 / opus) to get a transcoded response. Quota is identical regardless of format. The AudioSeal watermark survives MP3 encoding.

curl
# OGG/Opus — smallest file size, best for streaming/web
curl -X POST https://audexum.com/api/synthesize \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello", "voice": "M1", "lang": "en", "format": "ogg"}' \
  --output hello.ogg

# MP3 — maximum compatibility
curl -X POST https://audexum.com/api/synthesize \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello", "voice": "M1", "lang": "en", "format": "mp3"}' \
  --output hello.mp3
python
import requests

r = requests.post(
    "https://audexum.com/api/synthesize",
    headers={"Authorization": "Bearer sk_live_..."},
    json={"text": "Hello world", "voice": "M1", "lang": "en", "format": "ogg"},
)
r.raise_for_status()
with open("hello.ogg", "wb") as f:
    f.write(r.content)
# Content-Type: audio/ogg
formatContent-TypeCodec
wavaudio/wavPCM 16-bit, 44.1 kHz (no transcode)
mp3audio/mpeglibmp3lame VBR ~192 kbps
oggaudio/ogglibopus 96 kbps, 48 kHz
opusaudio/opuslibopus 96 kbps, 48 kHz (Ogg container)

Streaming TTS

Add "stream": true to receive audio as it generates. The response is a chunked-transfer stream of concatenated WAV files. Each chunk is a complete, valid WAV (44-byte RIFF header + PCM) so you can begin playback immediately. Quota is reserved for the full text up front, identical to a non-streaming call. Streaming requires format=wav (the default).

curl
curl -X POST https://audexum.com/api/synthesize \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Sentence one. Sentence two. Sentence three.", "voice": "M1", "lang": "en", "stream": true}' \
  --no-buffer --output - | ffplay -nodisp -i pipe:0
python
import requests

with requests.post(
    "https://audexum.com/api/synthesize",
    headers={"Authorization": "Bearer sk_live_..."},
    json={
        "text": "First sentence. Second sentence. Third one.",
        "voice": "M1",
        "lang": "en",
        "stream": True,
    },
    stream=True,
) as r:
    r.raise_for_status()
    with open("streamed.wav", "wb") as f:
        for chunk in r.iter_content(chunk_size=None):
            f.write(chunk)
# Each yielded chunk is a standalone WAV file (header + PCM).
# Concatenating them produces valid gapless audio.
Streaming notes: text is split on sentence boundaries (~1–3 sentences per chunk). Format must be wav. First-chunk latency is logged server-side. The response header X-Stream: 1 confirms streaming mode.

POST/api/transcribe

Transcribe speech to text. Send audio as multipart/form-data. Requires an unrestricted key (widget keys are limited to /api/synthesize). Billed at 200 credits per minute of audio.

curl
curl -X POST https://audexum.com/api/transcribe \
  -H "Authorization: Bearer sk_live_..." \
  -F "[email protected]" \
  -F "timestamps=true"

Form fields

FieldNotes
audioRequired. An audio/* file, up to 25 MB and 600 seconds.
timestampsOptional bool. Include per-segment start/end times.
vocabularyOptional. Up to 2000 chars of domain terms to bias recognition.
clean / modeOptional. Post-process the transcript (clean, email, professional, bullets, concise).

Response

json
{
  "text": "Full transcript…",
  "duration_s": 12.3,
  "transcribe_ms": 480,
  "segments": [{ "start": 0.0, "end": 3.2, "text": "…" }],
  "raw_text": "…",   // present only when a clean/transform mode was requested
  "mode": "clean"
}

POST/v1/audio/transcriptions

OpenAI-compatible speech-to-text. A tool that lets you change its OpenAI base URL can use Audexum by setting it to https://audexum.com/v1, no code change. It runs the same engine and billing as /api/transcribe: 200 credits per minute of audio. Send multipart/form-data.

Auth is Authorization: Bearer sk_live_…. Create the key in Developer settings (sign-in required). Use a secret sk_live_ key; restricted widget keys get 403 here.

curl
curl https://audexum.com/v1/audio/transcriptions \
  -H "Authorization: Bearer sk_live_..." \
  -F [email protected] \
  -F model=whisper-1 \
  -F language=bg \
  -F response_format=json
# {"text": "…"}
python
from openai import OpenAI

client = OpenAI(api_key="sk_live_...", base_url="https://audexum.com/v1")

with open("recording.mp3", "rb") as f:
    result = client.audio.transcriptions.create(
        model="whisper-1",
        file=f,
        language="bg",   # optional; omit to auto-detect
    )
print(result.text)

Form fields

FieldTypeDefaultNotes
filefiledefault requiredUp to 25 MB and 600 seconds. Content-Type audio/*, video/webm, video/ogg or application/octet-stream (mp3, wav, m4a, ogg, flac, webm).
modelstringdefault —Accepted and ignored: there is one engine. Send whisper-1.
languageISO 639-1, e.g. bgdefault autoOptional. Omit it and the language is detected.
response_formatjson | text | verbose_json | srt | vttdefault jsonAn unknown value falls back to json.
timestamp_granularities[]segment | worddefault segmentverbose_json only. word adds a words array (see below).
promptstringdefault —Accepted and ignored.
temperaturefloatdefault 0Accepted and ignored.

verbose_json

Segment start/end come from the engine. Word timings (timestamp_granularities[]=word) are estimated: each segment's span is divided across its words by character length. They are not forced-aligned. Every segment carries a words key, empty unless you asked for words.

json
{
  "task": "transcribe",
  "language": "bg",          // echo of the request; "auto" when omitted
  "duration": 12.3,
  "text": "Full transcript…",
  "segments": [
    { "id": 0, "start": 0.0, "end": 3.2, "text": "…", "words": [] }
  ]
}

Not supported

  • POST /v1/audio/translations returns 405.
  • Streaming transcription and the Realtime API.

Errors

CodeBody
400{"detail": "audio_too_short"} · "no_speech_detected" · "clip_too_long: max 600s per request"
401{"detail": "authentication_required"}: missing or invalid key
402{"detail": {"error": "insufficient_credits", …}}: full body below
403{"detail": "email_not_verified"}: verify the account's email address
405{"detail": "Method Not Allowed"}: /v1/audio/translations
413{"detail": "audio_too_large"}: file over 25 MB
415{"detail": "unsupported_media_type"}: file part is not audio
json
// 402 — the balance cannot cover this file
{
  "detail": {
    "error": "insufficient_credits",
    "plan_tier": "free",
    "monthly_grant": 250,
    "monthly_used": 250,
    "pack_credits": 0,
    "requested": 41,
    "upgrade_url": "/pricing",
    "offer": {
      "reason": "insufficient_credits",
      "headline": "You are out of credits",
      "detail": "This request needs 41 credits. …",
      "plan": { "tier": "pro", "price_eur": 29, "credits_per_month": 1200000 },
      "actions": [
        { "kind": "upgrade", "label": "Upgrade to Pro — €29/mo", "url": "https://audexum.com/r/402" },
        { "kind": "pack", "label": "Buy 250,000 credits — €7", "url": "https://audexum.com/pricing#packs" }
      ]
    }
  }
}

POST/api/batch

Submit up to 50 texts in one request. Poll for completion, then download a zip.

curl
# submit
curl -X POST https://audexum.com/api/batch \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"texts": ["one", "two"], "voice": "F2", "lang": "en"}'

# poll
curl -H "Authorization: Bearer sk_live_..." \
  https://audexum.com/api/batch/$JOB_ID

# download once status=done
curl -H "Authorization: Bearer sk_live_..." \
  https://audexum.com/api/batch/$JOB_ID/download -O

Webhooks

Configure a webhook endpoint in your Developer settings to receive signed POST notifications. We send JSON with an X-Audexum-Signature header. Verify it before processing.

Payload shape

json
{
  "event": "quota.warning_80",
  "data": {
    "monthly_grant": 3000,
    "monthly_used": 24000,
    "monthly_remaining": 6000,
    "overage_enabled": false,
    "plan_tier": "free",
    "period_end": "2026-06-30"
  },
  "timestamp": "2026-06-12T14:22:00.000000+00:00"
}

Events

FieldTypeDefaultNotes
quota.warning_80stringdefault —Fired when monthly quota crosses 80% used.
quota.exhaustedstringdefault —Fired when monthly quota is fully consumed.
batch.completedstringdefault —Fired when a batch job finishes.
audiobook.completedstringdefault —Fired when an audiobook job finishes.

Signature verification

Every delivery includes X-Audexum-Signature: sha256=<hex>, computed as HMAC-SHA256 over the raw request body using your webhook secret.

python
import hashlib, hmac

def verify_signature(body: bytes, secret: str, sig_header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, sig_header)

# In your FastAPI/Flask/Django handler:
# body = await request.body()
# sig  = request.headers["X-Audexum-Signature"]
# if not verify_signature(body, WEBHOOK_SECRET, sig):
#     return 401
curl
# Configure endpoint (session auth — browser only)
curl -X PUT https://audexum.com/api/account/webhook \
  -H "Cookie: audexum_session=..." \
  -H "X-Requested-With: XMLHttpRequest" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-server.com/webhooks/audexum"}'
# Response contains secret — store it, shown once only.

# Remove endpoint
curl -X DELETE https://audexum.com/api/account/webhook \
  -H "Cookie: audexum_session=..." \
  -H "X-Requested-With: XMLHttpRequest"
HTTPS endpoints only. HTTP is rejected.
Private/loopback IPs blocked (SSRF protection).
5 s timeout, 2 retries with exponential backoff (1 s, 2 s).
Delivery is fire-and-forget: it never blocks a synthesis request.

Errors

CodeMeaning
400Bad request: invalid voice/lang, empty text, or stream+non-wav format
401Missing or invalid API key
402insufficient_credits: Monthly quota exceeded; see /api/usage and /pricing for upgrade options
402feature_gated: Feature requires a higher plan (see error.feature and error.min_tier)
422Invalid webhook URL (not HTTPS, private IP, or unresolvable)
429Too many requests: reduce your request rate and retry
503Server busy (concurrency cap reached): retry with exponential backoff; honor Retry-After

Rate limits & fair use

  • Shared concurrency cap: requests beyond it get 503 + Retry-After
  • Sustained abusive request rates may be throttled (fair use)
  • Per-month credit quota from your plan tier
  • Contact us for dedicated capacity or PAYG

Stuck? Email [email protected].

Your first call is one curl away.