Quickstart

走完 Cloud REST P0 主链:上传视频、完成语音级索引、找到可引用的转写片段,并导出对应视频片段。

1拿到 API key

启动 device flow,打开返回的 Cerul 授权页面,并在登录状态下批准代码。随后把已批准的 grant 换成短期 token,再创建限定权限的 Workspace key。

终端bash
export CERUL_QUICKSTART_RUN_ID="$(date +%s)-$RANDOM"

AUTH=$(curl https://api.cerul.ai/v1/oauth/device/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "cerul-cli",
    "scope": [
      "workspace:read",
      "api_credentials:write",
      "libraries:read",
      "libraries:write",
      "library-items:write",
      "assets:read",
      "assets:write",
      "jobs:read",
      "jobs:write",
      "artifacts:read",
      "artifacts:write"
    ]
  }')

echo "Open: $(printf '%s' "$AUTH" | jq -r '.verification_uri_complete')"
# Approve the code in Cerul, then continue:

TOKEN=$(curl https://api.cerul.ai/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",
    \"device_code\": \"$(printf '%s' "$AUTH" | jq -r '.device_code')\",
    \"client_id\": \"cerul-cli\"
  }")
export CERUL_ACCESS_TOKEN=$(printf '%s' "$TOKEN" | jq -r '.access_token')

PRINCIPAL=$(curl https://api.cerul.ai/v1/principal \
  -H "Authorization: Bearer $CERUL_ACCESS_TOKEN")
export CERUL_WORKSPACE_ID=$(printf '%s' "$PRINCIPAL" | jq -r '.data.workspace_id')

KEY=$(curl "https://api.cerul.ai/v1/workspaces/$CERUL_WORKSPACE_ID/api-credentials" \
  -H "Authorization: Bearer $CERUL_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-quickstart-key-$CERUL_QUICKSTART_RUN_ID" \
  -d '{
    "name": "Quickstart",
    "scopes": [
      "libraries:read",
      "libraries:write",
      "library-items:write",
      "assets:read",
      "assets:write",
      "jobs:read",
      "jobs:write",
      "artifacts:read",
      "artifacts:write"
    ]
  }')
export CERUL_API_KEY=$(printf '%s' "$KEY" | jq -r '.data.secret')

2添加一段视频

新建或选择一个资料库,请求直传地址,按返回的请求头 PUT 精确字节,再通过鉴权完成上传后开始索引。保存返回的 asset_id 供后续请求使用。

请求create → direct PUT → complete → index
FILE_SIZE=$(wc -c < ./talk.mp4 | tr -d ' ')

LIBRARY=$(curl https://api.cerul.ai/v1/libraries \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-quickstart-library-$CERUL_QUICKSTART_RUN_ID" \
  -d '{
    "name": "Quickstart",
    "ingestion_profile": {
      "id": "quickstart",
      "version": "1",
      "required_capabilities": [],
      "enrichment_schema": {},
      "segmentation_policy": {},
      "metadata_schema": {}
    }
  }')
export CERUL_LIBRARY_ID=$(printf '%s' "$LIBRARY" | jq -r '.data.id')

UPLOAD=$(curl https://api.cerul.ai/v1/uploads \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: upload-quickstart-$CERUL_QUICKSTART_RUN_ID" \
  -d "{
    \"filename\": \"talk.mp4\",
    \"media_type\": \"video/mp4\",
    \"byte_size\": $FILE_SIZE,
    \"execution_policy\": \"cloud_required\"
  }")

export CERUL_ASSET_ID=$(printf '%s' "$UPLOAD" | jq -r '.data.asset_id')
UPLOAD_ID=$(printf '%s' "$UPLOAD" | jq -r '.data.id')
UPLOAD_URL=$(printf '%s' "$UPLOAD" | jq -r '.data.upload_url')
UPLOAD_CONTENT_TYPE=$(printf '%s' "$UPLOAD" | jq -r '.data.required_headers["Content-Type"]')
UPLOAD_CONTENT_LENGTH=$(printf '%s' "$UPLOAD" | jq -r '.data.required_headers["Content-Length"]')
CONTENT_SHA256=$(shasum -a 256 ./talk.mp4 | awk '{print $1}')
DURATION_SECONDS=$(ffprobe -v error -show_entries format=duration -of csv=p=0 ./talk.mp4)
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: $UPLOAD_CONTENT_TYPE" \
  -H "Content-Length: $UPLOAD_CONTENT_LENGTH" \
  --upload-file ./talk.mp4

curl -X POST "https://api.cerul.ai/v1/uploads/$UPLOAD_ID/complete" \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  -H "Idempotency-Key: complete-quickstart-$CERUL_QUICKSTART_RUN_ID"

curl -X POST "https://api.cerul.ai/v1/libraries/$CERUL_LIBRARY_ID/items" \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: attach-quickstart-$CERUL_QUICKSTART_RUN_ID" \
  -d "{\"asset_id\": \"$CERUL_ASSET_ID\"}"

INDEX=$(curl https://api.cerul.ai/v1/index \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: prepare-quickstart-$CERUL_QUICKSTART_RUN_ID" \
  -d "{
    \"asset_id\": \"$CERUL_ASSET_ID\",
    \"library_id\": \"$CERUL_LIBRARY_ID\",
    \"duration_seconds\": $DURATION_SECONDS,
    \"content_sha256\": \"$CONTENT_SHA256\",
    \"index_level\": \"speech_only\",
    \"execution_policy\": \"cloud_required\"
  }")

JOB_ID=$(printf '%s' "$INDEX" | jq -r '.data.id')
while :; do
  STATUS=$(curl -s "https://api.cerul.ai/v1/jobs/$JOB_ID" \
    -H "Authorization: Bearer $CERUL_API_KEY" | jq -r '.data.status')
  [ "$STATUS" = "succeeded" ] && break
  [ "$STATUS" = "failed" ] && { echo "Preparation failed"; exit 1; }
  [ "$STATUS" = "canceled" ] && { echo "Preparation canceled"; exit 1; }
  sleep 5
done

检索刚上传的资产,读取转写证据的时间范围,异步发起片段导出,轮询任务,再获取 artifact 记录与需要鉴权的内容。

请求POST /v1/search → POST /v1/clips → artifact content
SEARCH=$(curl https://api.cerul.ai/v1/search \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"query\": \"when did they discuss scaling laws\",
    \"scope\": {
      \"library_ids\": [],
      \"asset_ids\": [\"$CERUL_ASSET_ID\"]
    },
    \"execution_policy\": \"cloud_required\"
  }")

export CERUL_EVIDENCE_ID=$(printf '%s' "$SEARCH" | jq -r '.data[0].evidence.id')
START_SECONDS=$(printf '%s' "$SEARCH" | jq -r '.data[0].evidence.start_seconds')
END_SECONDS=$(printf '%s' "$SEARCH" | jq -r '.data[0].evidence.end_seconds')

CLIP=$(curl https://api.cerul.ai/v1/clips \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: clip-quickstart-$CERUL_QUICKSTART_RUN_ID" \
  -d "{
    \"asset_id\": \"$CERUL_ASSET_ID\",
    \"start_seconds\": $START_SECONDS,
    \"end_seconds\": $END_SECONDS,
    \"evidence_ids\": [\"$CERUL_EVIDENCE_ID\"],
    \"execution_policy\": \"cloud_required\"
  }")

CLIP_JOB_ID=$(printf '%s' "$CLIP" | jq -r '.data.id')
while :; do
  CLIP_STATUS=$(curl -s "https://api.cerul.ai/v1/jobs/$CLIP_JOB_ID" \
    -H "Authorization: Bearer $CERUL_API_KEY" | jq -r '.data.status')
  [ "$CLIP_STATUS" = "succeeded" ] && break
  [ "$CLIP_STATUS" = "failed" ] && { echo "Clip export failed"; exit 1; }
  [ "$CLIP_STATUS" = "canceled" ] && { echo "Clip export canceled"; exit 1; }
  sleep 5
done

ARTIFACTS=$(curl "https://api.cerul.ai/v1/jobs/$CLIP_JOB_ID/artifacts" \
  -H "Authorization: Bearer $CERUL_API_KEY")
ARTIFACT_ID=$(printf '%s' "$ARTIFACTS" | jq -r '.data[0].id')

curl "https://api.cerul.ai/v1/artifacts/$ARTIFACT_ID" \
  -H "Authorization: Bearer $CERUL_API_KEY" | jq .

curl "https://api.cerul.ai/v1/artifacts/$ARTIFACT_ID/content" \
  -H "Authorization: Bearer $CERUL_API_KEY" \
  --output quickstart-clip.mp4

设备端搜索保留在 Cerul Desktop App 内;本公开文档只描述托管 Cloud API。

4读懂响应

P0 搜索结果只返回按 score 排序的 transcript evidence;精确时间范围可用于引用,也可直接交给片段导出端点。

以下为说明性响应:结构符合正式契约,evidence 文本、时间码与 score 会根据你上传的视频而变化。

响应200 OK · illustrative
{
  "request_id": "req_7c41ad",
  "execution": { "location": "cloud" },
  "usage": { "billable": false, "quantity": 0, "unit": "request" },
  "warnings": [],
  "data": [
    {
      "score": 0.83,
      "evidence": {
        "id": "ev_7c41ad",
        "asset_id": "asset_from_upload",
        "kind": "transcript",
        "start_seconds": 768,
        "end_seconds": 794,
        "quote": "When they discuss scaling laws, loss still matters more than benchmarks.",
        "locators": [{ "type": "cloud", "url": "https://api.cerul.ai/v1/artifacts/artifact_7c41ad" }]
      }
    }
  ]
}

evidence 字段的含义

Evidence 是 Cerul 里信任的最小单位。score 告诉你一条结果有多相关,evidence 告诉你为什么相关,并让人或 agent 都能对着原始视频复核这条结论,而不用重看一遍。

kindCloud REST P0 只返回 transcript evidence;本 Quickstart 不包含视觉证据。
start_seconds这一刻开始的秒数。把任何播放器 seek 到这里,就落在这句话上。
end_seconds这一刻结束的位置,方便你精确剪出或回放支撑它的那一段。
quote命中的转写文本;公开逐字引用前仍应对照原始素材复核。
locators可定位支撑证据的本地或云端 URL。
id · asset_id指向这条证据和它所在视频的稳定引用。

下一步