Skip to main content

Quickstart

This walkthrough takes a public video URL to searchable scene data. You need an API key from the dashboard's API Keys tab; see Authentication.

Export it once so the examples below can use it:

export VUF_API_KEY="vuf_live_xxxxxxxx.yyyyyyyy"
  1. Submit a video

    POST /api/v1/videos takes a publicly fetchable http/https URL and an optional title. It returns 202 Accepted immediately; indexing runs asynchronously through scene detection, captioning, embedding and moderation.

    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:

    {
    "video_id": "3f2c9d1e-...",
    "job_id": "a81b7c44-...",
    "created": true
    }

    created is false when the same URL is already being indexed for you. In that case you get the existing video_id and job_id back instead of a duplicate pipeline, still with status 202.

    See POST /api/v1/videos.

  2. Poll the job

    GET /api/v1/jobs/{job_id} returns the pipeline state. status moves pending → running → done or failed. done_scenes / total_scenes report progress while it runs.

    curl https://video.pyler.ai/api/v1/jobs/$JOB_ID \
    -H "Authorization: Bearer $VUF_API_KEY"

    Response while running:

    {
    "id": "a81b7c44-...",
    "video_id": "3f2c9d1e-...",
    "status": "running",
    "total_scenes": 42,
    "done_scenes": 18,
    "error": null,
    "reason_code": null,
    "reason": null,
    "started_at": "2026-06-04T10:23:01Z",
    "completed_at": null
    }

    On failed, branch on reason_code for the stable cause; error carries the raw text.

    See GET /api/v1/jobs/{job_id}.

  3. Read the scenes and search

    Once the job is done, page through the video's scenes with GET /api/v1/videos/{video_id}/scenes. Each page is a ScenesPage: items, offset, limit (1–200, default 50) and total.

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

    Each item is a SceneLite:

    {
    "items": [
    {
    "id": "c0a4e2f7-...",
    "video_id": "3f2c9d1e-...",
    "scene_index": 0,
    "start_ms": 0,
    "end_ms": 4120,
    "caption": "A child in a red jacket runs across a snowy yard.",
    "transcript": null,
    "text_on_screen": [],
    "valence": 4,
    "arousal": 3,
    "music_mood": "playful",
    "audio_events": ["laughter"],
    "garm_categories": [],
    "brands": [],
    "product_categories_visible": [],
    "people": [],
    "locations": ["backyard"],
    "action": ["running"],
    "human_activity": ["playing"],
    "weather": "snow",
    "keyframe_url": "https://...",
    "clip_url": "https://..."
    }
    ],
    "offset": 0,
    "limit": 50,
    "total": 42
    }
    warning

    An unknown video_id returns an empty page (total: 0), not a 404. Use GET /api/v1/videos/{video_id} if you need to tell "no such video" from "a video with no scenes".

    Now search the corpus. POST /api/v1/search takes a natural-language query, a modality (caption, transcript, omni, both or all) and k (1–100, default 20). Add video_id to restrict the search to one video's scenes.

    curl -X POST https://video.pyler.ai/api/v1/search \
    -H "Authorization: Bearer $VUF_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"query": "child playing in snow", "modality": "both", "k": 10}'

    The response groups scene matches by video, one column per modality. With modality: "both" the caption and omni lists are populated and transcript is empty.

    {
    "caption": [
    {
    "score": 0.81,
    "video": {
    "id": "3f2c9d1e-...",
    "title": "demo clip",
    "duration_ms": 183000
    },
    "scenes": [
    {
    "score": 0.79,
    "scene": {
    "id": "c0a4e2f7-...",
    "video_id": "3f2c9d1e-...",
    "scene_index": 0,
    "start_ms": 0,
    "end_ms": 4120,
    "caption": "A child in a red jacket runs across a snowy yard.",
    "keyframe_url": "https://...",
    "clip_url": "https://..."
    }
    }
    ]
    }
    ],
    "transcript": [],
    "omni": []
    }

    See GET /api/v1/videos/{video_id}/scenes and POST /api/v1/search.

Where to go next​