Pular para o conteúdo
int8labs

Transcrição ao vivo

Áudio entra em tempo real; um rascunho sai a cada 600 ms e cada frase fechada vem como final, com início e fim. Escopo stt; R$ 0,11 por hora de áudio.

1 min de leitura · atualizado em 13/10/2026
WSwss://api.int8labs.com/v1/stt/stream

Autenticação

No servidor, Authorization: Bearer int8_live_… no handshake. No navegador, ?token=int8_tmp_… na URL (um token de POST /v1/tokens). Credencial errada recebe a mensagem de erro e o fechamento 4401.

Mensagens

VOCÊ →"start"

A primeira mensagem, em até 10 s: a taxa do áudio (8000, 16000, 24000, 44100 ou 48000), encoding sempre pcm_s16le e termos-chave opcionais.

{"type":"start","sample_rate":16000,"encoding":"pcm_s16le","keyterms":["Dra. Helena Prado"]}
← SERVIDOR"ready"

Pode mandar o áudio. O relógio da sessão começa aqui.

{"type":"ready"}
VOCÊ →áudiobinário

O som, no ritmo da fala. Pedaços de 100 a 250 ms dão a menor latência.

PCM16 LE mono, na taxa do start, até 1 s por mensagem
← SERVIDOR"partial"

Rascunho da frase em andamento, a cada 600 ms. O próximo partial ou o final do mesmo segment substitui este.

{"type":"partial","segment":3,"text":"pode confirmar a consulta de"}
← SERVIDOR"final"

A frase fechada (depois de 700 ms de silêncio ou 15 s de fala), com o início e o fim em milissegundos, contados do primeiro áudio da sessão.

{"type":"final","segment":3,"text":"pode confirmar a consulta de terça às catorze e trinta","start_ms":12480,"end_ms":15920}
VOCÊ →"stop"

Acabou o áudio. O servidor fecha as frases que faltam e manda o done.

{"type":"stop"}
← SERVIDOR"done"

Fim da sessão: áudio recebido, custo total e saldo. Depois dele a conexão fecha com 1000.

{"type":"done","audio_ms":63210,"cost_brl":0.00193142,"balance_brl":87.39806858}
← SERVIDOR"error"

Erro, com o mesmo code do REST. Os que fecham a conexão vêm seguidos do fechamento 4000 + o status HTTP.

{"type":"error","code":"insufficient_balance","message":"Saldo insuficiente. Recarregue em console › Faturamento.","request_id":"req_01JAA4M7Q2W8E5R3T6Y9U1I0PZ"}

Regras

  • Todo erro fecha a conexão (4000 + o status HTTP), depois de cobrar o áudio que já entrou.
  • O áudio não pode passar do relógio da sessão em mais de 10 s: quem manda um arquivo gravado de uma vez recebe rate_limited e o fechamento 4429. Arquivo gravado vai pelo lote.
  • 10 s sem áudio encerram a sessão com o done. A sessão dura até 4 horas.
  • O saldo e a credencial são conferidos no começo e a cada 60 s: saldo zerado fecha com 4402; chave revogada ou token vencido, com 4401. Antes do error chegam os final do áudio já recebido (e cobrado), sem done; o áudio enviado depois disso não é recebido nem cobrado.
  • Com 4 frases esperando o transcritor, o servidor para de ler até a fila andar (o TCP segura o cliente). Essa espera não conta como silêncio.

Fechamentos

FechamentoQuando
1000Fim normal: depois do done da transcrição ao vivo, por ociosidade ou ao passar de 4 h de sessão.
4400invalid_request na transcrição ao vivo ou no modo áudio do avatar. Na voz e no modo texto do avatar, o erro de uma fala chega sem fechar a conexão.
4401invalid_api_key, api_key_expired ou api_key_revoked, no começo ou no meio da sessão. Na transcrição ao vivo, os final do áudio já recebido chegam antes do erro.
4402insufficient_balance: o saldo acabou. O que já foi falado ou ouvido é cobrado; na transcrição ao vivo, os final desse áudio chegam antes do erro.
4403ip_not_allowed ou scope_denied.
4413payload_too_large no modo áudio do avatar (fala acima de 5 min).
4429rate_limited (áudio mais rápido que o tempo real) ou concurrency_limited (WebSockets demais na organização).
4500internal_error.
4503server_busy ou model_unavailable.

Exemplo

import asyncio
import json
import os
import wave
​
import websockets # pip install "websockets>=14"
​
PEDACO_MS = 100
​
​
async def transcrever(caminho: str) -> None:
with wave.open(caminho, "rb") as wav:
assert wav.getsampwidth() == 2 and wav.getnchannels() == 1, "precisa de PCM16 mono"
taxa = wav.getframerate() # 8000, 16000, 24000, 44100 ou 48000
amostras = wav.readframes(wav.getnframes())
​
async with websockets.connect(
"wss://api.int8labs.com/v1/stt/stream",
additional_headers={"Authorization": f"Bearer {os.environ['INT8_API_KEY']}"},
) as ws:
await ws.send(json.dumps({"type": "start", "sample_rate": taxa, "encoding": "pcm_s16le"}))
assert json.loads(await ws.recv())["type"] == "ready"
​
async def enviar() -> None:
passo = taxa * PEDACO_MS // 1000 * 2 # bytes de 100 ms
for inicio in range(0, len(amostras), passo):
await ws.send(amostras[inicio : inicio + passo])
await asyncio.sleep(PEDACO_MS / 1000) # no ritmo da fala; mais rápido fecha com 4429
await ws.send(json.dumps({"type": "stop"}))
​
envio = asyncio.create_task(enviar())
async for mensagem in ws:
evento = json.loads(mensagem)
if evento["type"] == "final":
print(f'[{evento["start_ms"]} ms] {evento["text"]}')
elif evento["type"] == "done":
print("custo R$", evento["cost_brl"])
elif evento["type"] == "error":
print("erro:", evento["code"], evento["message"])
await envio
​
​
asyncio.run(transcrever("reuniao.wav"))