{"openapi": "3.0.3", "info": {"title": "BulkTranscripts API", "version": "1.1.0", "description": "YouTube transcripts, search, and channel/playlist listings. Every request needs an API key: create one free at https://bulktranscripts.co/app?tab=mcp (Google sign-in, 30 free credits, no card) and send it as Authorization: Bearer bt_ak_... or X-API-Key. A license key from checkout and an MCP OAuth access token work as Bearer tokens too. A request with no credential returns 401 missing_api_key. A transcript costs 1 credit the first time it enters your library, even if another caller already cached it; repeat reads from your own library are free. Rate limits: 120 requests/min per IP overall, 30/min under /api/v1. Docs: https://bulktranscripts.co/docs"}, "servers": [{"url": "https://bulktranscripts.co"}], "components": {"securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer", "description": "A BulkTranscripts API key (bt_ak_...) created at /app -> MCP & API -> API keys, a license key from checkout, or an OAuth access token from the MCP sign-in."}, "apiKeyHeader": {"type": "apiKey", "in": "header", "name": "X-API-Key", "description": "The same API key, if you prefer a dedicated header over Authorization."}}, "schemas": {"BulkJob": {"type": "object", "properties": {"run_id": {"type": "string"}, "status": {"type": "string", "enum": ["running", "completed", "stopped", "failed"]}, "phase": {"type": "string", "enum": ["discovering", "extracting", "finished"]}, "source": {"type": "object", "properties": {"url": {"type": "string"}, "type": {"type": "string", "nullable": true}, "title": {"type": "string", "nullable": true}}}, "videos_found": {"type": "integer", "nullable": true, "description": "null until the listing has finished."}, "total": {"type": "integer"}, "completed": {"type": "integer"}, "cached": {"type": "integer"}, "failed": {"type": "integer"}, "skipped": {"type": "integer", "description": "Videos without captions."}, "quota": {"type": "integer", "description": "Not attempted for lack of credits."}, "interrupted": {"type": "integer"}, "remaining": {"type": "integer"}, "error": {"type": "object", "nullable": true, "properties": {"code": {"type": "string"}, "message": {"type": "string"}}}, "check_again_in_seconds": {"type": "integer", "description": "Minimum wait before the next poll; 0 when finished."}, "billing": {"$ref": "#/components/schemas/Billing"}}}, "BulkResults": {"type": "object", "properties": {"run_id": {"type": "string"}, "status": {"type": "string"}, "total": {"type": "integer"}, "items": {"type": "array", "items": {"type": "object", "properties": {"position": {"type": "integer"}, "video_id": {"type": "string", "nullable": true}, "title": {"type": "string", "nullable": true}, "status": {"type": "string", "enum": ["ok", "cached", "error"]}, "url": {"type": "string", "nullable": true}, "channel": {"type": "string", "nullable": true}, "duration": {"type": "integer", "nullable": true}, "word_count": {"type": "integer", "nullable": true}, "error": {"type": "object", "properties": {"code": {"type": "string"}, "message": {"type": "string"}}}}}}, "next_cursor": {"type": "integer", "nullable": true}, "note": {"type": "string"}}}, "Error": {"type": "object", "required": ["error"], "properties": {"error": {"type": "object", "required": ["code", "message"], "properties": {"code": {"type": "string", "enum": ["invalid_input", "resolution_failed", "empty_source", "playlist_private", "private_video", "members_only", "age_restricted", "missing_api_key", "invalid_api_key", "invalid_token", "sign_in_required", "out_of_credits", "access_blocked", "no_transcript", "not_found", "rate_limited", "too_many_runs", "restarting", "still_fetching", "internal_error"]}, "message": {"type": "string"}, "purchase_url": {"type": "string", "description": "out_of_credits only."}, "setup_url": {"type": "string", "description": "out_of_credits only."}, "next_step": {"type": "string", "description": "out_of_credits only."}, "reconnect_steps": {"type": "array", "items": {"type": "string"}, "description": "out_of_credits only."}}}}}, "Billing": {"type": "object", "description": "Present on every credited response. When billing is disabled on a self-hosted install only enabled/unlimited are set.", "properties": {"enabled": {"type": "boolean"}, "unlimited": {"type": "boolean"}, "kind": {"type": "string", "enum": ["anon", "license", "admin"]}, "remaining": {"type": "integer"}, "used": {"type": "integer"}, "granted": {"type": "integer"}, "freeLimit": {"type": "integer"}, "creditsCharged": {"type": "integer"}}}, "Segment": {"type": "object", "properties": {"text": {"type": "string"}, "start": {"type": "number"}, "duration": {"type": "number"}}}, "Transcript": {"type": "object", "required": ["video_id", "url", "title", "text", "paragraphs", "billing"], "properties": {"video_id": {"type": "string"}, "url": {"type": "string"}, "title": {"type": "string"}, "channel": {"type": "string", "nullable": true}, "duration": {"type": "integer", "nullable": true}, "duration_text": {"type": "string", "nullable": true}, "upload_date": {"type": "string", "nullable": true}, "language": {"type": "string"}, "source": {"type": "string", "enum": ["manual_caption", "auto_caption"]}, "cached": {"type": "boolean"}, "word_count": {"type": "integer"}, "text": {"type": "string"}, "paragraphs": {"type": "array", "items": {"type": "string"}}, "segments": {"type": "array", "items": {"$ref": "#/components/schemas/Segment"}, "description": "Omitted when segments=0."}, "billing": {"$ref": "#/components/schemas/Billing"}}}, "SearchResults": {"type": "object", "properties": {"query": {"type": "string"}, "type": {"type": "string"}, "count": {"type": "integer"}, "results": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string"}, "type": {"type": "string"}, "title": {"type": "string"}, "url": {"type": "string"}, "channel": {"type": "string", "nullable": true}}}}, "billing": {"$ref": "#/components/schemas/Billing"}}}, "VideoList": {"type": "object", "properties": {"title": {"type": "string", "nullable": true}, "count": {"type": "integer"}, "has_more": {"type": "boolean", "description": "True whenever the result reached limit. No pagination; raise limit."}, "results": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string"}, "url": {"type": "string"}, "title": {"type": "string"}, "channel": {"type": "string", "nullable": true}, "published": {"type": "string", "nullable": true, "description": "ISO date; may be null when YouTube omits it."}, "views": {"type": "string", "nullable": true}}}}, "billing": {"$ref": "#/components/schemas/Billing"}}}, "LatestUploads": {"type": "object", "properties": {"channel_id": {"type": "string"}, "channel": {"type": "string", "nullable": true}, "count": {"type": "integer"}, "source": {"type": "string", "enum": ["listing"], "description": "Present only when YouTube's feed was unavailable and a direct listing was used; published may then be null."}, "results": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string"}, "url": {"type": "string"}, "title": {"type": "string"}, "channel": {"type": "string", "nullable": true}, "published": {"type": "string", "nullable": true, "description": "ISO date; may be null when YouTube omits it."}, "views": {"type": "string", "nullable": true}}}}, "billing_note": {"type": "string"}}}, "Account": {"type": "object", "properties": {"billing": {"$ref": "#/components/schemas/Billing"}, "docs": {"type": "string"}}}}}, "paths": {"/api/v1/transcript": {"get": {"operationId": "getTranscript", "summary": "Get a video's transcript", "description": "Get a video's transcript Cost: 1 credit on first library addition; repeat reads are free.", "parameters": [{"name": "video", "in": "query", "required": true, "schema": {"type": "string"}, "description": "YouTube URL or 11-char video id."}, {"name": "language", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Preferred caption language codes, comma-separated. Default en; falls back to whatever the video has."}, {"name": "format", "in": "query", "required": false, "schema": {"type": "string"}, "description": "json (default), txt, md, srt, vtt, csv, or ai. Non-JSON formats return the rendered file."}, {"name": "segments", "in": "query", "required": false, "schema": {"type": "string"}, "description": "0 to omit the timestamped segment array."}, {"name": "timestamps", "in": "query", "required": false, "schema": {"type": "string"}, "description": "1 to include timestamps in txt/md output."}, {"name": "fresh", "in": "query", "required": false, "schema": {"type": "string"}, "description": "1 to bypass the cache (always charges a credit)."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Transcript"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "402": {"description": "Out of credits (out_of_credits; carries purchase_url and reconnect_steps)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found (no_transcript (video has no captions; not charged) or not_found)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/search": {"get": {"operationId": "searchYouTube", "summary": "Search YouTube videos, channels, or playlists", "description": "Search YouTube videos, channels, or playlists Cost: 1 credit.", "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string"}, "description": "Search query."}, {"name": "type", "in": "query", "required": false, "schema": {"type": "string"}, "description": "video (default), channel, or playlist."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Max results 1-50, default 10."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SearchResults"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "402": {"description": "Out of credits (out_of_credits; carries purchase_url and reconnect_steps)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/channel/search": {"get": {"operationId": "searchChannel", "summary": "Search within one channel's uploads", "description": "Search within one channel's uploads Cost: 1 credit.", "parameters": [{"name": "channel", "in": "query", "required": true, "schema": {"type": "string"}, "description": "@handle, channel URL, or UC… id."}, {"name": "q", "in": "query", "required": true, "schema": {"type": "string"}, "description": "Topic to search for within the channel."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Max results 1-50, default 10."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SearchResults"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "402": {"description": "Out of credits (out_of_credits; carries purchase_url and reconnect_steps)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/channel/videos": {"get": {"operationId": "listChannelVideos", "summary": "List a channel's videos", "description": "List a channel's videos Cost: 1 credit.", "parameters": [{"name": "channel", "in": "query", "required": true, "schema": {"type": "string"}, "description": "@handle, channel URL, or UC… id."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Max videos 1-1000, default 100. No pagination."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoList"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "402": {"description": "Out of credits (out_of_credits; carries purchase_url and reconnect_steps)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/channel/latest": {"get": {"operationId": "getLatestUploads", "summary": "Latest uploads from a channel", "description": "Latest uploads from a channel Cost: free.", "parameters": [{"name": "channel", "in": "query", "required": true, "schema": {"type": "string"}, "description": "@handle, channel URL, or UC… id."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LatestUploads"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/playlist/videos": {"get": {"operationId": "listPlaylistVideos", "summary": "List a playlist's videos in order", "description": "List a playlist's videos in order Cost: 1 credit.", "parameters": [{"name": "playlist", "in": "query", "required": true, "schema": {"type": "string"}, "description": "Playlist URL or id."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Max videos 1-1000, default 100. No pagination."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoList"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "402": {"description": "Out of credits (out_of_credits; carries purchase_url and reconnect_steps)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/account": {"get": {"operationId": "getAccount", "summary": "Current credit balance for the caller", "description": "Current credit balance for the caller Cost: free.", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Account"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/bulk": {"post": {"operationId": "startBulkJob", "summary": "Start a background job that fetches every transcript of a channel, playlist or video URL", "description": "Returns 202 with a run_id immediately. Listing is free; each transcript newly added to the caller's library costs 1 credit, cache hits and repeats are free, failures are never charged. Up to 1,000 videos per job and 2 jobs per account at a time.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["url"], "properties": {"url": {"type": "string", "description": "Channel (@handle or URL), playlist URL or id, or a single video URL."}, "max_videos": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 100}, "language": {"type": "string", "default": "en"}}}}}}, "responses": {"202": {"description": "Job accepted", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkJob"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "503": {"description": "Restarting (restarting): the server is deploying, start again in a minute", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/bulk/{run_id}": {"get": {"operationId": "getBulkJob", "summary": "Progress of a bulk job", "description": "Free. Poll no sooner than check_again_in_seconds; 0 means finished.", "parameters": [{"name": "run_id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkJob"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found (no_transcript (video has no captions; not charged) or not_found)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}, "/api/v1/bulk/{run_id}/results": {"get": {"operationId": "listBulkResults", "summary": "One page of per-video outcomes for a bulk job (no transcript text)", "description": "Free. Pass next_cursor from the previous page to continue.", "parameters": [{"name": "run_id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "integer"}}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkResults"}}}}, "400": {"description": "Bad request (invalid_input, resolution_failed, or empty_source)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Unauthorized (missing_api_key, invalid_api_key, or invalid_token)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found (no_transcript (video has no captions; not charged) or not_found)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limited (rate_limited; honour the Retry-After header (seconds))", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "security": [{"bearerAuth": []}, {"apiKeyHeader": []}]}}}}