Pular para o conteúdo principal

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.

As integrações estão disponíveis nas assinaturas Max. O processamento de vídeos usa seus minutos e limites de plano atuais.

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:

  1. Crie o envio com POST /v1/uploads, metadados reais e Idempotency-Key.
  2. Consulte a operação. Em completed, use o resourceId final como ID do envio. Pare em failed.
  3. Consulte GET /v1/uploads/{uploadId} para tamanho, quantidade e partes já enviadas.
  4. Peça URL com POST /v1/uploads/{uploadId}/parts e {"partNumbers":[1]}; no máximo 32 partes por solicitação.
  5. Faça PUT de cada parte pendente na URL retornada, com comprimento exato. Não envie chave de API para essas URL.
  6. Envie {} a POST /v1/uploads/{uploadId}/complete e confirme uploaded.

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.

Usamos cookies para lembrar preferências, medir o desempenho do site e melhorar o CaptionBolt.