Aller au contenu principal

Utilisez la REST API

Créez une clé API, importez une vidéo, créez un projet sous-titré et téléchargez son export.

Les intégrations sont disponibles avec les abonnements Max. Le traitement vidéo utilise vos minutes et limites de forfait actuelles.

Créez une clé API

Les intégrations sont disponibles avec Max. Ouvrez Paramètres → Intégrations, choisissez Créer une clé API, indiquez un nom et les permissions.

Accordez uniquement le nécessaire. Lecture, écriture et export sont sélectionnés par défaut. Conservez la lecture pour suivre la progression ou télécharger les résultats.

| Permission dans les paramètres | Scope | Usage | | --- | --- | --- | | Lire les projets | projects:read | Compte, styles, préréglages, projets, opérations et résultats | | Créer et gérer les projets | projects:write | Imports, création, annulation et nouvelles tentatives | | Exporter les projets | exports:write | Exports, export automatique et nouvelles tentatives d’export | | Gérer les Webhooks | webhooks:manage | Endpoints et livraisons ; facultatif, non sélectionné par défaut |

Les clés expirent après 90 jours par défaut ; choisissez 1–365 jours ou aucune expiration. Révoquez les clés inutilisées. La révocation bloque les nouvelles requêtes sans annuler les tâches acceptées.

Vérifiez votre première requête

L’URL de base est https://api.captionbolt.com. Envoyez la clé dans Authorization depuis votre serveur ou script local. Ne l’exposez pas dans le code du navigateur, les URL ou les conversations AI. Les cookies de session sont refusés.

Saisissez la clé dans Bash sans l’inscrire dans l’historique :

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"

La réponse indique solde, limites d’import, formats et durée d’accès. Les valeurs du solde sont en secondes ; divisez par 60 pour les minutes. Vérifiez credits.availableCreditsSec avant de démarrer.

Consultez GET /v1/styles pour les styles et GET /v1/presets pour vos préréglages. Les listes acceptent search, limit (1–100, 50 par défaut) et offset.

Importez votre vidéo

Importez les octets MP4, MOV, WebM ou AVI avant de créer le projet. Les URL vidéo sont refusées :

  1. Créez l’import avec POST /v1/uploads, les métadonnées réelles et Idempotency-Key.
  2. Suivez l’opération. À completed, utilisez le resourceId définitif comme ID d’import ; arrêtez à failed.
  3. Lisez GET /v1/uploads/{uploadId} pour connaître taille, nombre et parties déjà importées.
  4. Demandez les URL avec POST /v1/uploads/{uploadId}/parts et {"partNumbers":[1]} ; au maximum 32 parties par appel.
  5. Envoyez chaque partie manquante par PUT à son URL, avec la longueur exacte. N’envoyez pas de clé API à ces URL.
  6. Envoyez {} à POST /v1/uploads/{uploadId}/complete et vérifiez uploaded.

Fournissez la durée mesurée dans durationSec. Sans elle, CaptionBolt réserve la durée maximale du forfait ; même une courte vidéo peut être refusée si le solde est faible. La durée réelle est vérifiée avant traitement.

Exemple d’import et de création

Cet exemple Node.js 24 réalise un import multipart et crée un projet review. Enregistrez-le dans upload-and-create.mjs. Il lit par parties sans charger toute la vidéo en mémoire. Ne modifiez pas le fichier pendant l’exécution.

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

Générez une fois les clés d’action pour cet import et cette création. Conservez-les pour les mêmes requêtes ; utilisez de nouvelles clés pour une autre vidéo ou action.

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

Remplacez le chemin et 60 par votre fichier et sa durée mesurée en secondes. Utilisez FFprobe ou un outil similaire, sans déduire la durée de la taille. Le script affiche les ID d’opération pour la récupération, puis les ID d’import et de projet.

L’empreinte SHA-256 combine le préfixe de métadonnées UTF-8 indiqué et les premiers et derniers 64 KiB, sans compter deux fois les octets communs. Conservez nom, taille, date de modification et MIME lors de la reprise.

Suivez votre projet

Définissez PROJECT_ID avec l’ID affiché :

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

Lisez status et respectez pollAfterSeconds entre les consultations :

| État | Action | | --- | --- | | processing | Attendre la préparation et les sous-titres | | ready | Vérifier et enregistrer dans CaptionBolt, puis exporter | | exporting | Attendre l’export | | completed | Récupérer et télécharger les fichiers | | failed | Lire error, corriger la cause et demander une nouvelle tentative | | cancelled | Arrêter les consultations ; cette exécution est annulée |

Une opération completed ne signifie pas vidéo terminée. L’opération décrit une action ; le projet décrit la vidéo. Consultez GET /v1/operations/{id} ; arrêtez à failed et lisez error.

À la création, fournissez language et un styleId ou presetId des listes. Omettez les deux pour le style par défaut. mode: "auto-export" produit un MP4 après traitement et nécessite aussi exports:write. Si le projet reste à ready, inspectez son opération autoExport.

Exportez les modifications enregistrées

Pour review, vérifiez et enregistrez dans l’éditeur CaptionBolt. Générez une clé d’export une fois, puis envoyez :

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

Suivez l’opération puis le projet jusqu’à completed ; arrêtez en cas d’échec. Seules les dernières modifications enregistrées sont exportées. outputFormats accepte mp4, srt, vtt, ass et txt, selon le forfait actuel. MP4 est le format par défaut.

Téléchargez vos fichiers

Lorsque le projet est completed :

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

La réponse contient files, avec format et url pour chacun, et expiresAt pour les liens. Téléchargez sans envoyer la clé API aux URL des fichiers.

Les liens durent au maximum dix minutes. Renouvelez-les tant que le projet reste accessible : 14 jours depuis sa création. Le renouvellement ne prolonge pas l’accès. Le expiresAt du projet limite l’accès ; celui du résultat limite les liens.

Les projets terminés conservent leur export. Créez une copie modifiable dans CaptionBolt pour les modifier à nouveau.

Gérez les erreurs et la reprise

| Réponse | Étape suivante | | --- | --- | | 400 | Vérifier les champs et en-têtes d’idempotence obligatoires | | 401 | Vérifier que la clé est valide, non expirée et non révoquée | | 403 | Vérifier permissions et limites ; l’écriture ne permet pas de lire la progression | | 404 | Vérifier ID et compte ; projets supprimés ou expirés indisponibles | | 409 | Vérifier l’état ou une clé réutilisée avec des données différentes | | 429 | Respecter Retry-After et lire l’erreur ; fréquence ou capacité d’import/tâches peut être limitée. REST et MCP partagent 120 requêtes par compte par minute | | 503 ou timeout | Attendre, consulter une opération connue ou répéter l’action identique avec sa clé initiale |

Un succès HTTP peut accepter une action qui échoue ensuite. Vérifiez operation.state et operation.error. PAYMENT_REQUIRED indique des minutes disponibles insuffisantes, éventuellement réservées par des tâches actives. PAYLOAD_TOO_LARGE indique un fichier trop volumineux. Consultez GET /v1/account, corrigez puis ne lancez une nouvelle action avec une nouvelle clé qu’après confirmation de l’échec. La clé initiale retourne la même opération échouée.

Créer imports ou projets, exporter, réessayer et annuler nécessite Idempotency-Key : 1–128 caractères ASCII imprimables sans espaces. Nouvelle action, nouvelle clé. Après timeout, gardez clé et données initiales ; une réponse perdue ne prouve pas l’échec.

Pour reprendre, comparez métadonnées et empreinte via GET /v1/uploads/{uploadId}, puis appelez POST /v1/uploads/{uploadId}/resume. Utilisez l’état et les parties retournés. Si l’état reste completing, attendez ; en uploading, envoyez les parties manquantes puis terminez. expired ou aborted nécessite un nouvel import. DELETE /v1/uploads/{uploadId} abandonne un import non consommé, sans annuler un projet créé.

Pour une action explicite, envoyez un objet JSON vide à POST /v1/projects/{projectId}/retry ou /cancel avec sa propre clé. Une nouvelle tentative d’export nécessite aussi exports:write.

GET /health/integrations, sans clé, retourne 200 si le service est prêt ou 503 sinon. Il ne vérifie ni vos identifiants ni la fin du traitement vidéo.

Consultez la référence API (s’ouvre dans une nouvelle fenêtre) et OpenAPI pour les schémas complets. Utilisez les Webhooks pour recevoir les étapes sans interrogation périodique.

Nous utilisons des cookies pour mémoriser vos préférences, mesurer les performances du site et améliorer CaptionBolt.