Use a REST API
Crie uma chave de API, envie um vídeo, crie um projeto de legendas e baixe a exportação concluída.
Crie uma chave de API
As integrações estão disponíveis nas assinaturas Max. Abra Configurações → Integrações, selecione Criar chave de API, informe um nome e escolha permissões.
Conceda apenas o necessário. Leitura, escrita e exportação vêm selecionadas. Mantenha leitura para consultar progresso ou baixar resultados.
| Permissão nas configurações | Scope | Uso |
| --- | --- | --- |
| Ler projetos | projects:read | Conta, estilos, predefinições, projetos, operações e resultados |
| Criar e gerenciar projetos | projects:write | Envios, criação, cancelamento e novas tentativas |
| Exportar projetos | exports:write | Exportação, exportação automática e novas tentativas de exportação |
| Gerenciar Webhooks | webhooks:manage | Endpoints e entregas; opcional e desmarcada por padrão |
As chaves expiram em 90 dias por padrão. Escolha 1–365 dias ou sem expiração. Revogue chaves sem uso nas configurações. Isso bloqueia novas solicitações, sem cancelar tarefas já aceitas.
Verifique a primeira solicitação
A URL base é https://api.captionbolt.com. Envie a chave no cabeçalho Authorization pelo servidor ou script local. Não a exponha no navegador, em URL ou conversas com AI. Cookies de sessão não são aceitos.
Digite a chave no Bash sem gravá-la no histórico:
read -r -s -p 'CaptionBolt API key: ' CAPTIONBOLT_API_KEY
export CAPTIONBOLT_API_KEY
printf '\n'
curl --fail-with-body https://api.captionbolt.com/v1/account \
-H "Authorization: Bearer $CAPTIONBOLT_API_KEY"
A resposta inclui saldo, limites de envio, formatos e prazo de acesso. Valores de saldo estão em segundos; divida por 60 para minutos. Consulte credits.availableCreditsSec antes de começar.
Use GET /v1/styles para estilos e GET /v1/presets para predefinições salvas. Listas aceitam search, limit (1–100, padrão 50) e offset.
Envie seu vídeo
Antes de criar o projeto, envie os bytes de MP4, MOV, WebM ou AVI. URL de vídeo não é aceita:
- Crie o envio com
POST /v1/uploads, metadados reais eIdempotency-Key. - Consulte a operação. Em
completed, use oresourceIdfinal como ID do envio. Pare emfailed. - Consulte
GET /v1/uploads/{uploadId}para tamanho, quantidade e partes já enviadas. - Peça URL com
POST /v1/uploads/{uploadId}/partse{"partNumbers":[1]}; no máximo 32 partes por solicitação. - Faça PUT de cada parte pendente na URL retornada, com comprimento exato. Não envie chave de API para essas URL.
- Envie
{}aPOST /v1/uploads/{uploadId}/completee confirmeuploaded.
Informe a duração medida em durationSec. Se omitida, o CaptionBolt reserva a duração máxima do plano, podendo rejeitar até vídeos curtos quando restam poucos minutos. A duração real é verificada antes de processar.
Exemplo de envio e criação
Este exemplo Node.js 24 realiza envio multipart e cria um projeto review. Salve como upload-and-create.mjs. Ele lê por partes, sem carregar todo o vídeo na memória. Não altere o arquivo durante a execução.
import { createHash } from "node:crypto";
import { openAsBlob } from "node:fs";
import { stat } from "node:fs/promises";
import { basename, extname } from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
const apiKey = process.env.CAPTIONBOLT_API_KEY;
const uploadKey = process.env.CAPTIONBOLT_UPLOAD_KEY;
const createKey = process.env.CAPTIONBOLT_CREATE_KEY;
const [filePath, durationText] = process.argv.slice(2);
const durationSec = Number(durationText);
if (!apiKey || !uploadKey || !createKey || !filePath || !Number.isFinite(durationSec) || durationSec <= 0) {
throw new Error("Set the API key and action keys, then supply a file and measured duration in seconds.");
}
async function api(method, path, input, idempotencyKey) {
const response = await fetch(`https://api.captionbolt.com${path}`, {
method,
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
},
...(input === undefined ? {} : { body: JSON.stringify(input) }),
redirect: "error",
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
const error = await response.json().catch(() => null);
const detail = error?.message ?? error?.code ?? "Request failed";
throw new Error(`API HTTP ${response.status}: ${detail}; Retry-After: ${response.headers.get("retry-after") ?? "none"}`);
}
return response.json();
}
async function waitForOperation(operation) {
console.log("Operation ID:", operation.id);
const deadline = Date.now() + 120000;
while (operation.state !== "completed") {
if (operation.state === "failed") throw new Error(operation.error ?? "Action failed");
if (Date.now() >= deadline) throw new Error(`Continue polling operation ${operation.id}; do not start a new action.`);
await sleep(Math.max(1, operation.pollAfterSeconds) * 1000);
operation = await api("GET", `/v1/operations/${operation.id}`);
}
return operation;
}
const contentType = {
".mp4": "video/mp4", ".mov": "video/quicktime",
".webm": "video/webm", ".avi": "video/x-msvideo",
}[extname(filePath).toLowerCase()];
if (!contentType) throw new Error("Choose an MP4, MOV, WebM or AVI file.");
const metadata = await stat(filePath);
if (!metadata.isFile()) throw new Error("Choose a regular video file.");
const video = await openAsBlob(filePath, { type: contentType });
const fileName = basename(filePath);
const lastModifiedMs = Math.trunc(metadata.mtimeMs);
const fingerprint = createHash("sha256")
.update(`captionbolt-upload-v1\0${fileName}\0${video.size}\0${lastModifiedMs}\0${contentType}`)
.update(Buffer.from(await video.slice(0, 65536).arrayBuffer()))
.update(Buffer.from(await video.slice(Math.max(65536, video.size - 65536)).arrayBuffer()))
.digest("hex");
const uploaded = await waitForOperation(await api("POST", "/v1/uploads", {
fileName, fileSize: video.size, lastModifiedMs, contentType, fingerprint, durationSec,
}, uploadKey));
const uploadId = uploaded.resourceId;
console.log("Upload ID:", uploadId);
let upload = await api("GET", `/v1/uploads/${uploadId}`);
if (upload.status === "uploading" || upload.status === "completing") {
upload = await api("POST", `/v1/uploads/${uploadId}/resume`, {});
}
if (upload.status === "completing") {
throw new Error("Upload completion is still in progress. Wait, then rerun with the same action keys.");
}
if (upload.status === "expired" || upload.status === "aborted") {
throw new Error(`Upload is ${upload.status}. Start a new upload with new action keys.`);
}
if (upload.status !== "uploaded" && upload.status !== "consumed") {
const completed = new Set(upload.completedPartNumbers);
for (let number = 1; number <= upload.partCount; number++) {
if (completed.has(number)) continue;
const signed = await api("POST", `/v1/uploads/${uploadId}/parts`, { partNumbers: [number] });
for (const part of signed.signedParts) {
const start = (part.partNumber - 1) * upload.partSize;
const bytes = video.slice(start, start + part.contentLength);
if (bytes.size !== part.contentLength) throw new Error("File part length changed.");
const response = await fetch(part.url, {
method: "PUT", body: bytes,
headers: { "Content-Length": String(part.contentLength) },
redirect: "error", signal: AbortSignal.timeout(120000),
});
if (!response.ok) throw new Error(`Part ${part.partNumber}: HTTP ${response.status}`);
}
}
const finished = await api("POST", `/v1/uploads/${uploadId}/complete`, {});
if (finished.status !== "uploaded") throw new Error("Upload is not ready for project creation.");
}
const created = await waitForOperation(await api("POST", "/v1/projects", {
uploadSessionId: uploadId, mode: "review",
}, createKey));
console.log("Project ID:", created.resourceId);
Gere uma vez as chaves de ação para o envio e a criação. Preserve-as ao repetir as mesmas solicitações; use novas chaves para outro vídeo ou ação.
export CAPTIONBOLT_UPLOAD_KEY="$(node -e 'console.log(crypto.randomUUID())')"
export CAPTIONBOLT_CREATE_KEY="$(node -e 'console.log(crypto.randomUUID())')"
node upload-and-create.mjs /absolute/path/lesson.mp4 60
Troque o caminho e 60 pelo arquivo e duração medida em segundos. Use FFprobe ou outra ferramenta de inspeção; não deduza pelo tamanho. O script mostra ID de operações para recuperação, depois ID de envio e projeto.
A impressão digital SHA-256 usa o prefixo de metadados UTF-8 mostrado, seguido dos primeiros e últimos 64 KiB sem sobreposição. Preserve nome, tamanho, data de modificação e MIME ao retomar.
Acompanhe seu projeto
Defina PROJECT_ID com o ID mostrado:
export PROJECT_ID='<project-id>'
curl --fail-with-body "https://api.captionbolt.com/v1/projects/$PROJECT_ID" \
-H "Authorization: Bearer $CAPTIONBOLT_API_KEY"
Leia status e consulte no intervalo de pollAfterSeconds:
| Estado | Próxima etapa |
| --- | --- |
| processing | Aguarde preparação e legendas |
| ready | Revise e salve no CaptionBolt; depois exporte |
| exporting | Aguarde a exportação |
| completed | Obtenha o resultado e baixe os arquivos |
| failed | Leia error, corrija a causa e solicite uma nova tentativa |
| cancelled | Pare de consultar; esta execução foi cancelada |
Uma operação completed não significa vídeo concluído. Operações descrevem ações, e o estado do projeto descreve o vídeo. Consulte GET /v1/operations/{id}; pare em failed e leia error.
Ao criar, envie language e um styleId ou presetId das listas. Omita ambos para o estilo padrão. mode: "auto-export" exporta MP4 após processar e também exige exports:write. Se ficar em ready, confira a operação autoExport.
Exporte as alterações salvas
Para review, revise e salve no editor do CaptionBolt. Gere uma chave de exportação uma vez e solicite:
export CAPTIONBOLT_EXPORT_KEY="$(node -e 'console.log(crypto.randomUUID())')"
curl --fail-with-body "https://api.captionbolt.com/v1/projects/$PROJECT_ID/export" \
-H "Authorization: Bearer $CAPTIONBOLT_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $CAPTIONBOLT_EXPORT_KEY" \
--data '{"outputFormats":["mp4"]}'
Consulte a operação e depois o projeto até completed; pare se algum falhar. A exportação usa as últimas alterações salvas. outputFormats aceita mp4, srt, vtt, ass e txt, conforme seu plano. MP4 é o padrão.
Baixe os arquivos
Quando o projeto estiver em completed:
curl --fail-with-body "https://api.captionbolt.com/v1/projects/$PROJECT_ID/result" \
-H "Authorization: Bearer $CAPTIONBOLT_API_KEY"
A resposta contém files, cada um com format e url, além de expiresAt dos links. Baixe pelas URL sem enviar a chave.
Links duram no máximo dez minutos. Renove enquanto o projeto estiver acessível, por 14 dias desde a criação. Renovar links não amplia esse prazo. expiresAt do projeto limita acesso; o do resultado limita os links.
Projetos concluídos mantêm a exportação existente. Crie uma cópia editável no CaptionBolt para novas alterações.
Recupere-se de erros
| Resposta | Próxima etapa |
| --- | --- |
| 400 | Confira campos e cabeçalhos de idempotência obrigatórios |
| 401 | Confira validade, expiração e revogação da chave |
| 403 | Confira permissões e limites; escrita não permite ler progresso |
| 404 | Confira ID e conta; projetos excluídos ou expirados não estão disponíveis |
| 409 | Confira estado e reutilização da chave com dados diferentes |
| 429 | Respeite Retry-After e leia o erro; pode ser frequência ou capacidade de envios/tarefas. REST e MCP compartilham 120 solicitações por conta por minuto |
| 503 ou timeout | Aguarde, consulte operação conhecida ou repita a ação idêntica com a chave original |
Sucesso HTTP pode aceitar uma ação que falha depois. Leia operation.state e operation.error. PAYMENT_REQUIRED significa minutos disponíveis insuficientes, talvez reservados por tarefas ativas. PAYLOAD_TOO_LARGE significa arquivo acima do limite atual. Consulte GET /v1/account e corrija. Inicie nova ação com nova chave só após confirmar a falha; a chave original retorna a mesma operação falha.
Criar envios ou projetos, exportar, tentar novamente e cancelar exige Idempotency-Key: 1–128 caracteres ASCII imprimíveis sem espaços. Uma nova ação precisa de nova chave. Após timeout, mantenha chave e dados; perder a resposta não confirma falha.
Para retomar, compare metadados e impressão digital com GET /v1/uploads/{uploadId}, depois chame POST /v1/uploads/{uploadId}/resume. Use o estado e as partes retornadas. Se continuar em completing, aguarde; em uploading, envie partes pendentes e complete. Sessões expired ou aborted precisam de novo envio. DELETE /v1/uploads/{uploadId} aborta envio não consumido, sem cancelar um projeto criado.
Para ações explícitas, envie JSON vazio a POST /v1/projects/{projectId}/retry ou /cancel, com chave própria. Repetir exportação também exige exports:write.
GET /health/integrations não exige chave. Retorna 200 se o serviço estiver pronto ou 503 se indisponível. Não verifica credenciais nem se o vídeo terminou.
As estruturas completas estão na referência da API (abre em uma nova janela) e no OpenAPI. Configure Webhooks para receber marcos de progresso sem consultas periódicas.