This page covers the only two endpoints with dedicated surface over a product's variants and media: reading/updating an existing variant, and uploading images via a presigned URL. It also explicitly documents what falls outside this surface — because it's more than you'd assume.
{ "code": "not-found", "message": "Product CAM-ROJO-M not found" }
Required permission:products:read
This endpoint is already documented with its full response example in Retrieve a product — it returns {sku, variants[], options[], hasVariants, hasOptions}.
{ "code": "not-found", "message": "Product 'CAM-ROJO-M' or variant 'CAM-ROJO-M-CH' not found" }
Required permission:products:update
The request body is a flat object { [field]: value } with any valid variant field (price, title, position, taxable, shipping, minAlertStock, sellIfOutOfStock, countryOfOrigin, etc.).
compareAtPrice is NOT validated at the variant level
At the product level, compareAtPrice must be greater than price or the save fails. That same rule does not apply to an individual variant's compareAtPrice — you can save a variant with a compareAtPrice lower than or equal to its price without the request failing.
No create/delete for a variant — only updating an existing one
This endpoint updates fields of a variant that already exists. There is no POST to create a new variant nor a DELETE to remove one. The full structure of the variants[] array (adding, removing, reordering variants) is managed by resending the entire array within the payload of PUT /products/{id}.
{ "code": "bad-request/invalid-content-type", "message": "Content type \"application/pdf\" is not allowed. Allowed: JPEG, PNG, GIF, WEBP, MP4, WebM, MOV", "parameter": "contentType"}
Required permission:products:update
This endpoint does not upload the file — it returns a presigned S3 URL (uploadUrl) to which you upload the binary directly via PUT from your client, and a publicUrl which is the one you'll persist in the product's media[].
# 1. Request the presigned URLcurl -X POST https://api.fenicia.io/products/media/upload \ -H "Authorization: Bearer fkapi_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "contentType": "image/jpeg", "filename": "cam-rojo-m-1.jpg" }'# 2. Upload the binary directly to the returned uploadUrlcurl -X PUT "https://fenicia-media-uploads.s3.amazonaws.com/..." \ -H "Content-Type: image/jpeg" \ --data-binary @cam-rojo-m-1.jpg
After uploading the binary, add the entry to the product's media[] array with publicUrl as src, via PUT /products/{id}:
There is no dedicated route to remove an image or change its order. The lambda's source code has explicit TODO comments confirming that supporting this is pending. To delete, reorder, or modify any entry in media[], resend the full media[] array (with the entry removed, reordered, or modified) within the payload of PUT /products/{id}.
Several parts of the product model have no granular read/write endpoint of their own — they are managed exclusively by sending the full product (or the relevant subset of fields) to PUT /products/{id}. This is confirmed by explicit TODO comments in the lambda's source code, not an omission of this documentation:
Area
How it's read
How it's written
Attributes / custom fields (attributes[])
As part of the full product
Resending the full attributes[] via PUT /products/{id}
Resending the full priceSchemas[] via PUT /products/{id}
Media — create, delete, reorder
As part of the full product
Resending the full media[] via PUT /products/{id} (uploading the binary does have its own endpoint, see above)
bundleConfig follows the same pattern
There is no "bundle" entity with its own endpoints in the public API. bundleConfig (the components of a bundle-type product) is another sub-shape of Product, also managed within the full payload of PUT /products/{id} — there is no route like /products/{sku}/bundle. The only product-of-products composition mechanism with its own endpoints is BOM (bill of materials), oriented to manufacturing/consumption, not "sale kits".