API Endpoints
Complete reference for the SubKit API — 8 endpoints covering subtitle extraction, batch processing, playlist support, AI analysis, and more.
Base URL
Production: https://api.ytsubs.dev
Development: http://localhost:8000
Overview
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST | /extract | Extract subtitles from a single video | API Key |
POST | /batch | Extract from up to 10 videos in one request | API Key |
GET | /playlist | Extract from all videos in a playlist | API Key |
GET | /validate | Check subtitle availability before extracting | None |
POST | /summarize | AI-generated video summary | API Key (Pro+) |
GET | /comments | Extract video comments | API Key (Pro+) |
POST | /comments/analyze | AI sentiment and theme analysis of comments | API Key (Pro+) |
GET | /health | API status and version | None |
Extract Subtitles
/api/v1/extractExtract subtitles from a YouTube video. Supports multiple languages and output formats (timed segments or plain text).
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | YouTube video URL. Supports youtube.com/watch, youtu.be, and youtube.com/shorts formats. |
language | string | Optional(default: "en") | ISO 639-1 language code for desired subtitle language. |
include_timestamps | boolean | Optional(default: true) | When true, returns timed segments. When false, returns plain text. |
force_refresh | boolean | Optional(default: false) | Bypass cache and fetch fresh subtitles from YouTube. |
include | string[] | Optional(default: []) | Optional metadata fields to include. See Video Metadata section below. |
format | string | Optional(default: "json") | Output format. Options: json, srt, vtt, txt, md. See Output Formats section. |
curl -X POST https://api.ytsubs.dev/api/v1/extract \
-H "Content-Type: application/json" \
-H "X-API-Key: yt_sub_your_api_key_here" \
-d '{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"language": "en",
"include_timestamps": true,
"include": ["description", "view_count"]
}'Response (Timed Mode)
When include_timestamps is true (default), subtitles are returned as an array of timed segments:
| Field | Type | Description |
|---|---|---|
video_id | string | YouTube video ID extracted from URL |
metadata | object | Video metadata (see Video Metadata section) |
language | string | Language code of returned subtitles |
subtitle_type | string | "manual" (human-created) or "auto_generated" |
subtitles | array | Array of subtitle segments with timing |
subtitles[].text | string | Text content of the segment |
subtitles[].start | number | Start time in seconds |
subtitles[].duration | number | Duration in seconds |
cached | boolean | Whether response was served from cache |
extracted_at | string | ISO 8601 timestamp of extraction |
rate_limit | object | Current rate limit status (see below) |
request_id | string | Unique request identifier for support |
{
"video_id": "dQw4w9WgXcQ",
"metadata": {
"title": "Rick Astley - Never Gonna Give You Up",
"channel": "Rick Astley",
"channel_id": "UCuAXFkgsw1L7xaCfnd5JJOw",
"duration": 213,
"upload_date": "20091025",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"description": "The official video for...",
"view_count": 1500000000
},
"language": "en",
"subtitle_type": "manual",
"subtitles": [
{
"text": "We're no strangers to love",
"start": 18.0,
"duration": 3.5
},
{
"text": "You know the rules and so do I",
"start": 21.5,
"duration": 3.2
}
],
"cached": false,
"extracted_at": "2024-01-15T10:30:00Z",
"rate_limit": {
"remaining": 99,
"limit": 100,
"reset": 1705363200
},
"request_id": "req_abc123"
}Response (Plain Text Mode)
When include_timestamps is false, subtitles are returned as concatenated plain text:
| Field | Type | Description |
|---|---|---|
video_id | string | YouTube video ID extracted from URL |
metadata | object | Video metadata (see Video Metadata section) |
language | string | Language code of returned subtitles |
subtitle_type | string | "manual" or "auto_generated" |
text | string | Full subtitle text without timestamps |
segment_count | number | Number of original segments combined |
cached | boolean | Whether response was served from cache |
extracted_at | string | ISO 8601 timestamp of extraction |
rate_limit | object | Current rate limit status |
request_id | string | Unique request identifier |
{
"video_id": "dQw4w9WgXcQ",
"metadata": {
"title": "Rick Astley - Never Gonna Give You Up",
"channel": "Rick Astley",
"channel_id": "UCuAXFkgsw1L7xaCfnd5JJOw",
"duration": 213,
"upload_date": "20091025",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg"
},
"language": "en",
"subtitle_type": "manual",
"text": "We're no strangers to love You know the rules and so do I...",
"segment_count": 42,
"cached": true,
"extracted_at": "2024-01-15T10:30:00Z",
"rate_limit": {
"remaining": 98,
"limit": 100,
"reset": 1705363200
},
"request_id": "req_def456"
}Output Formats
Use the format parameter to receive subtitles in different formats. The default is JSON.
| Format | Content-Type | Description |
|---|---|---|
json | application/json | Default. Full response with metadata and timing. |
srt | text/srt | SubRip format. Compatible with most video players. |
vtt | text/vtt | WebVTT format. Native HTML5 video support. |
txt | text/plain | Plain text. One line per segment, no timing. |
md | text/markdown | Markdown with optional timestamps. Great for AI/LLM input. |
When to Use Each Format
- JSON — API integrations, programmatic access, when you need metadata
- SRT — Video editing software (Premiere, DaVinci), media players (VLC)
- VTT — Web video players, HTML5 <track> element, streaming platforms
- TXT — Search indexing, simple text processing, reading transcripts
- MD — AI/LLM context, documentation, note-taking apps
Format Examples
1
00:00:18,000 --> 00:00:21,500
We're no strangers to love
2
00:00:21,500 --> 00:00:24,700
You know the rules and so do IWEBVTT
00:00:18.000 --> 00:00:21.500
We're no strangers to love
00:00:21.500 --> 00:00:24.700
You know the rules and so do IWe're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of# Rick Astley - Never Gonna Give You Up
**Channel:** Rick Astley
**Duration:** 3:33
## Transcript
[0:18] We're no strangers to love
[0:21] You know the rules and so do I
[0:25] A full commitment's what I'm thinking ofMarkdown Timestamps: The include_timestamps parameter controls whether timestamps appear in Markdown output. Set to false for cleaner prose-style text.
AI Summary
/api/v1/summarizeGenerate an AI-powered summary of a YouTube video using its transcript. Requires Pro or Business tier.
Credit Cost
This endpoint uses credits. Cost varies by video duration: 2 credits (≤10 min), 4 credits (10-30 min), 6 credits (30+ min). See Credits for details.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
video_url | string | Required | YouTube video URL to summarize. |
style | string | Optional(default: "brief") | Summary style: "brief" (concise paragraph), "detailed" (comprehensive), "key_points" (bullet list). |
curl -X POST https://api.ytsubs.dev/api/v1/summarize \
-H "Content-Type: application/json" \
-H "X-API-Key: yt_sub_your_api_key_here" \
-d '{
"video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"style": "key_points"
}'Response
| Field | Type | Description |
|---|---|---|
summary | string | AI-generated summary text |
video_id | string | YouTube video ID |
credits_used | number | Credits charged (2, 4, or 6 based on duration) |
cached | boolean | Whether served from cache (0 credits if you previously paid) |
video_metadata | object | Video title, duration, and channel |
timestamps | array | Structured timestamps with topic labels (may be empty for short videos) |
{
"summary": "This video presents the classic 1987 hit 'Never Gonna Give You Up' by Rick Astley...",
"video_id": "dQw4w9WgXcQ",
"credits_used": 2,
"cached": false,
"video_metadata": {
"title": "Rick Astley - Never Gonna Give You Up",
"duration": 213,
"channel": "Rick Astley"
},
"timestamps": [
{ "seconds": 0, "topic": "Introduction", "text": "Opening instrumental" },
{ "seconds": 18, "topic": "First Verse", "text": "Main chorus begins" }
]
}Summary Styles
| Style | Description |
|---|---|
brief | Concise 2-3 sentence summary. Best for quick overviews. |
detailed | Comprehensive paragraph covering main topics and key points. |
key_points | Bullet-point list of key takeaways. Best for educational content. |
Caching: Summaries are cached for 24 hours. If you request the same video again, you get the cached result at no additional cost. Different users requesting the same video are each charged once.
Comments Download
/api/v1/commentsExtract comments from a YouTube video. Supports up to 2,000 comments per request. Requires Pro or higher tier.
Credit Cost
1 credit per 500 comments (rounded up). Example: 501 comments = 2 credits. See Credits for details.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | YouTube video URL. |
max | number | Optional(default: 500) | Maximum comments to return (1-2000). |
sort | string | Optional(default: "relevance") | Sort order: "relevance" (YouTube default) or "time" (newest first). |
curl "https://api.ytsubs.dev/api/v1/comments?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ&max=100&sort=time" \
-H "X-API-Key: yt_sub_your_api_key_here"Response
| Field | Type | Description |
|---|---|---|
comments | array | Array of comment objects |
comments[].author | string | Comment author username |
comments[].text | string | Comment text content |
comments[].likes | number | Number of likes on comment |
comments[].published_at | number | Unix timestamp when published |
comments[].is_reply | boolean | True if reply to another comment |
video_id | string | YouTube video ID |
comment_count | number | Number of comments returned |
comments_disabled | boolean | True if video has comments disabled |
cached | boolean | Whether served from cache (0 credits) |
credits_used | number | Credits charged for this request |
request_id | string | Unique request identifier |
{
"comments": [
{
"author": "MusicFan123",
"text": "This song never gets old!",
"likes": 1523,
"published_at": 1609459200,
"is_reply": false
},
{
"author": "RetroLover",
"text": "The 80s were the best era for music",
"likes": 842,
"published_at": 1612137600,
"is_reply": false
}
],
"video_id": "dQw4w9WgXcQ",
"comment_count": 100,
"comments_disabled": false,
"cached": false,
"credits_used": 1,
"request_id": "req_abc123"
}Comments Disabled: If a video has comments disabled, the response returns an empty array with comments_disabled: true. No credits are charged in this case.
Comments Analysis
/api/v1/comments/analyzeAI-powered sentiment analysis of video comments. Identifies themes, sentiment, and extracts key representative quotes. Requires Pro or higher tier.
Credit Cost
Fixed 3 credits per analysis, regardless of comment count. Minimum 10 comments required.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
video_url | string | Required | YouTube video URL to analyze comments for. |
sample_size | number | Optional(default: 50) | Number of comments to sample for analysis (10-100). |
curl -X POST https://api.ytsubs.dev/api/v1/comments/analyze \
-H "Content-Type: application/json" \
-H "X-API-Key: yt_sub_your_api_key_here" \
-d '{
"video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"sample_size": 50
}'Response
| Field | Type | Description |
|---|---|---|
sentiment | string | Overall sentiment: "positive", "negative", or "mixed" |
themes | array | Up to 5 key themes identified in comments |
key_quotes | array | Up to 10 representative quotes with author |
key_quotes[].text | string | The comment text (may be truncated) |
key_quotes[].author | string | Comment author name |
video_id | string | YouTube video ID |
sample_size | number | Actual number of comments analyzed |
total_comments | number | Total comments available on video |
credits_used | number | Credits charged (always 3, or 0 if cached) |
cached | boolean | Whether served from cache |
request_id | string | Unique request identifier |
{
"sentiment": "positive",
"themes": [
"Nostalgia for 80s music",
"Appreciation for the music video",
"Rick Astley's vocal talent",
"Internet meme culture",
"Timeless appeal"
],
"key_quotes": [
{
"text": "This song is genuinely great, meme status aside",
"author": "MusicCritic42"
},
{
"text": "The 80s really knew how to make music videos",
"author": "RetroFan"
}
],
"video_id": "dQw4w9WgXcQ",
"sample_size": 50,
"total_comments": 2453,
"credits_used": 3,
"cached": false,
"request_id": "req_xyz789"
}Sampling Strategy: Comments are sampled using a balanced approach: 50% from top comments (by likes) and 50% from recent comments. This ensures analysis captures both popular sentiment and current reactions.
Batch Extraction
/api/v1/batchExtract subtitles from multiple YouTube videos in a single request. Process up to 10 videos concurrently with partial success handling.
Credit Cost
1 credit per successful extraction. Failed extractions are free. Batch counts as 1 request toward your daily rate limit.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
urls | string[] | Required | Array of YouTube video URLs (1-10 URLs). |
language | string | Optional(default: "en") | ISO 639-1 language code for subtitles. |
format | string | Optional(default: "json") | Output format: json, srt, vtt, txt, or md. |
include_timestamps | boolean | Optional(default: true) | Include timing data in output. |
include | string[] | Optional(default: []) | Optional metadata fields to include. |
curl -X POST https://api.ytsubs.dev/api/v1/batch \
-H "Content-Type: application/json" \
-H "X-API-Key: yt_sub_your_api_key_here" \
-d '{
"urls": [
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"https://youtu.be/9bZkp7q19f0"
],
"language": "en",
"format": "json"
}'Response
| Field | Type | Description |
|---|---|---|
results | array | One result per input URL (success or error) |
results[].success | boolean | True if extraction succeeded |
results[].video_id | string | YouTube video ID |
results[].url | string | Original URL submitted |
results[].subtitles | array | Subtitle segments (if success) |
results[].error_code | string | Error code (if failed) |
results[].error_message | string | Error message (if failed) |
credits_charged | number | Total credits charged |
credits_details | object | Breakdown: successful, failed, total_attempted |
rate_limit | object | Current rate limit status |
extracted_at | string | ISO8601 timestamp |
{
"results": [
{
"success": true,
"video_id": "dQw4w9WgXcQ",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"subtitles": [{ "text": "...", "start": 0.0, "duration": 3.5 }],
"language": "en",
"metadata": { "title": "...", "channel": "...", "duration": 213 },
"subtitle_type": "manual"
},
{
"success": false,
"url": "https://youtu.be/invalid123",
"video_id": null,
"error_code": "VIDEO_NOT_FOUND",
"error_message": "Video not found or unavailable"
}
],
"credits_charged": 1,
"credits_details": { "successful": 1, "failed": 1, "total_attempted": 2 },
"rate_limit": { "limit": 10000, "remaining": 9999, "reset": 1705449600 },
"extracted_at": "2024-01-16T10:30:00Z"
}Timeout: Batch requests have a 60-second total timeout, distributed across videos. Partial results are returned even if some videos timeout.
Playlist Extraction
/api/v1/playlistExtract subtitles from all videos in a YouTube playlist. Supports up to 25 videos per request with extended timeout.
Credit Cost
1 credit per successful extraction. Failed extractions are free. Playlist counts as 1 request toward your daily rate limit.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | YouTube playlist URL. |
max | number | Optional(default: 10) | Maximum videos to process (1-25). |
language | string | Optional(default: "en") | ISO 639-1 language code for subtitles. |
format | string | Optional(default: "json") | Output format: json, srt, vtt, txt, or md. |
include_timestamps | boolean | Optional(default: true) | Include timing data in output. |
include | string | Optional(default: "") | Comma-separated optional metadata fields. |
curl "https://api.ytsubs.dev/api/v1/playlist?url=https://www.youtube.com/playlist?list=PLxxx&max=5&language=en" \
-H "X-API-Key: yt_sub_your_api_key_here"Response
| Field | Type | Description |
|---|---|---|
playlist | object | Playlist metadata |
playlist.title | string | Playlist title |
playlist.uploader | string | Channel/uploader name |
playlist.total_videos | number | Total videos in playlist |
extracted_count | number | Number of videos processed |
results | array | One result per processed video |
credits_charged | number | Total credits charged |
credits_details | object | Breakdown of successful/failed |
rate_limit | object | Current rate limit status |
extracted_at | string | ISO8601 timestamp |
{
"playlist": {
"title": "My Music Playlist",
"uploader": "MusicChannel",
"total_videos": 50
},
"extracted_count": 5,
"results": [
{
"success": true,
"video_id": "dQw4w9WgXcQ",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"subtitles": [{ "text": "...", "start": 0.0, "duration": 3.5 }],
"language": "en",
"metadata": { "title": "...", "channel": "...", "duration": 213 },
"subtitle_type": "manual"
}
],
"credits_charged": 5,
"credits_details": { "successful": 5, "failed": 0, "total_attempted": 5 },
"rate_limit": { "limit": 10000, "remaining": 9999, "reset": 1705449600 },
"extracted_at": "2024-01-16T10:30:00Z"
}Extended Timeout: Playlist requests have a 120-second total timeout (vs 60s for batch) to accommodate larger video counts. Per-video timeout is calculated as max(120/video_count, 10) seconds.
Pre-flight Validation
/api/v1/validateCheck if subtitles are available for a video before extraction. This is a free, public endpoint — no authentication required, no rate limit impact.
Free & Public
No API key required. No credits charged. Does not count toward your rate limit. Response cached for 1 hour.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | YouTube video URL to validate. |
curl "https://api.ytsubs.dev/api/v1/validate?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ"Response
| Field | Type | Description |
|---|---|---|
valid | boolean | True if URL is a valid YouTube video |
has_subtitles | boolean | True if any subtitles are available |
available_languages | object | Available subtitle languages |
available_languages.manual | string[] | Human-created subtitle languages (ISO 639-1) |
available_languages.auto_generated | string[] | Auto-generated subtitle languages |
video_title | string | Video title |
duration | number | Video duration in seconds |
cached | boolean | Whether response was served from cache |
{
"valid": true,
"has_subtitles": true,
"available_languages": {
"manual": ["en", "es"],
"auto_generated": ["en", "de", "fr", "ja", "pt", "zh"]
},
"video_title": "Rick Astley - Never Gonna Give You Up",
"duration": 213,
"cached": false
}Use Cases
- URL Validation — Verify URLs before adding to a batch
- Language Discovery — Check available languages before extraction
- UI Display — Show video info and subtitle availability in your app
- Cost Estimation — Preview which videos will succeed before committing credits
Video Metadata
Every response includes a metadata object with video information. Some fields are always included, while others are opt-in via the include parameter.
Always-On Fields
These 6 fields are always included in the metadata object:
| Field | Type | Description |
|---|---|---|
title | string | Video title |
channel | string | Channel name |
channel_id | string | YouTube channel ID |
duration | number | Video duration in seconds |
upload_date | string | Upload date in YYYYMMDD format |
thumbnail_url | string | URL to video thumbnail image |
Opt-In Fields
Request additional fields using the include array. Only requested fields will appear in the response:
| Field | Type | Description |
|---|---|---|
description | string | Full video description text |
tags | string[] | Video tags set by the uploader |
categories | string[] | YouTube category names |
view_count | number | Total view count |
like_count | number | Number of likes |
comment_count | number | Number of comments |
chapters | object[] | Video chapters with title and start_time |
is_live | boolean | True if video is currently live streaming |
was_live | boolean | True if video was a live stream |
curl -X POST https://api.ytsubs.dev/api/v1/extract \
-H "Content-Type: application/json" \
-H "X-API-Key: yt_sub_your_api_key_here" \
-d '{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"include": ["description", "view_count", "like_count", "tags"]
}'Note: Invalid field names in the include array will return a 400 Bad Request error with the list of valid fields.
Rate Limiting
API requests are rate-limited based on your subscription tier. Rate limit information is included in both response headers and body.
Response Headers
| Field | Type | Description |
|---|---|---|
X-RateLimit-Limit | number | Maximum requests allowed per day |
X-RateLimit-Remaining | number | Requests remaining in current period |
X-RateLimit-Reset | string | ISO 8601 timestamp when limit resets |
Rate Limit Object
Each successful response includes a rate_limit object:
{
"rate_limit": {
"remaining": 99,
"limit": 100,
"reset_at": "2024-01-16T00:00:00Z"
}
}Tier Limits
| Tier | Daily Limit | Reset Time |
|---|---|---|
| Free | 100 requests | Midnight UTC |
| Pro | 10,000 requests | Midnight UTC |
| Enterprise | Unlimited | — |
Rate Limit Exceeded
When you exceed your rate limit, requests return a 429 Too Many Requests error. See the error reference for details.
HTTP Status Codes
| Status | Description |
|---|---|
| 200 OK | Subtitles extracted successfully |
| 400 Bad Request | Invalid request body or parameters |
| 401 Unauthorized | Missing or invalid API key |
| 404 Not Found | Video not found or no subtitles available |
| 429 Too Many Requests | Rate limit exceeded |
| 500 Internal Server Error | Server error (include request_id when reporting) |
See the error reference for detailed error codes and troubleshooting.