Sellvik / developers
Admin API

Media

Upload assets to Cloudflare R2, attach them to products.

Media uploads are two-step:

  1. Request a presigned R2 URL from Sellvik.
  2. 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/upload

Scope: media:write

Request body

{
  "filename": "kurti-emerald.jpg",
  "contentType": "image/jpeg",
  "kind": "PRODUCT_IMAGE"
}
FieldRequiredNotes
filenameyesOriginal filename. Used for the storage key suffix.
contentTypeyesMIME type. Must match what you PUT to R2.
kindnoPRODUCT_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.jpg

200 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}/media

Scope: products:write (note: not media:write — attaching is a product mutation).

Request body

{
  "mediaId": "med_a1b2c3...",
  "isMain": false,
  "sortOrder": 2
}
FieldRequiredNotes
mediaIdyesFrom the upload step.
isMainnoDefault false. Setting true demotes any existing main.
sortOrdernoLower = 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.

On this page