Admin API
Media
Upload assets to Cloudflare R2, attach them to products.
Media uploads are two-step:
- Request a presigned R2 URL from Sellvik.
- PUT the bytes directly to R2 from your server.
Once uploaded, attach the resulting mediaId to a product. The product
gallery is just a list of attached Media rows with display order and a
"main" flag.
Request a presigned upload URL
POST /api/v1/admin/media/uploadScope: media:write
Request body
{
"filename": "kurti-emerald.jpg",
"contentType": "image/jpeg",
"kind": "PRODUCT_IMAGE"
}| Field | Required | Notes |
|---|---|---|
filename | yes | Original filename. Used for the storage key suffix. |
contentType | yes | MIME type. Must match what you PUT to R2. |
kind | no | PRODUCT_IMAGE (default), PRODUCT_VIDEO, CATEGORY_IMAGE. |
Response
{
"mediaId": "med_a1b2c3...",
"uploadUrl": "https://r2.sellvik.com/upload/...?X-Amz-Signature=...",
"publicUrl": "https://r2.sellvik.com/shops/acme/products/kurti-emerald-a1b2.jpg",
"expiresAt": "2026-05-27T14:15:00.000Z"
}mediaId— the row in Sellvik. Pass to the attach endpoint.uploadUrl— short-lived (15 min) presigned PUT URL.publicUrl— where the file will be served from once uploaded. Safe to store in your DB even before the PUT completes — but the URL 404s until the upload finishes.
Upload step (you → R2 directly)
curl -X PUT "<uploadUrl>" \
-H "Content-Type: image/jpeg" \
--data-binary @./kurti-emerald.jpg200 OK from R2 means the upload succeeded. No Sellvik notification needed
— the next attach call will find the file.
Attach media to a product
POST /api/v1/admin/products/{id}/mediaScope: products:write (note: not media:write — attaching is a product
mutation).
Request body
{
"mediaId": "med_a1b2c3...",
"isMain": false,
"sortOrder": 2
}| Field | Required | Notes |
|---|---|---|
mediaId | yes | From the upload step. |
isMain | no | Default false. Setting true demotes any existing main. |
sortOrder | no | Lower = earlier in the gallery. Default 100. |
Response
201 Created.
{
"id": "pm_a1b2c3...",
"productId": "f3a7...",
"mediaId": "med_a1b2...",
"fileUrl": "https://r2.sellvik.com/shops/acme/products/kurti-emerald-a1b2.jpg",
"isMain": false,
"sortOrder": 2
}Detach a media row
DELETE /api/v1/admin/products/{id}/media/{mediaId}Scope: products:write
Removes the link. The underlying Media row and the R2 file are retained —
they may be used by other products. Hard-deletion of orphaned R2 files
happens on a scheduled garbage-collection job.
204 No Content on success.