Медиа
Загрузка картинок в медиатеку сайта: размеры и WebP-копии, безопасные повторы, поиск по источнику и почему в контент идёт path, а не url.
POST /media кладёт картинку в медиатеку сайта так же, как это сделал бы сам WordPress: файл ложится в загрузки сайта в папку текущего месяца, генерируются все зарегистрированные размеры — ядровые плюс onpress_sm, onpress_md, onpress_xl, — обработчик картинок пишет WebP-копию рядом с каждым JPEG и PNG, сохраняется alt, а вложение получает язык Polylang, если медиа на сайте переводимые.
Контракт живёт в Node-сервисе: он проверяет форму запроса, адрес источника, размер и настоящий тип присланных байтов. Работу делает PHP-исполнитель на собственном домене сайта: размеры и WebP-копии завязаны на хуки темы и под switch_to_blog() не срабатывают. Одна загрузка — один запрос; пакетной формы нет, так что грузите в цикле и помните про лимит запросов.
POST /media
| Поле | Тип | Описание |
|---|---|---|
url | string | откуда скачать: только http или https, без учётных данных, не локальный и не частный хост |
data | string | файл в base64 или data:-URI; взаимоисключается с url |
filename | string | обязательно с data; расширение исправляется под настоящий тип байтов |
alt | string | alt-текст |
title | string | название вложения; по умолчанию имя файла |
lang | string | язык Polylang; по умолчанию язык сайта |
Принимаются JPEG, PNG, WebP, GIF и SVG, и решает это содержимое, а не расширение и не Content-Type источника. Предел — 15 МБ.
SVG принимается только этой ручкой и отклоняется с unsafe_svg, если в нём есть элемент script, обработчики on*=, ссылки javascript:, <!ENTITY, foreignObject или встроенные фреймы и объекты. У SVG нет размеров и WebP-копии; width и height берутся из корневого элемента или его viewBox.
Сервис принимает тело запроса до BODY_LIMIT, по умолчанию 10 МБ, а base64 раздувает файл на треть, поэтому всё, что больше примерно 7 МБ, нужно грузить по url.
curl -s -X POST "$BASE/media" \
-H "Authorization: Bearer $OP_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"blog_id": 235,
"url": "https://example.onpress.pro/wp-content/uploads/sites/56/2026/07/hub.png",
"alt": "Pomelli hub",
"lang": "en"
}'
Ответ описывает вложение — той же формы, что GET /media/{id} ниже, — с deduped: false у новой загрузки.
Идемпотентность
Загрузку можно повторять. Каждое вложение помнит, откуда оно взялось (_onpress_source_url, по строке на источник), и что в нём (_onpress_sha256). Повторный url находится без скачивания. Те же байты под другим адресом или присланные в data находятся по хешу, и новый адрес дописывается во вложение, чтобы следующий поиск по нему был прямым. В обоих случаях ответ — существующее вложение с deduped: true и deduped_by: source_url или sha256.
Найденное вложение не меняется: alt, title и lang применяются только при создании. Параллельные загрузки одного источника выстраиваются блокировкой в базе, так что два гоняющихся запроса дают одно вложение.
В контент — path, а не url
У каждого адреса в ответе две формы. path отсчитывается от корня сайта и работает на любом домене, который отдаёт сайт. url WordPress строит для домена, на котором работал исполнитель. Стенд делит базу с продом, но пишет файлы на свой диск: абсолютный адрес в контенте указывает то на один хост, то на другой, а на проде файл появляется только после синхронизации загрузок. В markdown и в данные кладите path:

webp равно null, где WebP-копии нет: у источников GIF, WebP и SVG или если WebP выключен в обработчике картинок. Картинка шире предела обработчика, по умолчанию 1920 px, хранится уменьшенной с -scaled в имени, а оригинал лежит рядом. Суффикс -scaled, уже бывший в имени файла-источника, отбрасывается, поэтому файл, взятый с другого сайта на WordPress, сохраняет своё базовое имя.
GET /media по источнику
?source_url= находит вложение, загруженное с этого адреса, ?sha256= (64 шестнадцатеричных символа) — по содержимому. Ответ той же формы, что у POST /media, с deduped: false; ничего не нашлось — 404 media_not_found. Если нужен только id или путь, ищите до загрузки.
curl -s -G "$BASE/media" \
-H "Authorization: Bearer $OP_TOKEN" \
-H "X-OnPress-Blog-Id: 235" \
--data-urlencode "source_url=https://example.onpress.pro/wp-content/uploads/sites/56/2026/02/cropped-993838e9_1771708768.png"
GET /media/{id}
То же описание для вложения по id. Работает для любого вложения сайта, не только загруженного через v2; у таких source_urls пуст, а sha256 равно null. Настоящий ответ для иконки сайта 235:
curl -s "$BASE/media/6425" -H "Authorization: Bearer $OP_TOKEN" -H "X-OnPress-Blog-Id: 235"
{
"success": true,
"blog_id": 235,
"id": 6425,
"url": "https://example.onpress.pro/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768.png",
"path": "/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768.png",
"mime": "image/png",
"width": 512,
"height": 512,
"filesize": 4043,
"webp": "/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768.webp",
"sizes": {
"medium": {
"path": "/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768-300x300.png",
"width": 300,
"height": 300,
"mime": "image/png",
"webp": "/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768-300x300.webp"
},
"thumbnail": {
"path": "/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768-150x150.png",
"width": 150,
"height": 150,
"mime": "image/png",
"webp": "/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768-150x150.webp"
}
},
"alt": "Pomelli",
"title": "site-icon",
"lang": "en",
"source_urls": ["https://example.onpress.pro/wp-content/uploads/sites/56/2026/02/cropped-993838e9_1771708768.png"],
"sha256": "07273749f6d7cfa42c4ae5113dafb344af5f883254cd1afb775883e5ccdfe6b5",
"deduped": false
}
В sizes только те размеры, что были сгенерированы: у маленькой картинки больших размеров нет.
Коды ошибок
| Код | HTTP | Значение |
|---|---|---|
source_required | 400 | нет ни url, ни data |
source_ambiguous | 400 | переданы и url, и data |
filename_required | 400 | data без filename |
bad_url | 400 | не http и не https, с учётными данными или на локальный либо частный хост |
bad_data | 400 | data — не base64 |
bad_field | 400 | alt, title или lang — не строка |
empty_file | 400, 422 | ноль байтов |
unknown_language | 400 | такого языка на сайте нет; известные — в languages |
source_url_required | 400 | GET /media без source_url и sha256 |
bad_source_url, bad_sha256, bad_id | 400 | кривой параметр поиска |
media_not_found | 404 | вложения с таким источником, хешем или id нет |
file_too_large | 413 | больше 15 МБ |
unsupported_type | 415 | не JPEG, PNG, WebP, GIF или SVG — по расширению или по содержимому |
unsafe_svg | 422 | SVG не прошёл проверку; причина — в message |
source_not_found | 422 | источник ответил 404 |
download_failed | 422 | источник не скачался |
sideload_failed | 422 | WordPress отказался принять файл |
media_failed | 502 | исполнитель ответил что-то неожиданное |
media_unavailable | 502, 503 | исполнитель не выложен на сайт или не ответил |
Загрузка файла и вставка его на страницу по шагам — в рецепте Загрузить картинку.