Программный доступ к скачиванию видео с 100+ платформ. JSON. API-ключ. Async-задачи.
Placevid Public API v1 предоставляет программный доступ к инфраструктуре скачивания видео с YouTube, VK, TikTok, Instagram и более 100 других платформ на базе yt-dlp. API предназначен для разработчиков и команд, которым нужна надёжная обработка видео без браузерной автоматизации: контент-агрегаторы, исследовательские проекты (анализ публичного контента, медиамониторинг), мобильные приложения с встроенным загрузчиком, B2B-автоматизация и пакетная обработка больших очередей ссылок.
Базовый URL всех запросов: https://placevid.ru/api/v1
Все запросы и ответы используют формат application/json в кодировке UTF-8. Авторизация выполняется через статичный ключ в заголовке каждого запроса: X-API-Key: pv_xxxxxxxxxxxxxxxx. OAuth, сессии и cookies не используются — ключ выдаётся один раз в личном кабинете и остаётся неизменным до отзыва.
Типичные сценарии использования:
GET /api/v1/infoPOST /api/v1/downloadGET /api/v1/jobs/{job_id}Все запросы к /api/v1/* требуют API-ключ. Запросы без ключа возвращают 401 Unauthorized.
Как получить ключ
my-bot-prod).Формат ключа
Ключ имеет вид pv_ + 32 шестнадцатеричных символа, например:
pv_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
Передача ключа в запросе
Передавайте ключ в HTTP-заголовке X-API-Key для каждого запроса:
curl -s https://placevid.ru/api/v1/info \
-H "X-API-Key: pv_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d" \
-G --data-urlencode "url=https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Лучшие практики
PV_API_KEY, и считывайте её в коде — не вставляйте ключ напрямую в исходники..env и *.env в .gitignore.Приватность и логирование
last_used_at), статус ответа и платформа запрошенного URL.Возвращает метаданные видео без скачивания файла: заголовок, длительность, превью, доступные форматы качества и аудиодорожки. Используется для предпросмотра в UI перед инициацией загрузки.
Метод и URL
GET https://placevid.ru/api/v1/info
Заголовки запроса
| Заголовок | Обязателен | Описание |
|---|---|---|
X-API-Key |
Да | API-ключ из личного кабинета placevid.ru/account |
Query-параметры
| Параметр | Тип | Обязателен | По умолчанию | Описание |
|---|---|---|---|---|
url |
string | Да | — | Прямая ссылка на видео. Поддерживается 100+ платформ: YouTube, VK, TikTok, Instagram, Rutube, Dzen, Vimeo, Twitch, Facebook, Twitter/X, SoundCloud, Bilibili, Dailymotion, Coub и другие. Полный список — /platforms. |
lang |
string | Нет | ru |
Язык сообщений об ошибках в поле error.message.
Допустимые значения: ru, en, kk.
|
Ответ 200 OK
Content-Type: application/json
| Поле | Тип | Всегда | Описание | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
platform |
string | Да | Идентификатор платформы: youtube, tiktok, vk, instagram, rutube, dzen, vimeo, twitch, generic и др. |
||||||||||||||||||||||||
title |
string | Да | Название видео. | ||||||||||||||||||||||||
thumbnail |
string | Да | URL превью-изображения (HTTPS). | ||||||||||||||||||||||||
duration |
number | Да | Длительность видео в секундах (целое число). | ||||||||||||||||||||||||
uploader |
string | Да | Имя автора или канала на платформе. | ||||||||||||||||||||||||
view_count |
number | Нет | Число просмотров. Отсутствует, если платформа не раскрывает данные. | ||||||||||||||||||||||||
channel |
string | Нет | URL или handle канала на платформе. Отсутствует для платформ без концепции канала. | ||||||||||||||||||||||||
video_formats |
array | Да |
Список доступных видеоформатов. Каждый объект:
|
||||||||||||||||||||||||
audio_formats |
array | Да |
Список доступных аудиоформатов (audio-only дорожки). Каждый объект:
|
||||||||||||||||||||||||
subtitles |
object | Да |
Словарь субтитров. Ключ — BCP-47 код языка ("ru", "en" и др.),
значение — массив объектов {"ext": "vtt", "url": "https://..."}.
Пустой объект {}, если субтитры отсутствуют.
|
Коды ошибок
| HTTP-статус | Причина |
|---|---|
400 Bad Request |
Параметр url не передан, невалидный URL, либо платформа не поддерживается. |
401 Unauthorized |
Заголовок X-API-Key отсутствует или ключ недействителен. |
429 Too Many Requests |
Превышен лимит запросов. Заголовок Retry-After содержит количество секунд до сброса лимита. |
503 Service Unavailable |
Yt-dlp extractor не смог получить метаданные: платформа недоступна, geo-блокировка, временный IP-бан. Поле error.message содержит человекочитаемое описание на выбранном языке. |
Тело всех ошибочных ответов: {"error": {"code": "RATE_LIMIT_EXCEEDED", "message": "..."}}
Примеры
curl:
curl -G "https://placevid.ru/api/v1/info" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "url=https://www.youtube.com/watch?v=dQw4w9WgXcQ" \
--data-urlencode "lang=ru"
Python (requests):
import requests
response = requests.get(
"https://placevid.ru/api/v1/info",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"lang": "ru",
},
timeout=30,
)
response.raise_for_status()
data = response.json()
print(data["title"]) # "Rick Astley - Never Gonna Give You Up"
print(data["platform"]) # "youtube"
print(data["duration"]) # 213
best_video = max(
data["video_formats"],
key=lambda f: (f["height"] or 0, f["fps"] or 0),
)
print(best_video["format_id"], best_video["height"], "p") # "248" 1080 p
Фрагмент ответа (200 OK):
{
"platform": "youtube",
"title": "Rick Astley - Never Gonna Give You Up (Official Music Video)",
"thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"duration": 213,
"uploader": "Rick Astley",
"view_count": 1500000000,
"channel": "https://www.youtube.com/@RickAstleyYT",
"video_formats": [
{
"format_id": "137",
"ext": "mp4",
"height": 1080,
"fps": 25,
"filesize": 148721664,
"vcodec": "avc1.640028",
"acodec": "none"
},
{
"format_id": "22",
"ext": "mp4",
"height": 720,
"fps": 25,
"filesize": 87031808,
"vcodec": "avc1.64001F",
"acodec": "mp4a.40.2"
}
],
"audio_formats": [
{
"format_id": "140",
"ext": "m4a",
"abr": 128,
"filesize": 3407872,
"acodec": "mp4a.40.2"
}
],
"subtitles": {
"en": [{"ext": "vtt", "url": "https://..."}],
"ru": [{"ext": "vtt", "url": "https://..."}]
}
}
Запускает асинхронную задачу скачивания видео или аудио. Файл не возвращается напрямую — после постановки задачи в очередь используйте poll_url для отслеживания прогресса и получения ссылки на готовый файл.
Метод и URL
POST https://api.placevid.ru/api/v1/download
Аутентификация
Обязательный заголовок X-API-Key: <ваш_ключ>. Ключ доступен в личном кабинете на placevid.ru/account.
Тело запроса (application/json)
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
url |
string | да | Ссылка на видео. Поддерживаются YouTube, VK, TikTok, Rutube и ещё 1 800+ сайтов через yt-dlp. |
format_id |
string | нет | Идентификатор формата из ответа GET /api/v1/info. Если не передан, выбирается лучший доступный MP4. |
audio_only |
boolean | нет (default: false) |
Если true — извлекается только аудиодорожка и перекодируется в MP3 192 kbps. Параметр format_id при этом игнорируется. |
subtitle_langs |
array<string> | нет | Языки субтитров для скачивания в формате BCP-47, например ["ru", "en"]. Субтитры прикладываются к заданию и доступны отдельными файлами после завершения. |
callback_url |
string | нет | Только B2B-тир. URL, на который придёт POST-уведомление по завершении задачи (webhook). Подробности о формате тела webhook — в разделе Webhooks. |
Ответ 202 Accepted
| Поле | Тип | Описание |
|---|---|---|
job_id |
string (UUID) | Уникальный идентификатор задачи. Используйте его в GET /api/v1/jobs/{job_id}. |
status |
string | В момент создания всегда "queued". |
created_at |
string (ISO 8601) | Время постановки задачи в очередь, UTC. Например: 2026-05-30T11:42:00Z. |
poll_url |
string | Относительный URL для polling: /api/v1/jobs/{job_id}. |
estimated_seconds |
number | Оценочное время выполнения в секундах. Значение приблизительное; не используйте его для жёстких таймаутов. |
Коды ошибок
| HTTP | Причина |
|---|---|
400 |
Некорректные параметры: невалидный URL, указан неизвестный format_id, неверный тип данных в полях. |
401 |
Заголовок X-API-Key отсутствует или ключ недействителен. |
402 |
Исчерпана квота Free-тира: 3 задачи в сутки. Сброс происходит в 00:00 UTC. Для снятия лимита перейдите на PRO или B2B. |
413 |
Видео превышает 2 GB — запрещено на Free- и PRO-тирах. Доступно только на B2B-тире. |
429 |
Rate limit: не более 10 запросов в минуту на ключ. Попробуйте повторить через указанное в заголовке Retry-After время. |
Примеры
Скачивание YouTube-видео в 1080p MP4 через curl:
curl -X POST https://api.placevid.ru/api/v1/download \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_live_ВАШ_КЛЮЧ" \
-d '{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"format_id": "137+140"
}'
Пример ответа:
{
"job_id": "a3f7c921-84be-4d2e-b501-9e1234567890",
"status": "queued",
"created_at": "2026-05-30T11:42:00Z",
"poll_url": "/api/v1/jobs/a3f7c921-84be-4d2e-b501-9e1234567890",
"estimated_seconds": 18
}
То же самое через Python (библиотека requests):
import requests
API_KEY = "pk_live_ВАШ_КЛЮЧ"
BASE_URL = "https://api.placevid.ru"
response = requests.post(
f"{BASE_URL}/api/v1/download",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
json={
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"format_id": "137+140", # 1080p видео + аудио; узнайте format_id через GET /api/v1/info
},
)
response.raise_for_status()
data = response.json()
job_id = data["job_id"]
poll_url = BASE_URL + data["poll_url"]
print(f"Задача создана: {job_id}")
print(f"Проверяйте статус: GET {poll_url}")
Получить format_id для конкретного видео можно заранее через GET /api/v1/info?url=... — ответ содержит список доступных форматов с разрешениями, кодеками и ориентировочным размером файла.
Возвращает текущий статус задачи, созданной через
POST /api/v1/download. Рекомендуется поллить endpoint
раз в 2–5 секунд до тех пор, пока поле status не
примет значение done или failed.
Path parameter
| Параметр | Тип | Описание |
|---|---|---|
job_id |
string (UUID) | Идентификатор задачи, полученный в ответе на POST /api/v1/download. |
Response 200 OK
| Поле | Тип | Всегда | Описание |
|---|---|---|---|
job_id |
string | да | UUID задачи. |
status |
string enum | да | Текущий статус (см. таблицу статусов ниже). |
progress |
number 0–100 | да | Процент выполнения скачивания. 0 в статусах queued и failed, 100 при done. |
speed_kbps |
number | нет | Текущая скорость загрузки в кбит/с. Присутствует во время downloading. |
eta_seconds |
number | нет | Оценочное время до завершения в секундах. Присутствует во время downloading. |
file_url |
string | нет | Временная CDN-ссылка на готовый файл. Присутствует только при status=done. Истекает через 24 часа — скачайте файл немедленно. |
file_size_bytes |
number | нет | Размер итогового файла в байтах. Присутствует при status=done. |
title |
string | да | Заголовок видео, полученный с платформы. |
platform |
string | да | Платформа-источник: youtube, vk, tiktok, rutube и др. |
format_id |
string | да | Идентификатор формата, переданный в запросе (mp4_1080p, mp3_192 и т.д.). |
error |
string | нет | Человекочитаемое сообщение об ошибке на языке, указанном в lang запроса. Присутствует только при status=failed. |
Статусы задачи
| Статус | Описание | Терминальный |
|---|---|---|
queued |
Задача принята и ждёт свободного воркера. | нет |
downloading |
Идёт загрузка медиа с платформы. Поля progress, speed_kbps, eta_seconds обновляются. |
нет |
postprocessing |
Файл загружен, выполняется перекодирование или извлечение аудио. | нет |
done |
Задача завершена. Поле file_url содержит ссылку для скачивания. |
да |
failed |
Ошибка обработки. Поле error содержит описание причины. |
да |
cancelled |
Задача отменена вызовом POST /api/v1/jobs/{job_id}/cancel. |
да |
Ошибки
| HTTP-код | Описание |
|---|---|
401 Unauthorized |
API-ключ отсутствует или недействителен. |
403 Forbidden |
Задача принадлежит другому API-ключу. |
404 Not Found |
Задача с таким job_id не существует или устарела (старше 72 часов). |
429 Too Many Requests |
Превышен лимит частоты запросов. Не опрашивайте endpoint чаще одного раза в 2 секунды. |
Рекомендации по поллингу
429.status=done скачайте файл по file_url немедленно: ссылка истекает через 24 часа.callback_url в теле запроса POST /api/v1/download — это исключает необходимость поллинга полностью.Пример: Python — поллинг до завершения и скачивание файла
import time
import urllib.request
import urllib.error
import json
API_KEY = "pv_live_xxxxxxxxxxxxxxxx"
BASE_URL = "https://api.placevid.ru/api/v1"
def poll_job(job_id: str, output_path: str = None) -> dict:
"""
Опрашивает GET /api/v1/jobs/{job_id} с backoff до терминального статуса.
При status=done скачивает файл по file_url.
Возвращает финальный объект задачи.
"""
delays = [2, 2, 5, 5, 5, 10, 10, 10] # backoff-сетка (с)
deadline = time.time() + 30 * 60 # не ждать дольше 30 мин
step = 0
while time.time() < deadline:
# --- запрос статуса ---
req = urllib.request.Request(
f"{BASE_URL}/jobs/{job_id}",
headers={"X-API-Key": API_KEY, "Accept": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
job = json.loads(resp.read())
except urllib.error.HTTPError as e:
raise RuntimeError(f"HTTP {e.code}: {e.reason}") from e
status = job["status"]
progress = job.get("progress", 0)
speed = job.get("speed_kbps")
eta = job.get("eta_seconds")
speed_str = f" {speed:.0f} кбит/с" if speed is not None else ""
eta_str = f" ETA {eta}с" if eta is not None else ""
print(f"[{status:>15}] {progress:5.1f}%{speed_str}{eta_str}")
# --- терминальные статусы ---
if status == "done":
file_url = job["file_url"]
if output_path is None:
# имя файла из заголовка Content-Disposition или job_id
output_path = f"{job_id}.mp4"
print(f"Скачиваю файл → {output_path}")
urllib.request.urlretrieve(file_url, output_path)
print(f"Готово. Размер: {job.get('file_size_bytes', '?')} байт")
return job
if status == "failed":
raise RuntimeError(f"Задача завершилась с ошибкой: {job.get('error')}")
if status == "cancelled":
raise RuntimeError("Задача была отменена.")
# --- следующая итерация ---
delay = delays[min(step, len(delays) - 1)]
step += 1
time.sleep(delay)
raise TimeoutError(f"Задача {job_id} не завершилась за 30 минут.")
# --- использование ---
if __name__ == "__main__":
JOB_ID = "3fa85f64-5717-4562-b3fc-2c963f66afa6" # из ответа POST /download
result = poll_job(JOB_ID, output_path="video.mp4")
print("Платформа:", result["platform"])
print("Заголовок:", result["title"])
API использует многоуровневую систему ограничений. Лимиты применяются к аккаунту, а не к IP-адресу — аутентификация по Bearer-токену обязательна для всех тарифов выше Free.
| Параметр | Free | PRO (49 ₽/мес) | Business / B2B (2 990 ₽/мес) |
|---|---|---|---|
| Скачиваний в сутки | 3 | Без ограничений | Без ограничений |
Запросов /info в минуту |
10 | 60 | 300 |
| Максимальное качество видео | 720p | 4K | 4K |
| Максимальная длительность | 30 минут | 4 часа | 12 часов |
callback_url |
Нет | Нет | Да |
| Очередь обработки | Общая | Общая | Приоритетная |
| Поддержка | — | — | Технический Telegram-чат с командой |
Headers ответа. Каждый ответ API содержит заголовки текущего состояния rate limit:
X-RateLimit-Limit — максимальное число запросов /info, разрешённых в текущем окне (1 минута).X-RateLimit-Remaining — сколько запросов осталось до сброса в текущем окне.X-RateLimit-Reset — момент сброса счётчика в формате ISO 8601 (UTC), например 2026-05-30T14:07:00Z.Ответ 429 Too Many Requests — rate limit по /info. Это означает превышение минутной квоты. Рекомендуемые действия:
Retry-After из ответа — он содержит число секунд до следующего допустимого запроса. Не отправляйте запросы раньше этого момента.delay = min(2^attempt + random(0, 1), 60) секунд), чтобы избежать синхронных пиков от нескольких клиентов./info на стороне клиента — повторный запрос одного и того же URL расходует квоту.Ответ 402 Payment Required — исчерпана суточная квота (только Free). Это означает, что лимит 3 скачивания в сутки израсходован. Счётчик сбрасывается в 00:00 UTC. Для продолжения работы без ожидания перейдите на тариф PRO — лимит скачиваний снимается полностью. Ссылка для апгрейда: https://placevid.ru/account.
Как считается «1 скачивание». В суточный лимит входят только фактически завершённые задачи:
done. Задачи со статусом error, cancelled или прерванные до завершения не тратят квоту./info (получение метаданных без скачивания) в суточный лимит не входят — они учитываются только в минутной квоте.Все ошибки возвращаются как JSON с тремя полями:
{
"detail": "human-readable message",
"code": "machine_code",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
Поле request_id уникально для каждого запроса — всегда включайте его в обращение в поддержку.
| HTTP | code | Описание | Что делать |
|---|---|---|---|
400 |
invalid_url |
URL не распознан yt-dlp: невалидный формат, обрезанная ссылка или неподдерживаемая схема. | Проверьте, что URL начинается с https:// и не обрезан. Попробуйте раскрыть короткую ссылку (bit.ly, youtu.be и т.п.) перед передачей. |
400 |
unsupported_platform |
Домен не входит в список 1800+ поддерживаемых экстракторов yt-dlp. | Сверьтесь с каталогом платформ /platforms. Передача прямой ссылки на медиафайл (.mp4, .m3u8) не поддерживается. |
401 |
missing_api_key |
Заголовок X-API-Key отсутствует в запросе. |
Добавьте заголовок: X-API-Key: <ваш_ключ>. Ключ доступен в разделе API личного кабинета. |
401 |
invalid_api_key |
Ключ не найден в базе или был отозван. | Проверьте, что ключ скопирован полностью без пробелов. Если ключ был сброшен — сгенерируйте новый в личном кабинете. |
402 |
quota_exceeded |
Исчерпан суточный лимит скачиваний на тарифе Free. | Дождитесь сброса квоты в полночь по МСК или перейдите на тариф PRO для снятия ограничений. |
403 |
job_not_owned |
Запрошенный job_id создан другим API-ключом. |
Убедитесь, что используете тот же ключ, которым создавали задание. Между аккаунтами задания не разделяются. |
404 |
not_found |
Задание с указанным job_id не существует или истекло (TTL 72 часа). |
Создайте новое задание через POST /api/prepare. Не храните job_id дольше 72 часов без опроса статуса. |
413 |
file_too_large |
Размер файла превышает 2 GB — ограничение тарифа Free. | Запросите более низкое качество (например, 720p вместо 4K) или перейдите на PRO, где лимит увеличен. |
423 |
platform_locked |
Платформа временно недоступна: Instagram отключён, TikTok заблокировал наш IP-пул. | Повторите запрос через 5–15 минут. Статус платформ доступен на status.placevid.ru. Не делайте частые ретраи — это продлит блокировку IP. |
429 |
rate_limited |
Превышено количество запросов в секунду (RPS) для вашего ключа. | Применяйте экспоненциальный backoff: подождите 2^n секунд перед повтором. Заголовок Retry-After содержит рекомендованное время ожидания в секундах. |
451 |
legal_block |
Контент защищён DRM или заблокирован по авторскому праву (Netflix, Spotify, Apple Music и др.). | Скачивание данного контента через API невозможно по юридическим причинам. Ретрай не поможет. |
503 |
extractor_failed |
Экстрактор yt-dlp для данной платформы сломан: площадка обновила внутренний API быстрее, чем вышел патч зависимости. | Повторите через 1–6 часов с экспоненциальным backoff. Следите за status.placevid.ru и каналом @placevid. |
503 |
degraded |
Сервис placevid находится в режиме технического обслуживания. | Повторите через несколько минут с экспоненциальным backoff. Плановые работы анонсируются в @placevid заранее. |
Стратегия обработки ошибок:
request_id из ответа — без него поддержка не сможет найти запрос в трассировке.429 и 503 — используйте экспоненциальный backoff (1с → 2с → 4с → 8с), максимум 4–5 попыток.4xx кроме 429 — это инвариантные ошибки (неверные входные данные или права доступа), повтор не изменит результат.423: один отложенный retry через 10–15 минут допустим, но не в цикле.#!/usr/bin/env bash
# placevid_download.sh — скачать видео через placevid.ru Public API v1
# Использование: ./placevid_download.sh "https://youtube.com/watch?v=..."
set -euo pipefail
API_KEY="TODO_INSERT_YOUR_KEY" # TODO: вставить свой ключ
BASE_URL="https://placevid.ru"
VIDEO_URL="${1:?Укажи URL видео первым аргументом}"
FORMAT_ID="best"
OUTPUT_DIR="./downloads"
mkdir -p "$OUTPUT_DIR"
echo "→ Запускаем загрузку: $VIDEO_URL"
# 1. POST /api/v1/download — создать задачу
RESPONSE=$(curl -sf -X POST "$BASE_URL/api/v1/download" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"url\": \"$VIDEO_URL\", \"format_id\": \"$FORMAT_ID\"}")
STATUS_CODE=$?
if [ $STATUS_CODE -ne 0 ]; then
echo "✗ Ошибка запроса (curl exit $STATUS_CODE)" >&2; exit 1
fi
HTTP_STATUS=$(echo "$RESPONSE" | jq -r '.status // "error"')
if [ "$HTTP_STATUS" = "error" ]; then
echo "✗ API вернул ошибку: $(echo "$RESPONSE" | jq -r '.detail // .message')" >&2
exit 1
fi
JOB_ID=$(echo "$RESPONSE" | jq -r '.job_id')
echo "✓ Задача создана: job_id=$JOB_ID"
# 2. Polling /api/v1/jobs/{id} с backoff
ATTEMPTS=0
MAX_ATTEMPTS=40
SLEEP=3
while [ $ATTEMPTS -lt $MAX_ATTEMPTS ]; do
sleep $SLEEP
ATTEMPTS=$((ATTEMPTS + 1))
JOB=$(curl -sf "$BASE_URL/api/v1/jobs/$JOB_ID" \
-H "X-API-Key: $API_KEY") || { echo "✗ Polling error" >&2; exit 1; }
STATUS=$(echo "$JOB" | jq -r '.status')
PROGRESS=$(echo "$JOB" | jq -r '.progress // 0')
echo " [$ATTEMPTS/$MAX_ATTEMPTS] status=$STATUS progress=${PROGRESS}%"
case "$STATUS" in
done)
FILE_URL=$(echo "$JOB" | jq -r '.file_url')
FILENAME=$(echo "$JOB" | jq -r '.filename // "video.mp4"')
echo "✓ Готово! Скачиваем файл..."
curl -L -o "$OUTPUT_DIR/$FILENAME" \
-H "X-API-Key: $API_KEY" "$FILE_URL"
echo "✓ Сохранено: $OUTPUT_DIR/$FILENAME"
exit 0
;;
error)
echo "✗ Задача завершилась с ошибкой: $(echo "$JOB" | jq -r '.error')" >&2
exit 1
;;
*)
# processing / queued — ждём дальше
SLEEP=$(( SLEEP < 10 ? SLEEP + 1 : 10 ))
;;
esac
done
echo "✗ Таймаут: задача не завершилась за $(( MAX_ATTEMPTS * SLEEP ))с" >&2
exit 1
"""placevid_client.py — интеграция с placevid.ru Public API v1"""
import time
import pathlib
import requests
API_KEY = "TODO_INSERT_YOUR_KEY" # TODO: вставить свой ключ
BASE_URL = "https://placevid.ru"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
def download_video(url: str, format_id: str = "best", output_dir: str = "./downloads") -> pathlib.Path:
"""
Скачивает видео через placevid.ru API.
Возвращает Path к скачанному файлу.
"""
output_path = pathlib.Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
# 1. POST /api/v1/download
resp = requests.post(
f"{BASE_URL}/api/v1/download",
json={"url": url, "format_id": format_id},
headers=HEADERS,
timeout=30,
)
if resp.status_code == 401:
raise PermissionError("Неверный API-ключ (401). Проверь X-API-Key.")
if resp.status_code == 503:
raise RuntimeError("Сервис временно недоступен (503). Попробуй позже.")
resp.raise_for_status()
job_id = resp.json()["job_id"]
print(f"→ Задача создана: job_id={job_id}")
# 2. Polling с экспоненциальным backoff
delay = 3
for attempt in range(40):
time.sleep(delay)
poll = requests.get(
f"{BASE_URL}/api/v1/jobs/{job_id}",
headers=HEADERS,
timeout=15,
)
if poll.status_code == 429:
print(f" Лимит запросов (429), ждём {delay * 2}с...")
time.sleep(delay * 2)
continue
if poll.status_code == 401:
raise PermissionError("Неверный API-ключ (401).")
poll.raise_for_status()
job = poll.json()
status = job.get("status")
progress = job.get("progress", 0)
print(f" [{attempt + 1}/40] status={status} progress={progress}%")
if status == "done":
file_url = job["file_url"]
filename = job.get("filename", "video.mp4")
dest = output_path / filename
print(f"→ Скачиваем файл: {filename}")
with requests.get(file_url, headers={"X-API-Key": API_KEY},
stream=True, timeout=120) as r:
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1024 * 256):
f.write(chunk)
print(f"✓ Сохранено: {dest}")
return dest
if status == "error":
raise RuntimeError(f"Задача завершилась с ошибкой: {job.get('error')}")
delay = min(delay + 1, 10)
raise TimeoutError("Таймаут: задача не завершилась за отведённое время.")
if __name__ == "__main__":
path = download_video("https://youtube.com/watch?v=dQw4w9WgXcQ", format_id="best")
print(f"Файл готов: {path}")
// placevid-client.js — placevid.ru Public API v1 (Node.js 18+, native fetch)
// npm run: node placevid-client.js
import fs from "node:fs";
import path from "node:path";
import { pipeline } from "node:stream/promises";
import { Readable } from "node:stream";
const API_KEY = "TODO_INSERT_YOUR_KEY"; // TODO: вставить свой ключ
const BASE_URL = "https://placevid.ru";
/**
* Скачивает видео через placevid.ru API.
* @param {string} videoUrl — ссылка на видео
* @param {string} formatId — формат ('best', '1080p', 'mp3', …)
* @param {string} outputDir — куда сохранить файл
* @returns {Promise} путь к скачанному файлу
*/
async function downloadVideo(videoUrl, formatId = "best", outputDir = "./downloads") {
fs.mkdirSync(outputDir, { recursive: true });
// 1. POST /api/v1/download
const startResp = await fetch(`${BASE_URL}/api/v1/download`, {
method: "POST",
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ url: videoUrl, format_id: formatId }),
});
if (startResp.status === 401) throw new Error("Неверный API-ключ (401). Проверь X-API-Key.");
if (startResp.status === 503) throw new Error("Сервис недоступен (503). Попробуй позже.");
if (!startResp.ok) throw new Error(`Ошибка старта задачи: HTTP ${startResp.status}`);
const { job_id: jobId } = await startResp.json();
console.log(`→ Задача создана: job_id=${jobId}`);
// 2. Polling с backoff
let delay = 3000;
for (let attempt = 1; attempt <= 40; attempt++) {
await new Promise((r) => setTimeout(r, delay));
const pollResp = await fetch(`${BASE_URL}/api/v1/jobs/${jobId}`, {
headers: { "X-API-Key": API_KEY },
});
if (pollResp.status === 429) {
console.log(` Лимит запросов (429), ждём ${delay * 2 / 1000}с...`);
await new Promise((r) => setTimeout(r, delay * 2));
continue;
}
if (pollResp.status === 401) throw new Error("Неверный API-ключ (401).");
if (!pollResp.ok) throw new Error(`Polling error: HTTP ${pollResp.status}`);
const job = await pollResp.json();
console.log(` [${attempt}/40] status=${job.status} progress=${job.progress ?? 0}%`);
if (job.status === "done") {
const filename = job.filename ?? "video.mp4";
const dest = path.join(outputDir, filename);
console.log(`→ Скачиваем файл: ${filename}`);
const fileResp = await fetch(job.file_url, { headers: { "X-API-Key": API_KEY } });
if (!fileResp.ok) throw new Error(`Ошибка скачивания: HTTP ${fileResp.status}`);
await pipeline(Readable.fromWeb(fileResp.body), fs.createWriteStream(dest));
console.log(`✓ Сохранено: ${dest}`);
return dest;
}
if (job.status === "error") throw new Error(`Задача завершилась с ошибкой: ${job.error}`);
delay = Math.min(delay + 1000, 10000);
}
throw new Error("Таймаут: задача не завершилась за отведённое время.");
}
// Точка входа
downloadVideo("https://youtube.com/watch?v=dQw4w9WgXcQ")
.then((file) => console.log(`Файл готов: ${file}`))
.catch((err) => { console.error("✗", err.message); process.exit(1); });
<?php
/**
* PlacevidClient — интеграция с placevid.ru Public API v1
* Совместим с PHP 7.4+ | Laravel | WordPress (functions.php / plugin)
*/
define('PLACEVID_API_KEY', 'TODO_INSERT_YOUR_KEY'); // TODO: вставить свой ключ
define('PLACEVID_BASE_URL', 'https://placevid.ru');
/**
* Выполняет HTTP-запрос через cURL.
*/
function placevid_request(string $method, string $endpoint, array $payload = []): array
{
$ch = curl_init(PLACEVID_BASE_URL . $endpoint);
$headers = ['X-API-Key: ' . PLACEVID_API_KEY, 'Content-Type: application/json'];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => strtoupper($method),
]);
if (!empty($payload)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
}
$body = curl_exec($ch);
$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpStatus === 401) {
throw new RuntimeException('Неверный API-ключ (401). Проверь PLACEVID_API_KEY.');
}
if ($httpStatus === 503) {
throw new RuntimeException('Сервис недоступен (503). Попробуй позже.');
}
if ($httpStatus >= 400) {
throw new RuntimeException("HTTP ошибка $httpStatus: $body");
}
return json_decode($body, true);
}
/**
* Скачивает видео и сохраняет локально.
* @return string Путь к скачанному файлу
*/
function placevid_download_video(string $videoUrl, string $formatId = 'best', string $outputDir = '/tmp/placevid'): string
{
if (!is_dir($outputDir)) mkdir($outputDir, 0755, true);
// 1. POST /api/v1/download
$start = placevid_request('POST', '/api/v1/download', [
'url' => $videoUrl,
'format_id' => $formatId,
]);
$jobId = $start['job_id'] ?? null;
if (!$jobId) throw new RuntimeException('job_id не получен от API.');
echo "→ Задача создана: job_id=$jobId\n";
// 2. Polling с backoff
$delay = 3;
$maxTries = 40;
for ($attempt = 1; $attempt <= $maxTries; $attempt++) {
sleep($delay);
try {
$job = placevid_request('GET', "/api/v1/jobs/$jobId");
} catch (RuntimeException $e) {
// 429 — пропускаем итерацию с увеличенной паузой
if (str_contains($e->getMessage(), '429')) {
echo " Лимит запросов (429), ждём " . ($delay * 2) . "с...\n";
sleep($delay * 2);
continue;
}
throw $e;
}
$status = $job['status'] ?? 'unknown';
$progress = $job['progress'] ?? 0;
echo " [$attempt/$maxTries] status=$status progress={$progress}%\n";
if ($status === 'done') {
$fileUrl = $job['file_url'];
$filename = $job['filename'] ?? 'video.mp4';
$dest = "$outputDir/$filename";
echo "→ Скачиваем файл: $filename\n";
$fh = fopen($dest, 'wb');
$ch = curl_init($fileUrl);
curl_setopt_array($ch, [
CURLOPT_FILE => $fh,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 300,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . PLACEVID_API_KEY],
]);
curl_exec($ch);
$dlStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
fclose($fh);
if ($dlStatus !== 200) {
throw new RuntimeException("Ошибка скачивания файла: HTTP $dlStatus");
}
echo "✓ Сохранено: $dest\n";
return $dest;
}
if ($status === 'error') {
throw new RuntimeException('Задача завершилась с ошибкой: ' . ($job['error'] ?? 'unknown'));
}
$delay = min($delay + 1, 10);
}
throw new RuntimeException('Таймаут: задача не завершилась за отведённое время.');
}
// Точка входа
try {
$file = placevid_download_video('https://youtube.com/watch?v=dQw4w9WgXcQ');
echo "Файл готов: $file\n";
} catch (RuntimeException $e) {
fwrite(STDERR, '✗ ' . $e->getMessage() . "\n");
exit(1);
}
Официальные клиентские библиотеки находятся в стадии активной разработки. Всё перечисленное ниже — ранние alpha-версии; API может меняться между минорными релизами.
pip install placevidrequests. Поддерживает Python 3.9+. Статус: alpha.github.com/placevid/placevid-python (placeholder, репозиторий готовится к публичному открытию).
npm install @placevid/sdkhttps://placevid.ru/api/v1/openapi.json.openapi-generator из неё можно сгенерировать клиент на любом языке: Go, Java, PHP, C#, Kotlin и др.
@placevid_api_bot — позволяет вызвать основные методы API прямо из чата, без установки SDK и написания кода. Удобно для быстрой проверки ключа или формата ответа.
Roadmap. SDK для Go, Ruby и Java запланированы на Q3 2026 при наличии спроса со стороны сообщества. Community contributions приветствуются — открывайте PR или Issues в соответствующих репозиториях.
Поддержка и вопросы: [email protected] или Telegram @placevid.
Получи API-ключ в Личном кабинете → секция «Ключи API»
🔑 Открыть Личный кабинет