Upload an image
Put an image into the site's media library by URL or as base64, use it in a page and as the site icon. Uploads are safe to repeat.
POST /media puts a file into the media library exactly the way WordPress would: every registered size is generated, a WebP copy is written next to each JPEG and PNG, the alt text is stored and the attachment gets a language. One request is one file; there is no bulk form.
1. Upload by URL
The simplest source is a public address. The type is decided by the bytes, not by the extension: JPEG, PNG, WebP, GIF and SVG are accepted, up to 15 MB.
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/media" -d '{
"blog_id": 235,
"url": "https://example.onpress.pro/wp-content/uploads/sites/56/2026/07/pomelli-hub-t1.png",
"alt": "Pomelli hub",
"lang": "en"
}'
{
"success": true,
"blog_id": 235,
"id": 6349,
"url": "https://example.onpress.pro/wp-content/uploads/sites/235/2026/09/pomelli-hub-t1.png",
"path": "/wp-content/uploads/sites/235/2026/09/pomelli-hub-t1.png",
"mime": "image/png",
"width": 1376,
"height": 768,
"filesize": 188374,
"webp": "/wp-content/uploads/sites/235/2026/09/pomelli-hub-t1.webp",
"sizes": {
"medium": { "path": "/wp-content/uploads/sites/235/2026/09/pomelli-hub-t1-300x167.png", "width": 300, "height": 167, "mime": "image/png", "webp": "/wp-content/uploads/sites/235/2026/09/pomelli-hub-t1-300x167.webp" },
"…": "…"
},
"alt": "Pomelli hub",
"title": "pomelli-hub-t1",
"lang": "en",
"source_urls": ["https://example.onpress.pro/wp-content/uploads/sites/56/2026/07/pomelli-hub-t1.png"],
"sha256": "a115d5ec15baf3a4b44987f69eb8b538baabfd15e8982227f62367778845fba8",
"deduped": false
}
Keep id (for the site icon, a featured image, anything that takes an attachment) and path (for content).
2. Or upload the bytes
A file that has no public address goes as base64 in data (a data: URI works too), with filename — required here; its extension is corrected to the real type.
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/media" -d "$(jq -n --arg data "$(base64 < hero.png)" \
'{blog_id: 235, data: $data, filename: "hero.png", alt: "Hero", lang: "en"}')"
The answer has the same shape as above, with an empty source_urls. The request body is limited to 10 MB and base64 adds a third, so files above about 7 MB have to go by url. url and data together are 400 source_ambiguous, data without filename is 400 filename_required.
3. Repeat without fear
Every attachment remembers its source URLs and the SHA-256 of its bytes. Sending the same url again finds the attachment without downloading anything; the same bytes under another URL or as data are found by hash. Either way you get the existing attachment:
{
"success": true,
"blog_id": 235,
"id": 6349,
"path": "/wp-content/uploads/sites/235/2026/09/pomelli-hub-t1.png",
"…": "…",
"deduped": true,
"deduped_by": "source_url"
}
deduped_by is source_url or sha256. A found attachment is not modified: alt, title and lang apply only when the attachment is created. So a script that uploads every image of an article can simply be run again after a failure.
4. Insert it into a page
In markdown an image is a line of its own: alt in the brackets, path as the address, and an optional caption in quotes, which becomes the figcaption.
---
name: "Pomelli hub"
slug: "pomelli-hub"
url: "/pomelli-hub/"
lang: "en"
frame: "article"
---
The hub collects every campaign in one place.

Use path, not url. url is built for the domain that served the upload: the staging copy of the network shares the database with production but writes files to its own disk, so an absolute address stored in content points at one host or the other. path works on whichever domain serves the site.
5. Find an upload later
GET /media?source_url=… finds the attachment uploaded from that address, ?sha256= finds it by content, and GET /media/{id} describes any attachment of the site — including ones not uploaded through the API, for which source_urls is empty and sha256 is null.
curl -s -H "Authorization: Bearer $OP_TOKEN" "$BASE/media/6425?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": { "…": "…" }, "thumbnail": { "…": "…" } },
"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
}
Nothing found is 404 media_not_found.
6. Make it the site icon
The site icon is an attachment id in the site settings. Upload a square image (512×512 is what WordPress expects) and pass its id:
curl -s -X PATCH -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/site/settings" -d '{"blog_id": 235, "site_icon": 6425}'
{
"success": true,
"blog_id": 235,
"changed": ["site_icon"],
"settings": {
"blogname": "Pomelli",
"site_icon": 6425,
"site_icon_url": "https://example.onpress.pro/wp-content/uploads/sites/235/2026/09/cropped-993838e9_1771708768.png",
"…": "…"
},
"…": "…"
}
"site_icon": 0 removes the icon. A write that changes something flushes the site’s page cache.
Check
GET /media/{id} answers with the same path, webp is not null for a JPEG or PNG, and a second POST /media with the same source returns deduped: true and the same id. The page shows the image with its caption. GET /site/settings returns the new site_icon and a site_icon_url.
See also
- Media — every field, the SVG rules and the error codes.
- Sites and settings —
PATCH /site/settings. - Markdown syntax — images and the rest of the syntax.