# 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. - MCP: a voz do Lero dentro do Claude, do Codex e do Cursor, com um comando. Veja https://lero.studio/docs/ai#mcp - Build with AI: cole um prompt no seu agente de código e ele integra a API no seu produto. Veja https://lero.studio/docs/ai#prompt ## 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](https://app.lero.studio/desenvolvedores?aba=chaves) e rode o comando do seu agente, com a sua chave no lugar de `lero_sua_chave`: Claude Code: ```bash claude mcp add --env LERO_API_KEY=lero_sua_chave lero -- npx -y lero-mcp ``` Codex: ```bash codex mcp add lero --env LERO_API_KEY=lero_sua_chave -- npx -y lero-mcp ``` Cursor: ```json { "mcpServers": { "lero": { "command": "npx", "args": [ "-y", "lero-mcp" ], "env": { "LERO_API_KEY": "lero_sua_chave" } } } } ``` Claude Desktop: ```json { "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](https://lero.studio/docs/ai#mcp). Para colocar o Lero no código do seu produto, cole o [prompt de integração](https://lero.studio/docs/ai#prompt) 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](https://app.lero.studio/desenvolvedores?aba=chaves). 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: ```bash 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 ``` Python: ```python import 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"]) ``` JavaScript: ```javascript 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](https://lero.studio/docs#transcricao) 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 ` (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`. ```json {"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 `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. Corpo do pedido (JSON): - `texto` (string, obrigatório): O que o Lero vai falar, de 1 a 500 caracteres. Os espaços das pontas não contam. - `voz` (string, obrigatório): A voz: `rio` (Rio de Janeiro), `sp` (São Paulo) ou `niteroi` (Niterói). - `model` (string, opcional): `Lero v1`, o único de fala por enquanto. Headers da resposta: - `X-Pedido-Id` (string): O id do pedido. Mande junto se precisar de suporte. - `X-Creditos-Custo` (integer): Os créditos cobrados por este pedido. - `X-Creditos-Restantes` (integer): O saldo da conta depois do pedido. - `X-Duracao` (number): A duração do áudio, em segundos. Responses: - `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` cURL: ```bash 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 ``` Python: ```python import 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"]) ``` JavaScript: ```javascript 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())); ``` Resposta · Accept: application/json (200): ```json { "pedido": "3f9c1e0a7b2d4c6e8a1b3d5f7e9c0a2b", "custo": 45, "saldo": 1499955, "model": "Lero v1", "voz": "rio", "duracao": 3.12, "formato": "mp3", "audio": "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjYx..." } ``` ## Vozes As vozes do Lero e os sotaques. ### Listar vozes `GET /v1/voices` A lista das vozes, com o id e o nome. Não gasta crédito. Autenticação: chave. Responses: - `200`: As vozes. - `401`: `sem_chave` - `401`: `chave_invalida` - `401`: `chave_vencida` - `403`: `plano` - `403`: `bloqueado` cURL: ```bash curl "https://app.lero.studio/api/v1/voices" \ -H "Authorization: Bearer $LERO_API_KEY" ``` Python: ```python import os import requests r = requests.get( "https://app.lero.studio/api/v1/voices", headers={"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"}, timeout=30, ) r.raise_for_status() print(r.json()) ``` JavaScript: ```javascript const res = await fetch("https://app.lero.studio/api/v1/voices", { headers: { "Authorization": `Bearer ${process.env.LERO_API_KEY}` }, }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status} ${data.erro}`); console.log(data); ``` Resposta (200): ```json { "vozes": [ { "id": "rio", "nome": "Rio de Janeiro" }, { "id": "sp", "nome": "São Paulo" }, { "id": "niteroi", "nome": "Niterói" } ] } ``` ## 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. | `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). Corpo do pedido (multipart/form-data ou application/json): - `audio` (file, um 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. - `url` (string, um 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). - `formato` (string, opcional): `json` (padrão), `texto` (text/plain), `srt` ou `vtt` (legendas prontas). - `timestamps` (string, opcional): `frase` (padrão) ou `palavra`: cada segmento ganha `palavras`, com o início e o fim de cada palavra. Só no `json`. - `palavras_chave` (string[], 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`. - `formatar` (boolean, opcional): 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. - `idioma` (string, opcional): `pt-BR`, o único por enquanto. - `model` (string, opcional): `Lero v1`, o único de transcrição por enquanto. Headers da resposta: - `X-Pedido-Id` (string): O id do pedido. Mande junto se precisar de suporte. - `X-Creditos-Custo` (integer): Os créditos cobrados por este pedido. - `X-Creditos-Restantes` (integer): O saldo da conta depois do pedido. - `X-Duracao` (number): A duração do áudio, em segundos. Responses: - `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` cURL: ```bash # arquivo local (até 4 MB) curl -X POST "https://app.lero.studio/api/v1/stt" \ -H "Authorization: Bearer $LERO_API_KEY" \ -F "audio=@reuniao.mp3" \ -F "palavras_chave=Lero,Unflat,CNPJ" \ --max-time 300 # link https público (até 500 MB) e a legenda pronta curl -X POST "https://app.lero.studio/api/v1/stt" \ -H "Authorization: Bearer $LERO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://exemplo.com/reuniao.mp3", "formato": "srt"}' \ --max-time 300 \ --output legenda.srt ``` Python: ```python import os import requests H = {"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"} # arquivo local (até 4 MB) with open("reuniao.mp3", "rb") as f: r = requests.post( "https://app.lero.studio/api/v1/stt", headers=H, files={"audio": f}, data={"palavras_chave": "Lero,Unflat,CNPJ"}, timeout=300, ) r.raise_for_status() t = r.json() print(t["texto"]) for s in t["segmentos"]: print(s["inicio"], s["fim"], s["texto"]) # link https público (até 500 MB) e a legenda pronta r = requests.post( "https://app.lero.studio/api/v1/stt", headers=H, json={"url": "https://exemplo.com/reuniao.mp3", "formato": "srt"}, timeout=300, ) r.raise_for_status() with open("legenda.srt", "wb") as f: f.write(r.content) ``` JavaScript: ```javascript import { readFile, writeFile } from "node:fs/promises"; const headers = { "Authorization": `Bearer ${process.env.LERO_API_KEY}` }; // arquivo local (até 4 MB) const form = new FormData(); form.append("audio", new Blob([await readFile("reuniao.mp3")], { type: "audio/mpeg" }), "reuniao.mp3"); form.append("palavras_chave", "Lero,Unflat,CNPJ"); const res = await fetch("https://app.lero.studio/api/v1/stt", { method: "POST", headers, body: form, signal: AbortSignal.timeout(300_000), }); const t = await res.json(); if (!res.ok) throw new Error(`${res.status} ${t.erro}`); console.log(t.texto); for (const s of t.segmentos) console.log(s.inicio, s.fim, s.texto); // link https público (até 500 MB) e a legenda pronta const srt = await fetch("https://app.lero.studio/api/v1/stt", { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ url: "https://exemplo.com/reuniao.mp3", formato: "srt" }), signal: AbortSignal.timeout(300_000), }); if (!srt.ok) throw new Error(`${srt.status} ${(await srt.json()).erro}`); await writeFile("legenda.srt", await srt.text()); ``` Resposta · formato=json (200): ```json { "pedido": "3f9c1e0a7b2d4c6e8a1b3d5f7e9c0a2b", "custo": 1650, "saldo": 1498350, "model": "Lero v1", "idioma": "pt-BR", "duracao": 659.4, "formatado": true, "texto": "Bom dia, pessoal. Hoje a pauta é o lançamento da campanha de verão.\n\nPerfeito. Então...", "segmentos": [ { "inicio": 0.0, "fim": 3.2, "texto": "Bom dia, pessoal." }, { "inicio": 3.4, "fim": 7.9, "texto": "Hoje a pauta é o lançamento da campanha de verão." } ] } ``` ## 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 `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: - `200`: As opções de agora. - `401`: `sem_chave` - `401`: `chave_invalida` - `401`: `chave_vencida` cURL: ```bash curl "https://app.lero.studio/api/v1/agents" \ -H "Authorization: Bearer $LERO_API_KEY" ``` Python: ```python import os import requests r = requests.get( "https://app.lero.studio/api/v1/agents", headers={"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"}, timeout=30, ) r.raise_for_status() print(r.json()) ``` JavaScript: ```javascript const res = await fetch("https://app.lero.studio/api/v1/agents", { headers: { "Authorization": `Bearer ${process.env.LERO_API_KEY}` }, }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status} ${data.erro}`); console.log(data); ``` Resposta (200): ```json { "ligado": true, "recomendado": { "versao": "v0.4", "llm": "oss120b", "voz": "rio" }, "versoes": [ { "id": "v0.4", "nome": "Agente v0.4", "lingua": "pt", "vozes": true } ], "llms": [ { "id": "oss120b", "nome": "..." } ], "vozes": [ "rio", "sp", "niteroi" ] } ``` ### 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](#agente-guia). Autenticação: chave. Corpo do pedido (JSON): - `type` (string, obrigatório): Sempre `offer`. - `sdp` (string, obrigatório): A SDP offer inteira, com o ICE completo (non-trickle), até 20 mil caracteres. - `versao` (string, opcional): A versão do agente, um id do `GET /v1/agents`. Recomendado: `v0.4`. - `llm` (string, opcional): O LLM, um id do `GET /v1/agents`. Recomendado: `oss120b`. - `voz` (string, opcional): `rio`, `sp` ou `niteroi`. Ignorada na versão de voz única (`vozes: false`). - `instrucoes` (string, opcional): 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. - `conhecimento` (string | array, opcional): Knowledge base (RAG): texto ou `[{titulo, texto}]`, até 50 itens e 60 mil caracteres. O agente consulta e não lê em voz alta. - `abertura` (string, opcional): First message: como o agente abre a ligação. É uma instrução, não a frase exata. - `palavras` (string[], opcional): STT keywords: até 40 nomes e termos para reconhecer. - `silencio_s` (false | number[], opcional): Silence prompts. Desligado com `instrucoes`; `[20, 40, 60]` liga (2 toques e a despedida). - `pesquisa` (boolean, opcional): Web search tool. Desligada com `instrucoes`. - `max_tokens` (integer, opcional): Tamanho máximo de cada resposta, de 60 a 1000 (padrão 260). Responses: - `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` cURL: ```bash curl -X POST "https://app.lero.studio/api/v1/agents/conversation" \ -H "Authorization: Bearer $LERO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "offer", "sdp": "v=0\r\no=- ...", "versao": "v0.4", "llm": "oss120b", "voz": "rio", "instrucoes": "Você é a recepção da Clínica Exemplo. Marca consultas e tira dúvidas de preço.", "conhecimento": [{ "titulo": "Preços", "texto": "Consulta: R$ 250..." }] }' ``` JavaScript: ```javascript // browser: mic and speaker. In production, send the offer to YOUR backend, which calls Lero with the key. const pc = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }] }); const chat = pc.createDataChannel("chat"); // 1. data channel BEFORE the offer chat.onmessage = (e) => console.log(JSON.parse(e.data)); // events: estado, fala, latencia const mic = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true } }); mic.getTracks().forEach((t) => pc.addTrack(t, mic)); // 2. audio up const audio = new Audio(); audio.autoplay = true; pc.ontrack = (e) => { audio.srcObject = e.streams[0]; }; // agent voice down await pc.setLocalDescription(await pc.createOffer()); // 3. offer with complete ICE await new Promise((ok) => { if (pc.iceGatheringState === "complete") return ok(); pc.onicegatheringstatechange = () => pc.iceGatheringState === "complete" && ok(); setTimeout(ok, 2500); }); const res = await fetch("https://app.lero.studio/api/v1/agents/conversation", { // 4. signaling: offer → answer method: "POST", headers: { "Authorization": `Bearer ${LERO_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ type: "offer", sdp: pc.localDescription.sdp, versao: "v0.4", llm: "oss120b", voz: "rio" }), }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status} ${data.erro}`); await pc.setRemoteDescription(data); // done: the call starts // hang up: pc.close(); mic.getTracks().forEach((t) => t.stop()); ``` Python: ```python # headless (QA bots, tests, AI agents): pip install aiortc requests import asyncio, json, os import requests from aiortc import RTCPeerConnection, RTCSessionDescription from aiortc.contrib.media import MediaPlayer, MediaRecorder async def main(): pc = RTCPeerConnection() chat = pc.createDataChannel("chat") chat.on("message", lambda m: print(json.loads(m))) # transcript and state pc.addTrack(MediaPlayer("pergunta.wav").audio) # what the "person" says rec = MediaRecorder("resposta.wav") # what the agent says pc.on("track", lambda t: rec.addTrack(t)) await pc.setLocalDescription(await pc.createOffer()) # returns with complete ICE r = requests.post( "https://app.lero.studio/api/v1/agents/conversation", headers={"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"}, json={"type": "offer", "sdp": pc.localDescription.sdp, "versao": "v0.4", "llm": "oss120b"}, timeout=20, ) r.raise_for_status() await pc.setRemoteDescription(RTCSessionDescription(**r.json())) await rec.start() await asyncio.sleep(30) await rec.stop() await pc.close() asyncio.run(main()) ``` Resposta (200): ```json { "type": "answer", "sdp": "v=0\r\no=- ..." } ``` ## Conta Os créditos da conta. ### Ver saldo `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-Restantes` (integer): O saldo da conta. Responses: - `200`: O saldo e o plano. - `401`: `sem_chave` - `401`: `chave_invalida` - `401`: `chave_vencida` - `403`: `plano` - `403`: `bloqueado` cURL: ```bash curl "https://app.lero.studio/api/v1/balance" \ -H "Authorization: Bearer $LERO_API_KEY" ``` Python: ```python import os import requests r = requests.get( "https://app.lero.studio/api/v1/balance", headers={"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"}, timeout=30, ) r.raise_for_status() print(r.json()) ``` JavaScript: ```javascript const res = await fetch("https://app.lero.studio/api/v1/balance", { headers: { "Authorization": `Bearer ${process.env.LERO_API_KEY}` }, }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status} ${data.erro}`); console.log(data); ``` Resposta (200): ```json { "saldo": 1499955, "plano": "Dev" } ``` ## 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. 1. Em [app.lero.studio/desenvolvedores](https://app.lero.studio/desenvolvedores?aba=webhooks), 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. | 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=,v1=`. O `v1` é o HMAC-SHA256 de `"."` 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." })); ``` | 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`: JavaScript: ```javascript 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")); ``` Python: ```python 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.studio. Mande o `X-Pedido-Id` do pedido que deu errado. # Build with AI Duas formas de usar o Lero com IA. O MCP coloca a voz do Lero dentro do Claude, do Codex e do Cursor. O prompt faz o seu agente de código integrar a API no seu produto. ## MCP O MCP coloca o Lero dentro do seu agente. Ele ganha 3 ferramentas e passa a gerar áudio direto da conversa, sem você escrever código. Rode o comando do seu agente, com a sua chave no lugar de `lero_sua_chave`: Claude Code: ```bash claude mcp add --env LERO_API_KEY=lero_sua_chave lero -- npx -y lero-mcp ``` Codex: ```bash codex mcp add lero --env LERO_API_KEY=lero_sua_chave -- npx -y lero-mcp ``` Cursor: ```json { "mcpServers": { "lero": { "command": "npx", "args": [ "-y", "lero-mcp" ], "env": { "LERO_API_KEY": "lero_sua_chave" } } } } ``` Claude Desktop: ```json { "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. Depois é só pedir, por exemplo: *gera um áudio na voz de São Paulo dizendo "seu pedido saiu para entrega"*. | ferramenta | o que faz | custo | |---|---|---| | `lero_fala` | Transforma o texto em fala e salva o MP3. Até 500 caracteres por chamada. | 1 crédito por caractere | | `lero_vozes` | Lista as vozes. | grátis | | `lero_saldo` | Mostra os créditos da conta e o plano. | grátis | Os MP3 ficam em `~/Downloads/Lero`. Para mudar a pasta, adicione `LERO_PASTA` na config, do mesmo jeito que a chave. A chave fica só na config do seu agente, no seu computador, e vai direto para a API do Lero. ## O prompt Quer o Lero no código do seu produto? Cole este prompt no seu agente de código (Claude Code, Codex, Cursor ou outro que leia a web). Ele lê a documentação e escreve a integração. O mesmo texto está em [prompt.md](https://lero.studio/docs/prompt.md). ```markdown # Integrar a API do Lero Você vai integrar a API de voz do Lero no meu projeto. Siga estas instruções. ## O que é o Lero O Lero transforma texto em fala em português do Brasil, com sotaque. Você manda o texto e a voz e recebe um MP3. Vozes: `rio` (Rio de Janeiro), `sp` (São Paulo), `niteroi` (Niterói). Model: `Lero v1`. O Lero também transcreve áudio em português do Brasil (Speech-to-Text): você manda o arquivo ou um link e recebe o texto, as frases com o tempo de cada uma ou a legenda pronta (SRT ou VTT). ## Leia antes de escrever código 1. https://lero.studio/docs/llms-full.txt: a referência completa em Markdown. 2. https://lero.studio/docs/openapi.json: o contrato OpenAPI 3.1 das rotas. Siga o contrato. Não invente rota, campo nem código de erro. ## Conexão - Base URL: https://app.lero.studio/api/v1 - Toda request leva o header `Authorization: Bearer `. - Leia a chave da variável de ambiente `LERO_API_KEY`. Nunca escreva a chave no código. - Chame a API só do servidor. Nunca do navegador nem de um app de celular: quem tiver a chave gasta os créditos. ## Rotas 1. `POST /v1/tts` com `{"texto": "...", "voz": "rio"}`: devolve o MP3 (`audio/mpeg`). Com `Accept: application/json` (sem `*/*`), devolve JSON com o áudio em base64 no campo `audio`. 2. `POST /v1/stt` com o arquivo no campo `audio` em `multipart/form-data` (até 4 MB), ou JSON com `url` (link https público, até 500 MB): devolve JSON com `texto` e `segmentos` (as frases com `inicio` e `fim` em segundos). Com `formato` `texto`, `srt` ou `vtt`, devolve o arquivo direto. Opcionais: `timestamps` (`palavra`), `palavras_chave`, `formatar`. Só se o pedido envolver transcrição, ditado ou legenda. 3. `GET /v1/voices`: a lista das vozes. Grátis. 4. `GET /v1/balance`: os créditos da conta e o plano. Grátis. 5. Conversational Agent (voz ao vivo por WebRTC): `GET /v1/agents` e `POST /v1/agents/conversation`. Só se o pedido for um agente de voz ao vivo; siga a seção "Conversational Agent: passo a passo" do llms-full.txt. ## Regras - Fala: 1 crédito por caractere do texto, cobrado antes de gerar e devolvido se a geração falhar. - Fala: até 500 caracteres por pedido. Para um texto maior, divida em frases (nunca no meio de uma palavra) e junte os MP3 na ordem. - Transcrição: 150 créditos por minuto de áudio, cobrados só quando dá certo. Até 2 h por pedido. Arquivo maior que 4 MB vai por `url`. - Até 60 pedidos por minuto por conta. Se vier `429` ou `503`, espere os segundos do header `Retry-After` e tente de novo. - A fala leva alguns segundos e a transcrição, alguns minutos (1 h de áudio em uns 2 min): use timeout de 300 s. - Trate os erros pelo campo `erro` (código fixo), nunca pelo texto da `mensagem`. - Guarde o MP3 e a transcrição se for usar de novo: cada pedido gasta créditos, e o Lero não guarda o áudio nem o texto. - Guarde o `X-Pedido-Id` nos logs, para o suporte. ## Entrega - Um cliente da API com timeout, espera no `Retry-After` e tratamento de erro pelo `erro`. - Testes com respostas simuladas a partir dos exemplos do `openapi.json`. - Um resumo curto do que foi feito e de como rodar. ``` ## Conversational Agent Para o seu agente de código integrar o voice agent ao vivo (por exemplo, um entrevistador técnico que vê o code editor, ou um atendente com a sua base), cole este prompt com o seu objetivo: ```text Integre o Lero Conversational Agent no meu projeto. Leia https://lero.studio/docs/llms-full.txt (seções Conversational Agent e "Conversational Agent: passo a passo") e https://lero.studio/docs/openapi.json (/agents e /agents/conversation). Regras: o client WebRTC cria o data channel "chat" antes da offer; o signaling passa pelo meu backend, que guarda LERO_API_KEY; versao, llm e voz vêm do GET /v1/agents; o meu system prompt vai em "instrucoes" e a minha base em "conhecimento"; o que o agente precisa ver durante a ligação vai por "lero-contexto". Objetivo: . ``` O contrato tem o request inteiro (`instrucoes`, `conhecimento`, `abertura`, `palavras`, `silencio_s`, `pesquisa`, `max_tokens`), os events e os erros. Para testes automáticos sem browser, o agente pode usar o client headless em Python da [rota de conversa](https://lero.studio/docs#conversa). ## Docs para agentes O agente pode aprender a API sozinho, lendo estes arquivos. Nada precisa ser configurado. - [llms.txt](https://lero.studio/docs/llms.txt): Índice curto da documentação, com um link por seção e por rota. - [llms-full.txt](https://lero.studio/docs/llms-full.txt): A documentação inteira em um arquivo Markdown. É o melhor contexto para o agente. - [openapi.json](https://lero.studio/docs/openapi.json): Contrato OpenAPI 3.1 com as rotas. Serve para gerar cliente e validar exemplos. - [skill.md](https://lero.studio/docs/skill.md): Agent Skill `lero-api` para agentes de código, com as rotas e as regras. ## Instalar a skill A skill `lero-api` ensina as rotas e as regras da API ao seu agente de código. No Claude Code, instale com um comando: ```bash mkdir -p ~/.claude/skills/lero-api && curl -fsSL https://lero.studio/docs/skill.md -o ~/.claude/skills/lero-api/SKILL.md ``` Depois é só pedir, por exemplo: *integre a voz do Lero no meu backend*. A skill entra sozinha quando o pedido combina com ela. Em outros agentes, aponte para o [llms-full.txt](https://lero.studio/docs/llms-full.txt). ## Chave Crie a chave em [app.lero.studio/desenvolvedores](https://app.lero.studio/desenvolvedores?aba=chaves), nos planos Creator e Dev. Ela aparece inteira uma vez só. - Guarde a chave numa variável de ambiente (`LERO_API_KEY`) ou na config do MCP. - Nunca cole a chave no prompt, no chat ou no código. - Para o agente, crie uma chave só dele, com teto de créditos por mês e validade curta. Se vazar, revogue no app.