Главная → API документация

🔌 API Плейсвид — Public API v1

Программный доступ к скачиванию видео с 100+ платформ. JSON. API-ключ. Async-задачи.

📑 Содержание
  1. 🚀 Что такое Public API v1
  2. 🔑 Авторизация и API-ключи
  3. 📊 GET /api/v1/info — метаданные видео
  4. ⬇ POST /api/v1/download — запуск задачи
  5. ⏱ GET /api/v1/jobs/{job_id} — polling статуса
  6. 🛡 Rate-limits и тарифы
  7. ⚠️ Коды ошибок и обработка
  8. 💻 Полные примеры интеграции
  9. 📦 SDK и официальные клиенты

🚀 Что такое Public API v1

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 не используются — ключ выдаётся один раз в личном кабинете и остаётся неизменным до отзыва.

Типичные сценарии использования:

🔑 Авторизация и API-ключи

Все запросы к /api/v1/* требуют API-ключ. Запросы без ключа возвращают 401 Unauthorized.

Как получить ключ

  1. Откройте placevid.ru/account и перейдите в секцию «Ключи API».
  2. Нажмите «Создать ключ», задайте понятное название (например, my-bot-prod).
  3. Скопируйте ключ сразу — он отображается только один раз. После закрытия диалога восстановить его невозможно; при утере создайте новый и отзовите старый.

Формат ключа

Ключ имеет вид 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"

Лучшие практики

Приватность и логирование

📊 GET /api/v1/info — метаданные видео

Возвращает метаданные видео без скачивания файла: заголовок, длительность, превью, доступные форматы качества и аудиодорожки. Используется для предпросмотра в 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 Да Список доступных видеоформатов. Каждый объект:
ПолеТипОписание
format_idstringВнутренний ID формата yt-dlp, передаётся в /api/v1/prepare.
extstringРасширение файла: mp4, webm и др.
heightnumberВысота в пикселях (360, 720, 1080, 2160 и т.д.).
fpsnumberЧастота кадров.
filesizenumberПримерный размер в байтах. Может быть null если платформа не сообщает.
vcodecstringВидеокодек: avc1, vp9, av01 и др.
acodecstringАудиокодек в этом стриме или none для video-only дорожки.
audio_formats array Да Список доступных аудиоформатов (audio-only дорожки). Каждый объект:
ПолеТипОписание
format_idstringВнутренний ID формата.
extstringРасширение: m4a, webm, mp3 и др.
abrnumberБитрейт в кбит/с.
filesizenumberПримерный размер в байтах или null.
acodecstringАудиокодек: mp4a.40.2, opus и др.
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://..."}]
  }
}

⬇ POST /api/v1/download — запуск задачи

Запускает асинхронную задачу скачивания видео или аудио. Файл не возвращается напрямую — после постановки задачи в очередь используйте 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=... — ответ содержит список доступных форматов с разрешениями, кодеками и ориентировочным размером файла.

⏱ GET /api/v1/jobs/{job_id} — polling статуса

Возвращает текущий статус задачи, созданной через 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 секунды.

Рекомендации по поллингу

Пример: 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"])

🛡 Rate-limits и тарифы

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:

Ответ 429 Too Many Requests — rate limit по /info. Это означает превышение минутной квоты. Рекомендуемые действия:

Ответ 402 Payment Required — исчерпана суточная квота (только Free). Это означает, что лимит 3 скачивания в сутки израсходован. Счётчик сбрасывается в 00:00 UTC. Для продолжения работы без ожидания перейдите на тариф PRO — лимит скачиваний снимается полностью. Ссылка для апгрейда: https://placevid.ru/account.

Как считается «1 скачивание». В суточный лимит входят только фактически завершённые задачи:

⚠️ Коды ошибок и обработка

Все ошибки возвращаются как 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 заранее.

Стратегия обработки ошибок:

💻 Полные примеры интеграции

1. Bash + curl + jq — скрипт для CI/CLI

#!/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

2. Python 3 (requests) — функция download_video()

"""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}")

3. JavaScript / Node.js (native fetch) — async функция

// 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); });

4. PHP (curl + json_decode) — для WordPress / Laravel

<?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);
}

📦 SDK и официальные клиенты

Официальные клиентские библиотеки находятся в стадии активной разработки. Всё перечисленное ниже — ранние alpha-версии; API может меняться между минорными релизами.

Roadmap. SDK для Go, Ruby и Java запланированы на Q3 2026 при наличии спроса со стороны сообщества. Community contributions приветствуются — открывайте PR или Issues в соответствующих репозиториях.

Поддержка и вопросы: support@placevid.ru или Telegram @placevid.

Готов начать?

Получи API-ключ в Личном кабинете → секция «Ключи API»

🔑 Открыть Личный кабинет