# Cerul API Documentation: https://cerul.ai/docs Base URL: https://api.cerul.ai (no /v1). # クイックスタート Console で作成した Key に YOUR_API_KEY を置き換え、動画一覧を取得します。接続確認のためのアップロードは不要です。 ```bash curl --fail-with-body "https://api.cerul.ai/videos" \ -H "Authorization: Bearer YOUR_API_KEY" ``` 動画一覧は data に入ります。新しい Workspace では空の一覧が正常です。ready の動画があれば[検索](/docs.md?locale=ja&page=search)、なければ[アップロード](/docs.md?locale=ja&page=upload)へ進みます。 ## リクエストパラメータ | パラメータ | 型 · 必須 | 説明 | | --- | --- | --- | | Authorization | string · 必須 | Bearer YOUR_API_KEY | | library_id | string · 任意 | pattern: ^lib_[A-Za-z0-9_-]+$ | ## レスポンスの読み方 data[].id は動画 ID、filename はファイル名、status は処理状況です。ready は検索可能を意味しますが、coverage と warnings で音声・映像・OCR の実際の結果を確認してください。 ## 完全なサンプル [完全なサンプルをダウンロード](/docs-quickstart.sh) 同じ Bash セッションで実行します。curl(7.76 以降)、jq、Python 3、ffprobe が必要です。動画を talk.mp4 に置いてください。処理と検索はクレジットを消費し、同じ入力の再試行では run ID を保持します。 ベース URL は https://api.cerul.ai(/v1 なし)です。Authorization: Bearer を送信します。アップロード、索引化、検索、書き出しの前にメールを確認してください。Key はサーバー側で保持し、同じ Workspace の Key・Web・CLI のクラウド呼び出しは一つの残高を共有します。 ## 再試行とエラー 検索を含め、必要な API には 8~200 文字の Idempotency-Key を送信します。結果が不明な場合は同じキーと入力で再試行し、異なる操作や入力には新しいキーを使います。待機中のジョブは Retry-After に従い、期限を設定します。クライアントのタイムアウトはジョブを取り消しません。 • 401 · invalid_credentials: 認証情報の誤り・期限切れ・失効を確認してください。 • 403 · permission_denied / insufficient_entitlement: スコープ、メール確認、利用権、残高を確認してください。 • 429 · rate_limited: retry_after_seconds があれば従い、同時実行上限も確認してください。 --- # 動画をアップロード 動画を talk.mp4 に置き、YOUR_API_KEY を置き換えます。このリクエストは実際のサイズで動画とアップロード URL を作成します。ファイル本体の転送は次の手順です。 ```bash REQUEST_KEY="upload-$(uuidgen)" FILE_SIZE=$(wc -c < ./talk.mp4 | tr -d ' ') curl --fail-with-body "https://api.cerul.ai/videos" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $REQUEST_KEY" \ -d "{\"filename\":\"talk.mp4\",\"media_type\":\"video/mp4\",\"byte_size\":$FILE_SIZE}" ``` ## リクエストパラメータ | パラメータ | 型 · 必須 | 説明 | | --- | --- | --- | | Authorization | string · 必須 | Bearer YOUR_API_KEY | | filename | string · 必須 | minLength: 1; maxLength: 255 | | media_type | string · 必須 | minLength: 3; maxLength: 127; pattern: ^[Vv][Ii][Dd][Ee][Oo]/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$ | | byte_size | integer · 必須 | ≥ 1; ≤ 5368709120 | | library_id | string · 任意 | Omit to file the video in the workspace default library. — pattern: ^lib_[A-Za-z0-9_-]+$ | | execution_policy | string · 任意 | local_only, prefer_local, cloud_allowed, cloud_required | | Idempotency-Key | string · 必須 | minLength: 8; maxLength: 200 | Required scopes: assets:write. ## ファイル本体を転送 data.id を保存します。data.upload.parts の順に、指定されたバイト範囲を一時 URL へ PUT し、data.upload.required_headers を使用します。アップロード URL に API Key を付けないでください。最大 5 GiB、URL からの取り込みは未対応です。 [完全なサンプルをダウンロード](/docs-quickstart.sh) 同じ Bash セッションで実行します。curl(7.76 以降)、jq、Python 3、ffprobe が必要です。動画を talk.mp4 に置いてください。処理と検索はクレジットを消費し、同じ入力の再試行では run ID を保持します。 ## アップロード完了を確認 全パートの転送後、POST /videos/{video_id}/complete を Bearer、Content-Type: application/json、新しい Idempotency-Key とともに呼び出し、実際の長さと SHA-256 を送信します。標準では索引化も開始するため、重複して索引化を依頼しないでください。 | パラメータ | 型 · 必須 | 説明 | | --- | --- | --- | | Authorization | string · 必須 | Bearer YOUR_API_KEY | | duration_seconds | number · 索引化する場合は必須 | Source duration, used for quota admission before transcription. — > 0 | | content_sha256 | string · 索引化する場合は必須 | Declares source identity for indexing. The Worker verifies it against the uploaded bytes and successful unique-material billing is deduplicated by this hash; a distinct video asset may still be processed independently. — pattern: ^[a-fA-F0-9]{64}$ | | index | boolean · 任意 | false: upload only; true: start indexing — default: true | | processing | object · 任意 | | | processing.ocr | object · 任意 | | | processing.ocr.enabled | boolean · 任意 | Extract visible text as independently sampled evidence. Defaults to false. — default: false | | processing.ocr.interval_seconds | integer · 任意 | OCR sampling interval. Defaults to 10 seconds. — ≥ 1; ≤ 3600; default: 10 | | execution_policy | string · 任意 | local_only, prefer_local, cloud_allowed, cloud_required | | video_id | string · 必須 | A Video is the developer-facing name for a media Asset, and shares its identifier. — pattern: ^asset_[A-Za-z0-9_-]+$ | | Idempotency-Key | string · 必須 | minLength: 8; maxLength: 200 | Required scopes: assets:write, library-items:write. 索引化には jobs:write、assets:read、artifacts:write も必要です。保存のみの場合は index:false とし、後から POST /videos/{video_id}/index を呼び出します。 ## 索引化を待つ 期限を決めて GET /videos/{video_id} を確認します。ready は検索可能、failed は error を確認、processing は待機します。クライアントのタイムアウトではサーバーの処理は取り消されません。 OCR は標準で無効です。完了前に OCR_ENABLED=true を設定すると 10 秒間隔で画面内文字を索引化します。ready は検索可能を意味し、各モダリティの実際の結果は coverage と warnings で確認します。音声のない動画には文字起こしがありません。 --- # 検索 YOUR_API_KEY と YOUR_VIDEO_ID を置き換え、ready の動画を検索します。同じリクエストの再試行では REQUEST_KEY を変えないでください。 ```bash REQUEST_KEY="search-$(uuidgen)" curl --fail-with-body "https://api.cerul.ai/search" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $REQUEST_KEY" \ -d '{ "query": "When did they discuss scaling laws?", "filter": {"video_ids": ["YOUR_VIDEO_ID"]}, "limit": 5 }' ``` ## リクエストパラメータ | パラメータ | 型 · 必須 | 説明 | | --- | --- | --- | | Authorization | string · 必須 | Bearer YOUR_API_KEY | | query | string · 必須 | minLength: 1; maxLength: 1000 | | filter | object · 任意 | Omit to search the whole workspace. | | filter.video_ids | array · 任意 | uniqueItems | | filter.library_ids | array · 任意 | uniqueItems | | execution_policy | string · 任意 | local_only, prefer_local, cloud_allowed, cloud_required | | limit | integer · 任意 | ≥ 1; ≤ 100; default: 20 | | Idempotency-Key | string · 必須 | minLength: 8; maxLength: 200 | Required scopes: libraries:read, assets:read. ## 結果を読む data の各項目には evidence と score があります。evidence.id は証拠 ID、asset_id はソース、start_seconds と end_seconds は動画内の秒数です。quote は証拠テキスト、kind と modality は音声・OCR・映像を区別します。score は関連度の順位付けであり、正確さの確率ではありません。 request_id、usage、warnings を保存します。ソース・時刻・証拠テキストを表示し、元動画で確認してから[クリップを書き出し](/docs.md?locale=ja&page=clips)ます。 ## 一致がない場合 空の data はリクエスト失敗ではありません。ready、filter、coverage を確認してから検索文を調整します。有効な検索は一致がなくても課金される場合があり、索引済み証拠がない場合は非課金です。GET /videos/{video_id}/transcript は音声転記を返し、音声なしでは 422 no_audio_track になる場合があります。 --- # 書き出しと管理 Key と動画 ID を置き換え、30~45 秒のクリップを依頼します。動画にその区間があることを確認してください。返るのは非同期ジョブであり、完成済みのダウンロードではありません。 ```bash REQUEST_KEY="clip-$(uuidgen)" curl --fail-with-body "https://api.cerul.ai/clips" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $REQUEST_KEY" \ -d '{"video_id":"YOUR_VIDEO_ID","start_s":30,"end_s":45}' ``` ## リクエストパラメータ | パラメータ | 型 · 必須 | 説明 | | --- | --- | --- | | Authorization | string · 必須 | Bearer YOUR_API_KEY | | video_id | string · 必須 | A Video is the developer-facing name for a media Asset, and shares its identifier. — pattern: ^asset_[A-Za-z0-9_-]+$ | | start_s | number · 必須 | ≥ 0 | | end_s | number · 必須 | > 0 | | evidence_ids | array · 任意 | maxItems: 100; uniqueItems | | execution_policy | string · 任意 | local_only, prefer_local, cloud_allowed, cloud_required | | Idempotency-Key | string · 必須 | minLength: 8; maxLength: 200 | Required scopes: jobs:write, assets:read, artifacts:write. ## 完了を待ってダウンロード data.id を JOB_ID として保存し、Retry-After と期限に従い GET /jobs/{job_id} を確認します。succeeded なら GET /jobs/{job_id}/artifacts からタイプとメタデータでクリップを選び、Bearer 付きで GET /artifacts/{artifact_id}/content をダウンロードします。ジョブ参照には jobs:read、成果物には artifacts:read が必要です。failed または canceled では確認を停止します。 ```bash curl --fail-with-body "https://api.cerul.ai/artifacts/YOUR_ARTIFACT_ID/content" \ -H "Authorization: Bearer YOUR_API_KEY" \ --output clip.mp4 ``` ## 取消と削除 POST /jobs/{job_id}/cancel で取消を依頼し、終端状態まで確認します。DELETE /videos/{video_id} は動画と関連データ(assets:write)、DELETE /artifacts/{artifact_id} は単一の成果物(artifacts:write)を削除します。202 の受付後は GET /deletions/{deletion_id} で清掃完了を確認します。 正常な検索は一致がなくても課金される場合があります。索引済み証拠がない検索は非課金です。失敗・取消でも完了分は精算され、未使用の予約枠が解放されます。削除の 202 応答後は完了まで受領票を確認します。/billing/wallet で利用可能・予約済み・債務を別々に確認できます。 # Complete Bash example ```bash #!/usr/bin/env bash # Requires Bash, curl 7.76+, jq, Python 3, ffprobe and uuidgen. # Place a video at talk.mp4 and set CERUL_API_KEY before running. # Processing and search consume credits. OCR is off by default. set -euo pipefail : "${CERUL_API_KEY:?Set CERUL_API_KEY before running}" CERUL_QUICKSTART_RUN_ID=$(uuidgen) export CERUL_QUICKSTART_RUN_ID export OCR_ENABLED=false CERUL_TMP=$(mktemp -d) trap 'rm -rf "$CERUL_TMP"' EXIT cerul() { curl --fail-with-body --silent --show-error \ --connect-timeout 15 --max-time 120 \ -H "Authorization: Bearer $CERUL_API_KEY" \ -H "Content-Type: application/json" "$@" } cerul https://api.cerul.ai/videos | jq '.data' FILE_SIZE=$(wc -c < ./talk.mp4 | tr -d ' ') CONTENT_SHA256=$(python3 - <<'PY' import hashlib digest = hashlib.sha256() with open('talk.mp4', 'rb') as source: for chunk in iter(lambda: source.read(1024 * 1024), b''): digest.update(chunk) print(digest.hexdigest()) PY ) DURATION_SECONDS=$(ffprobe -v error -show_entries format=duration -of csv=p=0 ./talk.mp4) VIDEO=$(cerul https://api.cerul.ai/videos \ -H "Idempotency-Key: add-video-$CERUL_QUICKSTART_RUN_ID" \ -d "$(jq -n --argjson size "$FILE_SIZE" \ '{filename:"talk.mp4",media_type:"video/mp4",byte_size:$size}')") CERUL_VIDEO_ID=$(printf '%s' "$VIDEO" | jq -er '.data.id') export CERUL_VIDEO_ID printf 'Video ID: %s\n' "$CERUL_VIDEO_ID" UPLOAD_CONTENT_TYPE=$(printf '%s' "$VIDEO" | jq -er '.data.upload.required_headers["Content-Type"]') printf '%s' "$VIDEO" | jq -e '.data.upload.parts | length > 0' >/dev/null printf '%s' "$VIDEO" | jq -c '.data.upload.parts[]' > "$CERUL_TMP/parts.jsonl" # Upload exact byte ranges using only the signed headers, without the API Key. OFFSET=0 while IFS= read -r PART; do PART_URL=$(printf '%s' "$PART" | jq -er '.url') PART_SIZE=$(printf '%s' "$PART" | jq -er '.byte_size') python3 - "$OFFSET" "$PART_SIZE" "$CERUL_TMP/part.bin" <<'PY' import sys offset, size, target = sys.argv[1:] remaining = int(size) with open('talk.mp4', 'rb') as source, open(target, 'wb') as part: source.seek(int(offset)) while remaining: chunk = source.read(min(1024 * 1024, remaining)) if not chunk: raise RuntimeError('Source shorter than reserved upload') part.write(chunk) remaining -= len(chunk) PY curl --fail-with-body --silent --show-error -X PUT "$PART_URL" \ --connect-timeout 15 --max-time 1800 \ -H "Content-Type: $UPLOAD_CONTENT_TYPE" -H "Content-Length: $PART_SIZE" \ --upload-file "$CERUL_TMP/part.bin" OFFSET=$((OFFSET + PART_SIZE)) done < "$CERUL_TMP/parts.jsonl" # Defaults to speech and visual indexing. OCR is an explicit paid option. cerul "https://api.cerul.ai/videos/$CERUL_VIDEO_ID/complete" \ -H "Idempotency-Key: complete-video-$CERUL_QUICKSTART_RUN_ID" \ -d "$(jq -n --argjson duration "$DURATION_SECONDS" --arg hash "$CONTENT_SHA256" \ --argjson ocr "$OCR_ENABLED" \ '{duration_seconds:$duration,content_sha256:$hash,processing:{ocr:{enabled:$ocr,interval_seconds:10}}}')" \ > "$CERUL_TMP/completed.json" DEADLINE=$((SECONDS + 1800)) while :; do VIDEO=$(cerul "https://api.cerul.ai/videos/$CERUL_VIDEO_ID") STATUS=$(printf '%s' "$VIDEO" | jq -er '.data.status') case "$STATUS" in ready) printf '%s' "$VIDEO" | jq '.data.coverage'; break ;; failed) printf '%s' "$VIDEO" | jq '.data.error' >&2; exit 1 ;; uploading|uploaded|processing) ;; *) echo "Unexpected video status: $STATUS" >&2; exit 1 ;; esac if (( SECONDS >= DEADLINE )); then echo "Timed out; save $CERUL_VIDEO_ID and resume polling. The server job continues." >&2 exit 1 fi sleep 5 done SEARCH=$(cerul https://api.cerul.ai/search \ -H "Idempotency-Key: search-quickstart-$CERUL_QUICKSTART_RUN_ID" \ -d "$(jq -n --arg video "$CERUL_VIDEO_ID" \ '{query:"when did they discuss scaling laws",filter:{video_ids:[$video]},limit:5}')") printf '%s' "$SEARCH" | jq '{request_id,usage,warnings,data}' # A valid search can return no results. Export only a bounded evidence span. if ! HIT=$(printf '%s' "$SEARCH" | jq -ce 'first(.data[] | select( (.evidence.start_seconds | type) == "number" and (.evidence.end_seconds | type) == "number" and .evidence.end_seconds > .evidence.start_seconds))'); then echo "No evidence span to export. Review the query or available coverage." exit 0 fi CLIP_BODY=$(printf '%s' "$HIT" | jq --arg video "$CERUL_VIDEO_ID" \ '{video_id:$video,start_s:.evidence.start_seconds,end_s:.evidence.end_seconds,evidence_ids:[.evidence.id]}') CLIP=$(cerul https://api.cerul.ai/clips \ -H "Idempotency-Key: clip-quickstart-$CERUL_QUICKSTART_RUN_ID" -d "$CLIP_BODY") CLIP_JOB_ID=$(printf '%s' "$CLIP" | jq -er '.data.id') DEADLINE=$((SECONDS + 1800)) while :; do JOB=$(cerul "https://api.cerul.ai/jobs/$CLIP_JOB_ID" -D "$CERUL_TMP/job-headers") CLIP_STATUS=$(printf '%s' "$JOB" | jq -er '.data.status') case "$CLIP_STATUS" in succeeded) break ;; failed|canceled) printf '%s' "$JOB" | jq '.data' >&2; exit 1 ;; esac if (( SECONDS >= DEADLINE )); then echo "Timed out; resume polling job $CLIP_JOB_ID. The server job continues." >&2 exit 1 fi RETRY_AFTER=$(awk 'tolower($1)=="retry-after:" {gsub("\r", "", $2); print $2}' "$CERUL_TMP/job-headers") case "$RETRY_AFTER" in ''|*[!0-9]*) RETRY_AFTER=3 ;; esac sleep "$RETRY_AFTER" done ARTIFACTS=$(cerul "https://api.cerul.ai/jobs/$CLIP_JOB_ID/artifacts") ARTIFACT_ID=$(printf '%s' "$ARTIFACTS" | jq -er '.data[0].id') cerul "https://api.cerul.ai/artifacts/$ARTIFACT_ID" | jq '.data' cerul "https://api.cerul.ai/artifacts/$ARTIFACT_ID/content" --output quickstart-clip.mp4 ``` # API reference ```json { "openapi": "3.1.0", "info": { "title": "Cerul API — Daily integration", "version": "1.0.0-foundation", "description": "Customer upload, indexing, search, clip export and resource management." }, "servers": [ { "url": "https://api.cerul.ai", "description": "Cloud runtime" } ], "security": [ { "bearerAuth": [] } ], "tags": [ { "name": "Libraries", "description": "Content collection and membership" }, { "name": "Videos", "description": "Video ingestion and lifecycle" }, { "name": "Search", "description": "Scoped retrieval and evidence" }, { "name": "Jobs", "description": "Asynchronous capability and workflow execution" }, { "name": "Artifacts", "description": "Structured, renderable outputs and human review" }, { "name": "Deletions", "description": "Asynchronous workspace-scoped cloud content erasure" } ], "paths": { "/libraries": { "get": { "operationId": "listLibraries", "x-cerul-required-scopes": [ "libraries:read" ], "summary": "List libraries visible to the caller", "tags": [ "Libraries" ], "responses": { "200": { "description": "Libraries", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LibraryListResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } }, "post": { "operationId": "createLibrary", "x-cerul-required-scopes": [ "libraries:write" ], "summary": "Create a library", "tags": [ "Libraries" ], "parameters": [ { "$ref": "#/components/parameters/IdempotencyKey" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateLibraryRequest" } } } }, "responses": { "201": { "description": "Library created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LibraryResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/libraries/{library_id}/items": { "post": { "operationId": "addLibraryItem", "x-cerul-required-scopes": [ "library-items:write" ], "summary": "Add an asset to a library", "tags": [ "Libraries" ], "parameters": [ { "$ref": "#/components/parameters/LibraryId" }, { "$ref": "#/components/parameters/IdempotencyKey" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddLibraryItemRequest" } } } }, "responses": { "202": { "description": "Library item accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LibraryItemResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/videos": { "get": { "operationId": "listVideos", "x-cerul-required-scopes": [ "assets:read" ], "summary": "List videos", "description": "Lists videos in the caller's workspace, optionally restricted to one owned library, including its unfinished uploads. Empty libraries return an empty list; missing or foreign libraries return not_found. Per-video `data[].coverage` is authoritative. This collection does not aggregate coverage failures into a top-level warning without video identity.", "tags": [ "Videos" ], "parameters": [ { "name": "library_id", "in": "query", "required": false, "schema": { "$ref": "#/components/schemas/LibraryId" } } ], "responses": { "200": { "description": "Videos in the workspace, newest first", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VideoListResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } }, "post": { "operationId": "createVideo", "x-cerul-required-scopes": [ "assets:write" ], "summary": "Add a video", "description": "Starts ingesting one video and returns it with a derived status. Supply\n`filename`, `media_type` and `byte_size` to upload bytes: the response\ncarries short-lived presigned PUT URLs in `upload.parts`. Upload each\nexact byte range with the returned headers, then call\nPOST /videos/{video_id}/complete. Maximum size is 5 GiB. A verified\nemail is required. Omit library_id to use the workspace default library.\nURL ingestion, including YouTube links, is not available; the reserved\n`url` form returns HTTP 503 `capability_unavailable`.\n", "tags": [ "Videos" ], "parameters": [ { "$ref": "#/components/parameters/IdempotencyKey" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateVideoRequest" } } } }, "responses": { "201": { "description": "Video accepted, with upload instructions when bytes are expected", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateVideoResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/videos/{video_id}/complete": { "post": { "operationId": "completeVideoUpload", "x-cerul-required-scopes": [ "assets:write", "library-items:write" ], "summary": "Complete a video upload", "description": "Confirms the uploaded bytes, files the video in a library, and starts\nspeech and visual evidence indexing unless `index` is false. OCR is\ndisabled by default; opt in with processing.ocr.enabled and optionally\nprocessing.ocr.interval_seconds (default 10). Indexing additionally\nrequires jobs:write, assets:read and artifacts:write, a verified email\nand sufficient prepaid credits. Supply the actual duration and SHA-256.\nPoll GET /videos/{video_id} for ready, then inspect per-modality coverage;\nready does not guarantee every modality succeeded. Exact\nIdempotency-Key replays return the original operation. Successful\nunique-material billing is deduplicated by `content_sha256`; a distinct\nvideo asset with the same bytes may still be processed independently.\n", "tags": [ "Videos" ], "parameters": [ { "$ref": "#/components/parameters/VideoId" }, { "$ref": "#/components/parameters/IdempotencyKey" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompleteVideoRequest" } } } }, "responses": { "200": { "description": "Video with its derived status and index job", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VideoResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/videos/{video_id}/index": { "post": { "operationId": "indexVideo", "x-cerul-required-scopes": [ "jobs:write", "assets:read", "library-items:write", "artifacts:write" ], "summary": "Index a video", "description": "Starts speech and keyframe evidence indexing for a video completed with index false. OCR is disabled unless processing.ocr.enabled is true. Requires verified email and sufficient prepaid credits. execution_policy defaults to cloud_required. Idempotent by Idempotency-Key for the same video; successful unique-material billing is deduplicated by content hash.", "tags": [ "Videos" ], "parameters": [ { "$ref": "#/components/parameters/VideoId" }, { "$ref": "#/components/parameters/IdempotencyKey" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IndexVideoRequest" } } } }, "responses": { "202": { "description": "Index job accepted or replayed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/videos/{video_id}/transcript": { "get": { "operationId": "getVideoTranscript", "x-cerul-required-scopes": [ "artifacts:read" ], "summary": "Read a video transcript", "description": "Returns the whole transcript once indexing has succeeded, so a caller need not walk from the job to its artifacts.", "tags": [ "Videos" ], "parameters": [ { "$ref": "#/components/parameters/VideoId" } ], "responses": { "200": { "description": "Transcript segments with timecodes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscriptResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/videos/{video_id}": { "get": { "operationId": "getVideo", "x-cerul-required-scopes": [ "assets:read" ], "summary": "Get a video", "description": "Returns the video with a status derived from its newest index job, so one poll answers whether it is searchable yet.", "tags": [ "Videos" ], "parameters": [ { "$ref": "#/components/parameters/VideoId" } ], "responses": { "200": { "description": "Video", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VideoResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } }, "delete": { "operationId": "deleteVideo", "summary": "Delete a video and reclaim its persisted storage", "description": "Accepts an idempotent asynchronous erasure of the video and all\ncloud-derived content and references. After safety preflight succeeds,\nthe video becomes unavailable immediately, active work is canceled or\nfenced by the deletion workflow, and storage quota is released only\nafter cleanup succeeds. A failed safety preflight has no tombstone or\ncancellation side effect.\nContent-free billing and security audit records may be retained. This\noperation never deletes or modifies local App content.\nReusing the same Idempotency-Key only replays the current receipt. If\na non-retryable safety preflight later becomes resolvable, a new key\nre-arms the same canonical deletion resource without losing completed\ncleanup phases.\n", "tags": [ "Videos" ], "x-cerul-required-scopes": [ "assets:write" ], "parameters": [ { "$ref": "#/components/parameters/VideoId" }, { "$ref": "#/components/parameters/IdempotencyKey" } ], "responses": { "202": { "description": "Video deletion accepted or idempotently replayed", "headers": { "Location": { "description": "Relative URL of the deletion status resource", "required": true, "schema": { "type": "string", "format": "uri-reference" } }, "Cache-Control": { "description": "Deletion responses must not be cached", "required": true, "schema": { "type": "string", "example": "no-store" } }, "Retry-After": { "description": "Suggested delay in seconds before refreshing deletion state", "required": true, "schema": { "type": "integer", "minimum": 1 } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResourceDeletionResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/clips": { "post": { "operationId": "createClipExport", "summary": "Create an asynchronous cloud clip export from a bounded time range", "description": "The resulting clip_gallery Artifact exposes authenticated content at its content_url. In addition to jobs:write, authorization follows the media.clip@1 required_scopes in the canonical capability registry.", "tags": [ "Videos" ], "x-cerul-required-scopes": [ "jobs:write", "assets:read", "artifacts:write" ], "parameters": [ { "$ref": "#/components/parameters/IdempotencyKey" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClipExportRequest" } } } }, "responses": { "202": { "description": "Clip export job accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/search": { "post": { "operationId": "searchEvidence", "x-cerul-required-scopes": [ "libraries:read", "assets:read" ], "summary": "Search scoped libraries and assets for evidence", "description": "Cloud requests require verified email and sufficient prepaid credits. Omit filter to search the current workspace, or restrict by filter.video_ids or filter.library_ids. Scores rank candidate evidence; they are not correctness probabilities. A completed Cloud search with no matches may still be billable; a search with no indexed evidence is not billable. Reuse the same Idempotency-Key only with unchanged input.", "tags": [ "Search" ], "parameters": [ { "$ref": "#/components/parameters/IdempotencyKey" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchRequest" } } } }, "responses": { "200": { "description": "Search results", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/jobs/{job_id}": { "get": { "operationId": "getJob", "x-cerul-required-scopes": [ "jobs:read" ], "summary": "Get job state", "description": "Returns state and progress. Pending jobs include Retry-After; wait that many seconds before polling again. Failed jobs expose their asynchronous execution error.", "tags": [ "Jobs" ], "parameters": [ { "$ref": "#/components/parameters/JobId" } ], "responses": { "200": { "description": "Job", "headers": { "Retry-After": { "description": "Suggested polling delay in seconds; present while the job is pending.", "schema": { "type": "integer", "minimum": 1 } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/jobs/{job_id}/cancel": { "post": { "operationId": "cancelJob", "x-cerul-required-scopes": [ "jobs:write" ], "summary": "Request idempotent cancellation of a job", "description": "Queued cancellation releases unused prepaid reservations. Running jobs stop cooperatively, settle durable successful billable units, and release the remainder. Canceling a terminal job does not reverse completed usage.", "tags": [ "Jobs" ], "parameters": [ { "$ref": "#/components/parameters/JobId" }, { "$ref": "#/components/parameters/IdempotencyKey" } ], "responses": { "200": { "description": "Job cancellation state", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/jobs/{job_id}/artifacts": { "get": { "operationId": "listJobArtifacts", "x-cerul-required-scopes": [ "jobs:read", "artifacts:read" ], "summary": "List artifacts created by a job", "tags": [ "Artifacts" ], "parameters": [ { "$ref": "#/components/parameters/JobId" } ], "responses": { "200": { "description": "Job artifacts", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtifactListResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/artifacts/{artifact_id}": { "get": { "operationId": "getArtifact", "x-cerul-required-scopes": [ "artifacts:read" ], "summary": "Get a structured artifact", "tags": [ "Artifacts" ], "parameters": [ { "$ref": "#/components/parameters/ArtifactId" } ], "responses": { "200": { "description": "Artifact", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtifactResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } }, "delete": { "operationId": "deleteArtifact", "summary": "Delete an artifact and reclaim generated-object storage", "description": "Accepts an idempotent asynchronous erasure of the Artifact, its\nreviews, exclusive generated content, and retained references. The\nArtifact becomes unavailable immediately after safety preflight. A\nfailed preflight has no tombstone or cancellation side effect. Its\nsource Asset and unrelated sibling Artifacts are not deleted.\nContent-free billing and security audit records may be retained.\nReusing the same Idempotency-Key only replays the current receipt. If\na non-retryable safety preflight later becomes resolvable, a new key\nre-arms the same canonical deletion resource without losing completed\ncleanup phases.\n", "tags": [ "Artifacts" ], "x-cerul-required-scopes": [ "artifacts:write" ], "parameters": [ { "$ref": "#/components/parameters/ArtifactId" }, { "$ref": "#/components/parameters/IdempotencyKey" } ], "responses": { "202": { "description": "Artifact deletion accepted or idempotently replayed", "headers": { "Location": { "description": "Relative URL of the deletion status resource", "required": true, "schema": { "type": "string", "format": "uri-reference" } }, "Cache-Control": { "description": "Deletion responses must not be cached", "required": true, "schema": { "type": "string", "example": "no-store" } }, "Retry-After": { "description": "Suggested delay in seconds before refreshing deletion state", "required": true, "schema": { "type": "integer", "minimum": 1 } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResourceDeletionResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/deletions/{deletion_id}": { "get": { "operationId": "getResourceDeletion", "x-cerul-required-scopes": [], "summary": "Get asynchronous cloud deletion state", "description": "Requires assets:write for an Asset deletion and artifacts:write for an Artifact deletion.", "tags": [ "Deletions" ], "x-cerul-dynamic-required-scopes": "deletion_resource_type", "parameters": [ { "$ref": "#/components/parameters/DeletionId" } ], "responses": { "200": { "description": "Workspace-scoped deletion state", "headers": { "Cache-Control": { "description": "Deletion status responses must not be cached", "schema": { "type": "string", "example": "no-store" } }, "Retry-After": { "description": "Suggested polling delay in seconds while deletion is queued or running", "schema": { "type": "integer", "minimum": 1 } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResourceDeletionResponse" } } } }, "default": { "$ref": "#/components/responses/Error" } } } }, "/videos/{video_id}/content": { "get": { "operationId": "getVideoContent", "x-cerul-required-scopes": [ "assets:read" ], "summary": "Stream authenticated source video", "tags": [ "Videos" ], "description": "Requires assets:read. Supports a single HTTP byte range for seeking; content remains workspace private and unavailable after deletion. Full and ranged responses preserve the uploaded video's media type, represented by video/*; playback support depends on the client's container and codec support.", "parameters": [ { "$ref": "#/components/parameters/VideoId" }, { "in": "header", "name": "Range", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Private source video", "content": { "video/*": { "schema": { "type": "string", "format": "binary" } }, "video/mp4": { "schema": { "type": "string", "format": "binary" } }, "video/webm": { "schema": { "type": "string", "format": "binary" } } } }, "206": { "description": "Requested byte range", "headers": { "Content-Range": { "schema": { "type": "string" } } }, "content": { "video/*": { "schema": { "type": "string", "format": "binary" } }, "video/webm": { "schema": { "type": "string", "format": "binary" } }, "video/mp4": { "schema": { "type": "string", "format": "binary" } } } }, "416": { "description": "Unsatisfiable byte range" }, "default": { "$ref": "#/components/responses/Error" } } } }, "/artifacts/{artifact_id}/content": { "get": { "operationId": "getArtifactContent", "x-cerul-required-scopes": [ "artifacts:read" ], "summary": "Stream authenticated content for a cloud Artifact", "tags": [ "Artifacts" ], "parameters": [ { "$ref": "#/components/parameters/ArtifactId" } ], "responses": { "200": { "description": "Private Artifact content", "headers": { "Cache-Control": { "schema": { "type": "string" } } }, "content": { "application/x-ndjson": { "schema": { "type": "string" } }, "text/html": { "schema": { "type": "string" } }, "video/mp4": { "schema": { "type": "string", "format": "binary" } } } }, "default": { "$ref": "#/components/responses/Error" } } } } }, "components": { "schemas": { "LibraryListResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Library" } } } } ] }, "ResponseMetadata": { "type": "object", "required": [ "request_id", "execution", "usage", "warnings" ], "properties": { "request_id": { "$ref": "#/components/schemas/RequestId" }, "execution": { "$ref": "#/components/schemas/Execution" }, "usage": { "$ref": "#/components/schemas/Usage" }, "warnings": { "type": "array", "items": { "type": "string" } } } }, "RequestId": { "type": "string", "pattern": "^req_[A-Za-z0-9]+$" }, "Execution": { "type": "object", "additionalProperties": false, "required": [ "location" ], "properties": { "location": { "$ref": "#/components/schemas/RuntimeLocation" }, "capability_id": { "type": [ "string", "null" ] }, "capability_version": { "type": [ "string", "null" ] } } }, "RuntimeLocation": { "type": "string", "enum": [ "local", "cloud" ] }, "Usage": { "type": "object", "additionalProperties": false, "required": [ "billable", "quantity", "unit" ], "properties": { "billable": { "description": "True when this logical request is metered and consumes Cerul credits, whether funded by the one-time free grant or prepaid top-ups.", "type": "boolean" }, "quantity": { "description": "Quantity metered synchronously for this response. Async submissions report zero and identify deferred settlement in usage_pending.", "type": "number", "minimum": 0 }, "unit": { "description": "Unit for quantity, such as request.", "type": "string" }, "usage_pending": { "description": "Indicates that any metering happens when the accepted background job settles, not in this response.", "type": "string", "enum": [ "background_job" ] } } }, "Library": { "type": "object", "additionalProperties": false, "required": [ "id", "name", "ingestion_profile", "created_at", "updated_at" ], "properties": { "id": { "$ref": "#/components/schemas/LibraryId" }, "workspace_id": { "oneOf": [ { "$ref": "#/components/schemas/WorkspaceId" }, { "type": "null" } ] }, "name": { "type": "string", "minLength": 1 }, "ingestion_profile": { "$ref": "#/components/schemas/IngestionProfile" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } } }, "LibraryId": { "type": "string", "pattern": "^lib_[A-Za-z0-9_-]+$" }, "WorkspaceId": { "type": "string", "pattern": "^ws_[A-Za-z0-9_-]+$" }, "IngestionProfile": { "type": "object", "additionalProperties": false, "required": [ "id", "version", "required_capabilities", "enrichment_schema", "segmentation_policy", "metadata_schema" ], "properties": { "id": { "type": "string" }, "version": { "type": "string" }, "required_capabilities": { "type": "array", "items": { "type": "string" } }, "enrichment_schema": { "type": "object", "additionalProperties": true }, "segmentation_policy": { "type": "object", "additionalProperties": true }, "metadata_schema": { "type": "object", "additionalProperties": true } } }, "ErrorEnvelope": { "type": "object", "additionalProperties": false, "required": [ "request_id", "error" ], "properties": { "request_id": { "$ref": "#/components/schemas/RequestId" }, "error": { "$ref": "#/components/schemas/ErrorDetail" } } }, "ErrorDetail": { "type": "object", "additionalProperties": false, "required": [ "code", "message", "retryable" ], "properties": { "code": { "type": "string", "enum": [ "invalid_request", "invalid_credentials", "invalid_grant", "invalid_client", "unauthorized", "forbidden", "permission_denied", "already_exists", "session_expired", "not_found", "no_audio_track", "conflict", "authorization_pending", "slow_down", "access_denied", "expired_token", "capability_unavailable", "scope_violation", "data_egress_not_authorized", "insufficient_entitlement", "rate_limited", "internal_error" ] }, "message": { "type": "string" }, "retryable": { "type": "boolean" }, "retry_after_seconds": { "description": "Suggested delay before retrying a throttled request, when supplied.", "type": "integer", "minimum": 1 }, "field": { "type": [ "string", "null" ] }, "details": { "type": "object", "additionalProperties": true } } }, "CreateLibraryRequest": { "type": "object", "additionalProperties": false, "required": [ "name", "ingestion_profile" ], "properties": { "name": { "type": "string", "minLength": 1 }, "ingestion_profile": { "$ref": "#/components/schemas/IngestionProfile" } } }, "LibraryResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/Library" } } } ] }, "AddLibraryItemRequest": { "type": "object", "additionalProperties": false, "required": [ "asset_id" ], "properties": { "asset_id": { "$ref": "#/components/schemas/AssetId" } } }, "AssetId": { "type": "string", "pattern": "^asset_[A-Za-z0-9_-]+$" }, "LibraryItemResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/LibraryItem" } } } ] }, "LibraryItem": { "type": "object", "additionalProperties": false, "required": [ "id", "library_id", "asset_id", "enrichment", "created_at", "updated_at" ], "properties": { "id": { "$ref": "#/components/schemas/LibraryItemId" }, "library_id": { "$ref": "#/components/schemas/LibraryId" }, "asset_id": { "$ref": "#/components/schemas/AssetId" }, "enrichment": { "type": "object", "additionalProperties": true }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } } }, "LibraryItemId": { "type": "string", "pattern": "^li_[A-Za-z0-9_-]+$" }, "VideoListResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Video" } } } } ] }, "Video": { "type": "object", "additionalProperties": false, "required": [ "id", "status", "coverage", "filename", "media_type", "byte_size", "duration_seconds", "error", "created_at", "indexed_at", "index_job_id" ], "properties": { "id": { "$ref": "#/components/schemas/VideoId" }, "status": { "description": "Derived from the newest index Job, never stored. `ready` means searchable.", "type": "string", "enum": [ "uploading", "uploaded", "processing", "ready", "failed" ] }, "coverage": { "description": "Per-modality result for the retained index generation; null before a valid generation exists.", "oneOf": [ { "$ref": "#/components/schemas/VideoCoverage" }, { "type": "null" } ] }, "filename": { "type": [ "string", "null" ] }, "media_type": { "type": [ "string", "null" ] }, "byte_size": { "type": [ "integer", "null" ], "minimum": 0 }, "duration_seconds": { "type": [ "number", "null" ], "minimum": 0 }, "error": { "description": "Why indexing failed, when it did.", "type": [ "string", "null" ] }, "created_at": { "type": [ "string", "null" ], "format": "date-time" }, "indexed_at": { "type": [ "string", "null" ], "format": "date-time" }, "annotate_job_id": { "type": [ "string", "null" ], "description": "Latest annotation job for this uploaded video." }, "index_job_id": { "type": [ "string", "null" ] }, "upload": { "description": "Present only in the response that created the video, while bytes are still expected.", "$ref": "#/components/schemas/VideoUploadInstructions" } } }, "VideoId": { "description": "A Video is the developer-facing name for a media Asset, and shares its identifier.", "type": "string", "pattern": "^asset_[A-Za-z0-9_-]+$" }, "VideoCoverage": { "type": "object", "additionalProperties": false, "required": [ "speech", "visual", "ocr" ], "properties": { "speech": { "$ref": "#/components/schemas/VideoModalityCoverage" }, "visual": { "$ref": "#/components/schemas/VideoModalityCoverage" }, "ocr": { "$ref": "#/components/schemas/VideoModalityCoverage" } } }, "VideoModalityCoverage": { "type": "object", "additionalProperties": false, "required": [ "status", "indexed_count", "reason" ], "properties": { "status": { "type": "string", "enum": [ "indexed", "absent", "failed" ] }, "indexed_count": { "type": "integer", "minimum": 0 }, "reason": { "type": [ "string", "null" ], "enum": [ "no_audio_track", "partial_failure", "asr_unavailable", "embedding_failed", "decode_failed", "model_unavailable", "startup_failed", "timeout", "oom", "invalid_output", "internal_error", "disabled", "no_text_detected", null ] }, "failed_count": { "description": "Present only when status is indexed and reason is partial_failure; omitted for absent and failed outcomes.", "type": "integer", "minimum": 1 } } }, "VideoUploadInstructions": { "type": "object", "additionalProperties": false, "required": [ "method", "url", "required_headers", "expires_at", "parts" ], "properties": { "method": { "type": "string", "const": "PUT" }, "url": { "type": "string", "format": "uri" }, "required_headers": { "type": "object", "additionalProperties": false, "required": [ "Content-Type", "Content-Length" ], "properties": { "Content-Type": { "type": "string", "minLength": 3, "maxLength": 127 }, "Content-Length": { "type": "string", "pattern": "^[1-9][0-9]{0,9}$" } } }, "expires_at": { "type": "string", "format": "date-time" }, "parts": { "description": "Upload each part with its own PUT. A dropped connection costs one part, not the whole file. A small file has exactly one.", "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": [ "part_number", "url", "byte_size" ], "properties": { "part_number": { "type": "integer", "minimum": 1, "maximum": 10000 }, "url": { "type": "string", "format": "uri" }, "byte_size": { "type": "integer", "minimum": 1 } } } } } }, "CreateVideoRequest": { "oneOf": [ { "$ref": "#/components/schemas/CreateVideoFromUrlRequest" }, { "$ref": "#/components/schemas/CreateVideoUploadRequest" } ] }, "CreateVideoFromUrlRequest": { "type": "object", "additionalProperties": false, "required": [ "url" ], "properties": { "url": { "description": "Public source to fetch. Planned; currently answers capability_unavailable.", "type": "string", "format": "uri", "maxLength": 2048 }, "library_id": { "description": "Omit to file the video in the workspace default library.", "$ref": "#/components/schemas/LibraryId" }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } }, "ExecutionPolicy": { "type": "string", "enum": [ "local_only", "prefer_local", "cloud_allowed", "cloud_required" ] }, "CreateVideoUploadRequest": { "type": "object", "additionalProperties": false, "required": [ "filename", "media_type", "byte_size" ], "properties": { "filename": { "type": "string", "minLength": 1, "maxLength": 255 }, "media_type": { "type": "string", "minLength": 3, "maxLength": 127, "pattern": "^[Vv][Ii][Dd][Ee][Oo]/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$" }, "byte_size": { "type": "integer", "minimum": 1, "maximum": 5368709120 }, "library_id": { "description": "Omit to file the video in the workspace default library.", "$ref": "#/components/schemas/LibraryId" }, "data_egress_confirmed": { "description": "Desktop replication only. Must be true when source_installation_id is present, so a copy of local content always follows one explicit user action.", "type": "boolean", "const": true }, "source_installation_id": { "type": "string", "minLength": 1, "maxLength": 200 }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } }, "CreateVideoResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/Video" } } } ] }, "CompleteVideoRequest": { "oneOf": [ { "$ref": "#/components/schemas/CompleteVideoWithoutIndexRequest" }, { "$ref": "#/components/schemas/CompleteAndIndexVideoRequest" } ] }, "CompleteVideoWithoutIndexRequest": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "index" ], "properties": { "duration_seconds": { "description": "Source duration, used for quota admission before transcription.", "type": "number", "minimum": 0 }, "content_sha256": { "description": "Declares source identity for upload completion. When indexing occurs, the Worker verifies it against the uploaded bytes and successful unique-material billing is deduplicated by this hash; a distinct video asset may still be processed independently.", "type": "string", "pattern": "^[a-fA-F0-9]{64}$" }, "index": { "description": "Leave the uploaded video unindexed.", "type": "boolean", "const": false }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } }, { "type": "object", "additionalProperties": false, "required": [ "index", "source_installation_id", "local_asset_id", "data_egress_confirmed" ], "properties": { "duration_seconds": { "description": "Source duration, used for quota admission before transcription.", "type": "number", "minimum": 0 }, "content_sha256": { "description": "Declares source identity for upload completion. When indexing occurs, the Worker verifies it against the uploaded bytes and successful unique-material billing is deduplicated by this hash; a distinct video asset may still be processed independently.", "type": "string", "pattern": "^[a-fA-F0-9]{64}$" }, "index": { "description": "Leave the uploaded video unindexed.", "type": "boolean", "const": false }, "source_installation_id": { "description": "Desktop replication only. Present with local_asset_id and data_egress_confirmed, this records the AssetReplicaLink for a copy the user explicitly confirmed.", "type": "string", "minLength": 1, "maxLength": 200 }, "local_asset_id": { "type": "string", "minLength": 1, "maxLength": 200 }, "content_identity": { "description": "Sent only after the user confirms the copy, and only as sha256:<64 hex>.", "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, "data_egress_confirmed": { "description": "Must be true to replicate local content to the cloud. A single explicit user action.", "type": "boolean", "const": true }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } } ] }, "CompleteAndIndexVideoRequest": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "duration_seconds", "content_sha256" ], "properties": { "duration_seconds": { "description": "Source duration, used for quota admission before transcription.", "type": "number", "exclusiveMinimum": 0 }, "content_sha256": { "description": "Declares source identity for indexing. The Worker verifies it against the uploaded bytes and successful unique-material billing is deduplicated by this hash; a distinct video asset may still be processed independently.", "type": "string", "pattern": "^[a-fA-F0-9]{64}$" }, "index": { "description": "Start indexing once the upload is confirmed. Defaults to true.", "type": "boolean", "enum": [ true ] }, "processing": { "$ref": "#/components/schemas/VideoProcessingOptions" }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } }, { "type": "object", "additionalProperties": false, "required": [ "duration_seconds", "content_sha256", "source_installation_id", "local_asset_id", "data_egress_confirmed" ], "properties": { "duration_seconds": { "description": "Source duration, used for quota admission before transcription.", "type": "number", "exclusiveMinimum": 0 }, "content_sha256": { "description": "Declares source identity for indexing. The Worker verifies it against the uploaded bytes and successful unique-material billing is deduplicated by this hash; a distinct video asset may still be processed independently.", "type": "string", "pattern": "^[a-fA-F0-9]{64}$" }, "index": { "description": "Start indexing once the upload is confirmed. Defaults to true.", "type": "boolean", "enum": [ true ] }, "processing": { "$ref": "#/components/schemas/VideoProcessingOptions" }, "source_installation_id": { "description": "Desktop replication only. Present with local_asset_id and data_egress_confirmed, this records the AssetReplicaLink for a copy the user explicitly confirmed.", "type": "string", "minLength": 1, "maxLength": 200 }, "local_asset_id": { "type": "string", "minLength": 1, "maxLength": 200 }, "content_identity": { "description": "Sent only after the user confirms the copy, and only as sha256:<64 hex>.", "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, "data_egress_confirmed": { "description": "Must be true to replicate local content to the cloud. A single explicit user action.", "type": "boolean", "const": true }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } } ] }, "VideoProcessingOptions": { "type": "object", "additionalProperties": false, "properties": { "ocr": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "description": "Extract visible text as independently sampled evidence. Defaults to false.", "type": "boolean", "default": false }, "interval_seconds": { "description": "OCR sampling interval. Defaults to 10 seconds.", "type": "integer", "minimum": 1, "maximum": 3600, "default": 10 } } } } }, "VideoResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/Video" } } } ] }, "IndexVideoRequest": { "type": "object", "additionalProperties": false, "required": [ "duration_seconds", "content_sha256" ], "properties": { "duration_seconds": { "type": "number", "exclusiveMinimum": 0 }, "content_sha256": { "description": "Declares source identity for indexing. The Worker verifies it against the uploaded bytes and successful unique-material billing is deduplicated by this hash; a distinct video asset may still be processed independently.", "type": "string", "pattern": "^[a-fA-F0-9]{64}$" }, "library_id": { "$ref": "#/components/schemas/LibraryId" }, "processing": { "$ref": "#/components/schemas/VideoProcessingOptions" }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } }, "JobResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/Job" } } } ] }, "Job": { "type": "object", "additionalProperties": false, "required": [ "id", "status", "execution_policy", "scope", "created_at", "updated_at" ], "properties": { "id": { "$ref": "#/components/schemas/JobId" }, "status": { "type": "string", "enum": [ "queued", "running", "succeeded", "failed", "cancel_requested", "canceled" ] }, "capability_id": { "type": [ "string", "null" ] }, "capability_version": { "type": [ "string", "null" ] }, "progress": { "description": "Annotation stage progress. Failed media segments are reported in the validation artifact and coverage manifest.", "type": "object", "additionalProperties": false, "required": [ "stage", "completed", "total" ], "properties": { "stage": { "type": "string" }, "completed": { "type": "integer", "minimum": 0 }, "total": { "type": "integer", "minimum": 1 } } }, "failure": { "type": [ "string", "null" ], "description": "Failure reason for a terminal job, including media processing startup failures." }, "workflow": { "oneOf": [ { "$ref": "#/components/schemas/WorkflowReference" }, { "type": "null" } ] }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" }, "scope": { "$ref": "#/components/schemas/Scope" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } } }, "JobId": { "type": "string", "pattern": "^job_[A-Za-z0-9_-]+$" }, "WorkflowReference": { "type": "object", "additionalProperties": false, "required": [ "id", "version" ], "properties": { "id": { "type": "string" }, "version": { "type": "string" } } }, "Scope": { "type": "object", "additionalProperties": false, "required": [ "library_ids", "asset_ids" ], "properties": { "library_ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/components/schemas/LibraryId" } }, "asset_ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/components/schemas/AssetId" } } } }, "TranscriptResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/Transcript" } } } ] }, "Transcript": { "type": "object", "additionalProperties": false, "required": [ "video_id", "format", "segments", "text" ], "properties": { "video_id": { "type": [ "string", "null" ] }, "format": { "type": "string", "enum": [ "json", "srt", "vtt" ] }, "segments": { "type": "array", "items": { "$ref": "#/components/schemas/TranscriptSegment" } }, "text": { "type": "string" } } }, "TranscriptSegment": { "type": "object", "additionalProperties": false, "required": [ "start_s", "end_s", "text" ], "properties": { "start_s": { "type": [ "number", "null" ], "minimum": 0 }, "end_s": { "type": [ "number", "null" ], "minimum": 0 }, "text": { "type": "string" } } }, "ResourceDeletionResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/ResourceDeletion" } } } ] }, "ResourceDeletion": { "type": "object", "additionalProperties": false, "required": [ "id", "resource_type", "resource_id", "status", "status_url", "requested_at", "updated_at", "completed_at", "failure" ], "properties": { "id": { "$ref": "#/components/schemas/DeletionId" }, "resource_type": { "type": "string", "enum": [ "asset", "artifact" ] }, "resource_id": { "oneOf": [ { "$ref": "#/components/schemas/AssetId" }, { "$ref": "#/components/schemas/ArtifactId" } ] }, "status": { "type": "string", "enum": [ "queued", "running", "succeeded", "failed" ] }, "status_url": { "type": "string", "format": "uri-reference" }, "requested_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" }, "completed_at": { "type": [ "string", "null" ], "format": "date-time" }, "failure": { "oneOf": [ { "$ref": "#/components/schemas/DeletionFailure" }, { "type": "null" } ] } } }, "DeletionId": { "type": "string", "pattern": "^del_[A-Za-z0-9_-]+$" }, "ArtifactId": { "type": "string", "pattern": "^artifact_[A-Za-z0-9_-]+$" }, "DeletionFailure": { "type": "object", "additionalProperties": false, "required": [ "code", "retryable" ], "properties": { "code": { "type": "string", "enum": [ "deletion_failed" ] }, "retryable": { "type": "boolean" } } }, "ClipExportRequest": { "type": "object", "additionalProperties": false, "required": [ "video_id", "start_s", "end_s" ], "properties": { "video_id": { "$ref": "#/components/schemas/VideoId" }, "start_s": { "type": "number", "minimum": 0 }, "end_s": { "type": "number", "exclusiveMinimum": 0 }, "evidence_ids": { "type": "array", "maxItems": 100, "uniqueItems": true, "items": { "$ref": "#/components/schemas/EvidenceId" } }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" } } }, "EvidenceId": { "type": "string", "pattern": "^ev_[A-Za-z0-9_-]+$" }, "SearchRequest": { "type": "object", "additionalProperties": false, "required": [ "query" ], "properties": { "query": { "type": "string", "minLength": 1, "maxLength": 1000 }, "filter": { "description": "Omit to search the whole workspace.", "$ref": "#/components/schemas/SearchFilter" }, "execution_policy": { "$ref": "#/components/schemas/ExecutionPolicy" }, "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } } }, "SearchFilter": { "type": "object", "additionalProperties": false, "properties": { "video_ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/components/schemas/VideoId" } }, "library_ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/components/schemas/LibraryId" } } } }, "SearchResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/SearchResult" } } } } ] }, "SearchResult": { "type": "object", "additionalProperties": false, "required": [ "evidence", "score" ], "properties": { "evidence": { "$ref": "#/components/schemas/Evidence" }, "score": { "type": "number", "minimum": 0, "maximum": 1 } } }, "Evidence": { "type": "object", "additionalProperties": false, "required": [ "id", "asset_id", "kind", "start_seconds", "end_seconds", "quote", "locators" ], "properties": { "id": { "$ref": "#/components/schemas/EvidenceId" }, "asset_id": { "$ref": "#/components/schemas/AssetId" }, "kind": { "type": "string", "enum": [ "transcript", "frame", "video_clip", "segment" ] }, "start_seconds": { "type": "number", "minimum": 0 }, "end_seconds": { "type": "number", "minimum": 0 }, "quote": { "type": "string" }, "modality": { "description": "Processing station that produced this evidence unit.", "type": "string", "enum": [ "speech", "ocr", "keyframe" ] }, "payload": { "description": "Bounded text or a stable source-frame identity. Private perception-archive coordinates are never exposed.", "$ref": "#/components/schemas/EvidencePayload" }, "provenance": { "description": "Station, model, version, and segment identity used to produce the unit.", "$ref": "#/components/schemas/EvidenceProvenance" }, "language": { "type": [ "string", "null" ] }, "locators": { "type": "array", "items": { "$ref": "#/components/schemas/EvidenceLocator" } } } }, "EvidencePayload": { "oneOf": [ { "$ref": "#/components/schemas/EvidenceTextPayload" }, { "$ref": "#/components/schemas/EvidenceImagePayload" } ] }, "EvidenceTextPayload": { "type": "object", "additionalProperties": false, "required": [ "text" ], "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 4000 } } }, "EvidenceImagePayload": { "type": "object", "additionalProperties": false, "required": [ "image_ref" ], "properties": { "image_ref": { "$ref": "#/components/schemas/EvidenceImageReference" } } }, "EvidenceImageReference": { "type": "object", "additionalProperties": false, "required": [ "source_asset_id", "timestamp_seconds", "content_sha256" ], "properties": { "source_asset_id": { "$ref": "#/components/schemas/AssetId" }, "timestamp_seconds": { "type": "number", "minimum": 0 }, "content_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" } } }, "EvidenceProvenance": { "type": "object", "additionalProperties": false, "required": [ "station", "model_id", "model_version", "segment_key" ], "properties": { "station": { "type": "string", "enum": [ "speech", "visual" ] }, "model_id": { "type": "string", "minLength": 1, "maxLength": 128 }, "model_version": { "type": "string", "minLength": 1, "maxLength": 128 }, "segment_key": { "type": "string", "minLength": 8, "maxLength": 256 } } }, "EvidenceLocator": { "type": "object", "additionalProperties": false, "required": [ "type", "url" ], "properties": { "type": { "type": "string", "enum": [ "local", "cloud" ] }, "url": { "type": "string", "format": "uri" } } }, "ArtifactListResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Artifact" } } } } ] }, "Artifact": { "type": "object", "additionalProperties": false, "required": [ "id", "kind", "schema_version", "evidence_ids", "payload", "created_at" ], "properties": { "id": { "$ref": "#/components/schemas/ArtifactId" }, "kind": { "type": "string", "enum": [ "clip_gallery", "annotation_set", "annotation_layer", "validation_report", "comparison", "timeline", "highlight_reel", "storyboard", "dataset_manifest", "review_queue" ] }, "schema_version": { "type": "string" }, "job_id": { "oneOf": [ { "$ref": "#/components/schemas/JobId" }, { "type": "null" } ] }, "response_id": { "oneOf": [ { "$ref": "#/components/schemas/ResponseId" }, { "type": "null" } ] }, "evidence_ids": { "type": "array", "items": { "$ref": "#/components/schemas/EvidenceId" } }, "payload": { "type": "object", "additionalProperties": true }, "content_url": { "type": "string", "format": "uri-reference" }, "created_at": { "type": "string", "format": "date-time" } } }, "ResponseId": { "type": "string", "pattern": "^resp_[A-Za-z0-9_-]+$" }, "ArtifactResponse": { "unevaluatedProperties": false, "allOf": [ { "$ref": "#/components/schemas/ResponseMetadata" }, { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/Artifact" } } } ] } }, "responses": { "Error": { "description": "Standard Cerul error envelope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } } }, "parameters": { "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": true, "schema": { "type": "string", "minLength": 8, "maxLength": 200 } }, "LibraryId": { "name": "library_id", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/LibraryId" } }, "VideoId": { "name": "video_id", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/VideoId" } }, "JobId": { "name": "job_id", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/JobId" } }, "ArtifactId": { "name": "artifact_id", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/ArtifactId" } }, "DeletionId": { "name": "deletion_id", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/DeletionId" } } }, "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "Cerul access token" } } } } ```