UtteraUttera

Authentication

At a glance. The technical reference: authentication, extras, formats, voices, credits, plans, errors, limits, versioning and status. For whom: quick lookups while integrating.

An Authorization: Bearer sk-echo-... header on every request. The key is shown only once when you create it: save it. If you lose it, revoke it and create another.

Don't put it in the browser. The key gives access to your credit allowance. Call the API from your server, never from code the end user can read.

Restricting an API key to certain IPs

Each API key can be limited to the addresses it makes sense to be used from. If your integration lives on a server with a fixed IP, a stolen key is worth nothing outside it.

It's configured in your account, in the Allowed IPs column of each key. Individual addresses and networks are accepted:

203.0.113.7, 198.51.100.0/24, 192.0.2.10

Left empty, the key works from anywhere, which is the default behavior.

A request from an IP that isn't on the list gets 403 with the code ip_not_allowed and, in the body, the IP we saw — which is exactly what you need to add it if you left one out:

{
  "error": "ip_not_allowed",
  "message": "This API key is restricted to a list of IP addresses and this request does not come from one of them",
  "client_ip": "203.0.113.55"
}
It's a 403, not a 401. The key is good; what isn't valid is where it's calling from. Telling them apart matters: a 401 would have you rotating a key that was perfectly fine.

The restriction covers every authenticated endpoint, including /v1/usage/last. Whoever steals a key doesn't always want to spend it; sometimes seeing how much its owner spends is enough.

Before restricting a key in production, check which IP you actually come out of. It isn't always the one you think: behind NAT, a load balancer, or an internet connection with several lines, your traffic can appear from different addresses. Fire a request, look at the client_ip the 403 returns, and add that one.

Analysis in the same request

A transcription can bring voice analysis along without uploading the audio again. They're requested in the query string, comma-separated:

curl https://api.uttera.ai/v1/audio/transcriptions?extras=sentiment,profile,diarize \
  -H "Authorization: Bearer $UTTERA_API_KEY" \
  -F file=@recording.m4a \
  -F model=whisper-1
ExtraReturnsIn the response
sentimentemotional tonesentiment
profilespeaker profile (age, gender)profile
diarizewho speaks and whendiarize
entitiesentities in the text: people, places, amounts, identifiers…entities
redactmasks the personal data in the textreplaces text

The first three —sentiment, profile, diarize— run in parallel over the same audio, so asking for three takes almost as long as asking for one. entities and redact work on the text already transcribed, so they run after, are billed per character rather than per second, and aren't affected by the audio format. With redact, the response's text field arrives already masked; they're explained in Entities and redaction. Each analysis is billed separately, and only if it runs: if one fails, the transcription arrives all the same and the response carries an errors array saying which one was missing — and that one isn't billed.

sentiment and entities need the Developer plan or above; redact, Professional or above; profile and diarize, any plan with audio intelligence. A name that isn't one of the valid ones returns 400 with the list, not a transcription silently missing its analysis.

For sentiment with per-segment or dimensional detail (valence, arousal, dominance), use the standalone endpoint /v1/audio/sentiment: in extras it's always the global level.

Formats and languages

Input audio: wav, mp3, flac, ogg, opus, aiff, m4a and webm. webm is what a browser records and m4a is what a phone records, so both work as they come.

Every format works for voice analysis too — tone, speaker profile, speakers: webm and m4a are decoded on the node like the rest, so you don't have to convert them yourself.

Output audio (the response_format of speech): wav, mp3, opus, flac and pcm. pcm is raw PCM with no header. In the Studio it plays through a dedicated player (the ▶ Play button, not the normal one); when using it over the API you have to tell your decoder the four parameters, because the file doesn't carry them:

VoiceSample rateSampleChannelsByte order
standard24,000 Hzsigned 16-bit1 (mono)little-endian
HD48,000 Hzsigned 16-bit1 (mono)little-endian

The rate is each voice's native one and is not resampled: HD generates at 48 kHz and is delivered as is. The only thing that differs between the two is the rate; decoding it with the wrong one doesn't sound worse, it sounds at double or half speed. With wav, flac or opus this doesn't apply: the file declares it itself.

# standard voice
ffmpeg -f s16le -ar 24000 -ac 1 -i voice.pcm voice.wav
# HD voice
ffmpeg -f s16le -ar 48000 -ac 1 -i voice.pcm voice.wav

Any other value of response_format returns 422 with the valid list.

Size: up to 250 MB per request and 4 hours of audio. An uncompressed 2-hour WAV does not fit: send long audio compressed. The detail is in size and duration.

Transcription: detects the language automatically and covers the ones Whisper supports.

Speech: depends on the engine, and the difference is large. The standard voice (tts-1) speaks the nine in the table below. The high-quality voice (tts-1-hd) speaks around 30 — besides those nine, German, Russian, Polish, Dutch, the Nordic languages, Greek, Turkish, Arabic, Korean and several Southeast Asian ones, among others — and there's no need to declare the language: it's inferred from the text. It's explained in Three voices, not two.

Translation: ~50 text languages. The chain synthesizes with the standard voice, so with speech it's these nine:

CodeLanguageCodeLanguage
enEnglishitItalian
en-gbBritish EnglishjaJapanese
esSpanishptPortuguese
frFrenchzhChinese
hiHindi

Voice catalog

The six OpenAI-compatible voices, available with model: tts-1:

alloy · echo · fable · nova · onyx · shimmer

The catalog is quite a bit longer, and it is queried over the API rather than copied here, so what you read is what is actually being served right now.

GET /v1/audio/voices

curl https://api.uttera.ai/v1/audio/voices \
  -H "Authorization: Bearer $UTTERA_API_KEY"
{
  "voices": ["adam", "alex", "alex-pt", "alice", "alloy", …],
  "catalog": [
    {"name": "adam",    "language": "en"},
    {"name": "alex",    "language": "es"},
    {"name": "alex-pt", "language": "pt"},
    …
  ]
}

voices is the bare names, for filling a dropdown; catalog adds the language of each one. language can be null for voices that do not belong to one particular language. A voice that is not on this list returns 422.

Credits

Each service charges for what it consumes. These are the current coefficients:

ServiceBilled byCreditsOne hour of audio
Transcribesecond of input audio0.02542291.5
Standard voicesecond of generated audio0.02762699.5
Cloned voicesecond of generated audio0.3859021,389.2
Tonesecond of audio0.00509018.3
Speaker profilesecond of audio0.00346412.5
Speakerssecond of audio0.02093875.4
Translationsecond of input audio0.040000144.0
Summary (LLM)input token0.001605—
Summary (LLM)output token0.080650—
Entities & PII redactiontext character0.000500—
An output token costs 54 times an input one. Generating is far more expensive than reading, which is why the model's share dominates a summary's bill even when the transcript is long.

How each service is composed

ServiceFormula
TranscribeSTT
Transcribe + toneSTT + tone
SpeechTTS over the seconds generated
TranslateSTT + TTS of the generated audio + translation surcharge
SummarizeSTT + profile + speakers + model tokens

A real example

A 40-minute recording (2,424 s) translated into English with speech:

transcription     61.64   2,424.5 s x 0.025422
speech            43.18   2,055.8 s x 0.021004   (English comes out ~15% shorter)
translation       96.98   2,424.5 s x 0.040000
                 ──────
                 201.80 credits
What's billed is the audio generated, not the audio you upload. When translating, the target language almost never lasts the same as the source.

Plans and limits

Plan€/monthCreditsConcurrencyHD voiceCloningSummarizeEntitiesRedactionChaptersSLA
Free07501———————
Startup197,5003yesyesyes————
Developer9950,00015yesyesyesyes—yes—
Professional299200,00050yesyesyesyesyesyes95.0 %
Business999800,000150yesyesyesyesyesyes99.0 %
Enterprisecustomcustomcustomyesyesyesyesyesyes99.9 %

Credits renew on your subscription date, not on the 1st. If you move to a smaller plan, you keep the credits already paid for until the period ends.

This same table is served as JSON and without a key, so that a program —or an agent configuring itself— can read the limits before anyone signs up:

curl https://api.uttera.ai/v1/plans
{"plans": {"free": {"price_eur_per_month": 0, "credits_per_month": 500,
  "max_concurrent": 1, "rate_limits": {"stt": 1, "tts": 1, "sfx": 0,
  "intelligence": 0}, "summary_allowed": false, "voice_clone_allowed": false, …}, …}}

It is the same source that fills the table above: if something changes, both change at once.

Per-second limits

PlanSpeechTranscriptionAnalysis
Free11—
Startup5205
Developer10040050
Professional2501,000200
Business5002,000500

Every response carries X-RateLimit-Remaining-Second and X-Credits-Remaining-Monthly so you don't have to guess.

Maximum size and duration

WhatLimit
Size of the file you send250 MB
Audio duration4 hours
Pronunciation: size of the sample1 MB — it is a learner reading a sentence, a few seconds of audio
Summarise: length of the recordingthe same 4 hours — it has no shorter limit of its own
Text to speech, standard voice (tts-1)250,000 characters — roughly four to five hours of speech, depending on how dense the text is
Text to speech, standard voice streamingno length limit
Text to speech, high-quality and cloned voice (tts-1-hd)2,000 characters, about two and a half minutes

Long recordings: how the summary handles them

A long recording does not fit in the model's context in one go. Rather than summarise part of it, we split it, summarise each piece and then merge — and the merge is not a summary of summaries, which would lose information twice: it is asked to order and combine the pieces, keeping every figure, date and name.

So the summary has no shorter limit of its own: whatever you can transcribe, you can summarise. In an internal test, end to end on a four-hour recording with tone, speaker profile and diarisation: six minutes, complete, with the beginning and the end both present.

If a summary ever comes back cut short, the response says so in truncated. It is not something you should see, and we would rather tell you than hand you a shortened summary that reads like a whole one.

Four hours have to fit in 250 MB

The two limits are independent and you have to meet both. For a full four hours, that means staying under 145 kbps:

FormatWhat fits in 250 MB
mp3 at 64 kbps9 h 06 min
mp3 at 128 kbps4 h 33 min
mp3 at 160 kbps3 h 38 min
mp3 at 256 kbps2 h 17 min
wav 16-bit, mono, 16 kHz2 h 17 min
wav 16-bit, stereo, 44.1 kHz24 min

For speech, a 64 kbps mp3 loses nothing that matters to us and leaves you plenty of room. If you send uncompressed wav, expect four hours not to fit.

Text to speech: the limit depends on the voice

There is no single limit, because the two voices are produced by different machinery:

Standard voice (tts-1). It can be requested in two ways:

For a long narration, streaming is still the better way. You start hearing the result in seconds instead of waiting for the end, and nothing has to fit in a single response.

High-quality and cloned voice (tts-1-hd). The cap is 2,000 characters per request, about two and a half minutes of speech. That is considerably less, and it is a limitation of the model behind that voice, not a commercial decision. For this voice, streaming does not help with long text: use it in chunks of a couple of thousand characters and chain them.

If you send more, you get an error (not a cut). Above its cap —around 1,500 characters—, the high-quality voice returns a 413 before generating, instead of incomplete audio. Split the text into shorter chunks and chain them.
The minutes are an equivalence; the limit is counted in characters. Reading speed depends on the text: prose with long words advances faster, in characters per second, than dialogue in short sentences. There is more than 20% difference between the two cases, so the exact duration cannot be known in advance.

Errors

CodeWhat it meansWhat to do
400The file can't be decoded, or a parameter is invalid.The reason comes in detail.
401Key missing, malformed or revoked.Check the Authorization header.
402Credit allowance exhausted.Wait for the renewal or move up a plan.
403Your plan doesn't include that service.The message says which plan does.
413File too large.Split or compress the audio.
422Valid request, impossible to serve.For example, audio in a language with no voice. Not billed.
429Too many requests per second.Respect Retry-After.
502 503Temporary failure of a node.It's retried once automatically. Retry yourself after a few seconds.
If a stage fails, it isn't billed. In multi-step chains — translate, summarize — a failure in the middle returns the error without charging credits, and a partial failure returns what could be done with a warning, billing only that part.

Checking your usage

The charge is computed after the response is sent to you, so it doesn't come inside it. To find out exactly what a request cost you:

GET /v1/usage/last
GET /v1/usage/last?endpoint=/v1/summarize

It returns the credits charged and the breakdown by stage:

{
  "endpoint": "/v1/summarize",
  "credits": 108.978,
  "breakdown": { "stt": 3.1255, "diarize": 2.0029,
                 "profile": 0.1946, "llm": 103.655 },
  "audio_seconds": 95.66,
  "input_tokens": 2776, "output_tokens": 1230
}

It keeps the last ten charges for one hour. It's for checking on the spot, not a billing history.

How long each thing takes

Measured numbers, not promises. They vary with the node that serves you and the load at the time:

OperationTypical time
Transcribing 100 minutes of audio10 to 35 s
Transcribing a 40-minute recordingunder 30 s
Standard voice, one sentencetenths of a second
Cloned voiceConsiderably more: the sample has to be processed
Cache hitmilliseconds
Summary of a long recordingThe slowest of its stages, not the sum: they run in parallel

What really needs setting correctly is your client's timeout. The most common failure when integrating is a client with a 30-second default cutting off jobs that were doing fine. It's explained in Integration.

🔴 Synchronous limit: 10 minutes. A synchronous request (POST /v1/summarize without async) is meant to finish within 10 minutes at most; set your client's timeout to 600 seconds. Almost everything fits comfortably: transcribing and summarizing a normal recording takes seconds to minutes. But an intelligence report (?mode=intelligence, the evidence graph) over a long, dense recording —many claims to anchor— can approach or exceed those 10 minutes, because its cost grows with how much there is to extract, not with the audio's length. For those jobs, use async mode (?async=1): you submit the job, get a job_id immediately, and collect the result with GET /v1/jobs/<id> when it's ready, without holding a connection that would eventually be cut. If a synchronous request exceeds 10 minutes, the server cuts it and returns 504 with {"error":"report_timeout"} and does not charge you —detect it and resubmit the job in async mode—. In the Studio, the panel already waits up to those 10 minutes.

Versioning and changes

What you integrate today has to keep working tomorrow. This is the commitment:

Six months' notice for any breaking change. If one day we have to change something incompatibly, the previous version keeps working for at least six months from the announcement, and we notify you by email at your account address.

For that to mean anything, we have to say what counts as breaking and what doesn't:

Breaking (six months' notice)Not breaking (can happen any day)
Removing an endpoint or a parameterAdding a new endpoint
Removing or renaming a response fieldAdding a field to the response
Changing the type or meaning of a fieldAdding an optional parameter
Changing a parameter's default valueAdding a voice or a language
Making something mandatory that was optionalAdding a response header
Swapping one error code for a different oneImproving the text of an error message

From which the practical rule for your code follows: ignore fields you don't recognize instead of failing when you meet them. A client that blows up because the response carries a new key will break on its own, without anybody having broken anything.

Prices are a separate case: they aren't the interface, but they are your invoice. An increase in coefficients is announced with thirty days' notice and is not applied to a cycle already paid for. A decrease applies as soon as it exists.

Service status

If something goes wrong, the first thing is knowing whether it's yours or ours.

GET https://api.uttera.ai/health answers without a key and says whether the API is up. It's the check you can automate.

curl -s https://api.uttera.ai/health

Every response also carries an X-Request-Id header. Keep it when something fails: with that identifier we can tell you exactly what happened to your request, and without it the conversation starts by reconstructing which one it was.

For any incident, write to us with the X-Request-Id, the approximate time and what you expected to happen.

And there is a public status page: uttera.ai/en/status. It checks the API live and carries the incident history, with the duration and cause of each one. Every interruption that affected customer requests is published, including the short ones: a history that only shows the big outages is no use for judging a provider.

In the works

Things people ask for, that make sense, and that aren't there yet. We put them here because finding out something doesn't exist after integrating it is worse than knowing now:

WhatWhat forStatus
Pronunciation dictionaryTelling the engine how proper names, brands and acronyms are pronouncedTo be decided
Service accounts and multiple usersSo a company can have several people and separate keys under one accountAfter the beta

If one of them is blocking you, tell us: what customers ask for is what decides the order.