# Cerul API Documentation: https://cerul.ai/docs Base URL: https://api.cerul.ai (no /v1). # Quickstart Replace YOUR_API_KEY with a Key created in Console, then run this request to list your videos. No upload is needed to verify access. ```bash curl --fail-with-body "https://api.cerul.ai/videos" \ -H "Authorization: Bearer YOUR_API_KEY" ``` The response stores videos in data. An empty list is normal in a new workspace. With a ready video, continue to [Search](/docs.md?locale=en&page=search); otherwise [upload a video](/docs.md?locale=en&page=upload). ## Request parameters | Field | Type · Required | Description | | --- | --- | --- | | Authorization | string · Required | Bearer YOUR_API_KEY | | library_id | string · Optional | pattern: ^lib_[A-Za-z0-9_-]+$ | ## Reading the response data[].id identifies the video. filename is its original name; status reports processing progress. ready means searchable, but inspect coverage and warnings for missing speech, visual or OCR evidence. ## Complete example [Download complete example](/docs-quickstart.sh) Run the snippets in the same Bash session with curl (7.76+), jq, Python 3 and ffprobe installed. Set talk.mp4 to your video. Processing and search consume credits; preserve the run ID when retrying unchanged requests. Use https://api.cerul.ai without a /v1 prefix and send Authorization: Bearer . Verify your account email before uploading, indexing, searching or exporting. Keys stay on your server; a Workspace shares one cloud wallet across Keys and authenticated app/CLI calls. ## Retries and errors Send an Idempotency-Key of 8–200 characters where required, including search. Keep the same key and input after an ambiguous response; use a new key for a different operation/input. Poll pending jobs according to Retry-After and set a deadline. A client timeout does not cancel a server job. • 401 · invalid_credentials: Check or replace the expired/revoked credential. • 403 · permission_denied / insufficient_entitlement: Check scopes, verified email, entitlements and wallet balance. • 429 · rate_limited: Back off using retry_after_seconds when present; check concurrency. --- # Upload video Place your video at talk.mp4 and replace YOUR_API_KEY. This request creates a video and reserves upload URLs using its real byte size; it does not upload the bytes yet. ```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}" ``` ## Request parameters | Field | Type · Required | Description | | --- | --- | --- | | Authorization | string · Required | Bearer YOUR_API_KEY | | filename | string · Required | minLength: 1; maxLength: 255 | | media_type | string · Required | minLength: 3; maxLength: 127; pattern: ^[Vv][Ii][Dd][Ee][Oo]/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$ | | byte_size | integer · Required | ≥ 1; ≤ 5368709120 | | library_id | string · Optional | Omit to file the video in the workspace default library. — pattern: ^lib_[A-Za-z0-9_-]+$ | | execution_policy | string · Optional | local_only, prefer_local, cloud_allowed, cloud_required | | Idempotency-Key | string · Required | minLength: 8; maxLength: 200 | Required scopes: assets:write. ## Upload the file bytes Save data.id. For each entry in data.upload.parts, PUT the exact sequential byte range to its temporary URL using data.upload.required_headers. Do not send the API Key to the upload URL. Maximum file size is 5 GiB; URL ingestion is not supported. [Download complete example](/docs-quickstart.sh) Run the snippets in the same Bash session with curl (7.76+), jq, Python 3 and ffprobe installed. Set talk.mp4 to your video. Processing and search consume credits; preserve the run ID when retrying unchanged requests. ## Confirm the upload After all parts succeed, POST /videos/{video_id}/complete with Bearer, Content-Type: application/json and a new Idempotency-Key. Submit the real duration and SHA-256. Completion starts indexing by default; do not submit a duplicate index request. | Field | Type · Required | Description | | --- | --- | --- | | Authorization | string · Required | Bearer YOUR_API_KEY | | duration_seconds | number · Required for indexing | Source duration, used for quota admission before transcription. — > 0 | | content_sha256 | string · Required for indexing | 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 · Optional | false: upload only; true: start indexing — default: true | | processing | object · Optional | | | processing.ocr | object · Optional | | | processing.ocr.enabled | boolean · Optional | Extract visible text as independently sampled evidence. Defaults to false. — default: false | | processing.ocr.interval_seconds | integer · Optional | OCR sampling interval. Defaults to 10 seconds. — ≥ 1; ≤ 3600; default: 10 | | execution_policy | string · Optional | local_only, prefer_local, cloud_allowed, cloud_required | | video_id | string · Required | A Video is the developer-facing name for a media Asset, and shares its identifier. — pattern: ^asset_[A-Za-z0-9_-]+$ | | Idempotency-Key | string · Required | minLength: 8; maxLength: 200 | Required scopes: assets:write, library-items:write. Indexing additionally requires jobs:write, assets:read and artifacts:write. Set index:false only to retain an unindexed upload; later use POST /videos/{video_id}/index. ## Wait for indexing Poll GET /videos/{video_id} with a deadline: ready is searchable; failed means inspect error; processing means wait. A client timeout does not cancel the server job. OCR is off by default. Set OCR_ENABLED=true before completion to index visible text at a 10-second interval. A ready video is searchable; inspect coverage and warnings for missing speech, visual or OCR evidence. A video without audio has no transcript. --- # Search Replace YOUR_API_KEY and YOUR_VIDEO_ID to search a ready video. Keep REQUEST_KEY unchanged when retrying the same request. ```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 }' ``` ## Request parameters | Field | Type · Required | Description | | --- | --- | --- | | Authorization | string · Required | Bearer YOUR_API_KEY | | query | string · Required | minLength: 1; maxLength: 1000 | | filter | object · Optional | Omit to search the whole workspace. | | filter.video_ids | array · Optional | uniqueItems | | filter.library_ids | array · Optional | uniqueItems | | execution_policy | string · Optional | local_only, prefer_local, cloud_allowed, cloud_required | | limit | integer · Optional | ≥ 1; ≤ 100; default: 20 | | Idempotency-Key | string · Required | minLength: 8; maxLength: 200 | Required scopes: libraries:read, assets:read. ## Read the result Each data item contains evidence and score. evidence.id identifies the evidence; asset_id identifies its source. start_seconds and end_seconds locate it in the video. quote is the evidence text; kind and modality distinguish speech, OCR and visual evidence. score ranks relevance and is not a correctness probability. Keep request_id, usage and warnings. Display source, timestamps and evidence text, and verify the original clip before using it. Export a bounded span with [clips](/docs.md?locale=en&page=clips). ## No matches An empty data array is not a request failure. Check readiness, filter and coverage before refining the query. A valid completed search may be charged without matches; a request with no indexed evidence is not charged. GET /videos/{video_id}/transcript reads speech; no audio may return 422 no_audio_track. --- # Export and manage Replace the Key and video ID to request a clip from 30 to 45 seconds. Confirm the source contains this range. The response creates an asynchronous job, not a ready download. ```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}' ``` ## Request parameters | Field | Type · Required | Description | | --- | --- | --- | | Authorization | string · Required | Bearer YOUR_API_KEY | | video_id | string · Required | A Video is the developer-facing name for a media Asset, and shares its identifier. — pattern: ^asset_[A-Za-z0-9_-]+$ | | start_s | number · Required | ≥ 0 | | end_s | number · Required | > 0 | | evidence_ids | array · Optional | maxItems: 100; uniqueItems | | execution_policy | string · Optional | local_only, prefer_local, cloud_allowed, cloud_required | | Idempotency-Key | string · Required | minLength: 8; maxLength: 200 | Required scopes: jobs:write, assets:read, artifacts:write. ## Wait and download Save data.id as JOB_ID. Poll GET /jobs/{job_id}, honoring Retry-After and a deadline. On succeeded, GET /jobs/{job_id}/artifacts and select the clip by its type and metadata. Download GET /artifacts/{artifact_id}/content using Bearer authentication. Reading jobs needs jobs:read; reading artifacts needs artifacts:read. Stop on failed or canceled. ```bash curl --fail-with-body "https://api.cerul.ai/artifacts/YOUR_ARTIFACT_ID/content" \ -H "Authorization: Bearer YOUR_API_KEY" \ --output clip.mp4 ``` ## Cancel or delete POST /jobs/{job_id}/cancel requests cancellation; keep polling to a terminal state. DELETE /videos/{video_id} cleans up a video and associated data (assets:write). DELETE /artifacts/{artifact_id} deletes one artifact (artifacts:write). After a 202 deletion receipt, poll GET /deletions/{deletion_id} until cleanup completes. Successful searches may be charged even with no matches; no indexed evidence means no search charge. Failed or canceled processing can still settle completed billable work and release unused reservations. Deletions return 202: follow the deletion receipt until cleanup finishes. Read available, reserved and debt credits separately in /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" } } } } ```