LeroDocs
Referência da API

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-mcp

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

  1. Crie uma chave em app.lero.studio/desenvolvedores. A API está nos planos Creator e Dev.
  2. Guarde a chave no seu servidor, na variável LERO_API_KEY.
  3. 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.mp3

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.

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.

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.

JSON
{"erro": "sem_credito", "mensagem": "Os créditos da conta acabaram.", "pedido": "3f9c1e0a7b2d4c6e8a1b3d5f7e9c0a2b"}
statuserroquando
400texto_invalidoO texto está vazio, passou de 500 caracteres ou a voz não aceitou (emojis e símbolos).
400voz_invalidaA voz não existe. Use rio, sp ou niteroi.
400audio_invalidoTranscrição: o arquivo está vazio, não tem áudio que dê para ler ou o formato não é aceito.
400url_invalidaTranscrição: a url não é https pública, não respondeu 200 ou não deu para baixar.
400formato_invalidoTranscrição: o formato não é json, texto, srt nem vtt.
400idioma_invalidoTranscrição: o idioma não é pt-BR.
400modelo_invalidoO model não é Lero v1.
400pedido_invalidoO 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.
401sem_chaveO pedido veio sem chave.
401chave_invalidaA chave está errada ou foi revogada.
401chave_vencidaA chave passou da validade.
402sem_creditoOs 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.
402teto_da_chaveA chave chegou ao teto de créditos do mês.
403planoA conta não está num plano com API (Creator ou Dev).
403escopoA chave não tem o escopo Fala (vale para a fala e a transcrição).
403bloqueadoA conta está bloqueada.
413audio_grandeTranscrição: o arquivo no corpo passou de 4 MB (mande por url) ou o da url passou de 500 MB.
413audio_longoTranscrição: o áudio passou de 2 h.
429muitos_pedidosPassou de 60 pedidos por minuto na conta. Espere o Retry-After.
429simultaneasO plano já está no limite de gerações simultâneas (Creator 5, Dev 10). Espere um terminar.
429ocupadoA voz ou a transcrição está com fila. Espere o Retry-After.
503carregandoA voz está subindo. Espere o Retry-After.
503motor_desligadoA voz ou a transcrição está fora do ar. Espere o Retry-After.
502falhouA geração da fala ou a transcrição falhou. Tente de novo.
504demorouA geração da fala ou a transcrição passou do tempo. Tente de novo.
500internoErro do nosso lado. Tente de novo; se continuar, mande o X-Pedido-Id.
400oferta_invalidaFalta a SDP offer, ou ela não começa com v=0, ou passou de 20 mil caracteres.
400versao_invalidaA versao não está na lista do GET /v1/agents para a sua chave.
400llm_invalidoO llm não está na lista do GET /v1/agents.
400roteiro_invalidoUm campo do custom agent passou do limite. O campo vem em campo.
502sem_botO agente não respondeu à offer. Tente de novo.
503desligadoOs 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.

POST/v1/tts

Transforma 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

textostringobrigatório

O que o Lero vai falar, de 1 a 500 caracteres. Os espaços das pontas não contam.

vozstringobrigatório

A voz: rio (Rio de Janeiro), sp (São Paulo) ou niteroi (Niterói).

modelstringopcional

Lero v1, o único de fala por enquanto.

Headers da resposta

X-Pedido-Idstring

O id do pedido. Mande junto se precisar de suporte.

X-Creditos-Custointeger

Os créditos cobrados por este pedido.

X-Creditos-Restantesinteger

O saldo da conta depois do pedido.

X-Duracaonumber

A duração do áudio, em segundos.

Responses

statussignificado
200O MP3 da fala, com os headers acima. Com Accept: application/json, o JSON ao lado.
400texto_invalido
400voz_invalida
400modelo_invalido
400pedido_invalido
401sem_chave
401chave_invalida
401chave_vencida
402sem_credito
402teto_da_chave
403plano
403escopo
403bloqueado
429muitos_pedidos
429simultaneas
429ocupado
503carregando
503motor_desligado
502falhou
504demorou
500interno

O que cada erro quer dizer: Erros.

As vozes do Lero e os sotaques.

GET/v1/voices

A lista das vozes, com o id e o nome. Não gasta crédito.

Autenticação: chave.

Responses

statussignificado
200As vozes.
401sem_chave
401chave_invalida
401chave_vencida
403plano
403bloqueado

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

POST/v1/stt

Transforma 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.

formatoresposta
json (padrão)JSON com o texto, as frases e o custo (ao lado)
textotext/plain: só o texto, com parágrafos
srtapplication/x-subrip: a legenda SRT
vtttext/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

audiofileum dos dois

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.

urlstringum dos 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).

formatostringopcional

json (padrão), texto (text/plain), srt ou vtt (legendas prontas).

timestampsstringopcional

frase (padrão) ou palavra: cada segmento ganha palavras, com o início e o fim de cada palavra. Só no json.

palavras_chavestring[]opcional

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.

formatarbooleanopcional

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.

idiomastringopcional

pt-BR, o único por enquanto.

modelstringopcional

Lero v1, o único de transcrição por enquanto.

Headers da resposta

X-Pedido-Idstring

O id do pedido. Mande junto se precisar de suporte.

X-Creditos-Custointeger

Os créditos cobrados por este pedido.

X-Creditos-Restantesinteger

O saldo da conta depois do pedido.

X-Duracaonumber

A duração do áudio, em segundos.

Responses

statussignificado
200A transcrição: o JSON ao lado (formato=json) ou o arquivo direto (texto, srt, vtt), com os headers acima.
400audio_invalido
400url_invalida
400formato_invalido
400idioma_invalido
400modelo_invalido
400pedido_invalido
401sem_chave
401chave_invalida
401chave_vencida
402sem_credito
402teto_da_chave
403plano
403escopo
403bloqueado
413audio_grande
413audio_longo
429muitos_pedidos
429simultaneas
429ocupado
503motor_desligado
502falhou
504demorou
500interno

O que cada erro quer dizer: Erros.

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

GET/v1/agents

O 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

statussignificado
200As opções de agora.
401sem_chave
401chave_invalida
401chave_vencida

O que cada erro quer dizer: Erros.

Abrir conversa (WebRTC)

POST/v1/agents/conversation

O 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

typestringobrigatório

Sempre offer.

sdpstringobrigatório

A SDP offer inteira, com o ICE completo (non-trickle), até 20 mil caracteres.

versaostringopcional

A versão do agente, um id do GET /v1/agents. Recomendado: v0.4.

llmstringopcional

O LLM, um id do GET /v1/agents. Recomendado: oss120b.

vozstringopcional

rio, sp ou niteroi. Ignorada na versão de voz única (vozes: false).

instrucoesstringopcional

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.

conhecimentostring | arrayopcional

Knowledge base (RAG): texto ou [{titulo, texto}], até 50 itens e 60 mil caracteres. O agente consulta e não lê em voz alta.

aberturastringopcional

First message: como o agente abre a ligação. É uma instrução, não a frase exata.

palavrasstring[]opcional

STT keywords: até 40 nomes e termos para reconhecer.

silencio_sfalse | number[]opcional

Silence prompts. Desligado com instrucoes; [20, 40, 60] liga (2 toques e a despedida).

pesquisabooleanopcional

Web search tool. Desligada com instrucoes.

max_tokensintegeropcional

Tamanho máximo de cada resposta, de 60 a 1000 (padrão 260).

Responses

statussignificado
200A SDP answer: aplique com setRemoteDescription.
400voz_invalida
400pedido_invalido
401sem_chave
401chave_invalida
401chave_vencida
400oferta_invalida
400versao_invalida
400llm_invalido
400roteiro_invalido
502sem_bot
503desligado

O que cada erro quer dizer: Erros.

Os créditos da conta.

GET/v1/balance

Os 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

X-Creditos-Restantesinteger

O saldo da conta.

Responses

statussignificado
200O saldo e o plano.
401sem_chave
401chave_invalida
401chave_vencida
403plano
403bloqueado

O que cada erro quer dizer: Erros.

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.

  1. Em app.lero.studio/desenvolvedores, aba Webhooks, adicione o endereço https do seu servidor e escolha os eventos (até 5 webhooks por conta).
  2. Copie o segredo do webhook (whsec_...) para o seu servidor.
  3. Clique em Enviar teste: chega o evento webhook.teste.
  4. No seu servidor, confira a assinatura e responda 2xx em até 10 s.
eventoquando
agente.conversa.encerradaUma conversa do Conversational Agent da sua conta terminou. Traz versão, LLM, voz, início, fim, duração, motivo e o transcript.
webhook.testeVocê 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.

JSON
{
  "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):

javascript
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):

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). O id do 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).

  1. Discover. GET /v1/agents devolve as versões, os LLMs e as vozes que a sua chave pode usar.
  2. Connect. No client WebRTC, crie o data channel chat antes da offer, adicione 1 track de áudio (o microfone) e espere o ICE gathering.
  3. 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.
  4. Custom agent (opcional). No mesmo POST, instrucoes (system prompt), conhecimento (knowledge base), abertura, palavras e os ajustes fazem o agente ser o seu.
  5. Live context (opcional). Durante a ligação, mande pelo chat o que o agente precisa ver: o código de um live coding, o carrinho, a tela.
  6. Events. Leia pelo chat o transcript, o estado e a latência de cada turno.
  7. Hang up. Feche a RTCPeerConnection.

Live context e controle, pelo data channel (toda mensagem precisa do campo type):

javascript
// 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." }));
mensagemcamposo que faz
lero-contextochave (até 60), texto (até 20 mil), responderUm painel vivo por chave (até 8). Só a versão mais nova entra na próxima resposta. texto vazio apaga.
lero-instrucaotexto (até 4 mil), responderSystem note que a pessoa não ouve: troca de fase, tempo, dica.
lero-falartexto (até 2 mil)O agente fala exatamente o texto, sem passar pelo LLM.

Events que chegam pelo chat (campo tipo):

tipocampos
estadoestado: conectado, na_fila, ouvindo, pensando, falando, aviso_fim, encerrada; motivo no fim
falapapel (pessoa ou agente), texto, final, interrompido: o transcript ao vivo
latenciatotal_ms e as etapas do turno
ignoradoum 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.

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"));

Dúvidas e problemas: . Mande o X-Pedido-Id do pedido que deu errado.

Ir para o site