Usa la REST API
Crea una chiave API, carica un video, crea un progetto sottotitolato e scarica l’esportazione.
Crea una chiave API
Le integrazioni sono disponibili con gli abbonamenti Max. Apri Impostazioni → Integrazioni, scegli Crea chiave API, inserisci nome e autorizzazioni.
Concedi solo quanto serve. Lettura, scrittura ed esportazione sono selezionate. Mantieni lettura per controllare l’avanzamento o scaricare risultati.
| Autorizzazione nelle impostazioni | Scope | Uso |
| --- | --- | --- |
| Leggi progetti | projects:read | Account, stili, preset, progetti, operazioni e risultati |
| Crea e gestisci progetti | projects:write | Caricamenti, creazione, annullamento e nuovi tentativi |
| Esporta progetti | exports:write | Esportazione, esportazione automatica e relativi tentativi |
| Gestisci Webhooks | webhooks:manage | Endpoint e consegne; facoltativa, non selezionata |
Le chiavi scadono dopo 90 giorni per impostazione predefinita. Scegli 1–365 giorni o nessuna scadenza. Revoca le chiavi inutilizzate; blocca nuove richieste, senza annullare attività accettate.
Verifica la prima richiesta
L’URL base è https://api.captionbolt.com. Invia la chiave in Authorization dal server o script locale. Non inserirla nel codice del browser, negli URL o in conversazioni AI. I cookie di sessione non sono accettati.
Inserisci la chiave in Bash senza salvarla nella cronologia:
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 risposta include saldo, limiti di caricamento, formati e periodo di accesso. Il saldo è in secondi; dividi per 60 per ottenere minuti. Controlla credits.availableCreditsSec prima di iniziare.
Consulta GET /v1/styles per stili e GET /v1/presets per preset salvati. Le liste accettano search, limit (1–100, predefinito 50) e offset.
Carica il video
Prima di creare il progetto, carica i byte MP4, MOV, WebM o AVI. Gli URL video non sono accettati:
- Crea il caricamento con
POST /v1/uploads, metadati reali eIdempotency-Key. - Controlla l’operazione. In
completed, usa ilresourceIdfinale come ID di caricamento. Fermati sufailed. - Consulta
GET /v1/uploads/{uploadId}per dimensione, numero e parti già caricate. - Richiedi URL con
POST /v1/uploads/{uploadId}/partse{"partNumbers":[1]}; massimo 32 parti alla volta. - Invia ciascuna parte mancante via PUT al suo URL, con lunghezza esatta. Non inviare chiavi API a questi URL.
- Invia
{}aPOST /v1/uploads/{uploadId}/completee confermauploaded.
Invia la durata misurata come durationSec. Se omessa, si riserva la durata massima del piano; un saldo basso può quindi bloccare anche video brevi. La durata effettiva è verificata prima dell’elaborazione.
Esempio di caricamento e creazione
Questo esempio Node.js 24 carica parti e crea un progetto review. Salvalo come upload-and-create.mjs. Legge il file per parti senza caricare tutto il video in memoria. Non modificarlo durante l’esecuzione.
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);
Genera una volta le chiavi di azione per caricamento e creazione. Conservale per le stesse richieste; altri video o azioni richiedono nuove chiavi.
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
Sostituisci percorso e 60 con file e durata misurata in secondi. Usa FFprobe o simili; non stimare dalla dimensione. Lo script mostra ID operazione per recupero, poi ID caricamento e progetto.
L’impronta SHA-256 usa il prefisso di metadati UTF-8 indicato e i primi e ultimi 64 KiB senza sovrapposizioni duplicate. Mantieni nome, dimensione, modifica e MIME durante la ripresa.
Segui il progetto
Imposta PROJECT_ID con l’ID mostrato:
export PROJECT_ID='<project-id>'
curl --fail-with-body "https://api.captionbolt.com/v1/projects/$PROJECT_ID" \
-H "Authorization: Bearer $CAPTIONBOLT_API_KEY"
Leggi status e rispetta l’intervallo pollAfterSeconds:
| Stato | Cosa fare |
| --- | --- |
| processing | Attendi preparazione e sottotitoli |
| ready | Rivedi e salva in CaptionBolt, poi esporta |
| exporting | Attendi l’esportazione |
| completed | Recupera il risultato e scarica |
| failed | Leggi error, risolvi e richiedi esplicitamente un nuovo tentativo |
| cancelled | Ferma le richieste; questa esecuzione è annullata |
Un’operazione completed non indica video terminato. Le operazioni descrivono azioni; lo stato del progetto descrive il video. Usa GET /v1/operations/{id}; fermati su failed e leggi error.
Alla creazione puoi indicare language e uno tra styleId o presetId dalle liste. Ometti entrambi per lo stile predefinito. mode: "auto-export" esporta MP4 dopo elaborazione e richiede anche exports:write. Se resta su ready, controlla l’operazione autoExport.
Esporta le modifiche salvate
Per review, rivedi e salva nell’editor CaptionBolt. Genera una chiave di esportazione una volta e richiedi:
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"]}'
Controlla l’operazione e poi il progetto fino a completed; fermati se uno fallisce. Si esportano solo le ultime modifiche salvate. outputFormats accetta mp4, srt, vtt, ass e txt secondo il piano attuale. MP4 è predefinito.
Scarica i file
Quando il progetto è completed:
curl --fail-with-body "https://api.captionbolt.com/v1/projects/$PROJECT_ID/result" \
-H "Authorization: Bearer $CAPTIONBOLT_API_KEY"
La risposta include files, ciascuno con format e url, e expiresAt per i link. Scarica dagli URL senza inviare la chiave API.
I link durano al massimo dieci minuti. Rinnovali entro i 14 giorni di accesso dalla creazione. Rinnovare non estende l’accesso. expiresAt del progetto indica il termine dell’accesso, quello del risultato la scadenza dei link.
I progetti completati conservano l’esportazione. Crea una copia modificabile in CaptionBolt per editarli di nuovo.
Gestisci gli errori
| Risposta | Prossimo passo |
| --- | --- |
| 400 | Controlla campi e header di idempotenza richiesti |
| 401 | Controlla validità, scadenza e revoca della chiave |
| 403 | Controlla permessi e limiti; scrittura non consente di leggere avanzamento |
| 404 | Controlla ID e account; progetti eliminati o scaduti non disponibili |
| 409 | Controlla stato o riuso della chiave con input diverso |
| 429 | Rispetta Retry-After e leggi l’errore; frequenza o capacità di caricamenti/attività possono essere limitate. REST e MCP condividono 120 richieste per account al minuto |
| 503 o timeout | Attendi, controlla un’operazione nota o ripeti l’azione identica con la chiave originale |
Il successo HTTP può accettare un’azione che fallisce dopo. Controlla operation.state e operation.error. PAYMENT_REQUIRED indica minuti insufficienti, forse riservati da attività in corso. PAYLOAD_TOO_LARGE indica file oltre il limite. Consulta GET /v1/account, correggi e avvia nuova azione con nuova chiave solo dopo il fallimento confermato. La vecchia restituisce la stessa operazione fallita.
Creazione di caricamenti e progetti, esportazione, nuovi tentativi e annullamento richiedono Idempotency-Key: 1–128 caratteri ASCII stampabili senza spazi. Nuova azione, nuova chiave. Dopo timeout, conserva chiave e input; perdere la risposta non dimostra fallimento.
Per riprendere, confronta metadati e impronta con GET /v1/uploads/{uploadId}, poi chiama POST /v1/uploads/{uploadId}/resume. Usa stato e parti restituiti. Se resta completing, attendi; con uploading, carica solo le parti mancanti e completa. expired o aborted richiede un nuovo caricamento. DELETE /v1/uploads/{uploadId} interrompe un caricamento non consumato, non un progetto creato.
Per azioni esplicite invia JSON vuoto a POST /v1/projects/{projectId}/retry o /cancel, con chiave propria. Un nuovo tentativo di esportazione richiede anche exports:write.
GET /health/integrations non richiede chiave: 200 indica pronto, 503 indisponibile. Non verifica credenziali o completamento del video.
Schemi completi nella documentazione API (si apre in una nuova finestra) e in OpenAPI. Usa Webhooks per ricevere aggiornamenti senza polling.