---
name: lero-api
description: Integrates the Lero voice API (text to speech in Brazilian Portuguese, with a Rio, São Paulo or Niterói accent, and Brazilian Portuguese audio transcription) into a project. Use when the request is to generate speech, audio or MP3 with Lero, transcribe audio or video, make subtitles (SRT or VTT), add the Lero voice to a product, or call app.lero.studio/api/v1.
---

# Lero API

Complete reference: https://lero.studio/docs/en/llms-full.txt. Contract: https://lero.studio/docs/en/openapi.json.

You will integrate the Lero voice API into my project. Follow these instructions.

## What Lero is

Lero turns text into speech in Brazilian Portuguese, with a regional accent. You send the text and the voice and get back an MP3. Voices: `rio` (Rio de Janeiro), `sp` (São Paulo), `niteroi` (Niterói). Model: `Lero v1`.

Lero also transcribes Brazilian Portuguese audio (Speech-to-Text): you send the file or a link and get back the text, the sentences with their timestamps, or ready subtitles (SRT or VTT).

## Read before writing code

1. https://lero.studio/docs/en/llms-full.txt: the complete reference in Markdown.
2. https://lero.studio/docs/en/openapi.json: the OpenAPI 3.1 contract of the routes.

Follow the contract. Do not invent routes, fields or error codes.

## Connection

- Base URL: https://app.lero.studio/api/v1
- Every request carries the header `Authorization: Bearer <key>`.
- Read the key from the `LERO_API_KEY` environment variable. Never write the key in the code.
- Call the API only from the server. Never from the browser or a mobile app: whoever has the key spends the credits.

## Routes

1. `POST /v1/tts` with `{"texto": "...", "voz": "rio"}`: returns the MP3 (`audio/mpeg`). With `Accept: application/json` (without `*/*`), returns JSON with the audio as base64 in the `audio` field.
2. `POST /v1/stt` with the file in the `audio` field as `multipart/form-data` (up to 4 MB), or JSON with `url` (public https link, up to 500 MB): returns JSON with `texto` and `segmentos` (the sentences with `inicio` and `fim` in seconds). With `formato` `texto`, `srt` or `vtt`, returns the file itself. Optional: `timestamps` (`palavra`), `palavras_chave`, `formatar`. Only if the request involves transcription, dictation or subtitles.
3. `GET /v1/voices`: the list of voices. Free.
4. `GET /v1/balance`: the account credits and the plan. Free.
5. Conversational Agent (live voice over WebRTC): `GET /v1/agents` and `POST /v1/agents/conversation`. Only if the request is a live voice agent; follow the "Conversational Agent: step by step" section of llms-full.txt.

## Rules

- Speech: 1 credit per character of the text, charged before generating and refunded if generation fails.
- Speech: up to 500 characters per request. For longer text, split it into sentences (never mid-word) and join the MP3s in order.
- Transcription: 150 credits per minute of audio, charged only on success. Up to 2 h per request. Files over 4 MB go by `url`.
- Up to 60 requests per minute per account. On `429` or `503`, wait the seconds in the `Retry-After` header and try again.
- Speech takes a few seconds and transcription a few minutes (1 h of audio in about 2 min): use a 300 s timeout.
- Handle errors by the `erro` field (stable code), never by the `mensagem` text.
- Keep the MP3 and the transcript if you will use them again: each request spends credits, and Lero does not keep the audio or the text.
- Log the `X-Pedido-Id`, for support.

## Deliverables

- An API client with a timeout, waits on `Retry-After` and error handling by `erro`.
- Tests with mocked responses built from the `openapi.json` examples.
- A short summary of what was done and how to run it.

## Example

```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()));
```

## Example: transcription

```javascript
import { readFile, writeFile } from "node:fs/promises";

const headers = { "Authorization": `Bearer ${process.env.LERO_API_KEY}` };

// local file (up to 4 MB)
const form = new FormData();
form.append("audio", new Blob([await readFile("meeting.mp3")], { type: "audio/mpeg" }), "meeting.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);

// public https link (up to 500 MB) and ready subtitles
const srt = await fetch("https://app.lero.studio/api/v1/stt", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://example.com/meeting.mp3", formato: "srt" }),
  signal: AbortSignal.timeout(300_000),
});
if (!srt.ok) throw new Error(`${srt.status} ${(await srt.json()).erro}`);
await writeFile("subtitles.srt", await srt.text());
```
