# Transcribe audio

From the Lero API docs: https://lero.studio/docs/en#transcricao. Everything in one file: https://lero.studio/docs/en/llms-full.txt.

`POST /v1/stt`

Turns audio into text, in spoken Brazilian Portuguese: slang, acronyms, numbers and names come out right. Returns the text with paragraphs and the sentences with start and end times; on request, the time of each word or ready subtitles (SRT or VTT).

Send the file in the `audio` field as `multipart/form-data` (up to 4 MB), or a public https link in `url` (up to 500 MB; a signed S3, GCS or R2 link works). Files over 4 MB go by `url`, in a JSON body with the same options. Formats: MP3, WAV, M4A, OGG, OPUS, WEBM, FLAC and MP4 video (Lero uses the video's audio).

Costs 150 credits per minute of audio (2.5 per second), billed by the second and rounded up, only when the transcription succeeds: if it fails, nothing is charged. The whole file duration counts, silence included. Example: 11 min of audio cost 1,650 credits.

Up to 2 h of audio per request. Transcription runs at about 30 times real time (1 h of audio in about 2 min): use a 300 s timeout. Lero does not keep the audio or the transcript: it deletes both when it responds.

| `formato` | response |
|---|---|
| `json` (default) | JSON with the text, the sentences and the cost (on the side) |
| `texto` | `text/plain`: just the text, with paragraphs |
| `srt` | `application/x-subrip`: SRT subtitles |
| `vtt` | `text/vtt`: WebVTT subtitles |

In the JSON, `texto` has paragraphs (`\n\n`) at long pauses and `segmentos` has the sentences, with `inicio` and `fim` in seconds. With `timestamps=palavra`, each sentence also carries `palavras`: `[{inicio, fim, palavra}]`. `formatado` is `true` when the punctuation and capitalization pass covered the whole text. The `texto`, `srt` and `vtt` formats come in UTF-8.

Authentication: key with the Fala scope (the same as speech).

Request body (multipart/form-data or application/json):

- `audio` (file, one of two): The audio file, up to 4 MB: MP3, WAV, M4A, OGG, OPUS, WEBM, FLAC or MP4 video. Send `audio` or `url`, never both.
- `url` (string, one of two): Public https link to the audio, up to 500 MB; Lero downloads it. For files over 4 MB, use `url` (a signed S3, GCS, R2 etc. link works).
- `formato` (string, optional): `json` (default), `texto` (text/plain), `srt` or `vtt` (ready subtitles).
- `timestamps` (string, optional): `frase` (sentence, default) or `palavra` (word): each segment gets `palavras`, with the start and end of each word. Only with `json`.
- `palavras_chave` (string[], optional): Keyterms: up to 50 names, brands, acronyms and terms to recognize and keep spelled as given, e.g. `["Lero", "Unflat", "CNPJ"]`. In multipart, as a comma-separated list: `Lero,Unflat,CNPJ`.
- `formatar` (boolean, optional): The punctuation and capitalization pass, default `true`. With `false`, it returns faster, with the recognizer's punctuation; same price.
- `idioma` (string, optional): `pt-BR`, the only one for now.
- `model` (string, optional): `Lero v1`, the only transcription model for now.

Response headers:

- `X-Pedido-Id` (string): The request id. Send it if you need support.
- `X-Creditos-Custo` (integer): The credits charged for this request.
- `X-Creditos-Restantes` (integer): The account balance after the request.
- `X-Duracao` (number): The audio duration, in seconds.

Responses:

- `200`: The transcription: the JSON on the side (`formato=json`) or the file itself (`texto`, `srt`, `vtt`), with the headers above.
- `400`: `audio_invalido`
- `400`: `url_invalida`
- `400`: `formato_invalido`
- `400`: `idioma_invalido`
- `400`: `modelo_invalido`
- `400`: `pedido_invalido`
- `401`: `sem_chave`
- `401`: `chave_invalida`
- `401`: `chave_vencida`
- `402`: `sem_credito`
- `402`: `teto_da_chave`
- `403`: `plano`
- `403`: `escopo`
- `403`: `bloqueado`
- `413`: `audio_grande`
- `413`: `audio_longo`
- `429`: `muitos_pedidos`
- `429`: `simultaneas`
- `429`: `ocupado`
- `503`: `motor_desligado`
- `502`: `falhou`
- `504`: `demorou`
- `500`: `interno`

cURL:

```bash
# local file (up to 4 MB)
curl -X POST "https://app.lero.studio/api/v1/stt" \
  -H "Authorization: Bearer $LERO_API_KEY" \
  -F "audio=@meeting.mp3" \
  -F "palavras_chave=Lero,Unflat,CNPJ" \
  --max-time 300

# public https link (up to 500 MB) and ready subtitles
curl -X POST "https://app.lero.studio/api/v1/stt" \
  -H "Authorization: Bearer $LERO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/meeting.mp3", "formato": "srt"}' \
  --max-time 300 \
  --output subtitles.srt
```

Python:

```python
import os

import requests

H = {"Authorization": f"Bearer {os.environ['LERO_API_KEY']}"}

# local file (up to 4 MB)
with open("meeting.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"])

# public https link (up to 500 MB) and ready subtitles
r = requests.post(
    "https://app.lero.studio/api/v1/stt",
    headers=H,
    json={"url": "https://example.com/meeting.mp3", "formato": "srt"},
    timeout=300,
)
r.raise_for_status()
with open("subtitles.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}` };

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

Response · 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."
    }
  ]
}
```
