Upload and manage media library files
/admin/api/media
List all media files for the current shop, newest first.
/admin/api/media
Upload a new media file. Accepts multipart/form-data with a single field named "file". Images are resized to max 1200x1200 px and converted to WebP. Tier caps are enforced — exceeding either cap returns 402 with `error: tier_limit_exceeded`, `limit: max_media_bytes` or `max_single_file_bytes`. See the Tenant Tier reference.
/admin/api/media/{id}
Update a media file's slug. The slug is URL-friendly and used in templates. Input is slugified server-side.
/admin/api/media/{id}
Delete a media file. Removes both the database record and the file from object storage.
/admin/api/media
List all media files for the current shop, newest first.
[
{
"id": 1,
"slug": "hero_banner",
"storage_key": "shop1/media/a1b2c3.webp",
"url": "https://cdn.zeroshop.io/shop1/media/a1b2c3.webp",
"original_filename": "hero-banner.png",
"file_size": 48210,
"content_type": "image/webp",
"created_at": "2025-11-20T14:30:00Z"
},
{
"id": 2,
"slug": "logo",
"storage_key": "shop1/media/d4e5f6.webp",
"url": "https://cdn.zeroshop.io/shop1/media/d4e5f6.webp",
"original_filename": "logo.jpg",
"file_size": 12480,
"content_type": "image/webp",
"created_at": "2025-11-19T09:15:00Z"
}
]
curl "https://yourshop.zeroshop.io/admin/api/media" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/media",
headers={"Authorization": "Bearer zspat_..."}
)
media = resp.json()
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/media",
{ headers: { "Authorization": "Bearer zspat_..." } }
);
const media = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/media")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/media
Upload a new media file. Accepts multipart/form-data with a single field named "file". Images are resized to max 1200x1200 px and converted to WebP. Tier caps are enforced — exceeding either cap returns 402 with `error: tier_limit_exceeded`, `limit: max_media_bytes` or `max_single_file_bytes`. See the Tenant Tier reference.
| Name | Type | Required | Description |
|---|---|---|---|
| file | file | required | The image file to upload. Supported formats: JPEG, PNG, GIF, WebP. |
{
"id": 3,
"slug": "product_shot",
"storage_key": "shop1/media/g7h8i9.webp",
"url": "https://cdn.zeroshop.io/shop1/media/g7h8i9.webp",
"original_filename": "product-shot.png",
"file_size": 35600,
"content_type": "image/webp",
"created_at": "2025-11-21T10:00:00Z"
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/media" \
-H "Authorization: Bearer zspat_..." \
-F "file=@product-shot.png"
import requests
with open("product-shot.png", "rb") as f:
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/media",
headers={"Authorization": "Bearer zspat_..."},
files={"file": ("product-shot.png", f, "image/png")}
)
media = resp.json()
const form = new FormData();
form.append("file", fileInput.files[0]);
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/media",
{
method: "POST",
headers: { "Authorization": "Bearer zspat_..." },
body: form
}
);
const media = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/media")
boundary = "----RubyFormBoundary"
body = "--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"shot.png\"\r\nContent-Type: image/png\r\n\r\n#{File.binread("shot.png")}\r\n--#{boundary}--\r\n"
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req.content_type = "multipart/form-data; boundary=#{boundary}"
req.body = body
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/media/{id}
Update a media file's slug. The slug is URL-friendly and used in templates. Input is slugified server-side.
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Media file ID |
{
"slug": "new_hero_banner"
}
{
"slug": "new_hero_banner"
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/media/1" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"slug": "new_hero_banner"}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/media/1",
headers={"Authorization": "Bearer zspat_..."},
json={"slug": "new_hero_banner"}
)
result = resp.json()
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/media/1",
{
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ slug: "new_hero_banner" })
}
);
const result = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/media/1")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req.content_type = "application/json"
req.body = { slug: "new_hero_banner" }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/media/{id}
Delete a media file. Removes both the database record and the file from object storage.
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Media file ID |
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/media/1" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.delete(
"https://yourshop.zeroshop.io/admin/api/media/1",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/media/1",
{
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
}
);
// 204 No Content
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/media/1")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
# 204 No Content
We value your privacy
We use cookies for essential site functionality and, with your consent, analytics to understand how our platform is used. No personal data is shared with third parties. See our Privacy Policy for details.