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.
/api/video-flashcards/v1/jobsUpload 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.
/api/video-flashcards/v1/jobs/from-urlStart 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.
/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.
| Field | Type | Required | Notes |
|---|---|---|---|
video_file | file | Yes | Source video upload. Supported containers include MP4, MOV, MKV, WEBM, M4V, AVI, MPEG, and MPG. Default limit is 500 MB and 15 minutes. |
learning_direction | string | Yes | Supported values: `thai_to_chinese`, `chinese_to_thai`, `chinese_to_vietnamese`, `korean_to_chinese`, and `japanese_to_chinese`. |
source_language / target_language | string | No | Optional explicit language pair. If supplied, it must match the selected `learning_direction`. |
skip_vocal_separation | boolean-like string | No | Defaults to `false`. Use `true` for clean speech or lecture clips where separating vocals would slow processing without improving ASR. |
max_words_per_segment | integer | No | Defaults to 15. Must be between 1 and 80. Lower values produce shorter flashcard clips. |
include_rendered_segments | boolean-like string | No | Defaults to `true`. When enabled, completed segments include rendered clip URLs with word-card overlays. |
include_original_segments | boolean-like string | No | Defaults to `true`. When enabled, completed segments include the raw cut segment URLs. |
idempotency_key | string | No | Optional caller key up to 180 characters. Reusing it returns the active matching job instead of creating a duplicate. |
POST /api/video-flashcards/v1/jobs
Samplecurl -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`.
| Field | Type | Required | Notes |
|---|---|---|---|
url | string | Yes | A supported YouTube watch, Shorts, or youtu.be URL when `source_type=youtube`; a public direct video URL when `source_type=direct_video_url`. |
source_type | string | No | Defaults to `youtube`. Use `direct_video_url` for a public MP4/MOV/etc. file. Private network hosts and unsafe redirects are rejected. |
learning_direction | string | Yes | Supported values: `thai_to_chinese`, `chinese_to_thai`, `chinese_to_vietnamese`, `korean_to_chinese`, and `japanese_to_chinese`. |
source_language / target_language | string | No | Optional explicit language pair. If supplied, it must match the selected `learning_direction`. |
skip_vocal_separation | boolean | No | Defaults to `false`. Use `true` for clean speech or lecture clips where separating vocals would slow processing without improving ASR. |
max_words_per_segment | integer | No | Defaults to 15. Must be between 1 and 80. Lower values produce shorter flashcard clips. |
include_rendered_segments | boolean | No | Defaults to `true`. When enabled, completed segments include rendered clip URLs with word-card overlays. |
include_original_segments | boolean | No | Defaults to `true`. When enabled, completed segments include the raw cut segment URLs. |
idempotency_key | string | No | Optional 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
Samplecurl -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
| Field | Type | Required | Notes |
|---|---|---|---|
job_id | string | Always | Public job id returned by the start request. It is prefixed with `vf_`. |
status | string | Always | One of `queued`, `processing`, `done`, `failed`, or `cancelled`. |
ready / successful | boolean / boolean | null | On status responses | Poll until `ready=true`. Then read `result` when `successful=true`, or `error` when `successful=false`. |
progress | object | Always | Best-effort progress with `percent`, `stage`, and `message`. |
result.media | object | When done | Source metadata including title, duration, source URL, and uploaded original video URL when available. |
result.transcript | object | When done | Full transcript text plus timed transcript segments in the source language. |
result.segments[] | array | When done | Flashcard-ready clip segments with source text, translation, English gloss, timing, words, and video URLs. |
result.segments[].word_items[] | array | When done | Per-word vocabulary data with `word`, `pronunciation`, `meaning`, `meaning_en`, and optional timing/probability. |
video.original_segment_url / video.rendered_segment_url | string | null | When requested | Original cut and rendered word-card clip URLs. `has_word_cards=true` means the rendered clip is available. |
error | object | On failure | Failure code and message when the job fails or is cancelled. |
GET /api/video-flashcards/v1/jobs/{job_id}
Samplecurl -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"
}
}