Primitive/audio5 min read

Introducing /audio: give your agent ears and a voice

Transcription, speech synthesis, and native audio conversations across a managed catalog of speech models — one endpoint, automatic routing with fallbacks, exact-cost billing in Naïve credits, and no speech-provider accounts to manage.

Audio primitive →Read the docs →

Primitive/audio
TL;DR
  • /audio speech to text, text to speech, and native audio conversations behind one Naïve key
  • Automatic routing send `auto` and Naïve ranks every eligible model, with fallbacks when a provider fails
  • Speech to speech is not transcribe-then-speak the model hears the audio itself, including tone and hesitation
  • Billed in credits by audio duration, input characters, or both, at the exact upstream cost
  • Route traces on every call see which candidates were considered, filtered, and attempted
  • Opt-in by design audio egresses voice to a third-party subprocessor, so a workspace must enable it deliberately

Today we're launching /audio — speech to text, text to speech, and native audio conversations behind a single Naïve key. Automatic routing across a managed catalog of speech models, fallbacks when a provider fails, exact-cost billing in credits, and a route trace on every call so you can see exactly what happened. The same key that sends email, issues cards, and reaches 300+ language models now hears and speaks.

The problem: speech is three integrations, not one

Agents that touch the real world end up needing audio. A support agent transcribes calls. A voice agent answers them. A content agent narrates. And each of those is a separate vendor relationship:

  • Different providers win at different things. The best transcription model for English meetings isn't the best one for Portuguese phone audio, and neither is the best voice for dramatic narration. Picking one vendor means being wrong most of the time.
  • They fail, and failure is per-provider. Speech APIs rate-limit, reject formats, and go down. Every one of those is a bespoke retry path you write yourself.
  • Speech-to-speech is not a pipeline. Stitching transcription into an LLM into synthesis loses everything that made the audio worth listening to — tone, hesitation, urgency. And it's slow.

For a platform where an agent already has a phone number, an inbox, and a card behind one key, making it hold three more speech accounts is backwards. Until now.

How /audio works

One endpoint per modality, and a model selection you don't have to make. Send auto and Naïve ranks every eligible model for the request, applies your constraints, and falls through to the next candidate if one fails.

import { readFile } from "node:fs/promises";
 
const bytes = await readFile("meeting.wav");
 
const t = await naive.audio.transcribeFile(bytes, "wav", {
  language: "en",        // skips detection — faster and more accurate
  diarize: true,         // speaker labels, normalized to spk_N
  timestamps: "word",
});
 
console.log(t.text);
console.log(t.route.mode, t.route.candidates);  // "auto", 33
console.log(t.credits_used);

Pass language whenever you know it. On an automatic route it skips language detection entirely — lower latency on every request, and the model transcribes against the language you named instead of guessing.

Routing you can inspect

Every response carries a route object. This isn't decoration — it's the audit trail for a decision Naïve made on your behalf:

"route": {
  "mode": "auto",
  "reason": "Route alias stt/auto ranked by balanced",
  "candidates": 33,
  "attempts": [
    { "provider": "groq",      "status": "skipped", "error_code": "provider_unavailable" },
    { "provider": "deepinfra", "status": "skipped", "error_code": "unsupported_media" },
    { "provider": "fal",       "status": "success", "latency_ms": 1108 }
  ]
}

Two providers failed; the third served the request. Your code never saw it. That's the whole value of a router — but you can still read exactly what happened, per request, after the fact.

Constrain it when you need to:

await naive.audio.transcribe({
  input_audio: { data: base64, format: "wav" },
  model: "stt/auto",
  provider: {
    only: ["deepgram", "groq"],
    require_features: true,             // don't silently degrade
    max_price: { per_min: 0.01 },
    region: ["us", "global"],
  },
});

Synthesis, steered by capability

For text to speech, you usually don't want to name a model — you want to name what matters. Send a feature and Naïve ranks eligible voices for it:

const { audio, request_id } = await naive.audio.speech({
  model: "tts/auto",
  feature: "Acting",                   // or Expressiveness, Long-form, Acoustic quality…
  input: "Deliver this like the opening of a thriller.",
  voice: "auto",
  response_format: "mp3",
});
 
await writeFile("line.mp3", Buffer.from(audio));

Omit feature entirely and it's inferred from your text, instructions, language, and length.

Speech to speech: the model actually listens

This is the one that isn't a pipeline. The model receives your audio directly — not a transcript of it — and answers in speech. It hears tone, hesitation, and emphasis, and you get both transcripts back alongside the reply audio.

const reply = await naive.audio.converse({
  model: "s2s/auto",
  voice: "auto",
  input_audio: { data: base64Question, format: "wav", channels: 1 },
  output_audio: { format: "wav" },
  instructions: "Answer the speaker briefly.",
});
 
console.log(reply.input_transcript);  // what it heard
console.log(reply.transcript);        // what it said
await writeFile("answer.wav", Buffer.from(reply.audio.data, "base64"));

Long recordings, queued

Anything long belongs on the async path. Queue it, get a request id, poll it:

naive audio transcribe podcast.wav --async
# → req_k7a2be (queued)
 
naive audio transcription req_k7a2be
# → succeeded, with the transcript

The credit charge lands once, on the poll that first sees succeeded — polling again never double-charges.

Call it from the CLI or raw REST

naive audio transcribe call.mp3 --language en --diarize
naive audio speak "Your order shipped this morning." -o update.mp3
naive audio converse question.wav -o answer.wav
curl -X POST https://api.usenaive.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $NAIVE_API_KEY" \
  -F "model=auto" \
  -F "language=en" \
  -F "file=@meeting.wav"

Opt-in, and pinned by default

/audio sends tenant voice to a third-party router that selects among many speech providers per request. That's a real subprocessor relationship, so we treat it like one:

  • The primitive is opt-in. A fresh workspace has audio disabled. You enable it deliberately on an Account Kit — the same standard we hold /brain to.
  • Requests are region-pinned to US and global by default, so audio doesn't leave those regions unless you say so.
  • Transcription sets a minimal retention policy by default, so nothing sits upstream longer than the request needs.

All three are defaults, not ceilings. Pass your own provider.region or retention and yours wins.

What you can build with /audio

Transcribe every support call, in any language — Point /audio at your recordings with diarize on and get speaker-labelled transcripts with word timings. Pair it with /phone to close the loop from call to record.

Give an agent a voice on the same balance — Synthesis is one call, steered by the capability you care about. No voice vendor account, no separate invoice.

Build voice agents that hear tone — Speech to speech means the model responds to how something was said, not just what. Ask for Emotion Alignment and it notices when a "yes, fine" isn't fine.

Meter speech spend per customer — In a multi-tenant build, scope /audio to a tenant user and read the exact per-request cost to bill inference downstream alongside /billing.

Compose with the rest of the stack — Transcribe with /audio, reason with /llm, cut with /clips — audio, text, and media generation all behind one key.

Get started

Drop this starter prompt into any coding agent to wire up Naïve:

Read https://usenaive.ai/skill.md and use it to set up Naïve in my project.

Frequently Asked Questions
What is /audio?+
/audio is Naïve's speech routing primitive. One endpoint covers three modalities: transcription (speech to text), synthesis (text to speech), and native speech-to-speech turns where a model listens to audio and answers it directly. Naïve holds the upstream credentials, picks the model per request, and bills the exact cost against your Naïve credit balance.
How does automatic routing work?+
Send `auto` (or `stt/auto`, `tts/auto`, `s2s/auto`) instead of a model slug and Naïve ranks every eligible model for the request, applying your constraints — language, format, price ceiling, region, provider allow/block lists. If the first candidate fails, it tries the next. Every response carries a `route` object showing what was considered, what was filtered out, and what was attempted.
Is speech-to-speech just transcription plus synthesis?+
No, and that's the point. A speech-to-speech model receives the audio itself, so it hears tone, hesitation, emphasis, and emotion that a transcript throws away — and answers in speech. Naïve exposes it as one request: audio in, audio out, plus transcripts of both sides.
How is it billed?+
In Naïve credits, at the exact cost each request reports. Transcription is billed by audio duration, synthesis by input characters, and audio conversations by seconds in and out. Catalog, usage, and route-trace reads are free. Async transcriptions are charged once, when the job completes.
Which languages does it support?+
Routing spans a large multilingual catalog. Pass `language` whenever you know it — on an automatic route it skips language detection entirely, which lowers latency and improves accuracy, because the model transcribes against the language you named instead of guessing.
Where does my audio go?+
To a managed router that selects among many speech providers per request. Because that means tenant voice reaches a third-party subprocessor, /audio is opt-in: you enable it explicitly on an Account Kit. Requests are also pinned to US and global regions by default, and transcription sets a minimal retention policy — both overridable per request.
How do I get started with /audio?+
Run `naive audio transcribe meeting.wav`, or call `naive.audio.transcribeFile(bytes, "wav")` with the SDK. Enable the `audio` primitive on your Account Kit first. The full guide is at usenaive.ai/docs/getting-started/audio.
DZ
Dennis ZaxCTO

CTO of Naïve. Building the open-source agent runtime.

@denniszax
Keep reading