API do Lero
Voz com sotaque brasileiro no seu produto: Text-to-Speech (TTS) em MP3, Speech-to-Text (STT) do português falado, com o tempo de cada frase e legenda pronta, e um Conversational Agent ao vivo por WebRTC. Voice Clone chega em breve. A API está nos planos Creator e Dev: o TTS gasta 1 crédito por caractere e o STT, 150 créditos por minuto de áudio.
Integração direta
Use o Lero no Claude, no Codex ou no Cursor sem escrever código. Crie uma chave em app.lero.studio/desenvolvedores e rode o comando do seu agente, com a sua chave no lugar de lero_sua_chave:
claude mcp add --env LERO_API_KEY=lero_sua_chave lero -- npx -y lero-mcpcodex mcp add lero --env LERO_API_KEY=lero_sua_chave -- npx -y lero-mcp{
"mcpServers": {
"lero": {
"command": "npx",
"args": [
"-y",
"lero-mcp"
],
"env": {
"LERO_API_KEY": "lero_sua_chave"
}
}
}
}{
"mcpServers": {
"lero": {
"command": "npx",
"args": [
"-y",
"lero-mcp"
],
"env": {
"LERO_API_KEY": "lero_sua_chave"
}
}
}
}No Cursor, o JSON vai em ~/.cursor/mcp.json. No Claude Desktop, em Settings > Developer > Edit Config. Precisa do Node 18 ou mais novo.
Pronto: peça um áudio na conversa, por exemplo gera um áudio na voz do Rio dizendo "bom dia, tudo certo?", e o agente salva o MP3 em ~/Downloads/Lero. Mais sobre o MCP em Build with AI.
Para colocar o Lero no código do seu produto, cole o prompt de integração no seu agente de código, ou siga o início rápido abaixo.
Início rápido
- Crie uma chave em app.lero.studio/desenvolvedores. A API está nos planos Creator e Dev.
- Guarde a chave no seu servidor, na variável
LERO_API_KEY. - Peça a primeira fala:
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("restam", 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 de todas as rotas: https://app.lero.studio/api/v1. Pedidos e respostas em JSON (UTF-8), menos o áudio: a fala devolve MP3 e a transcrição recebe o arquivo em multipart/form-data.
As rotas têm nome em inglês: /v1/tts, /v1/stt, /v1/voices, /v1/balance, /v1/agents e /v1/agents/conversation. Os caminhos antigos, em português (/v1/fala, /v1/transcricao, /v1/vozes, /v1/saldo, /v1/agentes e /v1/agentes/conversa), seguem funcionando igual.
Chave
Toda rota pede a chave no header Authorization: Bearer <sua chave> (ou no header X-Api-Key). A chave começa com lero_ e aparece inteira uma vez só, na hora de criar. Depois, o app mostra só os 4 últimos caracteres.
- Use a chave só no servidor. Nunca no navegador, num app de celular ou num repositório: quem tiver a chave gasta os seus créditos.
- Cada chave pode ter um nome, um teto de créditos por mês e uma validade (1, 7, 30 ou 90 dias, ou nunca vencer).
- Vazou? Revogue a chave no app. Ela para na hora, e você cria outra.
- Até 10 chaves ativas por conta.
Erros
Todo erro volta em JSON, com um código fixo no campo erro e uma mensagem em português. Trate pelo erro, nunca pelo texto da mensagem. Nos erros da fala e da transcrição também vêm pedido e, quando é um campo, campo.
{"erro": "sem_credito", "mensagem": "Os créditos da conta acabaram.", "pedido": "3f9c1e0a7b2d4c6e8a1b3d5f7e9c0a2b"}| status | erro | quando |
|---|---|---|
| 400 | texto_invalido | O texto está vazio, passou de 500 caracteres ou a voz não aceitou (emojis e símbolos). |
| 400 | voz_invalida | A voz não existe. Use rio, sp ou niteroi. |
| 400 | audio_invalido | Transcrição: o arquivo está vazio, não tem áudio que dê para ler ou o formato não é aceito. |
| 400 | url_invalida | Transcrição: a url não é https pública, não respondeu 200 ou não deu para baixar. |
| 400 | formato_invalido | Transcrição: o formato não é json, texto, srt nem vtt. |
| 400 | idioma_invalido | Transcrição: o idioma não é pt-BR. |
| 400 | modelo_invalido | O model não é Lero v1. |
| 400 | pedido_invalido | O corpo não é JSON. Na transcrição: o corpo não é multipart nem JSON, ou veio sem audio e sem url, ou com os dois. |
| 401 | sem_chave | O pedido veio sem chave. |
| 401 | chave_invalida | A chave está errada ou foi revogada. |
| 401 | chave_vencida | A chave passou da validade. |
| 402 | sem_credito | Os créditos da conta não cobrem o pedido. Na transcrição, o JSON traz custo (os créditos que o áudio precisa) e saldo. |
| 402 | teto_da_chave | A chave chegou ao teto de créditos do mês. |
| 403 | plano | A conta não está num plano com API (Creator ou Dev). |
| 403 | escopo | A chave não tem o escopo Fala (vale para a fala e a transcrição). |
| 403 | bloqueado | A conta está bloqueada. |
| 413 | audio_grande | Transcrição: o arquivo no corpo passou de 4 MB (mande por url) ou o da url passou de 500 MB. |
| 413 | audio_longo | Transcrição: o áudio passou de 2 h. |
| 429 | muitos_pedidos | Passou de 60 pedidos por minuto na conta. Espere o Retry-After. |
| 429 | simultaneas | O plano já está no limite de gerações simultâneas (Creator 5, Dev 10). Espere um terminar. |
| 429 | ocupado | A voz ou a transcrição está com fila. Espere o Retry-After. |
| 503 | carregando | A voz está subindo. Espere o Retry-After. |
| 503 | motor_desligado | A voz ou a transcrição está fora do ar. Espere o Retry-After. |
| 502 | falhou | A geração da fala ou a transcrição falhou. Tente de novo. |
| 504 | demorou | A geração da fala ou a transcrição passou do tempo. Tente de novo. |
| 500 | interno | Erro do nosso lado. Tente de novo; se continuar, mande o X-Pedido-Id. |
| 400 | oferta_invalida | Falta a SDP offer, ou ela não começa com v=0, ou passou de 20 mil caracteres. |
| 400 | versao_invalida | A versao não está na lista do GET /v1/agents para a sua chave. |
| 400 | llm_invalido | O llm não está na lista do GET /v1/agents. |
| 400 | roteiro_invalido | Um campo do custom agent passou do limite. O campo vem em campo. |
| 502 | sem_bot | O agente não respondeu à offer. Tente de novo. |
| 503 | desligado | Os agentes estão fora do ar agora. Espere o Retry-After. |
Quando a geração da fala falha depois da cobrança, o Lero devolve os créditos na hora: o campo cobrado vem 0 e a mensagem termina com "Nada foi cobrado.". A transcrição só cobra quando dá certo: erro na transcrição nunca gasta crédito.
Créditos e limites
Text-to-Speech (TTS)
- 1 crédito por caractere do texto, cobrado antes de gerar e devolvido se a geração falhar.
- Até 500 caracteres por pedido. Para um texto maior, divida em frases e junte os MP3.
- Cada pedido gera um áudio novo. Guarde o MP3 se for usar de novo.
Speech-to-Text (STT)
- 150 créditos por minuto de áudio (2,5 por segundo), cobrado pelo segundo e arredondado para cima, só quando a transcrição dá certo. Conta o arquivo inteiro, silêncio incluído.
- Até 2 h de áudio por pedido. O arquivo no corpo vai até 4 MB; por
url, até 500 MB. - Cerca de 30 vezes o tempo real: 1 h de áudio sai em uns 2 min. Use timeout de 300 s.
- O Lero não guarda o áudio nem o texto da transcrição. Guarde a resposta se for usar de novo.
A conta
- Até 60 pedidos por minuto por conta, somando todas as chaves e as rotas.
- Gerações simultâneas (fala e transcrição): 5 no Creator e 10 no Dev. Acima disso,
429 simultaneas. - A conversa ao vivo gasta 1.000 créditos por minuto de ligação, cobrados pelo segundo no fim; para ligar, a conta precisa de pelo menos 1 minuto de saldo.
- A franquia do plano entra no 1º uso de cada mês (fuso de Brasília). Os créditos do plano valem 30 dias e não passam para o mês seguinte.
- Créditos avulsos, só no Dev, na proporção do plano: valem 6 meses.
Text-to-Speech (TTS)
Texto em fala, em MP3, com sotaque brasileiro.
Gerar fala
/v1/ttsTransforma o texto em fala e devolve o MP3 (audio/mpeg). Custa 1 crédito por caractere do texto, cobrado antes de gerar e devolvido se a geração falhar.
Prefere JSON? Mande Accept: application/json (sem */*) e o áudio vem em base64 no campo audio.
Autenticação: chave com o escopo Fala.
Request body application/json
O que o Lero vai falar, de 1 a 500 caracteres. Os espaços das pontas não contam.
A voz: rio (Rio de Janeiro), sp (São Paulo) ou niteroi (Niterói).
Lero v1, o único de fala por enquanto.
Headers da resposta
O id do pedido. Mande junto se precisar de suporte.
Os créditos cobrados por este pedido.
O saldo da conta depois do pedido.
A duração do áudio, em segundos.
Responses
| status | significado |
|---|---|
| 200 | O MP3 da fala, com os headers acima. Com Accept: application/json, o JSON ao lado. |
| 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 |
O que cada erro quer dizer: Erros.
Vozes
As vozes do Lero e os sotaques.
Listar vozes
/v1/voicesA lista das vozes, com o id e o nome. Não gasta crédito.
Autenticação: chave.
Responses
| status | significado |
|---|---|
| 200 | As vozes. |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
| 403 | plano |
| 403 | bloqueado |
O que cada erro quer dizer: Erros.
Speech-to-Text (STT)
Áudio em texto, no português falado do Brasil (gírias, siglas e números), com o tempo de cada frase e legenda pronta.
Transcrever áudio
/v1/sttTransforma o áudio em texto, no português falado do Brasil: gírias, siglas, números e nomes no lugar. Devolve o texto com parágrafos e as frases com o tempo de início e fim; se você pedir, o tempo de cada palavra ou a legenda pronta (SRT ou VTT).
Mande o arquivo no campo audio, em multipart/form-data (até 4 MB), ou um link https público em url (até 500 MB; link assinado de S3, GCS ou R2 serve). Arquivo maior que 4 MB vai por url, num JSON com as mesmas opções. Formatos: MP3, WAV, M4A, OGG, OPUS, WEBM, FLAC e vídeo MP4 (o Lero usa o áudio do vídeo).
Custa 150 créditos por minuto de áudio (2,5 por segundo), cobrado pelo segundo e arredondado para cima, só quando a transcrição dá certo: se falhar, nada é cobrado. Conta a duração do arquivo inteiro, silêncio incluído. Exemplo: 11 min de áudio custam 1.650 créditos.
Até 2 h de áudio por pedido. A transcrição sai em cerca de 30 vezes o tempo real (1 h de áudio em uns 2 min): use timeout de 300 s. O Lero não guarda o áudio nem o texto da transcrição: apaga tudo ao responder.
formato | resposta |
|---|---|
json (padrão) | JSON com o texto, as frases e o custo (ao lado) |
texto | text/plain: só o texto, com parágrafos |
srt | application/x-subrip: a legenda SRT |
vtt | text/vtt: a legenda WebVTT |
No JSON, texto traz parágrafos (\n\n) nas pausas longas e segmentos traz as frases, com inicio e fim em segundos. Com timestamps=palavra, cada frase traz também palavras: [{inicio, fim, palavra}]. O formatado vem true quando a revisão de pontuação e maiúsculas cobriu o texto todo. Os formatos texto, srt e vtt vêm em UTF-8.
Autenticação: chave com o escopo Fala (o mesmo da fala).
Request body multipart/form-data · application/json
O arquivo de áudio, até 4 MB: MP3, WAV, M4A, OGG, OPUS, WEBM, FLAC ou vídeo MP4. Mande audio ou url, nunca os dois.
Link https público do áudio, até 500 MB; o Lero baixa. Para arquivo maior que 4 MB, use url (link assinado de S3, GCS, R2 etc. serve).
json (padrão), texto (text/plain), srt ou vtt (legendas prontas).
frase (padrão) ou palavra: cada segmento ganha palavras, com o início e o fim de cada palavra. Só no json.
Até 50 nomes, marcas, siglas e termos para reconhecer e manter a grafia, ex. ["Lero", "Unflat", "CNPJ"]. No multipart, numa lista separada por vírgula: Lero,Unflat,CNPJ.
A revisão de pontuação e maiúsculas, padrão true. Com false, volta mais rápido, com a pontuação do reconhecimento; o preço é o mesmo.
pt-BR, o único por enquanto.
Lero v1, o único de transcrição por enquanto.
Headers da resposta
O id do pedido. Mande junto se precisar de suporte.
Os créditos cobrados por este pedido.
O saldo da conta depois do pedido.
A duração do áudio, em segundos.
Responses
| status | significado |
|---|---|
| 200 | A transcrição: o JSON ao lado (formato=json) ou o arquivo direto (texto, srt, vtt), com os headers acima. |
| 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 |
O que cada erro quer dizer: Erros.
Voice Clone
Em breve. Uma voz própria a partir das gravações da pessoa dona da voz, com o consentimento dela.
Conversational Agent
Voice agent em tempo real por WebRTC, com o seu system prompt, knowledge base e live context. Em beta.
Listar versões e LLMs
/v1/agentsO discovery do Conversational Agent: as versões do agente que a sua chave pode usar, os LLMs, as vozes e o setup recomendado. Chame antes de abrir a conversa: a lista muda. Não gasta crédito.
Autenticação: chave.
Responses
| status | significado |
|---|---|
| 200 | As opções de agora. |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
O que cada erro quer dizer: Erros.
Abrir conversa (WebRTC)
/v1/agents/conversationO signaling da conversa ao vivo: você manda a SDP offer do seu client WebRTC e recebe a SDP answer. Depois, o áudio e o data channel chat vão peer-to-peer entre o seu client e o agente (STT, LLM e TTS em streaming, com barge-in e turn detection). Sem versao, llm e voz, vale o recomendado. Com instrucoes, o agente vira o seu: o system prompt substitui a persona do Lero. Passo a passo em Conversational Agent: passo a passo.
Autenticação: chave.
Request body application/json
Sempre offer.
A SDP offer inteira, com o ICE completo (non-trickle), até 20 mil caracteres.
A versão do agente, um id do GET /v1/agents. Recomendado: v0.4.
O LLM, um id do GET /v1/agents. Recomendado: oss120b.
rio, sp ou niteroi. Ignorada na versão de voz única (vozes: false).
System prompt, até 30 mil caracteres. Substitui a persona do Lero; as voice rules (nada de markdown, código ou URL na fala) vão junto.
Knowledge base (RAG): texto ou [{titulo, texto}], até 50 itens e 60 mil caracteres. O agente consulta e não lê em voz alta.
First message: como o agente abre a ligação. É uma instrução, não a frase exata.
STT keywords: até 40 nomes e termos para reconhecer.
Silence prompts. Desligado com instrucoes; [20, 40, 60] liga (2 toques e a despedida).
Web search tool. Desligada com instrucoes.
Tamanho máximo de cada resposta, de 60 a 1000 (padrão 260).
Responses
| status | significado |
|---|---|
| 200 | A SDP answer: aplique com 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 |
O que cada erro quer dizer: Erros.
Conta
Os créditos da conta.
Ver saldo
/v1/balanceOs créditos da conta agora, já com a franquia do mês, e o plano. Não gasta crédito.
Autenticação: chave.
Headers da resposta
O saldo da conta.
Responses
| status | significado |
|---|---|
| 200 | O saldo e o plano. |
| 401 | sem_chave |
| 401 | chave_invalida |
| 401 | chave_vencida |
| 403 | plano |
| 403 | bloqueado |
O que cada erro quer dizer: Erros.
Webhooks
Webhooks são o Lero chamando o seu servidor quando algo acontece, em vez de você perguntar de tempos em tempos. Exemplo: a conversa do Conversational Agent terminou, e o seu sistema recebe na hora o transcript, a duração e o motivo do fim, para salvar no CRM, avaliar uma entrevista ou abrir um ticket.
- Em app.lero.studio/desenvolvedores, aba Webhooks, adicione o endereço
httpsdo seu servidor e escolha os eventos (até 5 webhooks por conta). - Copie o segredo do webhook (
whsec_...) para o seu servidor. - Clique em Enviar teste: chega o evento
webhook.teste. - No seu servidor, confira a assinatura e responda
2xxem até 10 s.
| evento | quando |
|---|---|
agente.conversa.encerrada | Uma conversa do Conversational Agent da sua conta terminou. Traz versão, LLM, voz, início, fim, duração, motivo e o transcript. |
webhook.teste | Você clicou em Enviar teste. |
O Speech-to-Text responde na mesma chamada, sem webhook. Eventos de Voice Clone entram quando o produto sair.
Cada envio é um POST com Content-Type: application/json e os headers Lero-Evento, Lero-Evento-Id e Lero-Assinatura: t=<unix>,v1=<hex>. O v1 é o HMAC-SHA256 de "<t>.<corpo cru>" com o segredo do webhook. Recuse quando a assinatura não bate ou quando t tem mais de 5 minutos.
{
"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 }
]
}
}Conferir a assinatura (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
}Conferir a assinatura (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"
- Sem
2xx, o Lero tenta de novo depois de 2 s e de 8 s (3 tentativas). Oiddo evento é o mesmo nas tentativas: use para não processar 2 vezes. - Redirect não é seguido: use o endereço final.
- As últimas entregas, com o status e o tempo, aparecem na aba Webhooks.
Conversational Agent: passo a passo
O Conversational Agent é uma ligação de voz em tempo real. O HTTP só faz o discovery e o signaling; depois, o áudio e os eventos vão peer-to-peer por WebRTC. Latência típica de ~1 s, com barge-in (a pessoa interrompe e o agente para).
- Discover.
GET /v1/agentsdevolve as versões, os LLMs e as vozes que a sua chave pode usar. - Connect. No client WebRTC, crie o data channel
chatantes da offer, adicione 1 track de áudio (o microfone) e espere o ICE gathering. - Signaling. Mande a SDP offer para
POST /v1/agents/conversation(do seu backend, com a chave) e aplique a SDP answer. A conversa começa e o agente cumprimenta. - Custom agent (opcional). No mesmo POST,
instrucoes(system prompt),conhecimento(knowledge base),abertura,palavrase os ajustes fazem o agente ser o seu. - Live context (opcional). Durante a ligação, mande pelo
chato que o agente precisa ver: o código de um live coding, o carrinho, a tela. - Events. Leia pelo
chato transcript, o estado e a latência de cada turno. - Hang up. Feche a
RTCPeerConnection.
Live context e controle, pelo data channel (toda mensagem precisa do campo type):
// 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." }));| mensagem | campos | o que faz |
|---|---|---|
lero-contexto | chave (até 60), texto (até 20 mil), responder | Um painel vivo por chave (até 8). Só a versão mais nova entra na próxima resposta. texto vazio apaga. |
lero-instrucao | texto (até 4 mil), responder | System note que a pessoa não ouve: troca de fase, tempo, dica. |
lero-falar | texto (até 2 mil) | O agente fala exatamente o texto, sem passar pelo LLM. |
Events que chegam pelo chat (campo tipo):
| tipo | campos |
|---|---|
estado | estado: conectado, na_fila, ouvindo, pensando, falando, aviso_fim, encerrada; motivo no fim |
fala | papel (pessoa ou agente), texto, final, interrompido: o transcript ao vivo |
latencia | total_ms e as etapas do turno |
ignorado | um backchannel curto ("uhum") que não interrompeu o agente |
A ligação acaba quando você fecha a conexão, na despedida por silêncio (se silencio_s estiver ligado) ou depois de 10 minutos sem fala. Toda ligação fica gravada (áudio, transcript e a config do agente) para a revisão de qualidade.
Exemplos
Um cliente completo, com timeout e espera no 429 e no 503:
import { writeFile } from "node:fs/promises";
const espera = s => new Promise(ok => setTimeout(ok, s * 1000));
// gera a fala e devolve o MP3; espera o Retry-After no 429 e no 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("a voz seguiu ocupada");
}
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):
"""Gera a fala e devolve o MP3; espera o Retry-After no 429 e no 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("a voz seguiu ocupada")
with open("fala.mp3", "wb") as f:
f.write(falar("Oi! Aqui é o Lero, de Niterói.", voz="niteroi"))Suporte
Dúvidas e problemas: oi@lero.studioemail copiado. Mande o X-Pedido-Id do pedido que deu errado.