Transcrições
Crie e consulte transcrições pela API: URL ou uploadId, idioma, separação de falantes, glossário, webhook e o formato da resposta com timestamps.
Criar uma transcrição
POST /v1/transcripts| Campo | Tipo | Descrição |
|---|---|---|
url | string | URL pública (http/https) do áudio. |
uploadId | string | ID de um arquivo enviado. Informe url ou uploadId, nunca os dois. |
language | string | Opcional. Código do idioma (ex.: pt, en). Omitido: detecta sozinho. |
speakers | boolean, integer, range | Opcional. Separa os falantes. Muda a tarifa. |
webhookUrl | string | Opcional. URL http(s) que recebe um POST quando a transcrição termina. |
glossary | objeto | Opcional. Correções que valem só para este áudio (nomes próprios, jargão). |
reuseIfIdentical | boolean | Opcional. Devolve a transcrição que a conta já tem deste mesmo áudio, sem cobrar de novo. |
O campo speakers
Um campo só decide tudo sobre falantes:
| Valor | Significado |
|---|---|
| omitido | Texto corrido, sem separar falantes. |
true | Separa os falantes; a quantidade quem descobre é a API. |
3 | São exatamente 3 falantes. |
"2-5" | São entre 2 e 5 falantes. |
Só informe a quantidade quando souber: um palpite errado força a separação para a
contagem errada. Com speakers, cada item de words traz o rótulo do falante
("A", "B", …) e a resposta traz speakerCount.
curl https://api.transcrevo.com/v1/transcripts \
-H "Authorization: Bearer $TRANSCREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/reuniao.mp3",
"speakers": 5
}'Resposta 201:
{
"data": {
"id": "6b9f2b81-1c9a-4f4e-9d5f-8f2a7c1e3b90",
"status": "processing",
"text": null,
"words": null,
"error": null,
"language": null,
"durationSeconds": 0,
"speakerCount": 0,
"cost": 0,
"currency": "USD",
"model": "trv-1",
"url": "https://example.com/reuniao.mp3",
"createdAt": "2026-08-24T18:00:00.000Z"
}
}Formatos aceitos: mp3, wav, m4a/aac, ogg, opus, flac, webm, wma, amr e a
trilha de áudio de arquivos de vídeo (mp4, mov, mkv). O que não decodifica
volta como failed com audio_invalid, sem cobrança. Duração máxima:
10 horas por áudio.
Como fica quando conclui
O mesmo objeto, agora com text, words, durationSeconds e cost
preenchidos. start e end são segundos, em número decimal (1.5 é um
segundo e meio, não 1500), contados do começo do áudio:
{
"data": {
"id": "6b9f2b81-1c9a-4f4e-9d5f-8f2a7c1e3b90",
"status": "done",
"text": "Bom dia a todos. Vamos começar.",
"words": [
{ "start": 0.32, "end": 0.78, "text": "Bom", "speaker": "A" },
{ "start": 0.78, "end": 1.04, "text": "dia", "speaker": "A" },
{ "start": 1.04, "end": 1.51, "text": "a", "speaker": "A" },
{ "start": 1.51, "end": 2.06, "text": "todos.", "speaker": "A" },
{ "start": 2.44, "end": 2.9, "text": "Vamos", "speaker": "B" },
{ "start": 2.9, "end": 3.37, "text": "começar.", "speaker": "B" }
],
"error": null,
"language": "pt",
"durationSeconds": 3,
"speakerCount": 2,
"cost": 0.0042,
"currency": "USD",
"model": "trv-1",
"url": "https://example.com/reuniao.mp3",
"createdAt": "2026-08-24T18:00:00.000Z"
}
}text é a junção das palavras de words, e words é o que serve para legenda,
busca por trecho e corte de vídeo.
A saída não é reproduzível
Mandar o mesmo arquivo duas vezes pode devolver transcritos ligeiramente
diferentes: um "então mostra" vira "um monstro", a segmentação muda em algumas
dezenas de blocos, a normalização de fala informal ("pra" e "para") oscila. Não
é bug e não tem seed: a transcrição não é uma função determinística do
arquivo.
Na prática: não use o transcrito como base de comparação bit a bit entre
execuções. Para auditar uma reclamação, guarde o transcrito que você entregou (o
id dele é permanente) em vez de gerar de novo.
Aviso de conclusão (webhook)
Um VOD de duas horas leva minutos para transcrever. Perguntar de três em três
segundos são ~100 requisições por job só para ouvir processing. Mande
webhookUrl e a API avisa uma vez, quando termina:
curl https://api.transcrevo.com/v1/transcripts \
-H "Authorization: Bearer $TRANSCREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uploadId": "0f3c2d11-8a4b-4c6e-b2d9-5e7f1a9c3b42",
"webhookUrl": "https://seu-servidor.com/hooks/transcrevo"
}'O aviso é um POST com o resumo da transcrição, no mesmo envelope de
sempre, e vale tanto para done quanto para failed:
{
"data": {
"id": "6b9f2b81-…",
"status": "done",
"durationSeconds": 8700,
"language": "pt",
"speakerCount": 6,
"cost": 12.08,
"currency": "USD",
"error": null,
"model": "trv-1",
"url": null,
"createdAt": "2026-08-27T18:00:00.000Z"
}
}text e words não vão no corpo: num áudio de duas horas eles passam de
10 MB. Recebido o aviso, busque o transcrito uma vez em
GET /v1/transcripts/{id}.
Conferir a assinatura
Cada entrega leva o header Transcrevo-Signature, no formato
t=<unix>,v1=<hex>. O v1 é o HMAC-SHA256 de <t>.<corpo cru>, e o segredo é
o de webhook da sua conta (whsec_...), mostrado em
/dashboard/webhooks.
Ele é independente das chaves de API de propósito: a chave autentica as suas requisições e é trocada quando vaza; o segredo verifica assinatura e é trocado quando o seu verificador muda. Amarrados, rotacionar a chave derrubava a verificação do seu endpoint no mesmo instante.
Ao rotacionar, o segredo antigo continua verificando por 24 horas: publique o novo, confirme, e o antigo vence sozinho. Nessa janela, aceite qualquer um dos dois.
import { createHmac, timingSafeEqual } from "node:crypto";
const secret = process.env.TRANSCREVO_WEBHOOK_SECRET;
export function valid(header, rawBody) {
const { t, v1 } = Object.fromEntries(header.split(",").map((part) => part.split("=")));
// Aviso velho é aviso repetido: fora de cinco minutos, recuse.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Responda 2xx para confirmar. Qualquer outra coisa (ou nenhuma resposta em 10s)
é nova tentativa, com espera crescente: 30s, 2min, 10min, 30min, 2h e 6h. Depois
da sexta a API desiste do aviso; a transcrição continua lá para ser buscada.
Glossário por requisição
Nome próprio e jargão são onde todo motor de ASR erra, e você sabe de antemão
quais vão aparecer no seu áudio. glossary é { "o que ele ouve": "o que escrever" }, aplicado depois da transcrição e só naquele áudio:
curl https://api.transcrevo.com/v1/transcripts \
-H "Authorization: Bearer $TRANSCREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/podcast.mp3",
"glossary": { "gatu": "Gatto", "bertual": "Bertuol" }
}'Até 100 entradas, de até 80 caracteres cada. A correção vale por sequência de
palavras, então "mote gruto": "Mateus Gruto" também funciona, e o intervalo de
tempo é repartido entre as palavras trocadas. O que você manda tem preferência
sobre as correções que já vêm de casa.
Não pagar duas vezes pelo mesmo áudio
Orquestrador que redistribui fila manda o mesmo vídeo mais de uma vez, e cada
envio é uma transcrição cobrada. Para desligar isso do seu lado sem manter
controle próprio, envie o arquivo com sha256 e crie a
transcrição com reuseIfIdentical:
curl https://api.transcrevo.com/v1/transcripts \
-H "Authorization: Bearer $TRANSCREVO_API_KEY" \
-H "Content-Type: application/json" \
-d "{ \"uploadId\": \"$UPLOAD_ID\", \"reuseIfIdentical\": true }"Existindo uma transcrição desta conta para aquele conteúdo, com os mesmos
parâmetros, a resposta é 200 com ela (em vez de 201 com uma nova) e nada é
cobrado. A busca alcança inclusive a que ainda está em processing, que é o caso
da rajada. Sem sha256 no upload não há como reconhecer o conteúdo, e o pedido
é recusado com 400 validation (reuse_needs_sha256) em vez de virar cobrança
silenciosa.
Duas requisições exatamente simultâneas ainda podem criar duas transcrições: o reaproveitamento olha o que já existe, e nesse instante nenhuma das duas existe.
Repetir com segurança
Se a conexão cair depois de a requisição chegar, repetir criaria uma segunda
transcrição (e uma segunda cobrança). Para evitar isso, mande um header
Idempotency-Key com qualquer string de até 64 caracteres:
curl https://api.transcrevo.com/v1/transcripts \
-H "Authorization: Bearer $TRANSCREVO_API_KEY" \
-H "Idempotency-Key: reuniao-2026-08-25-01" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/reuniao.mp3" }'A mesma chave devolve a mesma transcrição, quantas vezes você repetir. Usar
a mesma chave com um corpo diferente devolve 409 idempotency_conflict: é
sinal de que a chave foi reaproveitada por engano.
Se o áudio veio de um upload, use o próprio uploadId como chave. O
instinto é chavear pelo id da sua mídia, para que repetir o trabalho inteiro
não pague duas vezes, mas cada tentativa sobe o arquivo de novo, então o
uploadId do corpo muda a cada uma. Mesma chave, corpo diferente:
409 idempotency_conflict em toda tentativa, para sempre. Quem resolve
"mandei o mesmo vídeo duas vezes" é reuseIfIdentical, acima, não a chave de
idempotência.
Consultar uma transcrição
GET /v1/transcripts/{id}curl https://api.transcrevo.com/v1/transcripts/6b9f2b81-1c9a-4f4e-9d5f-8f2a7c1e3b90 \
-H "Authorization: Bearer $TRANSCREVO_API_KEY"Consulte periodicamente (a cada poucos segundos) até status sair de
processing. Se o volume é grande, prefira webhookUrl na criação: é uma
leitura por job em vez de uma centena.
Listar transcrições
GET /v1/transcripts| Parâmetro | Descrição |
|---|---|
page | Página, começando em 1. Padrão: 1. |
perPage | Itens por página, até 100. Padrão: 20. |
status | Opcional. Filtra por processing, done ou failed. |
since | Opcional. Só o que foi criado a partir desta data (ISO 8601). |
until | Opcional. Só o que foi criado antes desta data (ISO 8601). |
curl "https://api.transcrevo.com/v1/transcripts?status=done&perPage=50" \
-H "Authorization: Bearer $TRANSCREVO_API_KEY"O recorte por data é feito no banco, então fechar o custo de um dia não exige paginar a conta inteira:
curl "https://api.transcrevo.com/v1/transcripts?since=2026-08-27T00:00:00Z&until=2026-08-28T00:00:00Z" \
-H "Authorization: Bearer $TRANSCREVO_API_KEY"Da mais recente para a mais antiga. Os itens da lista trazem todos os campos
exceto text e words, que num áudio longo passam de 10 MB por item;
consulte a transcrição individual para obtê-los.
{
"data": {
"transcripts": [{ "id": "6b9f2b81-…", "status": "done", "durationSeconds": 1834 }],
"total": 128,
"page": 1,
"perPage": 20
}
}Apagar uma transcrição
DELETE /v1/transcripts/{id}Apaga o texto, as palavras e o áudio guardado. O registro de uso continua existindo, porque é dele que sai a fatura do período, mas o conteúdo some e não tem como recuperar.
Transcrição ainda em processing não pode ser apagada (409 transcript_processing): o resultado chegaria depois e repovoaria o que você
acabou de apagar. Espere terminar.
Campos da resposta
| Campo | Descrição |
|---|---|
id | Identificador da transcrição. |
status | processing, done ou failed. |
text | O texto transcrito. null até concluir. |
words | Cada palavra com start, end (em segundos, decimais), text e speaker. null até concluir. |
error | { code, message } quando status é failed; null fora disso. |
language | Idioma detectado (ou o informado). null enquanto processa. |
durationSeconds | Duração real do áudio. 0 enquanto processa. |
speakerCount | Quantos falantes distintos foram encontrados. null quando não se pediu speakers (que é diferente de 0: pedi e não achei ninguém). |
cost | Custo debitado, na menor unidade de currency (centavos), com fração: 0.0167 é um sexto de centavo. 0 enquanto processa e sempre 0 em falha. |
currency | Moeda de cost, em ISO 4217 (USD). |
model | Modelo que produziu o transcrito. |
url | A URL que você mandou. null quando o áudio veio de um arquivo enviado. |
createdAt | Data de criação (ISO 8601). |
Status
| Status | Significado |
|---|---|
processing | Na fila ou em processamento. |
done | Concluída: text disponível e custo debitado. |
failed | Falhou: veja error. Nada é cobrado em falhas. |
Autenticação
Autentique cada requisição com o header Authorization e uma chave tk_. Até 10 chaves por conta, rotação com 24 h de convivência e revogação.
Envio de arquivos
Envie áudio local para a API: uma requisição até 100 MB, ou chunks de até 16 MiB com retomada para arquivos de até 2 GiB, e transcreva pelo uploadId.