# Cerul API Documentation: https://cerul.ai/docs Base URL: https://api.cerul.ai (no /v1). # 快速开始 把 YOUR_API_KEY 换成在 Console 创建的 Key,复制下面的请求即可列出工作区中的视频。无需先上传文件。 ```bash curl --fail-with-body "https://api.cerul.ai/videos" \ -H "Authorization: Bearer YOUR_API_KEY" ``` 请求成功后,视频列表位于 data。新工作区没有视频时,返回空列表是正常结果。已有 ready 状态的视频,可以直接开始 [搜索](/docs.md?locale=zh&page=search);还没有视频,先 [上传视频](/docs.md?locale=zh&page=upload)。 ## 请求参数 | 参数 | 类型 · 必填 | 说明 | | --- | --- | --- | | Authorization | string · 必填 | 请求头。格式为 Bearer 加上你的 API Key。需要 assets:read 权限。 | | library_id | string · 可选 | URL 查询参数。只列出指定资料库中的视频;省略时列出工作区中的视频。 — pattern: ^lib_[A-Za-z0-9_-]+$ | 按资料库筛选时,在 URL 后添加 ?library_id=YOUR_LIBRARY_ID。基础地址统一为 https://api.cerul.ai,不加 /v1。 ## 返回结果怎么看 | 参数 | 必填 | 说明 | | --- | --- | --- | | data[].id | 返回字段 | 视频 ID。后续查询、搜索和剪辑都用它关联视频。 | | data[].filename | 返回字段 | 上传时的文件名。 | | data[].status | 返回字段 | ready 表示可搜索;processing 表示仍在处理。 | | data[].coverage | 返回字段 | 语音、画面和 OCR 的实际处理情况;就绪不保证每种证据都完整。 | ## 接下来做什么 1. [上传视频](/docs.md?locale=zh&page=upload):上传文件,等待索引就绪。 2. [搜索](/docs.md?locale=zh&page=search):发送自然语言查询,取得证据和时间范围。 3. [导出剪辑](/docs.md?locale=zh&page=clips):把选中的片段保存为视频文件。 需要一次跑通整个流程?[下载完整示例脚本](/docs-quickstart.sh),准备 talk.mp4,设置 CERUL_API_KEY 后用 Bash 运行。需要 curl 7.76+、jq、Python 3、ffprobe 和 uuidgen;示例中的索引和搜索会消耗额度。 ## 接入时记住这几点 Key 保存在服务端或本机环境变量中,不放在网页前端。上传、索引、搜索和导出前需验证账号邮箱,并确认相应权限及可用额度。 创建、索引、搜索、导出等接口按要求携带 8–200 字符的 Idempotency-Key。同一次操作响应不确定时,使用原键和原请求内容重试;修改输入或新建操作时换一个键。 401 检查 Key;403 检查权限、邮箱验证与额度;429 按 retry_after_seconds 退避。保留 request_id 便于排查。客户端超时不会自动取消服务端处理。 --- # 上传视频 把本地视频命名为 talk.mp4,替换 YOUR_API_KEY,运行下面的请求申请上传地址。它会按真实文件大小创建视频;文件字节的上传和确认步骤见下方。 ```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}" ``` 保存 data.id 和 data.upload。随后将文件分片上传至返回的临时地址,再确认完成。希望直接跑通上传、索引、搜索与剪辑,可使用 [完整示例脚本](/docs-quickstart.sh)。单个文件最大 5 GiB,当前不支持 URL 导入。 ## 创建视频的参数 | 参数 | 类型 · 必填 | 说明 | | --- | --- | --- | | Authorization | string · 必填 | Bearer YOUR_API_KEY | | filename | string · 必填 | 文件名,1–255 字符。 — minLength: 1; maxLength: 255 | | media_type | string · 必填 | 视频媒体类型,例如 video/mp4。 — minLength: 3; maxLength: 127; pattern: ^[Vv][Ii][Dd][Ee][Oo]/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$ | | byte_size | integer · 必填 | 文件实际字节数,整数,1–5,368,709,120。 — ≥ 1; ≤ 5368709120 | | library_id | string · 可选 | 目标资料库 ID;省略时使用工作区默认资料库。 — pattern: ^lib_[A-Za-z0-9_-]+$ | | execution_policy | string · 可选 | 字符串。枚举:local_only、prefer_local、cloud_allowed、cloud_required。Cloud 接入可省略,不使用 local_only。 — local_only, prefer_local, cloud_allowed, cloud_required | | Idempotency-Key | string · 必填 | 请求头。一次上传操作使用一个唯一键;不确定是否成功时保留原键重试。 — minLength: 8; maxLength: 200 | ## 返回的上传信息 | 参数 | 必填 | 说明 | | --- | --- | --- | | data.id | 返回字段 | 保存为 VIDEO_ID,用于确认上传及查询状态。 | | data.upload.parts | 返回字段 | 每一片的临时 PUT 地址和 byte_size,按顺序上传准确的字节区间。 | | data.upload.required_headers | 返回字段 | 上传文件时需要的请求头。使用这些头,不附加 Cerul API Key。 | 全部分片上传成功后,调用 POST /videos/{video_id}/complete,并提供 Bearer、Content-Type 和新的 Idempotency-Key。该步骤默认启动索引,不需要再提交一次索引任务。 ## 确认上传的参数 | 参数 | 类型 · 必填 | 说明 | | --- | --- | --- | | Authorization | string · 必填 | Bearer YOUR_API_KEY | | duration_seconds | number · 索引时必填 | 真实视频时长,单位秒,大于 0。 — > 0 | | content_sha256 | string · 索引时必填 | 原始文件 SHA-256,64 位十六进制字符串。 — pattern: ^[a-fA-F0-9]{64}$ | | index | boolean · 可选 | 默认 true,确认后索引;false 表示仅保存视频。 — default: true | | processing | object · 可选 | | | processing.ocr | object · 可选 | | | processing.ocr.enabled | boolean · 可选 | 默认 false。true 时提取画面文字,产生额外 OCR 用量。 — default: false | | processing.ocr.interval_seconds | integer · 可选 | OCR 采样间隔,整数 1–3600 秒,默认 10。 — ≥ 1; ≤ 3600; default: 10 | | execution_policy | string · 可选 | 同上;常规 Cloud 接入可省略。 — local_only, prefer_local, cloud_allowed, cloud_required | | video_id | string · 必填 | URL 路径中的视频 ID,取自创建响应。 — pattern: ^asset_[A-Za-z0-9_-]+$ | | Idempotency-Key | string · 必填 | minLength: 8; maxLength: 200 | 这里只列 Cloud 文件上传使用的参数。默认资料库即可完成首次接入,不需要配置 Desktop 复制字段。 ## 等待索引完成 GET /videos/{video_id} 返回 data.status。processing 时间隔查询并设置等待截止时间;ready 时可以搜索;failed 时读取 error。检查 coverage 和 warnings,确认语音、画面及 OCR 的实际结果。 没有音轨的视频不会生成语音转录。若完成上传时设置了 index: false,可以稍后调用 POST /videos/{video_id}/index。不要在默认索引已启动后重复提交。 需要按项目整理视频时,GET /libraries 查看资料库,在创建视频时传入 library_id 即可。下一步:[搜索](/docs.md?locale=zh&page=search)。 --- # 搜索 替换 YOUR_API_KEY 和 YOUR_VIDEO_ID,即可在一个已就绪的视频中搜索。每次新搜索使用新的幂等键;重试同一次请求时需保留原键。 ```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": "什么时候讨论了模型扩展规律?", "filter": {"video_ids": ["YOUR_VIDEO_ID"]}, "limit": 5 }' ``` 结果在 data 数组中。先查看 evidence.quote、start_seconds 和 end_seconds,核对原视频后即可 [导出剪辑](/docs.md?locale=zh&page=clips)。 ## 请求参数 | 参数 | 类型 · 必填 | 说明 | | --- | --- | --- | | Authorization | string · 必填 | Bearer Key,需要 libraries:read 和 assets:read。 | | query | string · 必填 | 自然语言查询,1–1000 字符。 — minLength: 1; maxLength: 1000 | | filter | object · 可选 | 搜索范围;省略时搜索当前工作区。 | | filter.video_ids | array · 可选 | 视频 ID 数组,同一数组中的 ID 不重复。 — uniqueItems | | filter.library_ids | array · 可选 | 资料库 ID 数组,同一数组中的 ID 不重复。 — uniqueItems | | execution_policy | string · 可选 | 字符串。枚举:local_only、prefer_local、cloud_allowed、cloud_required。Cloud 接入可省略,不使用 local_only。 — local_only, prefer_local, cloud_allowed, cloud_required | | limit | integer · 可选 | 返回数量,整数 1–100,默认 20。 — ≥ 1; ≤ 100; default: 20 | | Idempotency-Key | string · 必填 | 请求头,8–200 字符。同一个键只能对应不变的请求输入。 — minLength: 8; maxLength: 200 | ## 返回字段 | 参数 | 必填 | 说明 | | --- | --- | --- | | data[].evidence.id | 返回字段 | 证据 ID,可关联到剪辑请求的 evidence_ids。 | | data[].evidence.asset_id | 返回字段 | 证据所属的源资产标识。 | | data[].evidence.start_seconds / end_seconds | 返回字段 | 片段在原视频中的起止秒数。 | | data[].evidence.quote | 返回字段 | 证据文字,需结合 kind 和 modality 理解。 | | data[].evidence.kind | 返回字段 | transcript、frame、video_clip 或 segment。 | | data[].evidence.modality | 可选返回字段 | speech、ocr 或 keyframe,区分语音、画面文字和画面证据。 | | data[].score | 返回字段 | 候选相关性排序分数,不是答案正确概率。 | | request_id / usage / warnings | 响应信息 | 用于请求排查、用量与限制说明。 | ## 没有结果怎么办 先检查视频是否 ready、搜索范围是否正确,以及 coverage 中是否存在所需证据,再把查询写得更具体。data 为空不等于请求失败;完成有效检索但没有命中仍可能收费,没有已索引证据的请求不收搜索费。 应用展示结果时,建议显示“来源视频、时间范围、证据文字”,并提供回看入口。不要把 OCR 文字当成讲者说过的话。需要完整转录时使用 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}' ``` 保存返回的 data.id 作为 JOB_ID。这是异步任务,不是已经生成的下载地址。 ## 请求参数 | 参数 | 类型 · 必填 | 说明 | | --- | --- | --- | | Authorization | string · 必填 | Bearer Key,创建剪辑需要 jobs:write、assets:read、artifacts:write。 | | video_id | string · 必填 | 来源视频 ID。 — pattern: ^asset_[A-Za-z0-9_-]+$ | | start_s | number · 必填 | 开始时间,秒,不能小于 0。 — ≥ 0 | | end_s | number · 必填 | 结束时间,秒,必须大于开始时间并位于视频时长内。 — > 0 | | evidence_ids | array · 可选 | 关联的证据 ID 数组,最多 100 个且不重复。 — maxItems: 100; uniqueItems | | execution_policy | string · 可选 | 字符串。枚举:local_only、prefer_local、cloud_allowed、cloud_required。Cloud 接入可省略,不使用 local_only。 — local_only, prefer_local, cloud_allowed, cloud_required | | Idempotency-Key | string · 必填 | 请求头,同一操作重试时保留原键与原输入。 — minLength: 8; maxLength: 200 | ## 等待并下载 1. GET /jobs/{job_id} 查询状态,按 Retry-After 间隔轮询并设置截止时间。 2. succeeded 后,GET /jobs/{job_id}/artifacts 获取产物;有多个时按类型和元数据选择剪辑。 3. GET /artifacts/{artifact_id}/content 下载文件,需要 Bearer 鉴权。 查询任务需要 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} 查询到清理完成。不要把“已受理”显示为“已彻底删除”。具体状态和返回结构可展开下方接口参考查看。 # 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" } } } } ```