Lero API
Speech with a Brazilian accent in your product: Text-to-Speech (TTS) in MP3, Speech-to-Text (STT) for spoken Brazilian Portuguese, with timestamps for each sentence and ready subtitles, and a live Conversational Agent over WebRTC. Voice Clone is coming soon. The API is on the Creator and Dev plans: TTS uses 1 credit per character and STT, 150 credits per minute of audio.
Direct integration
Use Lero in Claude, Codex or Cursor without writing code. Create a key at app.lero.studio/desenvolvedores and run your agent's command, with your key in place of lero_your_key:
claude mcp add --env LERO_API_KEY=lero_your_key lero -- npx -y lero-mcpcodex mcp add lero --env LERO_API_KEY=lero_your_key -- npx -y lero-mcp{
"mcpServers": {
"lero": {
"command": "npx",
"args": [
"-y",
"lero-mcp"
],
"env": {
"LERO_API_KEY": "lero_your_key"
}
}
}
}{
"mcpServers": {
"lero": {
"command": "npx",
"args": [
"-y",
"lero-mcp"
],
"env": {
"LERO_API_KEY": "lero_your_key"
}
}
}
}In Cursor, the JSON goes in ~/.cursor/mcp.json. In Claude Desktop, in Settings > Developer > Edit Config. Needs Node 18 or newer.
Done: ask for audio in the chat, for example make an audio in the Rio voice saying "bom dia, tudo certo?", and the agent saves the MP3 in ~/Downloads/Lero. More about the MCP in Build with AI.
To put Lero in your product's code, paste the integration prompt into your coding agent, or follow the quickstart below.
Quickstart
- Create a key at app.lero.studio/desenvolvedores. The API is on the Creator and Dev plans.
- Keep the key on your server, in the
LERO_API_KEYvariable. - Ask for your first speech:
curl -X POST "https://app.lero.studio/api/v1/tts" \
-H "Authorization: Bearer $LERO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"texto": "Fala, tudo bem? Aqui é o Lero, direto do Rio.", "voz": "rio"}' \
--output fala.mp3import os
import requests
r = requests.post(
"https://app.lero.studio/api/v1/tts",
headers={"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"},
json={"texto": "Fala, tudo bem? Aqui é o Lero, direto do Rio.", "voz": "rio"},
timeout=300,
)
r.raise_for_status()
with open("fala.mp3", "wb") as f:
f.write(r.content)
print("credits left:", r.headers["X-Creditos-Restantes"])import { writeFile } from "node:fs/promises";
const res = await fetch("https://app.lero.studio/api/v1/tts", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.LERO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ texto: "Fala, tudo bem? Aqui é o Lero, direto do Rio.", voz: "rio" }),
});
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).erro}`);
await writeFile("fala.mp3", Buffer.from(await res.arrayBuffer()));Base URL for every route: https://app.lero.studio/api/v1. Requests and responses are JSON (UTF-8), except the audio: speech returns MP3 and transcription takes the file as multipart/form-data.
Route names are in English: /v1/tts, /v1/stt, /v1/voices, /v1/balance, /v1/agents and /v1/agents/conversation. The old Portuguese paths (/v1/fala, /v1/transcricao, /v1/vozes, /v1/saldo, /v1/agentes and /v1/agentes/conversa) keep working the same way.
Key
Every route needs the key in the Authorization: Bearer <your key> header (or in the X-Api-Key header). The key starts with lero_ and shows in full only once, when you create it. After that, the app shows only the last 4 characters.
- Use the key only on your server. Never in the browser, in a mobile app or in a repository: whoever has the key spends your credits.
- Each key can have a name, a monthly credit cap and an expiry (1, 7, 30 or 90 days, or never).
- Leaked? Revoke the key in the app. It stops at once, and you create another one.
- Up to 10 active keys per account.
Errors
Every error comes back as JSON, with a stable code in the erro field and a message in Portuguese. Handle errors by erro, never by the mensagem text. Speech and transcription errors also carry pedido and, when a field is wrong, campo.
{"erro": "sem_credito", "mensagem": "Os créditos da conta acabaram.", "pedido": "3f9c1e0a7b2d4c6e8a1b3d5f7e9c0a2b"}| status | erro | when |
|---|---|---|
| 400 | texto_invalido | The text is empty, longer than 500 characters, or the voice did not accept it (emojis and symbols). |
| 400 | voz_invalida | Unknown voice. Use rio, sp or niteroi. |
| 400 | audio_invalido | Transcription: the file is empty, has no readable audio, or the format is not supported. |
| 400 | url_invalida | Transcription: url is not a public https link, did not answer 200, or could not be downloaded. |
| 400 | formato_invalido | Transcription: formato is not json, texto, srt or vtt. |
| 400 | idioma_invalido | Transcription: idioma is not pt-BR. |
| 400 | modelo_invalido | model is not Lero v1. |
| 400 | pedido_invalido | The body is not JSON. On transcription: the body is neither multipart nor JSON, or it has neither audio nor url, or both. |
| 401 | sem_chave | No key in the request. |
| 401 | chave_invalida | The key is wrong or was revoked. |
| 401 | chave_vencida | The key expired. |
| 402 | sem_credito | The account credits do not cover the request. On transcription, the JSON carries custo (the credits the audio needs) and saldo. |
| 402 | teto_da_chave | The key reached its monthly credit cap. |
| 403 | plano | The account is not on a plan with the API (Creator or Dev). |
| 403 | escopo | The key does not have the Fala scope (it covers speech and transcription). |
| 403 | bloqueado | The account is blocked. |
| 413 | audio_grande | Transcription: the file in the body is over 4 MB (send it by url) or the one at url is over 500 MB. |
| 413 | audio_longo | Transcription: the audio is longer than 2 h. |
| 429 | muitos_pedidos | More than 60 requests per minute on the account. Wait for Retry-After. |
| 429 | simultaneas | The plan is already at its limit of concurrent requests (Creator 5, Dev 10). Wait for one to finish. |
| 429 | ocupado | The voice or the transcription is busy. Wait for Retry-After. |
| 503 | carregando | The voice is starting up. Wait for Retry-After. |
| 503 | motor_desligado | The voice or the transcription is offline. Wait for Retry-After. |
| 502 | falhou | Speech generation or transcription failed. Try again. |
| 504 | demorou | Speech generation or transcription timed out. Try again. |
| 500 | interno | An error on our side. Try again; if it persists, send us the X-Pedido-Id. |
| 400 | oferta_invalida | The SDP offer is missing, does not start with v=0, or is longer than 20,000 characters. |
| 400 | versao_invalida | versao is not in the GET /v1/agents list for your key. |
| 400 | llm_invalido | llm is not in the GET /v1/agents list. |
| 400 | roteiro_invalido | A custom agent field is over its limit. The field name comes in campo. |
| 502 | sem_bot | The agent did not answer the offer. Try again. |
| 503 | desligado | The agents are offline now. Wait for Retry-After. |
When speech generation fails after the charge, Lero refunds the credits at once: the cobrado field is 0 and the message ends with "Nada foi cobrado." ("Nothing was charged."). Transcription charges only on success: a transcription error never spends credits.
Credits and limits
Text-to-Speech (TTS)
- 1 credit per character of the text, charged before generating and refunded if generation fails.
- Up to 500 characters per request. For longer text, split it into sentences and join the MP3s.
- Each request generates a new audio. Keep the MP3 if you will use it again.
Speech-to-Text (STT)
- 150 credits per minute of audio (2.5 per second), billed by the second and rounded up, only when the transcription succeeds. The whole file counts, silence included.
- Up to 2 h of audio per request. A file in the body can be up to 4 MB; by
url, up to 500 MB. - About 30 times real time: 1 h of audio comes back in about 2 min. Use a 300 s timeout.
- Lero does not keep the audio or the transcript. Keep the response if you will use it again.
The account
- Up to 60 requests per minute per account, across all keys and routes.
- Concurrent requests (speech and transcription): 5 on Creator and 10 on Dev. Above that,
429 simultaneas. - Live conversation uses 1,000 credits per minute of call, charged per second at the end; starting a call needs at least 1 minute of balance.
- The plan allowance arrives on the first use of each month (Brasília time). Unused credits roll over up to 2 times the plan quota; anything above that expires at the turn of the month.
- Extra credits, Dev only, at the plan rate: valid for 6 months.
Text-to-Speech (TTS)
Text to speech, in MP3, with a Brazilian accent.
Generate speech
/v1/ttsTurns the text into speech and returns the MP3 (audio/mpeg). Costs 1 credit per character of the text, charged before generating and refunded if generation fails.
Prefer JSON? Send Accept: application/json (without */*) and the audio comes as base64 in the audio field.
Authentication: key with the Fala (speech) scope.
Request body application/json
What Lero will say, 1 to 500 characters. Leading and trailing spaces do not count.
The voice: rio (Rio de Janeiro), sp (São Paulo) or niteroi (Niterói).
Lero v1, the only speech model for now.
Response headers
The request id. Send it if you need support.
The credits charged for this request.
The account balance after the request.
The audio duration, in seconds.
Responses
| status | meaning |
|---|---|
| 200 | The speech MP3, with the headers above. With Accept: application/json, the JSON on the side. |
| 400 | texto_invalido |
| 400 | voz_invalida |
| 400 | modelo_invalido |
| 400 | pedido_invalido |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
| 402 | sem_credito |
| 402 | teto_da_chave |
| 403 | plano |
| 403 | escopo |
| 403 | bloqueado |
| 429 | muitos_pedidos |
| 429 | simultaneas |
| 429 | ocupado |
| 503 | carregando |
| 503 | motor_desligado |
| 502 | falhou |
| 504 | demorou |
| 500 | interno |
What each error means: Errors.
Voices
The Lero voices and their accents.
List voices
/v1/voicesThe list of voices, with id and name. Free.
Authentication: key.
Responses
| status | meaning |
|---|---|
| 200 | The voices. |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
| 403 | plano |
| 403 | bloqueado |
What each error means: Errors.
Speech-to-Text (STT)
Audio to text, in spoken Brazilian Portuguese (slang, acronyms and numbers), with timestamps for each sentence and ready subtitles.
Transcribe audio
/v1/sttTurns audio into text, in spoken Brazilian Portuguese: slang, acronyms, numbers and names come out right. Returns the text with paragraphs and the sentences with start and end times; on request, the time of each word or ready subtitles (SRT or VTT).
Send the file in the audio field as multipart/form-data (up to 4 MB), or a public https link in url (up to 500 MB; a signed S3, GCS or R2 link works). Files over 4 MB go by url, in a JSON body with the same options. Formats: MP3, WAV, M4A, OGG, OPUS, WEBM, FLAC and MP4 video (Lero uses the video's audio).
Costs 150 credits per minute of audio (2.5 per second), billed by the second and rounded up, only when the transcription succeeds: if it fails, nothing is charged. The whole file duration counts, silence included. Example: 11 min of audio cost 1,650 credits.
Up to 2 h of audio per request. Transcription runs at about 30 times real time (1 h of audio in about 2 min): use a 300 s timeout. Lero does not keep the audio or the transcript: it deletes both when it responds.
formato | response |
|---|---|
json (default) | JSON with the text, the sentences and the cost (on the side) |
texto | text/plain: just the text, with paragraphs |
srt | application/x-subrip: SRT subtitles |
vtt | text/vtt: WebVTT subtitles |
In the JSON, texto has paragraphs (\n\n) at long pauses and segmentos has the sentences, with inicio and fim in seconds. With timestamps=palavra, each sentence also carries palavras: [{inicio, fim, palavra}]. formatado is true when the punctuation and capitalization pass covered the whole text. The texto, srt and vtt formats come in UTF-8.
Authentication: key with the Fala scope (the same as speech).
Request body multipart/form-data · application/json
The audio file, up to 4 MB: MP3, WAV, M4A, OGG, OPUS, WEBM, FLAC or MP4 video. Send audio or url, never both.
Public https link to the audio, up to 500 MB; Lero downloads it. For files over 4 MB, use url (a signed S3, GCS, R2 etc. link works).
json (default), texto (text/plain), srt or vtt (ready subtitles).
frase (sentence, default) or palavra (word): each segment gets palavras, with the start and end of each word. Only with json.
Keyterms: up to 50 names, brands, acronyms and terms to recognize and keep spelled as given, e.g. ["Lero", "Unflat", "CNPJ"]. In multipart, as a comma-separated list: Lero,Unflat,CNPJ.
The punctuation and capitalization pass, default true. With false, it returns faster, with the recognizer's punctuation; same price.
pt-BR, the only one for now.
Lero v1, the only transcription model for now.
Response headers
The request id. Send it if you need support.
The credits charged for this request.
The account balance after the request.
The audio duration, in seconds.
Responses
| status | meaning |
|---|---|
| 200 | The transcription: the JSON on the side (formato=json) or the file itself (texto, srt, vtt), with the headers above. |
| 400 | audio_invalido |
| 400 | url_invalida |
| 400 | formato_invalido |
| 400 | idioma_invalido |
| 400 | modelo_invalido |
| 400 | pedido_invalido |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
| 402 | sem_credito |
| 402 | teto_da_chave |
| 403 | plano |
| 403 | escopo |
| 403 | bloqueado |
| 413 | audio_grande |
| 413 | audio_longo |
| 429 | muitos_pedidos |
| 429 | simultaneas |
| 429 | ocupado |
| 503 | motor_desligado |
| 502 | falhou |
| 504 | demorou |
| 500 | interno |
What each error means: Errors.
Voice Clone
Coming soon. Your own voice from recordings of the voice owner, with their consent.
Conversational Agent
Real-time voice agent over WebRTC, with your system prompt, knowledge base and live context. In beta.
List versions and LLMs
/v1/agentsConversational Agent discovery: the agent versions your key can use, the LLMs, the voices and the recommended setup. Call it before opening a call: the list changes. Free.
Authentication: key.
Responses
| status | meaning |
|---|---|
| 200 | The current options. |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
What each error means: Errors.
Open a call (WebRTC)
/v1/agents/conversationLive call signaling: you send the SDP offer of your WebRTC client and get the SDP answer. After that, audio and the chat data channel flow peer-to-peer between your client and the agent (streaming STT, LLM and TTS, with barge-in and turn detection). Without versao, llm and voz, the recommended setup applies. With instrucoes, the agent becomes yours: the system prompt replaces the Lero persona. Step by step in Conversational Agent: step by step.
Authentication: key.
Request body application/json
Always offer.
The full SDP offer, with complete ICE (non-trickle), up to 20,000 characters.
The agent version, an id from GET /v1/agents. Recommended: v0.4.
The LLM, an id from GET /v1/agents. Recommended: oss120b.
rio, sp or niteroi. Ignored on single-voice versions (vozes: false).
System prompt, up to 30,000 characters. Replaces the Lero persona; voice rules (no markdown, code or URLs in speech) are appended.
Knowledge base (RAG): text or [{titulo, texto}], up to 50 items and 60,000 characters. The agent consults it and never reads it aloud.
First message: how the agent opens the call. An instruction, not the literal sentence.
STT keywords: up to 40 names and terms to recognize.
Silence prompts. Off with instrucoes; [20, 40, 60] turns them on (2 nudges, then goodbye).
Web search tool. Off with instrucoes.
Max length of each reply, 60 to 1000 (default 260).
Responses
| status | meaning |
|---|---|
| 200 | The SDP answer: apply it with setRemoteDescription. |
| 400 | voz_invalida |
| 400 | pedido_invalido |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
| 400 | oferta_invalida |
| 400 | versao_invalida |
| 400 | llm_invalido |
| 400 | roteiro_invalido |
| 502 | sem_bot |
| 503 | desligado |
What each error means: Errors.
Account
The account credits.
Credits left
/v1/balanceThe account credits right now, with this month's allowance, and the plan. Free.
Authentication: key.
Response headers
The account balance.
Responses
| status | meaning |
|---|---|
| 200 | The balance and the plan. |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
| 403 | plano |
| 403 | bloqueado |
What each error means: Errors.
Webhooks
Webhooks are Lero calling your server when something happens, instead of you polling. Example: a Conversational Agent call ended, and your system gets the transcript, the duration and the end reason right away, to save in the CRM, score an interview or open a ticket.
- At app.lero.studio/desenvolvedores, Webhooks tab, add your server's
httpsURL and choose the events (up to 5 webhooks per account). - Copy the webhook secret (
whsec_...) to your server. - Click Enviar teste (send test): the
webhook.testeevent arrives. - On your server, verify the signature and answer
2xxwithin 10 s.
| event | when |
|---|---|
agente.conversa.encerrada | A Conversational Agent call of your account ended. Carries version, LLM, voice, start, end, duration, end reason and the transcript. |
webhook.teste | You clicked Enviar teste. |
Speech-to-Text answers in the same call, with no webhook. Voice Clone events arrive when that product launches.
Each delivery is a POST with Content-Type: application/json and the headers Lero-Evento, Lero-Evento-Id and Lero-Assinatura: t=<unix>,v1=<hex>. v1 is the HMAC-SHA256 of "<t>.<raw body>" with the webhook secret. Reject when the signature does not match or when t is older than 5 minutes.
{
"id": "6f1c2a9e-3b7d-4e58-9a0c-2d4f6b8e1a37",
"evento": "agente.conversa.encerrada",
"criado": "2026-11-11T15:42:10.512Z",
"dados": {
"conversa_id": "2eecec700b63",
"versao": "v0.4",
"llm": "oss120b",
"voz": "rio",
"lingua": "pt",
"inicio": "2026-11-11T15:31:07Z",
"fim": "2026-11-11T15:42:09Z",
"duracao_s": 662.4,
"motivo": "a pessoa desligou",
"transcript": [
{ "papel": "agente", "texto": "Oi! Aqui é a Bia, da Acme.", "t": 1.2 },
{ "papel": "pessoa", "texto": "Oi, tudo bem?", "t": 4.8 }
]
}
}Verify the signature (Node):
import crypto from "node:crypto";
// Next.js route: POST /api/lero-webhook. LERO_WEBHOOK_SECRET = o segredo do webhook (whsec_...)
export async function POST(req) {
const corpo = await req.text(); // o corpo cru, antes do JSON.parse
const [t, v1] = (req.headers.get("lero-assinatura") || "").split(",").map((p) => p.split("=")[1]);
const esperado = crypto.createHmac("sha256", process.env.LERO_WEBHOOK_SECRET).update(`${t}.${corpo}`).digest("hex");
const ok = v1 && v1.length === esperado.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado));
if (!ok || Math.abs(Date.now() / 1000 - Number(t)) > 300) return new Response("assinatura inválida", { status: 401 });
const evento = JSON.parse(corpo);
if (evento.evento === "agente.conversa.encerrada") {
// salve evento.dados.transcript, avalie a entrevista, abra o ticket...
}
return new Response("ok"); // responda 2xx rápido; o trabalho pesado vai para uma fila
}Verify the signature (Python):
import hashlib, hmac, json, os, time
from flask import Flask, request
app = Flask(__name__)
@app.post("/lero-webhook")
def lero_webhook():
corpo = request.get_data(as_text=True) # o corpo cru
partes = dict(p.split("=", 1) for p in request.headers.get("Lero-Assinatura", "").split(",") if "=" in p)
esperado = hmac.new(os.environ["LERO_WEBHOOK_SECRET"].encode(), f"{partes.get('t')}.{corpo}".encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(partes.get("v1", ""), esperado) or abs(time.time() - int(partes.get("t", 0))) > 300:
return "assinatura inválida", 401
evento = json.loads(corpo)
if evento["evento"] == "agente.conversa.encerrada":
pass # salve evento["dados"]["transcript"]...
return "ok"
- Without a
2xx, Lero retries after 2 s and 8 s (3 attempts). The eventidis the same across attempts: use it to avoid processing twice. - Redirects are not followed: use the final URL.
- The latest deliveries, with status and time, show in the Webhooks tab.
Conversational Agent: step by step
The Conversational Agent is a real-time voice call. HTTP only does discovery and signaling; then audio and events flow peer-to-peer over WebRTC. Typical latency ~1 s, with barge-in (the person interrupts and the agent stops).
- Discover.
GET /v1/agentsreturns the versions, LLMs and voices your key can use. - Connect. In the WebRTC client, create the
chatdata channel before the offer, add 1 audio track (the microphone) and wait for ICE gathering. - Signaling. Send the SDP offer to
POST /v1/agents/conversation(from your backend, with the key) and apply the SDP answer. The call starts and the agent greets. - Custom agent (optional). In the same POST,
instrucoes(system prompt),conhecimento(knowledge base),abertura,palavrasand the settings make the agent yours. - Live context (optional). During the call, send on
chatwhat the agent needs to see: the code in a live coding, the cart, the screen. - Events. Read the transcript, the state and the latency of each turn on
chat. - Hang up. Close the
RTCPeerConnection.
Live context and control, over the data channel (every message needs the type field):
// live context: one panel per key, always the latest version (send the full text on change, ~every 2 s)
chat.send(JSON.stringify({ type: "lero-contexto", chave: "codigo", texto: editor.getValue() }));
chat.send(JSON.stringify({ type: "lero-contexto", chave: "testes", texto: "3/5 testes passaram", responder: true }));
// system note: the person does not hear it; the agent acts on the next free turn (responder: false only stores it)
chat.send(JSON.stringify({ type: "lero-instrucao", texto: "O tempo da fase 2 acabou. Passe para a fase 3." }));
// exact utterance, bypasses the LLM
chat.send(JSON.stringify({ type: "lero-falar", texto: "Faltam 5 minutos." }));| message | fields | what it does |
|---|---|---|
lero-contexto | chave (up to 60), texto (up to 20,000), responder | One live panel per key (up to 8). Only the latest version enters the next reply. Empty texto removes it. |
lero-instrucao | texto (up to 4,000), responder | System note the person does not hear: phase change, timer, hint. |
lero-falar | texto (up to 2,000) | The agent says exactly the text, bypassing the LLM. |
Events on chat (field tipo):
| tipo | fields |
|---|---|
estado | estado: conectado, na_fila, ouvindo, pensando, falando, aviso_fim, encerrada; motivo at the end |
fala | papel (pessoa or agente), texto, final, interrompido: the live transcript |
latencia | total_ms and the turn stages |
ignorado | a short backchannel ("uhum") that did not interrupt the agent |
The call ends when you close the connection, on the silence goodbye (if silencio_s is on) or after 10 minutes without speech. Every call is recorded (audio, transcript and agent config) for quality review.
Examples
A complete client, with a timeout and waits on 429 and 503:
import { writeFile } from "node:fs/promises";
const espera = s => new Promise(ok => setTimeout(ok, s * 1000));
// generates the speech and returns the MP3; waits Retry-After on 429 and 503
async function falar(texto, voz = "rio", tentativas = 3) {
for (let i = 0; i < tentativas; i++) {
const res = await fetch("https://app.lero.studio/api/v1/tts", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.LERO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ texto, voz }),
signal: AbortSignal.timeout(300_000),
});
if (res.status === 429 || res.status === 503) {
await espera(Number(res.headers.get("Retry-After")) || 10);
continue;
}
if (!res.ok) {
const { erro, mensagem } = await res.json();
throw new Error(`${res.status} ${erro}: ${mensagem}`);
}
return Buffer.from(await res.arrayBuffer());
}
throw new Error("the voice stayed busy");
}
await writeFile("fala.mp3", await falar("Bom dia! Seu pedido saiu pra entrega.", "sp"));import os
import time
import requests
def falar(texto, voz="rio", tentativas=3):
"""Generates the speech and returns the MP3; waits Retry-After on 429 and 503."""
for _ in range(tentativas):
r = requests.post(
"https://app.lero.studio/api/v1/tts",
headers={"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"},
json={"texto": texto, "voz": voz},
timeout=300,
)
if r.status_code in (429, 503):
time.sleep(int(r.headers.get("Retry-After", "10")))
continue
if not r.ok:
e = r.json()
raise RuntimeError(f"{r.status_code} {e['erro']}: {e['mensagem']}")
return r.content
raise RuntimeError("the voice stayed busy")
with open("fala.mp3", "wb") as f:
f.write(falar("Oi! Aqui é o Lero, de Niterói.", voz="niteroi"))Support
Questions and problems: oi@lero.studioemail copied. Send the X-Pedido-Id of the request that failed.