Скачивание видео из своих программ: три метода, ответы в JSON, ключ в заголовке X-API-Key.
Базовый адрес: https://placevid.ru/api/v1. Запросы и ответы в JSON (UTF-8). В каждом запросе нужен заголовок X-API-Key с вашим ключом.
GET /api/v1/info?url=…: узнать название, длительность и доступные форматы. Шаг не обязателен.POST /api/v1/download: заказать скачивание. Номер заказа job_id приходит сразу, файл готовится в фоне.GET /api/v1/jobs/{job_id}: проверять статус раз в несколько секунд, пока не придёт done.file_url из ответа. Для этой ссылки ключ не нужен.Успешные ответы устроены одинаково: {"ok": true, …, "rate_remaining": N}. Данные о видео и о заказе лежат в поле data, а rate_remaining показывает, сколько запросов осталось в часовом лимите ключа.
Скачивания по ключу идут по тарифу вашего аккаунта на сайте: бесплатно 1 скачивание в сутки (общее с сайтом), на Pro и Max без суточного лимита. Подробнее в разделе «Лимиты».
Ключ выглядит так: pv_live_ и ещё 40 латинских букв и цифр. Его передают в заголовке каждого запроса:
X-API-Key: pv_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
Кнопки для ключей в личном кабинете пока нет. Ключ создаётся запросом из браузера, в котором вы вошли на placevid.ru:
fetch('/api/me/keys', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'my-bot'})
}).then(r => r.json()).then(console.log)
Ключ придёт в поле key. Он показывается один раз, сохраните его сразу. Название name (до 64 символов) нужно только вам, чтобы различать ключи. Активных ключей у аккаунта может быть до 5.
Список ключей: показывает начало ключа (prefix), число запросов и время последнего использования, сам ключ не показывает.
fetch('/api/me/keys').then(r => r.json()).then(console.log)
Отключить ключ (вместо ID подставьте id из списка). Отключённый ключ обратно не включается, при необходимости создайте новый:
fetch('/api/me/keys/ID', {method: 'DELETE'}).then(r => r.json()).then(console.log)
Подключается в личном кабинете кнопкой «Подключить Business»: 2 990 ₽ за 30 дней. Business поднимает лимит запросов для всех ключей аккаунта с 600 до 10 000 в час. Суточный лимит скачиваний и качество он не меняет: они зависят от тарифа Free, Pro или Max. Ключи работают и без Business.
PV_API_KEY), а не в коде и не в репозитории.Возвращает название, длительность, превью и список форматов. Файл при этом не скачивается, суточный лимит скачиваний не тратится.
curl -s -G "https://placevid.ru/api/v1/info" \
-H "X-API-Key: $PV_API_KEY" \
--data-urlencode "url=https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Параметры
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
url | string | да | Ссылка на видео, до 2048 символов. Передаётся в строке запроса, не забудьте её закодировать. |
Ответ 200 (пример, сокращён):
{
"ok": true,
"data": {
"title": "Rick Astley - Never Gonna Give You Up",
"thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"duration": 213,
"channel": "Rick Astley",
"video_formats": [
{"format_id": "137", "label": "1080p", "ext": "mp4", "filesize": 80000000,
"type": "video", "height": 1080, "total_size": 83400000},
{"format_id": "136", "label": "720p", "ext": "mp4", "filesize": 40000000,
"type": "video", "height": 720, "total_size": 43400000}
],
"audio_formats": [
{"format_id": "140", "label": "129kbps", "ext": "m4a", "filesize": 3400000,
"type": "audio", "abr": 129.5}
],
"subtitles": [{"lang": "en", "name": "English", "auto": false}],
"platform": "youtube",
"view_count": 1700000000,
"uploader": "Rick Astley",
"upload_date": "20091025",
"uploaded_at": "25.10.2009",
"timestamp": 1256453246
},
"rate_remaining": 599
}
| Поле в data | Что это |
|---|---|
title, thumbnail | Название и ссылка на превью. |
duration | Длительность в секундах. Может быть null. |
channel, uploader | Канал и автор, если площадка их отдаёт. |
video_formats | Форматы видео от лучшего к худшему. format_id передавайте в /download; label и height: качество (например, 720p); filesize и total_size: примерный размер в байтах, может быть null. Список показывает, что есть у площадки, а скачать можно не выше потолка вашего тарифа. |
audio_formats | Звуковые дорожки площадки (label и abr в kbps). Чтобы получить MP3, передайте в /download "audio_only": true. |
subtitles | Какие субтитры есть у ролика: lang, name, auto (созданы автоматически). Только для справки: через API субтитры не скачиваются. |
platform | Площадка: youtube, vk, rutube, tiktok и другие. |
view_count, upload_date, uploaded_at, timestamp | Просмотры и дата публикации: upload_date в виде ГГГГММДД, uploaded_at в виде ДД.ММ.ГГГГ, timestamp во времени Unix. Бывают пустыми. |
Набор полей зависит от площадки: у некоторых сайтов часть полей пустая. Со временем могут появиться новые поля, незнакомые поля просто пропускайте.
Сервер ждёт ответа площадки до 30 секунд. Если она отвечает дольше, придёт 504 (см. «Ошибки»): повторите запрос позже.
curl -s -X POST "https://placevid.ru/api/v1/download" \
-H "X-API-Key: $PV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "format_id": "136"}'
Тело запроса (JSON)
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
url | string | да | Ссылка на видео, до 2048 символов. |
format_id | string | нет | format_id из /info, до 32 символов. Без него качество выберет сервер. |
audio_only | boolean | нет | true: только звук, файл MP3 192 kbps. По умолчанию false. |
Другие поля в теле не учитываются.
Ответ 200
{
"ok": true,
"job_id": "job_3f9a1c0b7d2e4a6b8c01",
"status": "preparing",
"rate_remaining": 598
}
/jobs/{job_id}.job_id: номер заказа, строка вида job_ и 20 символов. Храните её как есть.402 с кодом QUOTA_EXCEEDED.curl -s "https://placevid.ru/api/v1/jobs/job_3f9a1c0b7d2e4a6b8c01" -H "X-API-Key: $PV_API_KEY"
Ответ 200, заказ в работе
{
"ok": true,
"data": {
"status": "downloading",
"progress": 42,
"downloaded": 12582912,
"total": 29884416,
"error": null,
"pausable": true,
"resumable": false,
"cancellable": true
},
"rate_remaining": 590
}
Ответ 200, файл готов
{
"ok": true,
"data": {
"status": "done",
"progress": 100,
"filename": "Rick Astley - Never Gonna Give You Up.mp4",
"display_name": "Rick Astley - Never Gonna Give You Up.mp4",
"size": 29884416,
"subtitle_files": [],
"pausable": false,
"resumable": false,
"cancellable": false,
"file_url": "https://placevid.ru/api/file/0a1b2c3d"
},
"rate_remaining": 589
}
| status | Что значит | Что делать |
|---|---|---|
preparing | Заказ принят, сервер начинает скачивание. progress равен 0. | Ждать. |
downloading | Идёт скачивание. progress от 0 до 100, downloaded и total в байтах. | Ждать. |
queued | Заказ ждёт очереди. | Ждать. |
paused, resuming | Скачивание на паузе или продолжается. | Ждать. |
done | Файл готов, есть file_url. | Скачать файл. |
error | Не получилось, причина в поле error. | Проверить ссылку, заказать заново. |
cancelled, expired | Заказ отменён или его срок истёк. | Заказать заново. |
404 с кодом JOB_NOT_FOUND.error с текстом «Подготовка прервалась». Закажите заново.file_url есть только при статусе done. Файл скачивается обычным GET без ключа, докачка через заголовок Range работает.404. Тогда закажите скачивание заново.file_url файл может скачать любой, у кого есть ссылка, поэтому не публикуйте её.Лимит считается на каждый ключ: 600 запросов в час ко всем методам вместе (/info, /download, /jobs). С тарифом Business: 10 000 запросов в час. Час отсчитывается от первого запроса, после этого счётчик обнуляется. Скачивание файла по file_url и запросы с неверным ключом в лимит не входят.
Остаток виден в поле rate_remaining каждого успешного ответа. Когда лимит исчерпан, сервер отвечает 429 с кодом RATE_LIMIT_EXCEEDED и заголовками Retry-After (через сколько секунд можно продолжить), X-RateLimit-Limit и X-RateLimit-Remaining:
{
"error": {"code": "RATE_LIMIT_EXCEEDED", "message": "Превышен лимит запросов: 600 в час. Повторите через 12 мин."},
"detail": "Превышен лимит запросов: 600 в час. Повторите через 12 мин."
}
Скачивания по ключу считаются так же, как на сайте, по тарифу аккаунта, которому принадлежит ключ:
Запросы /info и /jobs суточный лимит скачиваний не тратят. Цены тарифов: /tariffs.
Ошибка приходит с HTTP-кодом и JSON, где есть машинный код и понятный текст. Тот же текст продублирован в поле detail:
{
"error": {"code": "INVALID_API_KEY", "message": "Ключ не найден или отключён."},
"detail": "Ключ не найден или отключён."
}
Исключение: при неверных полях запроса (код 422) приходит только detail, и это список, где для каждого поля указано, что с ним не так.
| HTTP | code | Когда | Что делать |
|---|---|---|---|
400 | BAD_REQUEST | Ссылка не открылась, площадка не поддерживается или адрес не разрешён. | Проверить ссылку в браузере. Тот же запрос повторять бесполезно. |
401 | INVALID_API_KEY | Нет заголовка X-API-Key, ключ неверный, отключён, или аккаунт владельца удалён. | Проверить ключ, при необходимости создать новый. |
402 | QUOTA_EXCEEDED | Бесплатное скачивание на сегодня уже израсходовано (только /download). | Подождать сброса счётчика или перейти на Pro или Max. |
403 | FORBIDDEN | Сервер скачивания отказал в доступе к ролику. | Проверить, что видео открыто всем. |
404 | JOB_NOT_FOUND, NOT_FOUND | Нет заказа с таким номером у вашего аккаунта, или видео не найдено. | Проверить номер или ссылку. |
413 | FILE_TOO_LARGE | Файл больше потолка тарифа. | Выбрать качество ниже или тариф выше. |
422 | нет | Неверные параметры: нет url, url длиннее 2048 символов, format_id длиннее 32, тело не JSON. | Исправить запрос. |
429 | RATE_LIMIT_EXCEEDED | Исчерпан часовой лимит запросов ключа. | Подождать столько секунд, сколько в Retry-After. |
429 | BUSY | Сервер скачивания сейчас занят. | Повторить через минуту. |
502 | UPSTREAM_UNAVAILABLE, UPSTREAM_ERROR | Сервер скачивания временно недоступен или ответил ошибкой. | Повторить позже. |
504 | UPSTREAM_TIMEOUT | Сервер скачивания не ответил вовремя. | Повторить позже. |
Ошибки самого скачивания (ролик недоступен, файл слишком большой и т. п.) обычно приходят не кодом ответа, а статусом error в /jobs/{job_id} с текстом в поле error.
Повторять имеет смысл запросы с кодами 429, 502 и 504, с паузой, которая растёт с каждой попыткой. Остальные коды значат, что нужно исправить запрос или сменить тариф. В письме в поддержку укажите время запроса, ссылку на видео, номер заказа и текст ошибки.
Все три примера делают одно и то же: заказывают скачивание, ждут готовности и сохраняют файл. Ключ берут из переменной окружения PV_API_KEY.
#!/usr/bin/env bash
# Запуск: PV_API_KEY=pv_live_... ./pv.sh "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
set -euo pipefail
API="https://placevid.ru/api/v1"
KEY="${PV_API_KEY:?Задайте переменную PV_API_KEY}"
URL="$1"
# 1. Запускаем задачу
resp=$(curl -sS -X POST "$API/download" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d "$(jq -n --arg url "$URL" '{url: $url, audio_only: false}')" \
-w '\n%{http_code}')
code=$(tail -n1 <<<"$resp")
body=$(sed '$d' <<<"$resp")
if [ "$code" != "200" ]; then
echo "Ошибка $code: $(jq -r '.error.message // .detail' <<<"$body")" >&2; exit 1
fi
job=$(jq -r '.job_id // empty' <<<"$body")
[ -n "$job" ] || { echo "В ответе нет job_id: $body" >&2; exit 1; }
echo "Задача $job"
# 2. Ждём готовности (до 30 минут, запрос раз в 4 секунды)
status=""
for i in $(seq 1 450); do
st=$(curl -sS "$API/jobs/$job" -H "X-API-Key: $KEY")
status=$(jq -r '.data.status // empty' <<<"$st")
case "$status" in
done) break ;;
error|cancelled|expired)
echo "Задача: $status, $(jq -r '.data.error // ""' <<<"$st")" >&2; exit 1 ;;
"") echo "Ошибка: $(jq -r '.error.message // .detail // .' <<<"$st")" >&2; exit 1 ;;
esac
echo "$status $(jq -r '.data.progress // 0' <<<"$st")%"
sleep 4
done
[ "$status" = "done" ] || { echo "Не дождались готовности" >&2; exit 1; }
# 3. Скачиваем файл
file_url=$(jq -r '.data.file_url' <<<"$st")
name=$(jq -r '.data.filename // empty' <<<"$st")
name=$(basename "${name:-$job.mp4}")
curl -sS -L -o "$name" "$file_url"
echo "Готово: $name"
import os
import time
import requests
API = "https://placevid.ru/api/v1"
HEADERS = {"X-API-Key": os.environ["PV_API_KEY"]}
def api_error(r):
try:
body = r.json()
except ValueError:
return RuntimeError(f"HTTP {r.status_code}: {r.text[:300]}")
err = body.get("error") or {}
return RuntimeError(f"HTTP {r.status_code} {err.get('code', '')}: {err.get('message') or body.get('detail')}")
def get_info(url):
r = requests.get(f"{API}/info", headers=HEADERS, params={"url": url}, timeout=60)
if r.status_code != 200:
raise api_error(r)
return r.json()["data"]
def start_download(url, format_id=None, audio_only=False):
body = {"url": url, "audio_only": audio_only}
if format_id:
body["format_id"] = format_id
r = requests.post(f"{API}/download", headers=HEADERS, json=body, timeout=60)
if r.status_code != 200:
raise api_error(r)
job_id = r.json().get("job_id")
if not job_id:
raise RuntimeError("В ответе нет job_id, повторите запрос позже")
return job_id
def wait_job(job_id, every=4, max_wait=1800):
deadline = time.time() + max_wait
while time.time() < deadline:
r = requests.get(f"{API}/jobs/{job_id}", headers=HEADERS, timeout=30)
if r.status_code == 429: # лимит запросов: ждём, сколько сказал сервер
time.sleep(int(r.headers.get("Retry-After", "60")))
continue
if r.status_code != 200:
raise api_error(r)
job = r.json()["data"]
status = job.get("status")
if status == "done":
return job
if status in ("error", "cancelled", "expired"):
raise RuntimeError(f"Задача {job_id}: {status}, {job.get('error')}")
print(status, job.get("progress", 0), "%")
time.sleep(every)
raise TimeoutError(f"Задача {job_id} не готова за {max_wait} с")
def save_file(file_url, path):
with requests.get(file_url, stream=True, timeout=600) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1024 * 1024):
f.write(chunk)
return path
if __name__ == "__main__":
link = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
info = get_info(link)
print(info["title"], info["duration"], "с")
# берём лучшее качество не выше 1080p
fmt = next((f for f in info["video_formats"] if (f.get("height") or 0) <= 1080), None)
job_id = start_download(link, format_id=fmt["format_id"] if fmt else None)
job = wait_job(job_id)
name = os.path.basename(job.get("filename") or f"{job_id}.mp4")
save_file(job["file_url"], name)
print("Готово:", name)
// Node.js 18+. Запуск: PV_API_KEY=pv_live_... node pv.mjs "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
import { createWriteStream } from 'node:fs';
import { basename } from 'node:path';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
const API = 'https://placevid.ru/api/v1';
const KEY = process.env.PV_API_KEY;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function call(path, options = {}) {
const res = await fetch(API + path, {
...options,
headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json' },
});
const body = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(`HTTP ${res.status}: ${body.error?.message ?? JSON.stringify(body.detail ?? body)}`);
return body;
}
export async function downloadVideo(url, { formatId = null, audioOnly = false } = {}) {
const start = await call('/download', {
method: 'POST',
body: JSON.stringify({ url, format_id: formatId, audio_only: audioOnly }),
});
if (!start.job_id) throw new Error('В ответе нет job_id, повторите запрос позже');
for (let i = 0; i < 450; i++) {
const { data } = await call(`/jobs/${start.job_id}`);
if (data.status === 'done') {
const file = await fetch(data.file_url);
if (!file.ok) throw new Error(`Файл: HTTP ${file.status}`);
const name = basename(data.filename || `${start.job_id}.mp4`);
await pipeline(Readable.fromWeb(file.body), createWriteStream(name));
return name;
}
if (['error', 'cancelled', 'expired'].includes(data.status)) {
throw new Error(`Задача: ${data.status}, ${data.error || ''}`);
}
console.log(data.status, `${data.progress ?? 0}%`);
await sleep(4000);
}
throw new Error('Не дождались готовности');
}
const link = process.argv[2] || 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
downloadVideo(link)
.then((name) => console.log('Готово:', name))
.catch((e) => { console.error(e.message); process.exit(1); });
Из других языков API вызывается так же: обычные HTTP-запросы с заголовком X-API-Key и JSON в теле.
Описание методов в машинном виде: https://placevid.ru/api/v1/openapi.json. Из него можно собрать клиент через openapi-generator. Готовых библиотек для API пока нет.
Поддержка и вопросы: senorsap@yandex.ru. Новости сервиса: Telegram-канал @placevid.
Войдите на сайт и создайте ключ, как описано в разделе «Ключ API».
Открыть личный кабинет