본문으로 건너뛰기

영상 색인

색인 생성은 영상을 장면으로 나누고 캡션, 트랜스크립트, 임베딩, 브랜드 세이프티 평가를 만듭니다. 어떤 제출 경로든 video_id와 job_id를 바로 반환하고, 파이프라인(장면 감지 → 캡션 생성 → 임베딩 → 검수)은 비동기로 실행됩니다. 작업을 폴링한 뒤 결과를 읽으세요.

노트

모든 예제는 https://video.pyler.ai와 $VUF_API_KEY에 담긴 API 키를 사용합니다. 키 형식은 인증을 참고하세요.

제출 경로 고르기​

가진 것호출레퍼런스
공개 URL 하나POST /api/v1/videos영상 제출
로컬 파일 하나POST /api/v1/videos/upload파일 업로드
파싱한 URL 목록(최대 500개)kind: "url"로 POST /api/v1/videos/batch배치 생성
로컬 파일 여러 개POST /api/v1/videos/batch/upload배치 업로드
URL이 담긴 .csv 또는 .xlsxPOST /api/v1/videos/batch/upload-sheet시트 업로드
MRSS 피드대시보드에서 등록한 뒤 /api/v1/mrss-feeds로 관리MRSS 피드

어떤 경로를 쓰든 video_id를 보관하세요. video_id는 영상이 존재하는 동안 바뀌지 않지만, job_id는 재색인할 때마다 새로 바뀝니다.

URL 하나 제출하기​

POST /api/v1/videos는 공개적으로 가져올 수 있는 http(s) URL과 선택 항목인 title을 받습니다(title이 없으면 URL의 마지막 경로 세그먼트를 씁니다). 202 Accepted를 반환합니다.

curl -X POST https://video.pyler.ai/api/v1/videos \
-H "Authorization: Bearer $VUF_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/clip.mp4", "title": "demo clip"}'
Response (202)
{
"video_id": "3f6c1b2e-9a4d-4c7e-8b1f-2d5a6e7f8c90",
"job_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"created": true
}
팁

아직 색인 중인 URL을 다시 제출해도 파이프라인이 하나 더 시작되지 않습니다. 기존 video_id와 job_id가 created: false와 함께 똑같이 202로 돌아옵니다. 따라서 카탈로그 전체를 다시 제출해도 안전합니다. 실제로 새로 만들어진 영상 수는 true 값을 세어 확인하세요.

파일 업로드하기​

POST /api/v1/videos/upload는 로컬 파일(mp4, mov 등)을 위한 같은 비동기 흐름입니다. file 필드와 선택 항목인 title을 담아 multipart/form-data로 보내세요.

curl -X POST https://video.pyler.ai/api/v1/videos/upload \
-H "Authorization: Bearer $VUF_API_KEY" \
-F "file=@clip.mp4" \
-F "title=demo clip"

202 응답 본문에는 video_id, job_id, storage_path(업로드가 저장된 내부 객체 키)가 담깁니다. 파일이 많다면 파일마다 이 호출을 반복하거나 배치 업로드를 쓰세요.

작업 폴링하기​

GET /api/v1/jobs/{job_id}는 현재 파이프라인 상태를 반환합니다. status는 pending → running → done 또는 failed 순으로 바뀌며, 실행 중에는 done_scenes / total_scenes로 진행률을 알 수 있습니다.

while true; do
status=$(curl -s https://video.pyler.ai/api/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $VUF_API_KEY" | jq -r .status)
echo " $status"
[ "$status" = "done" ] && break
[ "$status" = "failed" ] && { echo "job failed"; exit 1; }
sleep 5
done
Response (200)
{
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"video_id": "3f6c1b2e-9a4d-4c7e-8b1f-2d5a6e7f8c90",
"status": "running",
"total_scenes": 42,
"done_scenes": 18,
"error": null,
"reason_code": null,
"reason": null,
"started_at": "2026-10-01T10:23:01Z",
"completed_at": null
}

failed일 때는 error를 파싱하지 말고 안정적인 원인 값인 reason_code(예: source_forbidden, url_unreachable, media_unreadable, processing_stalled, internal_error)로 분기하세요. 완료된 작업에도 이전 시도의 error 텍스트가 남아 있을 수 있습니다. 영상의 status가 failed이면 GET /api/v1/videos/{video_id}에도 같은 reason_code가 나옵니다.

정보

폴링하고 싶지 않다면 GET /api/v1/videos/{video_id}/scenes/stream을 쓰세요. 장면마다 캡션이 준비되는 즉시 보내 주고, 마지막에 done 줄로 끝납니다. 아래 따라 하기를 참고하세요.

따라 하기: 영상을 색인하고 장면 읽기​

  1. 영상 제출
    curl -s -X POST https://video.pyler.ai/api/v1/videos \
    -H "Authorization: Bearer $VUF_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"url": "https://example.com/clip.mp4", "title": "demo clip"}'
    # {"video_id": "3f6c...", "job_id": "a1b2...", "created": true}
    export VIDEO_ID=3f6c1b2e-9a4d-4c7e-8b1f-2d5a6e7f8c90
    export JOB_ID=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
  2. 작업 완료 기다리기

    status가 done이 될 때까지 GET /api/v1/jobs/$JOB_ID를 폴링하세요(위 반복문 참고). failed이면 reason_code를 확인하세요.

  3. 색인 결과 읽기

    GET /api/v1/videos/{video_id}는 영상 전체 결과를 반환합니다. AI summary, genre, IAB 카테고리(iab_content_leaf, iab_audience_interest_leaf, iab_audience_purchase_intent_leaf, iab_ad_product_leaf), garm_video의 GARM 평가, transcript_coverage, scenes의 첫 페이지, 모든 장면에 대한 가벼운 scene_markers, aggregations(브랜드, 제품, 인물, 장소, 오디오 이벤트, 음악 분위기, 화면 속 텍스트, 분위기 타임라인, 행동, 인간 활동, 날씨)가 담깁니다.

    curl -s "https://video.pyler.ai/api/v1/videos/$VIDEO_ID?scene_limit=100" \
    -H "Authorization: Bearer $VUF_API_KEY"
    Response (200, truncated)
    {
    "id": "3f6c1b2e-9a4d-4c7e-8b1f-2d5a6e7f8c90",
    "title": "demo clip",
    "duration_ms": 61234,
    "source_url": "https://example.com/clip.mp4",
    "created_at": "2026-10-01T10:00:00Z",
    "status": "done",
    "summary": "A family builds a snowman in a suburban backyard...",
    "genre": ["family"],
    "iab_content_leaf": ["Family and Relationships"],
    "garm_video": [],
    "external_content_id": null,
    "reason_code": null,
    "indexed_at": "2026-10-01T10:05:12Z",
    "index_trigger": "initial",
    "job_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "reindex_run_id": null,
    "transcript_coverage": 0.82,
    "scene_count": 42,
    "scenes": [
    {
    "id": "9c0d1e2f-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
    "video_id": "3f6c1b2e-9a4d-4c7e-8b1f-2d5a6e7f8c90",
    "scene_index": 0,
    "start_ms": 0,
    "end_ms": 4200,
    "caption": "A child in a red coat packs snow into a ball in a backyard.",
    "transcript": "Okay, let's make the bottom one really big.",
    "text_on_screen": [],
    "valence": 4,
    "arousal": 3,
    "music_mood": null,
    "audio_events": ["laughter"],
    "garm_categories": [],
    "brands": [],
    "product_categories_visible": [],
    "people": [],
    "locations": ["backyard"],
    "action": ["packing snow"],
    "human_activity": ["playing"],
    "weather": "snow",
    "keyframe_url": "https://...",
    "clip_url": "https://..."
    }
    ],
    "scene_markers": [
    { "id": "9c0d1e2f-...", "scene_index": 0, "start_ms": 0, "end_ms": 4200,
    "garm_level": "none", "garm_categories": [] }
    ],
    "aggregations": {
    "brands": [],
    "products": [],
    "locations": [{ "name": "backyard", "count": 12 }],
    "people": [],
    "audio_events": [{ "name": "laughter", "count": 5 }],
    "music_moods": [],
    "text_on_screen": [],
    "mood_timeline": [{ "scene_index": 0, "start_ms": 0, "valence": 4, "arousal": 3 }],
    "actions": [{ "name": "packing snow", "count": 3 }],
    "human_activities": [{ "name": "playing", "count": 20 }],
    "weather_counts": [{ "name": "snow", "count": 42 }]
    }
    }

    scenes에는 처음 scene_limit개의 장면이 담깁니다(1–200, 기본값 50, scene_offset으로 오프셋 지정). scene_markers는 항상 모든 장면을 포함합니다. source_url은 http(s) 출처일 때만 채워집니다.

  4. 나머지 장면 페이지 넘기기

    GET /api/v1/videos/{video_id}/scenes는 위와 같은 형태의 전체 장면을 offset / limit(1–200, 기본값 50), total과 함께 반환합니다.

    curl -s "https://video.pyler.ai/api/v1/videos/$VIDEO_ID/scenes?offset=50&limit=50" \
    -H "Authorization: Bearer $VUF_API_KEY"
    경고

    알 수 없는 video_id는 404가 아니라 빈 페이지(total: 0)를 반환합니다. "없는 영상"과 "장면이 없는 영상"을 구분해야 한다면 GET /api/v1/videos/{video_id}로 영상이 있는지 확인하세요.

  5. 선택: 색인 중에 장면 스트리밍

    GET /api/v1/videos/{video_id}/scenes/stream은 한 줄에 객체 하나씩 오는 NDJSON이며, 영상 전체가 끝날 때까지 기다리지 않습니다.

    • {"event": "scene", "scene": {...}} — 새로 분석된 장면(/scenes 항목과 같은 형태, 반복되지 않음)
    • {"event": "keepalive"} — 유휴 상태일 때 전송되며 무시하면 됩니다
    • {"event": "done", "job_status": "done"} — 색인 생성 완료. job_status는 failed일 수도 있고, 색인 작업이 없는 영상이면 null입니다. 이 줄 다음에 연결이 닫힙니다
    • {"event": "timeout"} — 서버 측 최대 스트림 시간에 도달했습니다. 다시 연결해 새 스냅숏을 받으세요
    curl -N "https://video.pyler.ai/api/v1/videos/$VIDEO_ID/scenes/stream" \
    -H "Authorization: Bearer $VUF_API_KEY"

    장면 순서는 scene_index가 아니라 분석이 끝난 순서를 따릅니다. 이미 색인된 영상에 연결하면 모든 장면을 즉시 다시 보내고 done으로 닫습니다. 종료 줄 없이 연결이 끊기면 다시 연결하세요. 이미 분석된 장면이 먼저 다시 전송됩니다. 여기서는 알 수 없는 video_id에 404를 반환합니다.

배치​

URL 목록​

kind: "url"로 POST /api/v1/videos/batch를 호출하면 한 번에 최대 500행을 제출합니다. 각 행에는 url(최대 2048자)과 선택 항목인 title(최대 500자)이 있습니다. 201 응답의 items 배열은 요청과 1:1로, 요청 순서대로 대응하므로 추가 호출 없이 source_url → video_id를 매핑할 수 있습니다.

curl -X POST https://video.pyler.ai/api/v1/videos/batch \
-H "Authorization: Bearer $VUF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "url",
"items": [
{"url": "https://example.com/a.mp4", "title": "A"},
{"url": "https://example.com/b.mp4"}
]
}'
Response (201)
{
"batch_id": "7d2e4f60-1a2b-4c3d-9e8f-5a6b7c8d9e0f",
"declared_total": null,
"accepted": 2,
"skipped": 0,
"failed": 0,
"items": [
{ "source_url": "https://example.com/a.mp4", "video_id": "3f6c...", "job_id": "a1b2...",
"status": "submitted", "error": null, "created": true },
{ "source_url": "https://example.com/b.mp4", "video_id": "5e7d...", "job_id": "c3d4...",
"status": "submitted", "error": null, "created": false }
]
}

행별 필드:

  • status는 submitted, skipped(검증에서 거부되어 제출되지 않음), failed(검증은 통과했지만 제출되지 않음) 중 하나입니다.
  • error는 skipped / failed 행에서 안정적인 사유 값(예: duplicate_url, blocked_target, dispatch_failed)이며, submitted 행에서는 null입니다.
  • created는 이 행이 영상을 새로 만들었을 때 true입니다. created: false인 submitted 행은 이미 색인 중인 영상에 연결된 것입니다.
  • 한 요청에 두 번 나온 URL은 error: "duplicate_url"로 skipped되지만, 첫 행의 video_id / job_id를 그대로 담습니다.

accepted는 기존 작업에 연결된 행을 포함해 파이프라인에 넘겨진 행 수입니다. 새로 만들어진 수는 items[].created를 세어 확인하세요. submitted는 "색인 완료"가 아니라 "파이프라인에 넘겨짐"을 뜻하며, 기준이 되는 상태는 GET /api/v1/videos/{video_id}입니다. failed 행은 다시 제출하는 것을 전제로 합니다. dispatch_failed URL은 작업이 실패로 표시되므로(best-effort) 아무것도 색인되지 않았습니다.

items가 500개를 넘으면 스키마 검증에 실패해 detail이 필드 오류 배열인 422를 반환합니다. kind가 file / url이 아니거나, kind: "file"인데 items가 비어 있지 않으면 400을 반환합니다.

한 요청에 여러 파일​

POST /api/v1/videos/batch/upload는 여러 file 필드를 multipart로 한 번에 업로드합니다. 파일마다 독립적으로 색인되며, 하나가 실패해도 나머지는 중단되지 않습니다.

curl -X POST https://video.pyler.ai/api/v1/videos/batch/upload \
-H "Authorization: Bearer $VUF_API_KEY" \
-F "file=@a.mp4" -F "file=@b.mp4" -F "file=@c.mp4"

202 응답 본문에는 개수만 담깁니다: batch_id, accepted, skipped(잘못된 확장자, 빈 파일 등으로 처음부터 거부됨), failed. 파일별 video_id와 job_id는 GET /api/v1/videos/batch/{batch_id}에서 확인합니다.

경고

권장: 요청당 파일 10개 이하. 더 큰 업로드는 여러 요청으로 나누세요. 파일 수에 엄격한 상한은 없지만, 500개를 넘으면 413을 반환하는 안전 장치가 있습니다. 이 수는 요청 단위로, 그리고 재사용한 batch_id 전체에 걸쳐 셉니다.

중간에 실패한 요청(예: 연결 끊김)에서 복구하기:

  1. 먼저 GET /api/v1/videos/batch/{batch_id}를 폴링하세요. 업로드에는 자연스러운 중복 제거 키가 없으므로, 이미 기록된 파일을 다시 보내면 중복으로 처리되지 않습니다.
  2. 아직 빠진 파일만 다시 보내세요. 이때 롤업에서 error가 dispatch_failed인 항목은 제외하세요. 해당 영상 행은 이미 커밋되어 있어 다시 보내면 파일이 두 번 색인됩니다.
  3. 업로드 호출이 응답을 반환했다면 배치 롤업보다 그 응답의 accepted / skipped / failed 개수를 믿으세요. 롤업의 항목 수가 accepted보다 적다면 기록 쓰기가 유실된 것이지, 파일이 제출되지 않은 것이 아닙니다.

업로드 호출이 아예 응답하지 않더라도 알려진 ID로 폴링하려면, POST /api/v1/videos/batch(kind: "file", 참고용 선택 항목 total 0–500)로 빈 배치를 먼저 만들고 그 batch_id를 업로드의 폼 필드로 넘기세요.

URL이 담긴 csv 또는 xlsx 시트​

POST /api/v1/videos/batch/upload-sheet는 url 열(과 선택 항목인 title 열)이 있는 .csv 또는 .xlsx를 파싱해 유효한 행을 한 번에 모두 제출합니다.

curl -X POST https://video.pyler.ai/api/v1/videos/batch/upload-sheet \
-H "Authorization: Bearer $VUF_API_KEY" \
-F "file=@catalogue.csv"
Response (201)
{
"batch_id": "7d2e4f60-1a2b-4c3d-9e8f-5a6b7c8d9e0f",
"accepted": 2,
"skipped": 1,
"failed": 0,
"rejected_before_submit": 1,
"rejected_rows": [
{ "row": 4, "value": "https://example.com/a.mp4", "reason": "duplicate_url" }
],
"items": [
{ "source_url": "https://example.com/a.mp4", "video_id": "3f6c...", "job_id": "a1b2...",
"status": "submitted", "error": null, "created": true },
{ "source_url": "https://example.com/b.mp4", "video_id": "5e7d...", "job_id": "c3d4...",
"status": "submitted", "error": null, "created": true }
]
}

URL 형식 검증에 실패한 행, 앞 행과 중복된 행, url/title이 너무 긴 행은 파싱 단계에서 제외됩니다. 이런 행은 rejected_before_submit에 집계되고 skipped에 합산되며, rejected_rows에 최대 100개까지 1부터 시작하는 row 번호(헤더가 1행), 원본 value, duplicate_url이나 url_too_long 같은 안정적인 reason과 함께 나열됩니다. 이 행들은 배치에 기록되지 않으므로 GET /api/v1/videos/batch/{batch_id}는 제출된 행만 셉니다. 제출된 모든 행은 시트 순서대로 URL 배치와 같은 행별 필드를 담아 items로 돌아옵니다.

시트 전용 오류:

상태조건본문
413파일이 시트 크기 상한 10 MB를 넘거나, 유효한 URL 행이 한 배치가 받는 수(현재 500개)보다 많습니다. 여러 시트로 나누세요.detail, code
422.csv / .xlsx가 아님, url 열 없음, 형식이 잘못되었거나 손상된 파일, 유효한 URL 행 없음, 데이터 행 10000개 초과, 열이 너무 많음, 압축 해제 크기 상한을 넘는 시트.detail, code

code가 no_valid_urls이면 오류 본문에 같은 수준의 키 rejected가 하나 더 담깁니다. 파싱 중 제외된 행 수(어떤 배치에도 기록되지 않음)입니다.

422 with code no_valid_urls
{ "detail": "no valid url rows in sheet", "code": "no_valid_urls", "rejected": 17 }

배치 접수 내역 읽기​

GET /api/v1/videos/batch/{batch_id}는 POST /api/v1/videos/batch 또는 POST /api/v1/videos/batch/upload로 만든 배치의 제출 접수 내역입니다. counts는 상태별 합계를, items는 각 행의 status, error, video_id, job_id를 보여 줍니다.

Response (200, truncated)
{
"batch_id": "7d2e4f60-1a2b-4c3d-9e8f-5a6b7c8d9e0f",
"kind": "file",
"total": 3,
"counts": { "pending": 0, "submitted": 2, "skipped": 0, "failed": 1 },
"items": [
{ "id": "...", "source_url": null, "storage_path": "s3://...", "title": "a.mp4",
"status": "submitted", "error": null, "video_id": "3f6c...", "job_id": "a1b2...",
"indexed_at": null, "index_trigger": null, "reindex_run_id": null }
]
}
경고

이 정보는 색인 진행률이 아닙니다. 각 항목의 status는 제출 시점에 한 번 기록된 뒤 갱신되지 않습니다. submitted는 "색인 완료"가 아니라 "접수됨"을 뜻합니다. 이 엔드포인트를 폴링하며 완료를 기다리면 영원히 기다리게 됩니다. counts.pending은 계산된 값(total에서 기록된 항목 수를 뺀 값, 즉 생성 시 선언했지만 아직 업로드로 채워지지 않은 자리)이며 저절로 줄어들지 않습니다. 빠진 파일을 보내세요.

색인 결과는 항목별 ID로 확인하세요. 영상 상태와 결과는 GET /api/v1/videos/{video_id}, 최근 시도의 진행률과 실패 사유는 GET /api/v1/jobs/{job_id}에서 봅니다. 여기의 job_id는 조회 시점에 확인한 영상의 최신 작업(진행 중인 작업 포함)이므로 재색인 후에는 바뀝니다. indexed_at, index_trigger, reindex_run_id는 가장 최근에 끝난 시도를 나타냅니다.

MRSS 피드​

피드는 대시보드에서 등록하고, API로 조회하고 관리합니다. 각 피드는 poll_interval_s 주기로 폴링되며, 피드에서 나온 모든 항목이 색인 대상으로 제출됩니다. 피드로 수집된 영상은 피드 항목의 ID를 external_content_id로 담고 있으며, GET /api/v1/videos?content_id=...로 이 ID와 정확히 일치하는 영상을 찾을 수 있습니다.

호출하는 일
GET /api/v1/mrss-feeds팀에 등록된 피드를 최신순으로 반환합니다: id, feed_url, enabled, plan_blocked, poll_interval_s, last_polled_at, next_poll_at, label.
GET /api/v1/mrss-feeds/{feed_id}피드 하나와 created_at, counts를 반환합니다. counts에는 제출 개수(pending / submitted / failed)와 색인 개수(indexed_done / indexed_running / indexed_failed)가 담깁니다.
PATCH /api/v1/mrss-feeds/{feed_id}enabled, poll_interval_s, label을 부분 수정합니다. 하나 이상 보내야 하며, 빈 본문은 422입니다.
DELETE /api/v1/mrss-feeds/{feed_id}동기화를 멈추고 피드를 삭제합니다. 204, 본문 없음.
GET /api/v1/mrss-feeds/{feed_id}/items피드에서 수집한 항목을 status(제출 상태)와/또는 index_status(색인 상태)로 필터링해 limit(1–200, 기본값 50) / offset으로 페이지를 나눠 반환합니다.
# Switch a feed to daily polling and give it a label
curl -X PATCH https://video.pyler.ai/api/v1/mrss-feeds/$FEED_ID \
-H "Authorization: Bearer $VUF_API_KEY" \
-H "Content-Type: application/json" \
-d '{"poll_interval_s": 86400, "label": "Partner catalogue"}'

# Items whose indexing failed
curl -s "https://video.pyler.ai/api/v1/mrss-feeds/$FEED_ID/items?index_status=failed&limit=100" \
-H "Authorization: Bearer $VUF_API_KEY"

알아 둘 규칙:

  • poll_interval_s는 3600, 21600, 86400(매시간 / 6시간마다 / 매일) 중 하나여야 합니다. 그 밖의 값은 code: mrss.bad_interval과 함께 422입니다. 값을 바꾸면 다음 폴링 시각을 새 UTC 슬롯으로 즉시 다시 계산합니다.
  • label은 최대 200자이며 제어 문자를 쓸 수 없습니다(mrss.bad_label). 비어 있거나 공백만 있는 label은 지워지고, 피드는 feed_url을 대신 보여 줍니다.
  • 피드 URL은 바꿀 수 없습니다. URL이 다르면 다른 피드이므로 새로 등록하세요.
  • 요금제에서 더 이상 MRSS를 허용하지 않는 피드를 다시 활성화하면 code: mrss.disabled와 함께 422를 반환합니다. 비활성화는 항상 가능합니다. 피드의 plan_blocked로 이 상태를 확인할 수 있습니다.
  • 알 수 없는 ID와 다른 팀 소유의 피드는 모두 404를 반환합니다. 잘못된 status / index_status 필터 값은 mrss.bad_status / mrss.bad_index_status와 함께 422를 반환합니다.
  • 삭제는 영구적입니다. 피드에서 이미 색인된 영상은 라이브러리에 남아 계속 검색되며 external_content_id도 유지됩니다. 삭제 시점에 색인 중이던 항목은 완료될 수도 있습니다. 나중에 같은 URL을 다시 등록하면 새 피드가 만들어지고 모든 항목을 다시 색인합니다.

각 피드 항목(FeedItemOut)에는 guid, title, title_en, source_url, status, error, video_id, delivery_status, delivered_at, delivery_error, index_status, index_error, index_reason_code, first_seen_at, pub_date, duration_s가 담깁니다.

맞춤 요약​

영상에 표준 요약이 생기면 POST /api/v1/videos/{video_id}/custom-summaries로 직접 작성한 instructions에 따라 두 번째 요약을 요청할 수 있습니다. 표준 결과는 바뀌지 않습니다.

curl -X POST https://video.pyler.ai/api/v1/videos/$VIDEO_ID/custom-summaries \
-H "Authorization: Bearer $VUF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"instructions": "One-line logline, tone and pacing, setting, themes. Neutral tone.",
"cast": [{"name": "Alex Rivera", "aliases": ["A. Rivera"]}],
"language": "en"
}'
Response (202)
{
"custom_summary_id": "cs_0f3c5a1e9b7d4c2a8e6f1b3d5a7c9e24",
"instruction_id": "ins_7f3a2b1c9d8e4f5a6b7c8d9e0f1a2b3c",
"status": "queued",
"base_version": "std-v3"
}

요청 필드:

instructionsstringbodyrequired

자유 텍스트, 최대 2000자. 요약 길이는 서비스가 정합니다.

castCastMember[]body

name(1–200자)과 선택 항목인 aliases(최대 20개)로 된 항목을 최대 100개까지 넣습니다. 요약에는 여기 있는 인물 이름만 쓸 수 있으며, cast가 없으면 인물 이름을 쓰지 않도록 지시합니다. cast 밖의 이름이 감지된 섹션은 null로 돌아옵니다. 이 검사는 best-effort라 이름을 놓칠 수도 있고, 장소나 브랜드 이름만 있는 섹션을 비울 수도 있습니다. 이름이 중요한 경우에는 결과를 검토하세요.

languagestringbodydefault: en

언어 태그(최대 35자). instructions에 적힌 언어보다 우선합니다.

GET /api/v1/videos/{video_id}/custom-summaries/{custom_summary_id}를 폴링하세요. 준비 중에는 status가 queued이고, 이후 ready, rejected(지시문이 범위를 벗어남, message 참고), failed(다시 요청) 중 하나가 됩니다. ready이면 summary에 logline, tone_pacing, setting, themes 섹션이 문자열 또는 null로 담깁니다. notes는 섹션이 null인 이유를 알려 줍니다. base_summary는 요청의 기준이 된 표준 요약이고, base_version은 그 버전입니다(해당 버전이 삭제되면 null).

Response (200)
{
"status": "ready",
"custom_summary_id": "cs_0f3c5a1e9b7d4c2a8e6f1b3d5a7c9e24",
"instruction_id": "ins_7f3a2b1c9d8e4f5a6b7c8d9e0f1a2b3c",
"base_version": "std-v3",
"summary": {
"logline": "A family turns a snow day into a backyard competition.",
"tone_pacing": "Warm and unhurried.",
"setting": null,
"themes": "Family, play, winter."
},
"notes": ["setting: no place is identifiable"],
"message": null,
"base_summary": "A family builds a snowman in a suburban backyard..."
}

GET /api/v1/videos/{video_id}/custom-summaries는 영상에 대한 조직의 요청을 최신순으로 나열하며, limit(1–100, 기본값 50)과 next_cursor에서 받은 cursor로 페이지를 나눕니다.

미리 고려할 동작:

  • 같은 지시문이 이미 범위를 벗어난다고 판정된 적이 있으면, POST가 곧바로 status: rejected로 응답하고 아무것도 생성하지 않습니다.
  • code: custom_summary_no_base_summary인 422는 영상에 아직 표준 요약이 없다는 뜻입니다. custom_summary_no_org는 키가 어떤 조직에도 속하지 않는다는 뜻이고, quota_exceeded_summaries는 요약 사용 한도를 모두 써서 아무것도 만들어지지 않았다는 뜻입니다.
  • 503은 요청이 대기열에 들어가지 않았다는 뜻입니다. 다시 보내세요. 재시도할 때마다 새 요청이 만들어집니다.
  • 요청 하나는 영상 사용 한도가 아니라 조직의 요약 사용 한도(GET /api/v1/usage의 summaries)에서 1건으로 셉니다. rejected로 끝난 요청도 포함됩니다. failed로 끝났거나, 영상이 삭제될 때 아직 queued였던 요청은 사용 한도로 돌려받습니다.

내 영상 목록​

GET /api/v1/videos는 내 계정이 소유한 영상을 최신순으로 나열합니다. 내 계정 범위로만 한정되며, 공유 샘플 코퍼스는 제외됩니다.

curl -s "https://video.pyler.ai/api/v1/videos?limit=20&status=done&length_bucket=short" \
-H "Authorization: Bearer $VUF_API_KEY"
Response (200, truncated)
{
"items": [
{
"id": "3f6c1b2e-9a4d-4c7e-8b1f-2d5a6e7f8c90",
"title": "demo clip",
"duration_ms": 61234,
"status": "done",
"is_user_upload": true,
"org_id": "org_...",
"created_at": "2026-10-01T10:00:00Z",
"summary": "A family builds a snowman...",
"genre": ["family"],
"iab_content_leaf": ["Family and Relationships"],
"garm_video": [],
"keyframe_url": "https://...",
"external_content_id": null,
"indexed_at": "2026-10-01T10:05:12Z",
"index_trigger": "initial",
"job_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"reindex_run_id": null
}
],
"next_cursor": null
}

페이지는 커서 기반으로 나눕니다. limit은 1–100(기본값 50)이며, 이전 응답의 next_cursor를 cursor로 넘기세요. next_cursor가 null이면 마지막 페이지입니다.

필터타입설명
statusstring영상 상태.
genrestring[]여러 값을 넣으려면 파라미터를 반복하세요.
iab_t1string[]IAB Tier-1 콘텐츠 카테고리.
garm_min_levelstring브랜드 세이프티 하한.
duration_min_sec, duration_max_secinteger초 단위 길이 범위.
length_bucketstringshort, medium, long 중 하나.
searchstring자유 텍스트 일치.
content_idstringMRSS 외부 콘텐츠 ID와 정확히 일치. 알 수 없는 ID는 빈 목록을 반환합니다.
reindexed_sincedate-time가장 최근에 끝난 색인 시도가 이 시각 또는 그 이후에 끝난 재색인인 영상. 실패한 재색인도 포함합니다. UTC 오프셋이 필요합니다(2026-10-01T00:00:00Z, +는 %2B로 쓰세요).
reindex_run_iduuid해당 재색인 실행에 작업이 있는 모든 영상. 아직 실행 중인 영상도 포함합니다.

한 번만 색인된 영상은 두 재색인 필터 어디에도 걸리지 않습니다. 형식이 잘못된 UUID나 UTC 오프셋이 없는 타임스탬프는 422를 반환합니다.

각 항목에는 색인 이력 필드인 indexed_at, index_trigger(initial 또는 reindex), job_id, reindex_run_id가 담깁니다. 모두 가장 최근에 끝난 시도를 나타내며, 첫 색인이 진행 중일 때는 모두 null입니다. 이미 done이었던 영상은 이후 시도가 실패해도 그 status를 유지합니다. 따라서 indexed_at을 마지막으로 본 값과 비교하고, 결과는 job_id로 확인하세요.

다음 단계​