# Transcrição ao vivo

> Envie o áudio do microfone ou de uma aba ao transcript1 por WebSocket e receba linhas de legenda, com tradução opcional em tempo real, enquanto as pessoas falam.

Canonical: https://transcript1.com/pt/docs/live
Language: pt-BR
Last updated: 2026-11-03

A transcrição ao vivo transforma um fluxo de áudio em linhas de legenda enquanto as pessoas ainda estão falando. Seu cliente envia áudio PCM bruto por um WebSocket, e o transcript1 devolve linhas que começam como um palpite parcial e depois se tornam finais. Adicione `translate_to` e cada linha também traz a tradução. Quando a sessão termina, o resultado é salvo como uma transcrição comum.

Não existem planos separados para o modo ao vivo: ele usa o mesmo saldo de minutos de todo o resto. Veja em [Cobrança](https://transcript1.com/pt/docs/live#billing) como o tempo conectado é contado.

## Quando usar o modo ao vivo

| Use o modo ao vivo quando | Use [uma transcrição comum](https://transcript1.com/pt/docs/getting-started) quando |
|---|---|
| O áudio ainda está sendo produzido: uma chamada, uma palestra, uma transmissão, um vídeo que você está assistindo. | Você tem um link ou um arquivo pronto. |
| Você precisa de legendas em um ou dois segundos após a fala. | Você quer a melhor precisão e pode esperar o resultado. |
| Você quer tradução em tempo real ao lado de cada linha. | Você quer traduzir uma transcrição pronta, ou para vários idiomas. |

O modo ao vivo recebe só áudio, como PCM bruto. Ele não aceita links de vídeo, uploads, WebM, Opus nem MP3. Capture o áudio no seu cliente e converta para PCM antes. [Capturar áudio no navegador](https://transcript1.com/pt/docs/live#capture-audio-in-a-browser) mostra como.

## Início rápido com o SDK

Instale o SDK 1.5.0 ou mais recente: `npm i @transcript1/sdk@latest`. `t1.live.start` cria a sessão, conecta e é resolvido quando a sessão começa, então você já pode enviar áudio.

```javascript
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';
import { Transcript1 } from '@transcript1/sdk';

const t1 = new Transcript1(); // reads TRANSCRIPT1_API_KEY

const live = await t1.live.start({ language: 'en', translateTo: 'es', sampleRate: 16000 });

live.on('line', line => {
  const mark = line.final ? 'final  ' : 'partial';
  console.log(mark, line.text, '→', line.translation?.text ?? '');
});
live.on('warning', warning => console.warn(warning.code));

// speech.pcm: 16 kHz, mono, signed 16-bit little-endian, no header
for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
  live.send(chunk);
  await sleep(100); // 3,200 bytes are 100 ms of audio: send at real-time speed
}

const result = await live.end();
console.log(result.reason, result.transcriptionId, result.minutesCharged);
```

As linhas são atualizadas no mesmo lugar. Substitua uma linha que você já tem quando chegar outra com o mesmo `id`, ou leia todas em `live.lines`. `end()` encerra a sessão, espera as últimas linhas e salva a transcrição. Veja todos os métodos e eventos em [SDK para Node.js](https://transcript1.com/pt/docs/sdk#live-transcription).

## Início rápido com um WebSocket puro

Sem o SDK são dois passos: criar uma sessão por HTTPS e abrir o WebSocket com o token de uso único que voltou.

### Criar uma sessão

```bash
curl -X POST https://api.transcript1.com/v1/live/sessions \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"language": "en", "translate_to": "es", "sample_rate": 16000}'
```

A API responde `201`:

```json
{
  "id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "status": "waiting",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "websocket_url": "wss://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD/stream",
  "token": "Zk3x...",
  "token_expires_at": "2026-11-02T12:01:00Z",
  "rate": 1
}
```

`transcription_id` é `null` quando `save` é `false`. O token é de uso único e expira em 60 segundos, então conecte-se logo. `rate` é o número de minutos de saldo cobrados por minuto iniciado desta sessão; veja [Cobrança](https://transcript1.com/pt/docs/live#billing).

O corpo da requisição aceita estes campos. Todos são opcionais, e o escopo necessário é `transcriptions:write`:

| Campo | Padrão | Regra |
|---|---|---|
| `language` | `auto` | `auto`, ou um código de duas letras que a transcrição ao vivo aceite. |
| `translate_to` | nenhum | Um código de duas letras. Um único idioma de destino por sessão. Veja [Traduzir ao vivo](https://transcript1.com/pt/docs/live#translate-live). |
| `diarize` | `false` | Identifica os falantes. Algumas configurações não conseguem, por exemplo junto com `language: "auto"`, e respondem `422`. Confira as [capacidades](https://transcript1.com/pt/docs/live#capabilities). |
| `audio_format` | `pcm_s16le` | O único valor. O campo existe para que outros formatos possam ser adicionados depois. |
| `sample_rate` | `16000` | Um inteiro de 8000 a 48000. Somente mono. |
| `save` | `true` | Com `false`, nada é mantido depois que a sessão termina. Veja [Transcrições salvas](https://transcript1.com/pt/docs/live#saved-transcripts). |

### Conectar e transmitir

Abra o WebSocket com o token como segundo subprotocolo. Isso funciona em navegadores e no Node 22+ sem cabeçalhos personalizados. Espere `session.started` antes de enviar áudio: os quadros binários enviados antes são descartados e você recebe um aviso `audio_dropped`.

```javascript
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';

const session = await fetch('https://api.transcript1.com/v1/live/sessions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TRANSCRIPT1_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ language: 'en', translate_to: 'es', sample_rate: 16000 }),
}).then(response => response.json());

const ws = new WebSocket(session.websocket_url, ['t1-live', session.token]);
const lines = new Map();

ws.addEventListener('message', async message => {
  const event = JSON.parse(message.data);

  if (event.type === 'session.started') {
    for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
      ws.send(chunk);
      await sleep(100);
    }
    ws.send(JSON.stringify({ type: 'end' }));
  } else if (event.type === 'line') {
    lines.set(event.line.id, event.line); // the full current state: replace by id
    console.log(event.line.final, event.line.text, event.line.translation?.text);
  } else if (event.type === 'session.ended') {
    console.log('ended:', event.reason, event.minutes_charged);
  }
});

ws.addEventListener('close', event => console.log('closed', event.code));
```

No Node 20 não há `WebSocket` nativo: use o pacote `ws`, que tem a mesma forma `new WebSocket(url, protocols)`.

Para um token válido, o servidor responde com o subprotocolo `t1-live` e mais nada. Um token inválido nunca faz o handshake falhar: o socket abre, você recebe um evento `error` e o socket fecha com um código 4xxx, para que o navegador mostre o motivo real em vez de um 1006 genérico. Veja [Códigos de fechamento](https://transcript1.com/pt/docs/live#close-codes).

## Capturar áudio no navegador

O áudio ao vivo precisa ser PCM mono de 16 bits com sinal, little-endian, na `sample_rate` da sessão. Os navegadores entregam amostras em ponto flutuante, então converta-as em um `AudioWorklet`. Este trecho captura o microfone como PCM de 16 kHz em blocos de 100 ms e o envia com o ponto de entrada do SDK para navegador. `getAccessToken` devolve um token de acesso OAuth; nunca coloque uma chave de API em um navegador. Veja [Segurança](https://transcript1.com/pt/docs/live#security).

```javascript
import { createLiveClient } from '@transcript1/sdk/live';

const workletSource = `
class Pcm16Processor extends AudioWorkletProcessor {
  constructor() {
    super();
    this.buffer = new Int16Array(1600); // 100 ms at 16 kHz
    this.filled = 0;
  }

  process(inputs) {
    const channel = inputs[0][0];
    if (!channel) return true;
    for (let i = 0; i < channel.length; i++) {
      const sample = Math.max(-1, Math.min(1, channel[i]));
      this.buffer[this.filled++] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
      if (this.filled === this.buffer.length) {
        this.port.postMessage(this.buffer.slice());
        this.filled = 0;
      }
    }
    return true;
  }
}
registerProcessor('pcm16', Pcm16Processor);
`;

const client = createLiveClient({ getAccessToken: () => myApp.getOAuthAccessToken() });

const stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 } });
const context = new AudioContext({ sampleRate: 16000 });
const moduleUrl = URL.createObjectURL(new Blob([workletSource], { type: 'text/javascript' }));
await context.audioWorklet.addModule(moduleUrl);

// the context's real rate: a browser that cannot do 16 kHz picks its own
const live = await client.start({ language: 'auto', sampleRate: context.sampleRate });
live.on('line', renderLine);

const microphone = context.createMediaStreamSource(stream);
const capture = new AudioWorkletNode(context, 'pcm16');
capture.port.onmessage = event => live.send(event.data); // an Int16Array
const mute = context.createGain();
mute.gain.value = 0;
microphone.connect(capture).connect(mute).connect(context.destination);

async function stop() {
  stream.getTracks().forEach(track => track.stop());
  await context.close();
  return live.end();
}
```

O worklet foi escrito para 16 kHz. Se o contexto rodar em outra taxa, o bloco deixa de ter 100 ms, mas o PCM continua correto porque `sampleRate` coincide com o contexto. Para capturar o som de uma aba ou de um vídeo em vez do microfone, crie o `MediaStream` com `getDisplayMedia` ou a partir do `captureStream()` de um elemento de mídia e conecte-o da mesma forma.

## Traduzir ao vivo

Adicione `translate_to` à sessão e o transcript1 traduz cada linha conforme ela é dita.

- Um único idioma de destino por sessão. Depois, adicione mais idiomas à transcrição salva com o [endpoint de traduções](https://transcript1.com/pt/docs/live#saved-transcripts).
- Cada `line` tem um original (`text`, `final`) e um objeto `translation` (`language`, `text`, `final`). `translation` é `null` quando `translate_to` não está definido.
- O original e a tradução se tornam finais de forma independente: uma linha pode ter o original final enquanto a tradução ainda muda. Mostre uma linha como final quando os dois forem.
- Uma linha que já está no idioma de destino usa o próprio texto como tradução e é final quando a linha fecha.
- Uma linha fecha quando o falante faz uma pausa, quando o falante muda, quando uma pausa entre palavras passa de 0.9 segundo, quando a linha chega a duas fileiras de legenda (84 caracteres) ou a 15 segundos, ou quando você envia `finalize`. Com a tradução ativada, uma linha pode demorar cerca de 3 segundos a mais para fechar enquanto a tradução chega. Nesse caso a tradução pode ficar aberta (`final: false`) e continuar crescendo depois que o original já é final.
- A tradução é gratuita. Ela não usa minutos.

Quais idiomas de destino e pares de idiomas estão disponíveis depende do provedor de voz ao vivo. Leia as [capacidades](https://transcript1.com/pt/docs/live#capabilities) em vez de fixar regras no código. Um destino não aceito retorna `422 unsupported_translation`. Por exemplo, um provedor pode exigir um `language` explícito em vez de `auto`, ou permitir apenas pares que incluam inglês.

## Protocolo WebSocket

### Conexão

```javascript
new WebSocket(websocket_url, ['t1-live', token]);
```

O token viaja como subprotocolo do WebSocket. Ele nunca aparece em uma URL nem em um log de acesso. Não envie o cabeçalho `Authorization`: o token é a credencial. O servidor envia um ping a cada 30 segundos; navegadores e o pacote `ws` respondem automaticamente. Um socket que não envia pong por 60 segundos é desconectado.

Uma conexão válida a uma sessão que já tem um socket substitui o anterior. Veja [Retomada e substituição](https://transcript1.com/pt/docs/live#resume-and-takeover).

### Mensagens do cliente

| Mensagem | Significado |
|---|---|
| Quadro binário | Áudio: PCM s16le mono na `sample_rate` da sessão. Cada quadro tem tamanho par e no máximo 256 KB. Cerca de 100 ms por quadro é um bom tamanho. |
| `{"type":"finalize"}` | Fecha a linha atual agora, por exemplo quando o vídeo é pausado. |
| `{"type":"end"}` | Encerra a sessão e a salva. O servidor responde `session.ended` e fecha o socket. |
| Qualquer outra coisa | Um evento `error` não fatal com o código `invalid_message`. |

Um quadro de tamanho ímpar ou grande demais é uma violação do protocolo e fecha o socket com `4008`.

Comece a enviar áudio depois de `session.started`. Um cliente que envia áudio mais rápido que o tempo real paga pelo tempo do áudio em vez do tempo conectado (veja [Cobrança](https://transcript1.com/pt/docs/live#billing)), então envie na velocidade em que o áudio é produzido.

### Eventos do servidor

Cada evento é um quadro de texto JSON com um `type`.

| Evento | Enviado quando |
|---|---|
| `session.started` | A conexão foi aceita. Envie áudio a partir de agora. |
| `line` | Uma linha de legenda foi criada ou alterada. |
| `usage` | Depois de cada cobrança. |
| `warning` | Algo pede atenção, mas a sessão continua. |
| `error` | Um problema não fatal, ou o motivo de um fechamento imediato. |
| `reconnect` | O servidor está prestes a reiniciar. Retome a sessão. |
| `session.ended` | A sessão terminou e foi salva. É o último evento antes de um fechamento normal. |

`session.started` informa se é uma retomada e quanto áudio a sessão já processou:

```json
{
  "type": "session.started",
  "session_id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "resumed": false,
  "offset_ms": 0
}
```

`line` traz o estado completo e atual de uma linha, então substitua a linha que você tem com o mesmo `id`. Veja [O objeto de linha](https://transcript1.com/pt/docs/live#the-line-object).

```json
{
  "type": "line",
  "line": {
    "id": 12,
    "start_ms": 53120,
    "end_ms": 56040,
    "speaker": null,
    "language": "en",
    "text": "Welcome back to the stream",
    "final": true,
    "translation": { "language": "es", "text": "Bienvenidos de nuevo", "final": false }
  }
}
```

`usage` é enviado depois de cada cobrança, com o tempo conectado, o tempo de áudio, os minutos de saldo cobrados até agora e os minutos que restam:

```json
{
  "type": "usage",
  "connected_ms": 61240,
  "audio_ms": 60100,
  "minutes_charged": 2,
  "minutes_remaining": 898
}
```

`warning` tem um `code`:

| `code` | Significado |
|---|---|
| `balance_low` | A próxima cobrança falharia. A sessão termina com `balance_exhausted` quando ela vencer. Não é enviado enquanto a [recarga automática](https://transcript1.com/pt/docs/billing-and-limits#auto-top-up) está ligada. |
| `audio_dropped` | Chegou áudio antes de a sessão estar pronta, ou áudio demais enquanto o provedor de voz se conectava. O áudio mais antigo foi descartado. |
| `idle_timeout_soon` | Sem áudio há 4 minutos. A sessão termina após 5 minutos sem áudio. Envie áudio para mantê-la. |

```json
{ "type": "warning", "code": "idle_timeout_soon" }
```

`error` tem um `code` e uma `message`. Estes códigos podem chegar em um socket ao vivo:

| `code` | Fatal | Significado |
|---|---|---|
| `invalid_message` | Não | Um quadro de texto que não é `finalize` nem `end`. |
| `invalid_token` | Sim | O token é inválido, expirou ou já foi usado. O socket fecha com `4001`. |
| `not_found` | Sim | A sessão não existe. O socket fecha com `4004`. |
| `session_ended` | Sim | A sessão já terminou. O socket fecha com `4004`. |

```json
{ "type": "error", "code": "invalid_message", "message": "Expected {\"type\":\"finalize\"} or {\"type\":\"end\"}" }
```

`reconnect` chega antes de o servidor fechar o socket com `1012`, por exemplo durante um deploy. Busque um token novo e retome.

```json
{ "type": "reconnect", "reason": "server_restart" }
```

`session.ended` é o último evento de toda sessão. Os números são os totais finais:

```json
{
  "type": "session.ended",
  "reason": "client_ended",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "minutes_charged": 3,
  "connected_ms": 142500,
  "audio_ms": 141900
}
```

### O objeto de linha

| Campo | Significado |
|---|---|
| `id` | Um número que começa em 0 e cresce de um em um a cada linha. É o índice do segmento na transcrição salva. |
| `start_ms`, `end_ms` | Início e fim no tempo de áudio da sessão. Esse tempo continua após as reconexões. |
| `speaker` | O rótulo do falante quando `diarize` está ativado; caso contrário, `null`. Os rótulos são os mesmos das transcrições comuns. |
| `language` | O idioma detectado ou informado da linha, ou `null` enquanto ainda não é conhecido. |
| `text` | O texto original até agora. |
| `final` | `true` quando o texto original não vai mais mudar. |
| `translation` | `null` sem `translate_to`; caso contrário, `language`, `text` e `final`. |

Mostre as linhas com `final: false` em um estilo mais claro: o texto delas ainda pode mudar. O texto de uma linha com `final: true` nunca mais muda, e uma linha final nunca é removida.

O SDK usa `camelCase` nos campos da linha: `startMs`, `endMs`.

### Motivos de encerramento

`session.ended` e `GET /v1/live/sessions/:id` trazem um `end_reason`:

| Motivo | Significado |
|---|---|
| `client_ended` | Você enviou `end`. |
| `balance_exhausted` | A cobrança seguinte falhou. A sessão termina na virada de um minuto e é salva. |
| `idle_timeout` | Sem áudio por 5 minutos. |
| `max_duration` | A sessão ficou conectada por 300 minutos. Inicie outra. |
| `disconnected` | O cliente ficou ausente por mais de 2 minutos sem retomar. |
| `upstream_error` | O provedor de voz falhou. |
| `internal_error` | Tivemos um erro interno. |
| `not_connected` | A sessão nunca se conectou em 5 minutos. Nunca é cobrada e só aparece por `GET`. |

### Códigos de fechamento

| Código | Significado |
|---|---|
| `1000` | Fechamento normal. Sempre vem depois de `session.ended`. |
| `1011` | Erro inesperado do servidor. O servidor recupera a sessão: retome com um token novo. |
| `1012` | Reinício do servidor. Vem depois de `reconnect`: retome com um token novo. |
| `4001` | O token é inválido, expirou ou já foi usado. |
| `4004` | A sessão não foi encontrada ou já terminou. |
| `4008` | Violação do protocolo: um quadro binário de tamanho ímpar ou grande demais. |
| `4010` | Substituída por uma conexão mais nova à mesma sessão. |

Enquanto a API reinicia, uma nova requisição de WebSocket pode receber HTTP `503`. Tente de novo em instantes.

## Retomada e substituição

Uma sessão sobrevive a uma queda de rede, a um deploy da API e a uma falha. Por 2 minutos após uma queda ela espera por você; depois disso, termina como `disconnected` e é salva.

1. Chame `POST /v1/live/sessions/:id/token`. A resposta traz `token`, `token_expires_at` e `websocket_url`, os mesmos campos de quando você criou a sessão. A chamada falha com `409 session_ended` depois que a sessão termina.
2. Abra um novo WebSocket com esse token.
3. `session.started` chega com `resumed: true`. `offset_ms` é a quantidade de áudio que a sessão já processou: continue com o áudio a partir desse ponto. Os ids das linhas continuam.

As linhas que já eram finais são mantidas. O trecho em andamento no momento da queda é perdido, então reenvie alguns segundos de áudio a partir de `offset_ms` se você o guarda em buffer. Os intervalos de reconexão não são cobrados.

A substituição é a mesma chamada. Se o servidor ainda acha que um socket está aberto, uma nova conexão válida o substitui, e o socket antigo é fechado com `4010` depois que suas linhas são salvas. O limite de sessões simultâneas nunca bloqueia uma retomada.

O SDK faz tudo isso por você: numa queda ou num evento `reconnect`, ele busca um token e reconecta com um intervalo crescente de 0.5 a 8 segundos por até 2 minutos, e reenvia os últimos 10 segundos de áudio enviados durante a queda.

## Cobrança

A transcrição ao vivo usa o mesmo saldo de minutos das transcrições comuns.

- **O tempo conectado é cobrado**, desde o momento em que o socket é aceito até ele fechar. O silêncio e um vídeo pausado contam. O intervalo entre uma queda e a retomada não conta.
- **Por minuto iniciado, com mínimo de 1.** O primeiro minuto é cobrado quando a primeira conexão é aceita, e cada minuto seguinte quando ele começa.
- **Mais rápido que o tempo real é cobrado como tempo de áudio.** Se o áudio enviado for mais longo que o tempo conectado, cobra-se o tempo do áudio.
- **A taxa é lida na API.** Um minuto iniciado custa `rate` minutos de saldo, arredondados para cima. A taxa costuma ser 1 e pode ter decimais. Leia a taxa ao vivo em `rates.live` de `GET /v1/account`, em `rate` de `GET /v1/live/capabilities` e em `rate` da sessão que você cria. A taxa de uma sessão fica fixa depois de criada.
- **A tradução é gratuita.**
- **Nunca conectada, nunca cobrada.** Uma sessão que não se conecta em 5 minutos termina como `not_connected`.
- **Falhas são reembolsadas.** Se uma sessão terminar com `upstream_error` ou `internal_error` antes de produzir uma única linha, todas as cobranças são devolvidas. Uma sessão vazia por causa do silêncio é cobrada e salva vazia.
- **Quando o saldo acaba** e a [recarga automática](https://transcript1.com/pt/docs/billing-and-limits#auto-top-up) está ligada, o transcript1 cobra no cartão salvo enquanto a sessão continua transmitindo, e a sessão segue. Se a recarga automática estiver desligada ou pausada, ou se a cobrança falhar, a sessão é finalizada, salva e termina com `balance_exhausted` na virada de um minuto. O aviso `balance_low` vem antes, a menos que a recarga automática esteja ligada.

Criar uma sessão retorna `402 insufficient_minutes` quando seu saldo utilizável é menor que um minuto iniciado na taxa atual. Com a recarga automática ligada, o transcript1 tenta recarregar primeiro. Veja [Planos, minutos e limites](https://transcript1.com/pt/docs/billing-and-limits#live-transcription).

## Limites

| | Free | Monthly | Annual |
|---|---|---|---|
| Sessões ao vivo simultâneas | 1 | 2 | 3 |

- Uma sessão conta enquanto está em `waiting`, `connected` ou `disconnected` (dentro da janela de 2 minutos). Acima do limite, `POST /v1/live/sessions` retorna `429 too_many_live_sessions` com `Retry-After: 30`.
- As sessões ao vivo não contam para o limite de transcrições ativas.
- Uma sessão dura no máximo 300 minutos de tempo conectado. Depois disso, termina com `max_duration`.
- Uma sessão sem áudio por 5 minutos termina com `idle_timeout`; você recebe `idle_timeout_soon` aos 4 minutos. Uma transmissão pausada continua cobrando tempo conectado e conta como inativa, então encerre a sessão se a pausa for longa.
- O transcript1 limita todas as sessões ao vivo em conjunto. Quando todas estão em uso, `POST /v1/live/sessions` retorna `503 live_capacity`, que permite nova tentativa. Espere `Retry-After` (30 segundos) e tente de novo. Isso só é verificado ao criar uma sessão, nunca no meio de uma.
- Criar sessões e buscar tokens contam para suas chamadas por minuto.
- O áudio é somente PCM bruto: `pcm_s16le`, mono, de 8000 a 48000 Hz.

## Capacidades

`GET /v1/live/capabilities` descreve o que a transcrição ao vivo aceita no momento, para que os clientes nunca fixem regras do provedor no código. O escopo necessário é `transcriptions:read`.

```json
{
  "available": true,
  "auto_language": true,
  "languages": ["en", "es"],
  "translation": {
    "targets": ["es", "de"],
    "pairs": "any",
    "requires_language": false
  },
  "diarize": true,
  "audio_formats": ["pcm_s16le"],
  "sample_rate": { "min": 8000, "max": 48000 },
  "max_session_minutes": 300,
  "rate": 1
}
```

As listas estão abreviadas aqui. Leia-as na API.

- `translation.pairs` é `"any"` (qualquer idioma detectado para qualquer destino) ou uma lista de objetos com `from` e `to`.
- `translation.requires_language` é `true` quando a tradução exige um `language` explícito em vez de `auto`.
- Quando a transcrição ao vivo não está configurada, a resposta é `available: false` com apenas `rate`, e criar uma sessão retorna `503 live_unavailable`.

Com o SDK: `await t1.live.capabilities()`.

## Consultar uma sessão

`GET /v1/live/sessions/:id` retorna o estado de uma sessão. O escopo necessário é `transcriptions:read`.

```bash
curl https://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY"
```

```json
{
  "id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "status": "ended",
  "end_reason": "client_ended",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "options": {
    "language": "en",
    "translate_to": "es",
    "diarize": false,
    "audio_format": "pcm_s16le",
    "sample_rate": 16000,
    "save": true
  },
  "connected_ms": 142500,
  "audio_ms": 141900,
  "minutes_charged": 3,
  "rate": 1,
  "created_at": "2026-11-02T11:58:12Z",
  "ended_at": "2026-11-02T12:00:35Z"
}
```

`status` é `waiting`, `connected`, `disconnected` ou `ended`. `end_reason` é `null` até a sessão terminar. Com o SDK: `await t1.live.get(id)`.

## Erros

Os erros de requisição usam o [formato de erro](https://transcript1.com/pt/docs/errors) de sempre. Estes são os que `POST /v1/live/sessions` e seus endpoints irmãos retornam:

| Status HTTP | `code` | Pode repetir? | Quando |
|---|---|---|---|
| 422 | `invalid_request` | Não | Um campo está ausente ou inválido, por exemplo um `sample_rate` fora do intervalo de 8000 a 48000. |
| 422 | `unsupported_language` | Não | `language` não é aceito na transcrição ao vivo. |
| 422 | `unsupported_translation` | Não | O idioma de destino, ou o par de idiomas, não é aceito. |
| 402 | `insufficient_minutes` | Não | Seu saldo utilizável é menor que um minuto iniciado na taxa atual. |
| 403 | `email_not_verified` | Não | Confirme primeiro o seu e-mail. |
| 429 | `too_many_live_sessions` | Sim | Todas as sessões ao vivo simultâneas do seu plano estão em uso. Espere `Retry-After`. |
| 503 | `live_capacity` | Sim | Toda a capacidade ao vivo está em uso. Espere `Retry-After` (30 segundos). |
| 503 | `live_unavailable` | Não | A transcrição ao vivo não está disponível no momento. |
| 409 | `session_ended` | Não | `POST /v1/live/sessions/:id/token` para uma sessão que já terminou. |

Os problemas no socket chegam como eventos `error` e [códigos de fechamento](https://transcript1.com/pt/docs/live#close-codes).

## Transcrições salvas

Com `save: true` (o padrão), a sessão é salva como uma transcrição comum quando termina. O áudio nunca é armazenado.

- `transcription_id` é devolvido quando você cria a sessão. O status é `live` enquanto a sessão roda e `completed` depois que ela termina. Uma sessão que nunca se conectou deixa uma transcrição `canceled`.
- A transcrição tem `source: { "platform": "live", "session_id": "lv_…" }`. Não há link nem arquivo. O painel a lista como uma sessão ao vivo.
- Cada linha vira um segmento, com o mesmo índice do `id` da linha. Leia a transcrição em JSON, texto, SRT ou VTT como qualquer outra: veja a [referência da API](https://transcript1.com/pt/docs/api-reference).
- A tradução ao vivo é salva junto. Leia-a com o parâmetro `translation`, como descrito em [Tradução](https://transcript1.com/pt/docs/translation#read-the-translation).
- Adicione mais idiomas de tradução depois com `POST /v1/transcriptions/:id/translations`, quando a transcrição estiver `completed`. Veja [Tradução](https://transcript1.com/pt/docs/translation#translate-an-existing-transcript).

Com `save: false`, nada é mantido depois que a sessão termina: não há transcrição e `transcription_id` é `null`. Você ainda recebe todas as linhas pelo socket, e a cobrança de sempre se aplica.

## Segurança

- O token que você recebe ao criar uma sessão é de uso único e expira em 60 segundos. Ele é enviado como subprotocolo do WebSocket, não na URL, então fica fora dos logs de acesso. Um token vazado não serve de nada depois de usado ou expirado.
- Use uma chave de API apenas em um servidor que você controla. Nunca coloque uma chave de API em uma página web nem em uma extensão.
- Em um navegador ou extensão, faça o login das pessoas com OAuth e passe o token de acesso OAuth para `getAccessToken` de `createLiveClient`. O token precisa do escopo `transcriptions:write` para iniciar sessões. Os tokens de acesso OAuth expiram em 1 hora, e cada retomada pede um novo a `getAccessToken`.
- Seu servidor também pode criar a sessão com a chave de API e entregar ao navegador só `websocket_url` e `token`. O cliente então se conecta diretamente, mas não consegue retomar sem um novo token seu.
