GET /health без ключ
Проверка дали API-то отговаря.
{ "ok": true, "version": "1.0" }Същото, което прави панелът, но от твоя код: бот, сайт, скрипт или друга услуга. Видео със звук, видео без звук или MP3, с избор на качество, кодек и битрейт.
https://yt.kg-scripts.com/api/v1
2026-10-26T14:11:55Z), размерите са в байтове.X-Request-Id; посочвай го, ако нещо се обърка.| В панела | В API-то |
|---|---|
| Преглед на видео: заглавие, продължителност, налични качества, размер | POST /info |
| Изтегли: със звук, без звук, само MP3; качество, кодек, битрейт | POST /jobs |
| Списък с изтегляния, прогрес по канали, опити по маршрути | GET /jobs, GET /jobs/{id} |
| Откажи / премахни | DELETE /jobs/{id} |
| Свали готовия файл | GET /jobs/{id}/file или подписан линк |
| Състояние на маршрутите, PO токени, версия на yt-dlp, опашка | GET /status |
| Тествай маршрутите / нулирай блокировките | POST /routes/test, /routes/reset |
| Източници: директно, WARP, проксита, кога се ползват cookies | GET / PATCH /settings |
| Качи / изтрий cookies | PUT / DELETE /cookies |
| Агент: опресни cookies, прехвърли качените, изход от акаунта | POST /agent/… |
| Обнови yt-dlp | POST /ytdlp/update |
Входът в YouTube акаунта на агента става с мишка и клавиатура в отдалечения браузър, затова е само в панела. Създаването и управлението на API ключове, както и паролата на панела, са нарочно само в панела. Така откраднат ключ не може да създаде нови ключове или да заключи собственика навън.
wait=120):
export YTP_KEY="ytp_…" # ключът от панела
curl -sS "https://yt.kg-scripts.com/api/v1/jobs?wait=120" \
-H "Authorization: Bearer $YTP_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/jNQXAC9IVRw", "mode": "audio", "bitrate": 320}'file.signed_url и изтегли файла. Този линк не изисква ключ:
curl -OJ "https://yt.kg-scripts.com/api/v1/files/CT4el5nMMU2I.1793027515.PaiFOjLd8oim630Jpj-E_NpD"Ако видеото е дълго и не стане за 2 минути, отговорът идва със "status": "downloading".
Тогава питай GET /jobs/{id}?wait=60, докато стане done.
Всяка заявка (освен /health, /openapi.json и подписаните линкове) изисква API ключ в хедър:
Authorization: Bearer ytp_sxpitw…Ew2w
# или
X-API-Key: ytp_sxpitw…Ew2wКлючът е ytp_ + 43 случайни символа (256 бита). Сървърът не пази самия ключ, а само негов HMAC-SHA256 хеш,
затова ключът се показва само веднъж, при създаване. Ако го загубиш, генерирай нов.
Никога не слагай ключа в URL (?api_key=…, ?token=… и подобни). Такава заявка се отказва с
400 key_in_url, а ключът се изключва автоматично, защото URL адресите попадат в логове, история и прокси сървъри.
Включваш го пак, като генерираш нов таен ключ от панела.
| Настройка | Какво прави |
|---|---|
| Права | Кои групи ендпойнти може да вика ключът. Виж Права. |
| Валидност | 30 дни, 90 дни (по подразбиране), 1 година или без срок. След това: 401 key_expired. |
| Разрешени IP адреси | Адреси или мрежи (203.0.113.10, 198.51.100.0/24). От други IP-та: 403 ip_not_allowed. |
| Лимити | Заявки в минута, задачи на час, едновременни задачи. Виж Лимити. |
| Изключи / Включи | Временно спиране без да губиш ключа: 403 key_disabled. |
| Нов таен ключ | Сменя тайната част. Старата спира веднага, настройките остават. Изисква паролата на панела. |
| Журнал | Последните заявки на ключа: време, IP, ендпойнт, статус, време за отговор. |
| Изтрий | Ключът спира веднага, а активните му задачи се отказват. |
| Право | Дава достъп до |
|---|---|
download | /info, /jobs (всичко), файловете на собствените задачи |
status | GET /status: само четене на маршрути, PO токени, yt-dlp и опашка |
admin | /settings, /routes/test, /routes/reset, /cookies, /agent/…, /ytdlp/update |
GET /me работи с всеки валиден ключ. Без нужното право отговорът е 403 insufficient_scope.
Давай на всеки ключ само правата, които наистина му трябват; за бот обикновено стига download.
Всеки ключ вижда само своите задачи. Чужда задача връща 404, сякаш не съществува.
| Лимит | По подразбиране | Обхват | При превишаване |
|---|---|---|---|
| Заявки в минута, на ключ | 60 | 1–600 | 429 rate_limited |
| Нови задачи на час, на ключ | 30 | 1–1000 | 429 job_quota_exceeded |
| Едновременни задачи, на ключ | 3 | 1–10 | 429 too_many_active_jobs |
| Невалидни ключове от един IP | 20 за 10 минути → IP-то е блокирано 15 минути | 429 auth_locked | |
| Заявки от един IP (всички ключове) | 10 в секунда, с буфер 40 | 429 rate_limited | |
Всеки отговор с ключ носи текущото състояние на лимита:
X-RateLimit-Limit: 60 # заявки в минута за този ключ
X-RateLimit-Remaining: 59 # колко остават в момента
X-RateLimit-Reset: 1 # след колко секунди лимитът е пак пълен
Retry-After: 20 # само при 429: след колко секунди да опиташ пакЛимитът на заявките е „кофа с жетони“: 60 в минута означава до 60 заявки наведнъж, после по една на секунда.
При 429 винаги чакай колкото казва Retry-After.
youtube.com, youtu.be, music.youtube.com, Shorts) или 11-символно ID на видео.queue_position).expired.Всяка грешка има един и същ формат и подходящ HTTP статус:
{
"error": {
"code": "insufficient_scope",
"message": "Ключът няма право „admin“.",
"details": { "required": "admin", "granted": ["download"] }
},
"request_id": "req_d49e108786469149"
}Проверявай error.code, не текста: съобщенията може да се променят, а кодовете остават.
| HTTP | Код | Значение |
|---|---|---|
| 400 | unsupported_url | Линкът не е от YouTube. |
| 400 | invalid_url | В линка няма видео. |
| 400 | playlist_not_supported | Подаден е плейлист. |
| 400 | invalid_option | Невалидна стойност на mode, quality, codec или bitrate. |
| 400 | live_stream, upcoming_stream, too_long | Видеото е на живо, предстоящо или по-дълго от 4 часа. |
| 400 | key_in_url | Ключът е подаден в URL; вече е изключен. |
| 400 | invalid_proxy, masked_proxy, invalid_cookies, no_cookies | Грешни данни за администрация (или няма качени cookies за прехвърляне в агента). |
| 401 | missing_api_key | Няма ключ в хедъра. |
| 401 | invalid_api_key | Ключът не съществува (грешен, изтрит или сменен). |
| 401 | key_expired | Срокът на ключа е изтекъл. |
| 403 | key_disabled | Ключът е изключен. |
| 403 | ip_not_allowed | Заявката идва от IP, което не е в списъка на ключа. |
| 403 | insufficient_scope | Ключът няма нужното право. |
| 404 | job_not_found, link_invalid, not_found | Няма такава задача, линкът е изтекъл или няма такъв ендпойнт. |
| 409 | file_not_ready | Задачата още не е готова. |
| 410 | file_gone | Задачата е с грешка, отказана или файлът е изтекъл. |
| 413 | payload_too_large | Файлът с cookies е над 2 MB. |
| 422 | validation_error | Невалиден JSON или непознато поле; details.fields казва кое. |
| 422 | video_unavailable | Видеото е частно, изтрито или недостъпно. |
| 422 | youtube_blocked | YouTube блокира всички маршрути. Добави прокси или cookies. |
| 422 | login_required | Нужен е вход (напр. възрастово ограничение) и няма cookies. |
| 422 | geo_restricted | Не е достъпно в региона на нито един маршрут. |
| 422 | route_unreachable, download_failed | Мрежов проблем или неочаквана грешка при извличането. |
| 429 | rate_limited, job_quota_exceeded, too_many_active_jobs, auth_locked | Превишен лимит; виж Retry-After. |
| 500 | internal_error | Грешка в сървъра. Изпрати request_id на администратора. |
| 503 | agent_unavailable | Агентът (браузърът на сървъра) не работи в момента. |
Когато грешка възникне по време на изтеглянето (след като задачата е приета), заявката е успешна, а задачата е със
"status": "error" и error.code от горната таблица (youtube_blocked, video_unavailable и т.н.).
Всяко изтегляне е задача. Сървърът сам минава през маршрутите (директно → WARP → проксита → cookies), ако YouTube
блокира някой от тях; опитите се виждат в attempts.
status | Значение |
|---|---|
queued | Чака свободно място (виж queue_position). |
starting | Свързва се с YouTube и избира формат. |
downloading | Тегли потоците; прогресът е в progress. |
processing | Обединява картина и звук, конвертира в MP3, добавя обложка. |
done | Файлът е готов (file). |
error | Не стана (error.code, error.message). |
cancelled | Отказана с DELETE. |
expired | Файлът е изтрит след 60 минути. |
stage уточнява стъпката: queued, extracting, cached, downloading,
merging, converting, cover, metadata, muting, finishing.
Не питай в цикъл без пауза. Ползвай параметъра wait (до 120 секунди): сървърът държи заявката отворена,
докато задачата приключи или времето изтече, и чак тогава отговаря. Така една заявка на минута стига за всяко видео.
Когато задачата е done, обектът file дава два начина:
| Поле | Как се ползва |
|---|---|
file.url | С API ключа в хедъра. За сървърен код, който сам записва файла. |
file.signed_url | Без ключ. Подписан линк, валиден до file.expires_at. Можеш да го дадеш на крайния потребител (например бот го праща в чат), без да издаваш ключа. |
Content-Disposition (UTF-8, работи и с кирилица); с curl ползвай -OJ.Range, затова прекъснато изтегляне може да продължи (curl -C -, мениджъри за изтегляне)./health без ключПроверка дали API-то отговаря.
{ "ok": true, "version": "1.0" }/me всеки ключИнформация за ключа, с който е заявката: права, лимити, срок и колко е използван.
curl -sS https://yt.kg-scripts.com/api/v1/me -H "Authorization: Bearer $YTP_KEY"{
"id": "key_06b66cabe167",
"name": "Discord бот",
"hint": "ytp_sxpitw…Ew2w",
"scopes": ["download"],
"created_at": "2026-10-26T14:11:45Z",
"expires_at": "2027-01-24T14:11:45Z",
"ip_allowlist": [],
"limits": { "rate_per_min": 60, "jobs_per_hour": 30, "max_active": 3 },
"usage": { "requests_total": 128, "active_jobs": 1, "jobs_last_hour": 4 }
}/info downloadИнформация за видео без да го теглиш: заглавие, автор, продължителност и наличните качества по кодек, с приблизителен размер.
Резултатът се пази 30 минути и следващото POST /jobs за същото видео го ползва, затова е по-бързо.
| Поле | Тип | Описание |
|---|---|---|
url задължително | string | Линк към видео в YouTube или 11-символно ID. |
curl -sS https://yt.kg-scripts.com/api/v1/info \
-H "Authorization: Bearer $YTP_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/jNQXAC9IVRw"}'{
"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"video_id": "jNQXAC9IVRw",
"title": "Me at the zoo",
"uploader": "jawed",
"duration_seconds": 19,
"thumbnail": "https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg",
"is_live": false,
"live_status": "not_live",
"formats": {
"video": {
"h264": [ { "quality": 240, "fps": 15, "size_bytes": 635110 }, { "quality": 144, "fps": 15, "size_bytes": 195278 } ],
"vp9": [ { "quality": 240, "fps": 15, "size_bytes": 293080 }, { "quality": 144, "fps": 15, "size_bytes": 185292 } ],
"av1": [ { "quality": 240, "fps": 15, "size_bytes": 223779 } ]
},
"audio": { "m4a_bytes": 309288, "opus_bytes": 252182 }
},
"route": "warp",
"used_cookies": false,
"cached_until": "2026-10-26T14:41:51Z"
}quality е по-малката страна на кадъра (вертикалните Shorts в 1080×1920 са 1080).
За приблизителен размер на видео със звук събери видеото и звука: m4a за H.264/AV1, opus за VP9.
/jobs downloadНова задача за изтегляне. Отговорът е 201 с обекта на задачата и хедър Location.
| Поле | Тип | Описание |
|---|---|---|
url задължително | string | Линк към видео в YouTube или 11-символно ID. |
mode | string | video (по подразбиране): видео със звук; mute: видео без звук; audio: само MP3. |
quality | string | max (по подразбиране) или максимална височина: 2160, 1440, 1080, 720, 480, 360, 240, 144. Ако точно това го няма, се взима най-близкото по-ниско. За video и mute. |
codec | string | h264 (по подразбиране): MP4, пуска се навсякъде, в YouTube обикновено до 1080p. vp9: WebM, до 4K. av1: MP4, до 4K, най-малък файл, но по-стари устройства може да не го пуснат. |
bitrate | integer | Битрейт на MP3: 320 (по подразбиране), 256, 192, 128. Само за audio. |
| Query параметър | Описание |
|---|---|
wait | 0–120. Изчакай до толкова секунди задачата да приключи, преди да отговориш. По подразбиране 0 (връща веднага). |
Непознати полета се отказват с 422, за да хванеш грешки като "codek" веднага.
curl -sS "https://yt.kg-scripts.com/api/v1/jobs?wait=60" \
-H "Authorization: Bearer $YTP_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/aqz-KE-bpKQ", "mode": "video", "quality": "1080", "codec": "h264"}'curl -sS "https://yt.kg-scripts.com/api/v1/jobs?wait=60" \
-H "Authorization: Bearer $YTP_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/aqz-KE-bpKQ", "mode": "mute", "quality": "2160", "codec": "vp9"}'curl -sS "https://yt.kg-scripts.com/api/v1/jobs?wait=60" \
-H "Authorization: Bearer $YTP_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/jNQXAC9IVRw", "mode": "audio", "bitrate": 320}'Отговор (тук задачата е приключила в рамките на wait):
{
"id": "CT4el5nMMU2I",
"status": "done",
"stage": "done",
"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"video_id": "jNQXAC9IVRw",
"title": "Me at the zoo",
"thumbnail": "https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg",
"options": { "mode": "audio", "quality": "max", "codec": "h264", "bitrate": 320 },
"progress": {
"percent": 100.0,
"speed_bps": null,
"eta_seconds": null,
"streams": {
"audio": { "downloaded_bytes": 252182, "total_bytes": 252182, "done": true, "percent": 100.0 }
}
},
"queue_position": null,
"route": "Cloudflare WARP",
"attempts": [ { "route": "warp", "cookies": false, "result": "ok", "duration_ms": 3611 } ],
"error": null,
"file": {
"name": "Me at the zoo.mp3",
"size_bytes": 787656,
"url": "https://yt.kg-scripts.com/api/v1/jobs/CT4el5nMMU2I/file",
"signed_url": "https://yt.kg-scripts.com/api/v1/files/CT4el5nMMU2I.1793027515.PaiFOjLd8oim630Jpj-E_NpD",
"expires_at": "2026-10-26T15:11:55Z"
},
"created_at": "2026-10-26T14:11:51Z",
"finished_at": "2026-10-26T14:11:55Z"
}| Поле | Описание |
|---|---|
progress.percent | Средно за потоците, които режимът тегли. При video картината и звукът се теглят поотделно, затова 50% може да значи „картината е готова“. |
progress.streams | По поток: video и/или audio с изтеглени и общо байтове. |
progress.speed_bps, eta_seconds | Скорост в байтове/с и оставащо време за текущия поток (само при downloading). |
route, attempts | През кой маршрут е минало и всички опити: result е ok, blocked, network, auth, geo, fatal, retry. |
error | null или {code, message} при status: "error". |
file | null, докато задачата не е done. Виж Изтегляне на файла. |
/jobs downloadЗадачите на този ключ от последните 24 часа, най-новите първи.
| Query параметър | Описание |
|---|---|
status | Филтър: queued, downloading, done, error и т.н. |
limit | 1–100, по подразбиране 50. |
{ "jobs": [ { "id": "CT4el5nMMU2I", "status": "done", "…": "…" } ], "total": 1 }/jobs/{id} downloadТекущото състояние на задача. С ?wait=60 отговорът идва, когато задачата приключи (или след 60 секунди).
curl -sS "https://yt.kg-scripts.com/api/v1/jobs/CT4el5nMMU2I?wait=60" -H "Authorization: Bearer $YTP_KEY"/jobs/{id} downloadАктивна задача се отказва (процесът спира, частичните файлове се трият): {"result": "cancelling"},
след секунда статусът е cancelled. Завършена задача се изтрива заедно с файла: {"result": "deleted"}.
/jobs/{id}/file downloadСамият файл (audio/mpeg, video/mp4 или video/webm) с име в Content-Disposition.
Ако още не е готов: 409 file_not_ready; ако го няма: 410 file_gone.
curl -sS -OJ https://yt.kg-scripts.com/api/v1/jobs/CT4el5nMMU2I/file -H "Authorization: Bearer $YTP_KEY"/files/{token} без ключПодписан линк от file.signed_url. Работи без ключ до file.expires_at. Невалиден или изтекъл линк:
404 link_invalid. Подписът е HMAC-SHA256 и не може да се подправи или да се отвори чужд файл.
/status statusСъщото като „Маршрути“ и „Система“ в панела: всеки маршрут с изходния му IP и дали е блокиран в момента, cookies, PO токен сървъра, версията на yt-dlp и опашката.
{
"routes": [
{
"name": "direct", "label": "Директно (IP на сървъра)", "proxy": null,
"state": "cooling", "cooldown_seconds": 891,
"exit_ip": "92.5.15.231", "location": "DE",
"successes": 0, "consecutive_blocks": 1, "last_ok_at": null,
"last_error": "Sign in to confirm you’re not a bot. This helps protect our community.",
"last_error_at": "2026-10-26T14:11:47Z"
},
{
"name": "warp", "label": "Cloudflare WARP", "proxy": "socks5://warp:1080",
"state": "ok", "cooldown_seconds": 0,
"exit_ip": "2a09:bac1:1e00:8::3a2:29", "location": "DE",
"successes": 2, "consecutive_blocks": 0, "last_ok_at": "2026-10-26T14:11:55Z",
"last_error": null, "last_error_at": null
}
],
"cookies": { "present": true, "youtube_cookies": 35, "logged_in": true, "uploaded_at": "2026-10-26T14:05:19Z", "valid": false },
"agent": {
"present": true, "alive": true, "ready": true, "browser": "running", "logged_in": true, "valid": true,
"cookies_exported_at": "2026-10-26T17:02:10Z", "login_checked_at": "2026-10-26T17:02:10Z",
"last_reload_at": "2026-10-26T16:41:03Z", "chromium": "Chrome/156.0.8078.47", "error": null
},
"cookies_in_use": "agent",
"po_token": { "enabled": true, "ok": true, "version": "2.0.2" },
"ytdlp": {
"version": "2026.10.23.233055", "channel": "nightly", "updating": false,
"last_update_at": "2026-10-26T14:11:46Z", "last_result": "Вече е последна версия (2026.10.23.233055)",
"auto_update_hours": 12
},
"queue": { "active": 0, "waiting": 0, "max_concurrent": 2 }
}cookies.valid и agent.valid: false, ако YouTube е отхвърлил cookies при последния опит (ротирани),
null, ако още не са пробвани. cookies_in_use казва кои cookies ще ползва следващото изтегляне: agent, upload или null.
state: ok (последният опит е минал), cooling (блокиран, почива още cooldown_seconds), unknown (още не е ползван).
Паролите в проксита винаги са маскирани (user:***@host).
/settings adminИзточниците, през които се тегли. PATCH променя само подадените полета.
| Поле | Тип | Описание |
|---|---|---|
use_direct | boolean | Пробвай директно през IP-то на сървъра. |
use_warp | boolean | Пробвай през Cloudflare WARP. |
proxies | string[] | Заменя целия списък с проксита, без ограничение в броя. Формати: socks5://user:pass@host:port, socks5h://…, http://…, както и host:port, user:pass@host:port и host:port:user:pass (приемат се за HTTP). Повторенията се махат. Задачите се редуват между здравите проксита. GET ги връща маскирани, затова при промяна подавай пълните адреси; маскиран адрес се отказва с masked_proxy. |
cookies_mode | string | fallback: cookies само ако без тях не става; always; never. |
cookies_source | string | agent (по подразбиране): свежите cookies от агента, а ако не е влязъл, качения файл; upload: само качения файл. |
curl -sS -X PATCH https://yt.kg-scripts.com/api/v1/settings \
-H "Authorization: Bearer $YTP_ADMIN_KEY" -H "Content-Type: application/json" \
-d '{"proxies": ["socks5://user:pass@198.51.100.20:1080"], "cookies_mode": "fallback"}'{
"use_direct": true,
"use_warp": true,
"warp_available": true,
"proxies": ["socks5://user:***@198.51.100.20:1080"],
"env_proxies": [],
"cookies_mode": "fallback",
"cookies_source": "agent"
}/routes/test · /routes/reset admin/routes/test пробва всеки маршрут (и с cookies, ако има) с кратко видео и връща кой минава; отнема до около минута.
/routes/reset маха блокировките, за да се пробват всички маршрути отначало.
{
"results": [
{ "route": "direct", "cookies": false, "result": "blocked", "duration_ms": 1962,
"error": "Sign in to confirm you’re not a bot. This helps protect our community." },
{ "route": "warp", "cookies": false, "result": "ok", "duration_ms": 3991 }
],
"routes": [ "…същият формат като в /status…" ]
}/cookies adminPUT качва cookies.txt (формат Netscape) като тяло на заявката, до 2 MB; заменя предишните.
Ако агентът работи и още не е влязъл в акаунт, cookies се прехвърлят автоматично в браузъра му ("agent_import": true),
т.е. агентът влиза с тях и оттам нататък сам поддържа сесията жива.
DELETE ги изтрива. И двата връщат състоянието на cookies (без самите стойности).
curl -sS -X PUT https://yt.kg-scripts.com/api/v1/cookies \
-H "Authorization: Bearer $YTP_ADMIN_KEY" -H "Content-Type: text/plain" \
--data-binary @cookies.txt{ "present": true, "youtube_cookies": 35, "logged_in": true, "uploaded_at": "2026-10-26T14:05:19Z", "valid": null, "agent_import": true }/agent/refresh · /agent/import · /agent/logout adminАгентът е истински браузър на сървъра, логнат в YouTube. Държи сесията жива и всяка минута дава свежи cookies на изтеглянията. Входът в акаунта се прави веднъж от панела (Настройки → Агент и cookies → Отвори браузъра на агента).
| Ендпойнт | Какво прави |
|---|---|
/agent/refresh | Презарежда YouTube в агента и обновява cookies веднага (иначе става всяка минута). |
/agent/import | Прехвърля качения cookies.txt в браузъра на агента, за да продължи той тази сесия. 400 no_cookies, ако няма качен файл. |
/agent/logout | Изтрива cookies в браузъра на агента (изход от акаунта), напр. за смяна на акаунт. |
И трите връщат състоянието на агента (същото като agent в /status). Ако агентът не работи: 503 agent_unavailable.
/ytdlp/update adminОбновява yt-dlp веднага (иначе става автоматично на всеки 12 часа). Отнема 10–30 секунди. Новите задачи ползват новата версия без рестарт.
{
"version": "2026.10.23.233055",
"channel": "nightly",
"updating": false,
"last_update_at": "2026-10-26T14:11:46Z",
"last_result": "Вече е последна версия (2026.10.23.233055)",
"auto_update_hours": 12
}Цялостен пример: пуска задача, изчаква я с wait, спазва Retry-After при лимит и записва файла.
Ключът се чете от променливата YTP_KEY, не е написан в кода.
# pip install requests
import os
import time
import requests
API = "https://yt.kg-scripts.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['YTP_KEY']}"
def call(method, path, **kwargs):
while True:
r = session.request(method, API + path, timeout=150, **kwargs)
if r.status_code == 429: # лимит: чакаме колкото казва сървърът
time.sleep(int(r.headers.get("Retry-After", "5")))
continue
if r.status_code >= 400:
err = r.json()["error"]
raise RuntimeError(f"{err['code']}: {err['message']}")
return r
def download(url, **options):
job = call("POST", "/jobs", params={"wait": 60}, json={"url": url, **options}).json()
while job["status"] in ("queued", "starting", "downloading", "processing"):
job = call("GET", f"/jobs/{job['id']}", params={"wait": 60}).json()
if job["status"] != "done":
raise RuntimeError(f"{job['error']['code']}: {job['error']['message']}")
with call("GET", f"/jobs/{job['id']}/file", stream=True) as r, open(job["file"]["name"], "wb") as out:
for chunk in r.iter_content(1 << 20):
out.write(chunk)
return job["file"]["name"]
print(download("https://youtu.be/aqz-KE-bpKQ", mode="video", quality="1080", codec="h264"))
print(download("https://youtu.be/jNQXAC9IVRw", mode="audio", bitrate=320))// Node.js 18+ (вграден fetch), запази като .mjs
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const API = "https://yt.kg-scripts.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.YTP_KEY}` };
const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
async function call(method, path, body) {
for (;;) {
const res = await fetch(API + path, {
method,
headers: body ? { ...headers, "Content-Type": "application/json" } : headers,
body: body && JSON.stringify(body),
});
if (res.status === 429) { await sleep(Number(res.headers.get("retry-after")) || 5); continue; }
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
return res;
}
}
async function download(url, options = {}) {
let job = await (await call("POST", "/jobs?wait=60", { url, ...options })).json();
while (["queued", "starting", "downloading", "processing"].includes(job.status)) {
job = await (await call("GET", `/jobs/${job.id}?wait=60`)).json();
}
if (job.status !== "done") throw new Error(`${job.error?.code}: ${job.error?.message}`);
const file = await call("GET", `/jobs/${job.id}/file`);
await pipeline(Readable.fromWeb(file.body), createWriteStream(job.file.name));
return job.file.name;
}
console.log(await download("https://youtu.be/aqz-KE-bpKQ", { mode: "mute", quality: "720", codec: "vp9" }));
console.log(await download("https://youtu.be/jNQXAC9IVRw", { mode: "audio", bitrate: 192 }));<?php
// PHP 8+ с разширение curl
const API = 'https://yt.kg-scripts.com/api/v1';
function ytp(string $method, string $path, ?array $body = null): array
{
while (true) {
$ch = curl_init(API . $path);
$headers = ['Authorization: Bearer ' . getenv('YTP_KEY')];
$opts = [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 150, CURLOPT_HEADER => true];
if ($body !== null) {
$headers[] = 'Content-Type: application/json';
$opts[CURLOPT_POSTFIELDS] = json_encode($body);
}
$opts[CURLOPT_HTTPHEADER] = $headers;
curl_setopt_array($ch, $opts);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$head = substr($raw, 0, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
$data = json_decode(substr($raw, strlen($head)), true);
curl_close($ch);
if ($status === 429) { // лимит: чакаме колкото казва сървърът
preg_match('/^retry-after:\s*(\d+)/mi', $head, $m);
sleep((int)($m[1] ?? 5));
continue;
}
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
return $data;
}
}
$job = ytp('POST', '/jobs?wait=60', ['url' => 'https://youtu.be/jNQXAC9IVRw', 'mode' => 'audio']);
while (in_array($job['status'], ['queued', 'starting', 'downloading', 'processing'], true)) {
$job = ytp('GET', "/jobs/{$job['id']}?wait=60");
}
if ($job['status'] !== 'done') {
exit($job['error']['message'] . PHP_EOL);
}
// подписаният линк не съдържа ключа: може да се даде директно на посетителя
echo $job['file']['signed_url'] . PHP_EOL;#!/usr/bin/env bash
# употреба: YTP_KEY=ytp_… ./ytp.sh <линк> [video|mute|audio]
set -euo pipefail
API=https://yt.kg-scripts.com/api/v1
AUTH="Authorization: Bearer $YTP_KEY"
body=$(jq -n --arg url "$1" --arg mode "${2:-video}" '{url: $url, mode: $mode}')
job=$(curl -sS --fail-with-body "$API/jobs?wait=60" -H "$AUTH" -H 'Content-Type: application/json' -d "$body")
id=$(jq -r .id <<<"$job")
while [[ $(jq -r .status <<<"$job") =~ ^(queued|starting|downloading|processing)$ ]]; do
job=$(curl -sS --fail-with-body "$API/jobs/$id?wait=60" -H "$AUTH")
done
if [[ $(jq -r .status <<<"$job") != done ]]; then
jq -r '.error.code + ": " + .error.message' <<<"$job" >&2
exit 1
fi
curl -sS -OJ "$(jq -r .file.signed_url <<<"$job")"
echo "Записано: $(jq -r .file.name <<<"$job")"signed_url, не file.url с ключа.Машинно четимо описание на API-то, например за Postman, Insomnia или генератор на клиенти:
https://yt.kg-scripts.com/api/v1/openapi.jsonВ Postman: Import → Link, после в колекцията задай Authorization → Bearer Token с ключа.