Skip to content
Sign in

Video Flashcards API

Turn uploaded or hosted videos into language-learning flashcard clips.

The Video Flashcards API exposes an asynchronous video-to-flashcards surface under `/api/video-flashcards/v1`. Submit a video upload, YouTube URL, or public direct video URL with a supported learning direction, receive a `job_id`, then poll until the result returns transcript segments, word-level vocabulary items, and optional rendered word-card clip URLs.

POST/api/video-flashcards/v1/jobs

Upload a source video

Accepts `multipart/form-data` with `video_file` and flashcard options. The route stores the media, validates duration, charges credits, and queues the flashcard pipeline.

POST/api/video-flashcards/v1/jobs/from-url

Start from a video URL

Accepts JSON for supported YouTube URLs or public direct video file URLs. Direct video URLs are downloaded with private-network and redirect safety checks.

GET/api/video-flashcards/v1/jobs/{job_id}

Poll job status

Returns progress while processing. When complete, `result.segments[]` contains text, translations, word items, and original/rendered segment video URLs.

Multipart upload fields

Use this route when your integration already has the video file. The pipeline transcribes the source language, translates to the target language, extracts word-level vocabulary, cuts source clips, and can render word-card overlays for each segment.

FieldTypeRequiredNotes
video_filefileYesSource video upload. Supported containers include MP4, MOV, MKV, WEBM, M4V, AVI, MPEG, and MPG. Default limit is 500 MB and 15 minutes.
learning_directionstringYesSupported values: `thai_to_chinese`, `chinese_to_thai`, `chinese_to_vietnamese`, `korean_to_chinese`, and `japanese_to_chinese`.
source_language / target_languagestringNoOptional explicit language pair. If supplied, it must match the selected `learning_direction`.
skip_vocal_separationboolean-like stringNoDefaults to `false`. Use `true` for clean speech or lecture clips where separating vocals would slow processing without improving ASR.
max_words_per_segmentintegerNoDefaults to 15. Must be between 1 and 80. Lower values produce shorter flashcard clips.
include_rendered_segmentsboolean-like stringNoDefaults to `true`. When enabled, completed segments include rendered clip URLs with word-card overlays.
include_original_segmentsboolean-like stringNoDefaults to `true`. When enabled, completed segments include the raw cut segment URLs.
idempotency_keystringNoOptional caller key up to 180 characters. Reusing it returns the active matching job instead of creating a duplicate.

POST /api/video-flashcards/v1/jobs

Sample
curl -sS \
  -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -F "video_file=@lesson.mp4;type=video/mp4" \
  -F "learning_direction=thai_to_chinese" \
  -F "max_words_per_segment=8" \
  -F "include_rendered_segments=true" \
  -F "include_original_segments=true" \
  -F "idempotency_key=lesson-001-th-zh" \
  "https://kaleidovid.com/api/video-flashcards/v1/jobs"

Queued response example

Sample
{
  "success": true,
  "job_id": "vf_clxq3videojob",
  "status": "queued",
  "statusPath": "/api/video-flashcards/v1/jobs/vf_clxq3videojob",
  "limits": {
    "requests_per_minute": 120,
    "concurrent_video_jobs": 3,
    "daily_video_minutes": 60,
    "today_video_minutes_used": null,
    "max_video_duration_seconds": 900,
    "max_upload_bytes": 524288000
  }
}

URL request fields

Use the URL route when the source lives on YouTube or as a public video file. For direct files, send `source_type=direct_video_url`; for YouTube, omit `source_type` or send `youtube`.

FieldTypeRequiredNotes
urlstringYesA supported YouTube watch, Shorts, or youtu.be URL when `source_type=youtube`; a public direct video URL when `source_type=direct_video_url`.
source_typestringNoDefaults to `youtube`. Use `direct_video_url` for a public MP4/MOV/etc. file. Private network hosts and unsafe redirects are rejected.
learning_directionstringYesSupported values: `thai_to_chinese`, `chinese_to_thai`, `chinese_to_vietnamese`, `korean_to_chinese`, and `japanese_to_chinese`.
source_language / target_languagestringNoOptional explicit language pair. If supplied, it must match the selected `learning_direction`.
skip_vocal_separationbooleanNoDefaults to `false`. Use `true` for clean speech or lecture clips where separating vocals would slow processing without improving ASR.
max_words_per_segmentintegerNoDefaults to 15. Must be between 1 and 80. Lower values produce shorter flashcard clips.
include_rendered_segmentsbooleanNoDefaults to `true`. When enabled, completed segments include rendered clip URLs with word-card overlays.
include_original_segmentsbooleanNoDefaults to `true`. When enabled, completed segments include the raw cut segment URLs.
idempotency_keystringNoOptional caller key up to 180 characters. Reusing it returns the active matching job instead of creating a duplicate.

POST /api/video-flashcards/v1/jobs/from-url

Sample
curl -sS \
  -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "source_type": "youtube",
    "learning_direction": "japanese_to_chinese",
    "skip_vocal_separation": true,
    "max_words_per_segment": 10,
    "idempotency_key": "yt-lesson-001-ja-zh"
  }' \
  "https://kaleidovid.com/api/video-flashcards/v1/jobs/from-url"

Python upload and polling example

Sample
# pip install requests

import time
import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://kaleidovid.com/api/video-flashcards/v1"

with open("lesson.mp4", "rb") as video:
    start = requests.post(
        f"{BASE_URL}/jobs",
        headers={"x-api-key": API_KEY},
        data={
            "learning_direction": "thai_to_chinese",
            "max_words_per_segment": "8",
            "include_rendered_segments": "true",
            "include_original_segments": "true",
            "idempotency_key": "lesson-001-th-zh",
        },
        files={"video_file": ("lesson.mp4", video, "video/mp4")},
        timeout=300,
    )

start.raise_for_status()
job_id = start.json()["job_id"]

while True:
    status = requests.get(f"{BASE_URL}/jobs/{job_id}", headers={"x-api-key": API_KEY}, timeout=60)
    status.raise_for_status()
    payload = status.json()
    if payload["ready"]:
        break
    time.sleep(5)

if not payload["successful"]:
    raise RuntimeError(payload["error"]["message"])

for segment in payload["result"]["segments"]:
    print(segment["text"], "=>", segment["translation"])
    print(segment["video"]["rendered_segment_url"])

Status response fields

FieldTypeRequiredNotes
job_idstringAlwaysPublic job id returned by the start request. It is prefixed with `vf_`.
statusstringAlwaysOne of `queued`, `processing`, `done`, `failed`, or `cancelled`.
ready / successfulboolean / boolean | nullOn status responsesPoll until `ready=true`. Then read `result` when `successful=true`, or `error` when `successful=false`.
progressobjectAlwaysBest-effort progress with `percent`, `stage`, and `message`.
result.mediaobjectWhen doneSource metadata including title, duration, source URL, and uploaded original video URL when available.
result.transcriptobjectWhen doneFull transcript text plus timed transcript segments in the source language.
result.segments[]arrayWhen doneFlashcard-ready clip segments with source text, translation, English gloss, timing, words, and video URLs.
result.segments[].word_items[]arrayWhen donePer-word vocabulary data with `word`, `pronunciation`, `meaning`, `meaning_en`, and optional timing/probability.
video.original_segment_url / video.rendered_segment_urlstring | nullWhen requestedOriginal cut and rendered word-card clip URLs. `has_word_cards=true` means the rendered clip is available.
errorobjectOn failureFailure code and message when the job fails or is cancelled.

GET /api/video-flashcards/v1/jobs/{job_id}

Sample
curl -sS \
  -H "x-api-key: YOUR_API_KEY" \
  "https://kaleidovid.com/api/video-flashcards/v1/jobs/vf_clxq3videojob"

Processing status example

Sample
{
  "job_id": "vf_clxq3videojob",
  "status": "processing",
  "ready": false,
  "successful": null,
  "progress": {
    "percent": 42,
    "stage": "translating",
    "message": "translating"
  },
  "media": {
    "title": "lesson",
    "duration": 312.4
  }
}

Completed status response example

Sample
{
  "job_id": "vf_clxq3videojob",
  "status": "done",
  "ready": true,
  "successful": true,
  "progress": {
    "percent": 100,
    "stage": "done",
    "message": "done"
  },
  "media": {
    "title": "lesson",
    "duration": 312.4
  },
  "result": {
    "learning_direction": "thai_to_chinese",
    "source_language": "th",
    "target_language": "zh",
    "media": {
      "title": "lesson",
      "duration": 312.4,
      "source_url": null,
      "original_video_url": "/uploads/video-flashcards/imports/user-1/lesson.mp4"
    },
    "transcript": {
      "text": "สวัสดีครับ วันนี้เราจะเรียนคำศัพท์ใหม่",
      "language": "th",
      "segments": [
        {
          "index": 0,
          "start": 1.2,
          "end": 4.6,
          "text": "สวัสดีครับ วันนี้เราจะเรียนคำศัพท์ใหม่"
        }
      ]
    },
    "segments": [
      {
        "index": 0,
        "start": 1.2,
        "end": 4.6,
        "duration": 3.4,
        "text": "สวัสดีครับ วันนี้เราจะเรียนคำศัพท์ใหม่",
        "translation": "你好,今天我们会学习新词。",
        "translation_en": "Hello, today we will learn new vocabulary.",
        "word_items": [
          {
            "word": "สวัสดี",
            "pronunciation": "sa-wat-dee",
            "meaning": "你好",
            "meaning_en": "hello",
            "start": 1.2,
            "end": 1.82,
            "probability": 0.94
          }
        ],
        "video": {
          "original_segment_url": "/uploads/flashcards/original_segments/segment_000.mp4",
          "rendered_segment_url": "/uploads/flashcards/rendered_segments/segment_000.mp4",
          "content_type": "video/mp4",
          "has_word_cards": true
        }
      }
    ]
  }
}

Failed status response example

Sample
{
  "job_id": "vf_clxq3videojob",
  "status": "failed",
  "ready": true,
  "successful": false,
  "progress": {
    "percent": 64,
    "stage": "asr",
    "message": "asr"
  },
  "media": {
    "title": "lesson",
    "duration": 312.4
  },
  "error": {
    "code": "processing_failed",
    "message": "Video flashcard job failed"
  }
}
Supported learning directions are intentionally explicit so clients can rely on stable source/target behavior: `thai_to_chinese`, `chinese_to_thai`, `chinese_to_vietnamese`, `korean_to_chinese`, and `japanese_to_chinese`. Use `idempotency_key` for retries; if the same team sends the same key while a matching job is still active, the API returns that job instead of creating another one.