kg/dl API документация v1 Към панела

API за изтегляне от YouTube

Същото, което прави панелът, но от твоя код: бот, сайт, скрипт или друга услуга. Видео със звук, видео без звук или MP3, с избор на качество, кодек и битрейт.

Базов адрес https://yt.kg-scripts.com/api/v1
  • Заявките и отговорите са JSON в UTF-8. Съобщенията са на български, а кодовете за грешки са стабилни английски низове, за да ги проверяваш в кода.
  • Времената са в ISO 8601, UTC (2026-10-26T14:11:55Z), размерите са в байтове.
  • Работи само през HTTPS. Всеки отговор има хедър X-Request-Id; посочвай го, ако нещо се обърка.

Какво от панела има в API-то

В панелаВ 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, проксита, кога се ползват cookiesGET / PATCH /settings
Качи / изтрий cookiesPUT / DELETE /cookies
Агент: опресни cookies, прехвърли качените, изход от акаунтаPOST /agent/…
Обнови yt-dlpPOST /ytdlp/update

Входът в YouTube акаунта на агента става с мишка и клавиатура в отдалечения браузър, затова е само в панела. Създаването и управлението на API ключове, както и паролата на панела, са нарочно само в панела. Така откраднат ключ не може да създаде нови ключове или да заключи собственика навън.

Бърз старт

  1. В панела отвори Настройки → API ключове → Нов ключ, дай име, остави правото „Изтегляне“ и копирай ключа. Показва се само веднъж.
  2. Пусни задача и изчакай до 2 минути да е готова (wait=120):
    bash
    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}'
  3. От отговора вземи file.signed_url и изтегли файла. Този линк не изисква ключ:
    bash
    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 ключ в хедър:

HTTP
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 (всичко), файловете на собствените задачи
statusGET /status: само четене на маршрути, PO токени, yt-dlp и опашка
admin/settings, /routes/test, /routes/reset, /cookies, /agent/…, /ytdlp/update

GET /me работи с всеки валиден ключ. Без нужното право отговорът е 403 insufficient_scope. Давай на всеки ключ само правата, които наистина му трябват; за бот обикновено стига download.

Всеки ключ вижда само своите задачи. Чужда задача връща 404, сякаш не съществува.

Лимити

ЛимитПо подразбиранеОбхватПри превишаване
Заявки в минута, на ключ601–600429 rate_limited
Нови задачи на час, на ключ301–1000429 job_quota_exceeded
Едновременни задачи, на ключ31–10429 too_many_active_jobs
Невалидни ключове от един IP20 за 10 минути → IP-то е блокирано 15 минути429 auth_locked
Заявки от един IP (всички ключове)10 в секунда, с буфер 40429 rate_limited

Всеки отговор с ключ носи текущото състояние на лимита:

HTTP
X-RateLimit-Limit: 60        # заявки в минута за този ключ
X-RateLimit-Remaining: 59    # колко остават в момента
X-RateLimit-Reset: 1         # след колко секунди лимитът е пак пълен
Retry-After: 20              # само при 429: след колко секунди да опиташ пак

Лимитът на заявките е „кофа с жетони“: 60 в минута означава до 60 заявки наведнъж, после по една на секунда. При 429 винаги чакай колкото казва Retry-After.

Ограничения на видеата

  • Само линкове от YouTube (youtube.com, youtu.be, music.youtube.com, Shorts) или 11-символно ID на видео.
  • Без плейлисти и излъчвания на живо. Видеото трябва да е до 4 часа.
  • Сървърът тегли до 2 файла едновременно за всички потребители; останалите задачи чакат на опашка (queue_position).
  • Готовите файлове се пазят 60 минути, после задачата става expired.

Грешки

Всяка грешка има един и същ формат и подходящ HTTP статус:

JSON
{
  "error": {
    "code": "insufficient_scope",
    "message": "Ключът няма право „admin“.",
    "details": { "required": "admin", "granted": ["download"] }
  },
  "request_id": "req_d49e108786469149"
}

Проверявай error.code, не текста: съобщенията може да се променят, а кодовете остават.

HTTPКодЗначение
400unsupported_urlЛинкът не е от YouTube.
400invalid_urlВ линка няма видео.
400playlist_not_supportedПодаден е плейлист.
400invalid_optionНевалидна стойност на mode, quality, codec или bitrate.
400live_stream, upcoming_stream, too_longВидеото е на живо, предстоящо или по-дълго от 4 часа.
400key_in_urlКлючът е подаден в URL; вече е изключен.
400invalid_proxy, masked_proxy, invalid_cookies, no_cookiesГрешни данни за администрация (или няма качени cookies за прехвърляне в агента).
401missing_api_keyНяма ключ в хедъра.
401invalid_api_keyКлючът не съществува (грешен, изтрит или сменен).
401key_expiredСрокът на ключа е изтекъл.
403key_disabledКлючът е изключен.
403ip_not_allowedЗаявката идва от IP, което не е в списъка на ключа.
403insufficient_scopeКлючът няма нужното право.
404job_not_found, link_invalid, not_foundНяма такава задача, линкът е изтекъл или няма такъв ендпойнт.
409file_not_readyЗадачата още не е готова.
410file_goneЗадачата е с грешка, отказана или файлът е изтекъл.
413payload_too_largeФайлът с cookies е над 2 MB.
422validation_errorНевалиден JSON или непознато поле; details.fields казва кое.
422video_unavailableВидеото е частно, изтрито или недостъпно.
422youtube_blockedYouTube блокира всички маршрути. Добави прокси или cookies.
422login_requiredНужен е вход (напр. възрастово ограничение) и няма cookies.
422geo_restrictedНе е достъпно в региона на нито един маршрут.
422route_unreachable, download_failedМрежов проблем или неочаквана грешка при извличането.
429rate_limited, job_quota_exceeded, too_many_active_jobs, auth_lockedПревишен лимит; виж Retry-After.
500internal_errorГрешка в сървъра. Изпрати request_id на администратора.
503agent_unavailableАгентът (браузърът на сървъра) не работи в момента.

Когато грешка възникне по време на изтеглянето (след като задачата е приета), заявката е успешна, а задачата е със "status": "error" и error.code от горната таблица (youtube_blocked, video_unavailable и т.н.).

Задачи и състояния

Всяко изтегляне е задача. Сървърът сам минава през маршрутите (директно → WARP → проксита → cookies), ако YouTube блокира някой от тях; опитите се виждат в attempts.

queued → starting → downloading → processing → done → expired ↘ error ↘ cancelled
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 -, мениджъри за изтегляне).
  • Подписаният линк спира да работи, ако ключът, създал задачата, бъде изключен или изтрит.
  • Файлът се пази 60 минути от завършването. Изтегли го и го запиши при себе си.

Ендпойнти

GET /health без ключ

Проверка дали API-то отговаря.

JSON
{ "ok": true, "version": "1.0" }

GET /me всеки ключ

Информация за ключа, с който е заявката: права, лимити, срок и колко е използван.

bash
curl -sS https://yt.kg-scripts.com/api/v1/me -H "Authorization: Bearer $YTP_KEY"
JSON
{
  "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 }
}

POST /info download

Информация за видео без да го теглиш: заглавие, автор, продължителност и наличните качества по кодек, с приблизителен размер. Резултатът се пази 30 минути и следващото POST /jobs за същото видео го ползва, затова е по-бързо.

ПолеТипОписание
url задължителноstringЛинк към видео в YouTube или 11-символно ID.
bash
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"}'
JSON
{
  "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.

POST /jobs download

Нова задача за изтегляне. Отговорът е 201 с обекта на задачата и хедър Location.

ПолеТипОписание
url задължителноstringЛинк към видео в YouTube или 11-символно ID.
modestringvideo (по подразбиране): видео със звук; mute: видео без звук; audio: само MP3.
qualitystringmax (по подразбиране) или максимална височина: 2160, 1440, 1080, 720, 480, 360, 240, 144. Ако точно това го няма, се взима най-близкото по-ниско. За video и mute.
codecstringh264 (по подразбиране): MP4, пуска се навсякъде, в YouTube обикновено до 1080p. vp9: WebM, до 4K. av1: MP4, до 4K, най-малък файл, но по-стари устройства може да не го пуснат.
bitrateintegerБитрейт на MP3: 320 (по подразбиране), 256, 192, 128. Само за audio.
Query параметърОписание
wait0–120. Изчакай до толкова секунди задачата да приключи, преди да отговориш. По подразбиране 0 (връща веднага).

Непознати полета се отказват с 422, за да хванеш грешки като "codek" веднага.

bash
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"}'
bash
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"}'
bash
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):

JSON
{
  "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.
errornull или {code, message} при status: "error".
filenull, докато задачата не е done. Виж Изтегляне на файла.

GET /jobs download

Задачите на този ключ от последните 24 часа, най-новите първи.

Query параметърОписание
statusФилтър: queued, downloading, done, error и т.н.
limit1–100, по подразбиране 50.
JSON
{ "jobs": [ { "id": "CT4el5nMMU2I", "status": "done", "…": "…" } ], "total": 1 }

GET /jobs/{id} download

Текущото състояние на задача. С ?wait=60 отговорът идва, когато задачата приключи (или след 60 секунди).

bash
curl -sS "https://yt.kg-scripts.com/api/v1/jobs/CT4el5nMMU2I?wait=60" -H "Authorization: Bearer $YTP_KEY"

DELETE /jobs/{id} download

Активна задача се отказва (процесът спира, частичните файлове се трият): {"result": "cancelling"}, след секунда статусът е cancelled. Завършена задача се изтрива заедно с файла: {"result": "deleted"}.

GET /jobs/{id}/file download

Самият файл (audio/mpeg, video/mp4 или video/webm) с име в Content-Disposition. Ако още не е готов: 409 file_not_ready; ако го няма: 410 file_gone.

bash
curl -sS -OJ https://yt.kg-scripts.com/api/v1/jobs/CT4el5nMMU2I/file -H "Authorization: Bearer $YTP_KEY"

GET /files/{token} без ключ

Подписан линк от file.signed_url. Работи без ключ до file.expires_at. Невалиден или изтекъл линк: 404 link_invalid. Подписът е HMAC-SHA256 и не може да се подправи или да се отвори чужд файл.

GET /status status

Същото като „Маршрути“ и „Система“ в панела: всеки маршрут с изходния му IP и дали е блокиран в момента, cookies, PO токен сървъра, версията на yt-dlp и опашката.

JSON
{
  "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).

GET PATCH /settings admin

Източниците, през които се тегли. PATCH променя само подадените полета.

ПолеТипОписание
use_directbooleanПробвай директно през IP-то на сървъра.
use_warpbooleanПробвай през Cloudflare WARP.
proxiesstring[]Заменя целия списък с проксита, без ограничение в броя. Формати: socks5://user:pass@host:port, socks5h://…, http://…, както и host:port, user:pass@host:port и host:port:user:pass (приемат се за HTTP). Повторенията се махат. Задачите се редуват между здравите проксита. GET ги връща маскирани, затова при промяна подавай пълните адреси; маскиран адрес се отказва с masked_proxy.
cookies_modestringfallback: cookies само ако без тях не става; always; never.
cookies_sourcestringagent (по подразбиране): свежите cookies от агента, а ако не е влязъл, качения файл; upload: само качения файл.
bash
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"}'
JSON
{
  "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"
}

POST /routes/test · /routes/reset admin

/routes/test пробва всеки маршрут (и с cookies, ако има) с кратко видео и връща кой минава; отнема до около минута. /routes/reset маха блокировките, за да се пробват всички маршрути отначало.

JSON
{
  "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…" ]
}

PUT DELETE /cookies admin

PUT качва cookies.txt (формат Netscape) като тяло на заявката, до 2 MB; заменя предишните. Ако агентът работи и още не е влязъл в акаунт, cookies се прехвърлят автоматично в браузъра му ("agent_import": true), т.е. агентът влиза с тях и оттам нататък сам поддържа сесията жива. DELETE ги изтрива. И двата връщат състоянието на cookies (без самите стойности).

bash
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
JSON
{ "present": true, "youtube_cookies": 35, "logged_in": true, "uploaded_at": "2026-10-26T14:05:19Z", "valid": null, "agent_import": true }

POST /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.

POST /ytdlp/update admin

Обновява yt-dlp веднага (иначе става автоматично на всеки 12 часа). Отнема 10–30 секунди. Новите задачи ползват новата версия без рестарт.

JSON
{
  "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, не е написан в кода.

Python
# 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))
JavaScript
// 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
// 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;
bash
#!/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")"

Сигурност

Какво прави сървърът

  • Ключовете са 256-битови и се пазят само като HMAC-SHA256 хеш с таен ключ на сървъра. Изтекла база с ключове не стига, за да се ползват.
  • Ключ, изпратен в URL, се изключва автоматично. Ключовете не се логват.
  • Права по ключ, срок, списък с разрешени IP адреси и отделни лимити за всеки ключ.
  • Блокиране на IP след 20 невалидни ключа за 10 минути, плюс лимит на заявките от IP на ниво nginx.
  • Всеки ключ вижда само своите задачи и файлове. Подписаните линкове са HMAC подписани, изтичат с файла и спират с ключа.
  • Журнал на всяка заявка (време, IP, ендпойнт, статус), видим в панела, плюс постоянен одит лог на сървъра.
  • Ключовете се управляват само от панела, с потвърждение с парола. API ключ не може да създава или променя ключове.
  • Строга проверка на входа: само линкове от YouTube, непознати полета се отказват, ограничени размери на заявките.
  • Само HTTPS (TLS 1.2/1.3, HSTS). Панелът е защитен от CSRF, clickjacking и XSS (Content-Security-Policy).

Какво трябва да правиш ти

  • Дръж ключа на сървъра, в променлива на средата или менажер на тайни. Никога не го слагай в код за браузър, мобилно приложение или публично хранилище.
  • Отделен ключ за всяка програма, с минималните права. Ако нещо изтече, спираш само него.
  • Ако програмата е на сървър със статичен IP, ограничи ключа до този IP.
  • Слагай срок на ключовете и ги сменяй периодично („Нов таен ключ“).
  • Ако видиш непознати IP адреси в журнала, натисни „Изключи“ веднага, после генерирай нов таен ключ.
  • Давай на потребителите signed_url, не file.url с ключа.

OpenAPI

Машинно четимо описание на API-то, например за Postman, Insomnia или генератор на клиенти:

https://yt.kg-scripts.com/api/v1/openapi.json

В Postman: Import → Link, после в колекцията задай Authorization → Bearer Token с ключа.