Przejdź do głównej treści

Korzystaj z REST API

Utwórz klucz API, prześlij film, utwórz projekt napisów i pobierz gotowy eksport.

Integracje są dostępne w subskrypcjach Max. Przetwarzanie filmów używa obecnych minut i limitów planu.

Utwórz klucz API

Integracje są dostępne w subskrypcjach Max. Otwórz Ustawienia → Integracje, wybierz Utwórz klucz API, podaj nazwę i uprawnienia.

Przyznaj tylko potrzebne prawa. Odczyt, zapis i eksport są domyślnie zaznaczone. Zachowaj odczyt do postępu i pobierania wyników.

| Uprawnienie w ustawieniach | Scope | Zastosowanie | | --- | --- | --- | | Odczyt projektów | projects:read | Konto, style, ustawienia zapisane, projekty, operacje i wyniki | | Tworzenie i obsługa projektów | projects:write | Przesyłanie, tworzenie, anulowanie i ponawianie | | Eksport projektów | exports:write | Eksport, automatyczny eksport i jego ponowienia | | Zarządzanie Webhooks | webhooks:manage | Endpointy i dostarczenia; opcjonalne, domyślnie wyłączone |

Klucze wygasają domyślnie po 90 dniach. Wybierz 1–365 dni lub brak wygaśnięcia. Unieważnij nieużywane klucze; blokuje to nowe żądania bez anulowania przyjętych zadań.

Sprawdź pierwsze żądanie

Bazowy URL to https://api.captionbolt.com. Wysyłaj klucz w Authorization z serwera lub lokalnego skryptu. Nie umieszczaj go w kodzie przeglądarki, URL ani rozmowie AI. Ciasteczka sesji nie są akceptowane.

Wpisz klucz w Bash bez zapisywania w historii:

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"

Odpowiedź zawiera saldo, limity przesyłania, formaty i okres dostępu. Saldo jest w sekundach; podziel przez 60 dla minut. Sprawdź credits.availableCreditsSec przed startem.

Style: GET /v1/styles; zapisane ustawienia: GET /v1/presets. Listy obsługują search, limit (1–100, domyślnie 50) i offset.

Prześlij film

Przed tworzeniem projektu prześlij bajty MP4, MOV, WebM lub AVI. URL filmu nie jest przyjmowany:

  1. Utwórz przesyłanie przez POST /v1/uploads z rzeczywistymi metadanymi i Idempotency-Key.
  2. Sprawdzaj operację. W completed końcowy resourceId jest ID przesyłania. Zatrzymaj się w failed.
  3. Odczytaj GET /v1/uploads/{uploadId} dla rozmiaru, liczby i przesłanych części.
  4. Żądaj URL przez POST /v1/uploads/{uploadId}/parts z {"partNumbers":[1]}; do 32 części naraz.
  5. Wyślij brakujące części metodą PUT z dokładną długością. Nie wysyłaj klucza API do tych URL.
  6. Wyślij {} do POST /v1/uploads/{uploadId}/complete i potwierdź uploaded.

Podaj zmierzoną długość filmu w durationSec. Bez niej rezerwowany jest maksymalny czas planu; małe saldo może zablokować krótki film. Rzeczywista długość jest sprawdzana przed przetwarzaniem.

Przykład przesyłania i tworzenia

Przykład Node.js 24 przesyła części i tworzy projekt review. Zapisz jako upload-and-create.mjs. Czyta części bez ładowania całego filmu do pamięci. Nie zmieniaj pliku podczas działania.

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);

Wygeneruj klucze akcji dla przesyłania i tworzenia raz. Zachowaj je dla tych samych żądań; inny film lub nowa akcja wymaga nowych kluczy.

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

Zastąp ścieżkę i 60 plikiem i zmierzonym czasem w sekundach. Użyj np. FFprobe, nie szacuj z rozmiaru. Skrypt podaje ID operacji do odzyskiwania, następnie przesyłania i projektu.

Odcisk SHA-256 używa pokazanego prefiksu metadanych UTF-8 oraz pierwszych i ostatnich 64 KiB bez podwójnego liczenia nakładania. Zachowaj nazwę, rozmiar, czas modyfikacji i MIME przy wznawianiu.

Śledź projekt

Ustaw PROJECT_ID na wyświetlone ID:

export PROJECT_ID='<project-id>'
curl --fail-with-body "https://api.captionbolt.com/v1/projects/$PROJECT_ID" \
  -H "Authorization: Bearer $CAPTIONBOLT_API_KEY"

Czytaj status i przestrzegaj pollAfterSeconds:

| Stan | Działanie | | --- | --- | | processing | Poczekaj na przygotowanie i napisy | | ready | Sprawdź i zapisz w CaptionBolt, potem eksportuj | | exporting | Poczekaj na eksport | | completed | Pobierz wynik i pliki | | failed | Czytaj error, napraw przyczynę i świadomie ponów | | cancelled | Zakończ odpytywanie; przebieg anulowano |

Operacja completed nie oznacza gotowego filmu. Operacje opisują działania, stan projektu opisuje film. Sprawdź GET /v1/operations/{id}; w failed zatrzymaj się i odczytaj error.

Przy tworzeniu można podać language oraz styleId lub presetId z list. Pominięcie obu wybiera styl domyślny. mode: "auto-export" daje MP4 po przetwarzaniu i wymaga exports:write. Jeśli pozostaje ready, sprawdź operację autoExport.

Eksportuj zapisane zmiany

Dla review sprawdź i zapisz w edytorze CaptionBolt. Utwórz raz klucz eksportu i wyślij:

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"]}'

Sprawdzaj operację, potem projekt do completed; zatrzymaj się przy błędzie. Tylko ostatnie zapisane zmiany są eksportowane. outputFormats przyjmuje mp4, srt, vtt, ass, txt zgodnie z planem. Domyślny format to MP4.

Pobierz pliki

Gdy projekt jest completed:

curl --fail-with-body "https://api.captionbolt.com/v1/projects/$PROJECT_ID/result" \
  -H "Authorization: Bearer $CAPTIONBOLT_API_KEY"

Odpowiedź zawiera files z format i url oraz expiresAt linków. Pobieraj pliki bez wysyłania klucza API.

Linki działają do dziesięciu minut. Odnawiaj podczas 14 dni dostępu od utworzenia projektu. Odnowienie nie wydłuża dostępu. expiresAt projektu oznacza koniec dostępu, wyniku — koniec linków.

Ukończone projekty zachowują eksport. Utwórz edytowalną kopię w CaptionBolt do dalszych zmian.

Napraw błędy

| Odpowiedź | Następny krok | | --- | --- | | 400 | Sprawdź pola i wymagane nagłówki idempotencji | | 401 | Sprawdź ważność, wygaśnięcie i unieważnienie klucza | | 403 | Sprawdź prawa i limity; zapis nie umożliwia odczytu postępu | | 404 | Sprawdź ID i konto; usunięte lub wygasłe projekty niedostępne | | 409 | Sprawdź stan lub użycie klucza z innymi danymi | | 429 | Przestrzegaj Retry-After i czytaj błąd; ograniczona może być częstotliwość lub pojemność przesyłania/zadań. REST i MCP dzielą 120 żądań na konto na minutę | | 503 lub timeout | Poczekaj, sprawdź znaną operację lub ponów identycznie z pierwotnym kluczem |

Sukces HTTP może przyjąć akcję, która później zawiedzie. Sprawdź operation.state i operation.error. PAYMENT_REQUIRED oznacza zbyt mało dostępnych minut, być może zarezerwowanych przez aktywne zadania. PAYLOAD_TOO_LARGE oznacza przekroczenie limitu pliku. Sprawdź GET /v1/account i napraw przyczynę. Dopiero po potwierdzonej porażce zacznij nową akcję z nowym kluczem; stary zwróci tę samą błędną operację.

Tworzenie przesyłań i projektów, eksport, ponawianie i anulowanie wymaga Idempotency-Key: 1–128 drukowalnych znaków ASCII bez spacji. Nowa akcja, nowy klucz. Po timeout zachowaj klucz i dane; utrata odpowiedzi nie dowodzi błędu.

Przy wznawianiu porównaj metadane i odcisk z GET /v1/uploads/{uploadId}, potem wywołaj POST /v1/uploads/{uploadId}/resume. Użyj zwróconego stanu i części. W completing poczekaj; w uploading wyślij brakujące części i zakończ. expired lub aborted wymaga nowego przesyłania. DELETE /v1/uploads/{uploadId} przerywa niewykorzystane przesyłanie, nie utworzony projekt.

Dla jawnych akcji wyślij pusty JSON do POST /v1/projects/{projectId}/retry lub /cancel z własnym kluczem. Ponowienie eksportu wymaga także exports:write.

GET /health/integrations bez klucza zwraca 200, gdy gotowe, lub 503 przy niedostępności. Nie sprawdza danych dostępu ani ukończenia filmu.

Pełne schematy: dokumentacja API (otwiera się w nowym oknie) i OpenAPI. Użyj Webhooks, aby dostawać etapy bez ciągłego odpytywania.

Używamy plików cookie, aby zapamiętywać ustawienia, mierzyć działanie witryny i ulepszać CaptionBolt.