Skip to main content

Upload Media

Host a file and return a URL. The same three calls cover checkout media and Creator-page media. Hosting does not attach the file: the gallery, cover, logos, and Creator-page draft stay as they are until a later write sends file_url.

Checkout gallery membership is Update Product Media. Checkout logos are Update Product Settings. There is no draft twin of this upload, and hosting does not return 409 while an unpublished product overlay exists.

The upload is three steps. Prepare reserves an id. You then PUT the raw bytes. Complete hosts the file and returns file_url.

Prepare and complete use a Bearer token and X-Easycart-Store, the same as other store calls. The byte PUT does not.

Complete is limited to 60 calls per 60 seconds per store. Prepare uses the general API rate limit. The byte PUT has its own limit: 20 requests per minute per client IP address, independent of the store limits.

Prepare​

POST /media-uploads

Content-Type: application/json. Bearer token required.

Request Body​

ParameterTypeRequiredDescription
filenamestringYesOriginal filename, including the extension.
content_typestringYesMedia type, for example image/jpeg. The PUT must send the same value.
sizeintegerYesExact size in bytes. The PUT Content-Length must equal this.
typestringNoimage (default) or file. image is for a photo or video. file is for a private downloadable.

No product id and no Creator-page id. A missing product or Creator page is rejected later, when you attach file_url.

image/heic, image/heif, and image/avif are rejected. Convert those files to JPG or PNG first. size below 1 byte, or above the store upload limit, is also rejected.

Example Request​

curl -X POST "https://cart.easy.tools/api/v1/media-uploads" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "X-Easycart-Store: 22b65ad7-ef20-4d46-9f04-110eba99cc77" \
-H "Content-Type: application/json" \
-d '{"filename":"pixel.jpg","content_type":"image/jpeg","size":695,"type":"image"}'

Success Response (201)​

{
"id": "ab14ebd9-7e59-4b98-a7ef-a63ddee00f36",
"expires_at": "2026-09-25T15:19:03+02:00",
"upload_request": {
"method": "PUT",
"url": "https://cart.easy.tools/api/v1/media-uploads/ab14ebd9-7e59-4b98-a7ef-a63ddee00f36",
"headers": {
"X-Upload-Token": "c3b42ed71076c00d82cb80482f7ae4318072c411019f68b4051a9b3abf52c8ee",
"Content-Type": "image/jpeg",
"Content-Length": "695"
}
}
}
FieldTypeNullableDescription
idstringNoUpload id. Use it on the PUT path and on complete.
expires_atstringNoRFC 3339 timestamp. The reservation ends at this time.
upload_request.methodstringNoAlways PUT.
upload_request.urlstringNoWhere to send the bytes.
upload_request.headersobjectNoCopy these headers onto the PUT.

The reservation lasts 15 minutes. X-Upload-Token authorizes that one PUT. Send the headers from this response; do not reuse a token from another upload.

Upload the bytes​

PUT /media-uploads/{id}

This call does not use Authorization. Copy upload_request.method, upload_request.url, and upload_request.headers as-is: X-Upload-Token, Content-Type, and Content-Length. The body is the raw file, not JSON.

Path Parameters​

ParameterTypeRequiredDescription
idstringYesUpload UUID from prepare

Example Request​

curl -X PUT "https://cart.easy.tools/api/v1/media-uploads/ab14ebd9-7e59-4b98-a7ef-a63ddee00f36" \
-H "X-Upload-Token: c3b42ed71076c00d82cb80482f7ae4318072c411019f68b4051a9b3abf52c8ee" \
-H "Content-Type: image/jpeg" \
-H "Content-Length: 695" \
--data-binary @pixel.jpg

Success Response (204)​

Empty body.

A second PUT for the same id returns 409. A Content-Type or Content-Length that does not match the reservation returns 422. The token stays valid until expires_at, so you can PUT again with the headers from prepare. A missing X-Upload-Token returns 401. An unknown id, a wrong X-Upload-Token, or an expired reservation returns 404.

Complete​

POST /media-uploads/{id}/complete

Empty body. Bearer token and X-Easycart-Store required, the same as prepare. id is the id from prepare.

Path Parameters​

ParameterTypeRequiredDescription
idstringYesUpload UUID from prepare

Example Request​

curl -X POST "https://cart.easy.tools/api/v1/media-uploads/ab14ebd9-7e59-4b98-a7ef-a63ddee00f36/complete" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "X-Easycart-Store: 22b65ad7-ef20-4d46-9f04-110eba99cc77"

Success Response (201)​

The gallery and the Creator-page draft are unchanged. type is the sniffed kind of the bytes: image, video, or file. It is not a copy of the prepare type. A still image prepared with type image comes back as WebP (the file_url ends in .webp). Video bytes prepared with type image come back as type video. A prepare type of file returns a private locator, not a public download.

Only image and video results can go into the checkout gallery. A file result can be used as a logo URL or as a downloadable file URL.

{
"name": "pixel.jpg",
"file_url": "https://cdn.example.com/TOmpXpfzxW1S1Ojp61XyQP7JakgbRHEN.webp",
"type": "image"
}
FieldTypeNullableDescription
namestringNoOriginal filename.
file_urlstringNoHosted location. Send this on the later write that should show the file.
typestringNoSniffed kind: image, video, or file.

Calling complete before a successful PUT returns 409. Calling complete again for the same upload returns 410. An unknown id, an expired reservation, or an id from another store returns 404.

Error Responses​

Bad Request (400)​

A malformed upload id on the PUT or on complete.

{
"message": "Invalid upload ID"
}

Unauthorized (401)​

Returned by the byte PUT when X-Upload-Token is missing.

{
"message": "Upload token is required."
}

Not Found Error (404)​

An unknown id or an expired reservation, on the PUT or on complete. On the PUT, also a wrong X-Upload-Token. On complete, also an upload that belongs to another store.

{
"message": "Upload with ID ab14ebd9-7e59-4b98-a7ef-a63ddee00f36 not found"
}

Conflict (409)​

The byte PUT has not succeeded yet, or this id was already stored by a previous PUT.

{
"message": "Upload bytes have not been stored yet."
}

A second PUT uses "Upload bytes have already been stored."

Gone (410)​

Returned when complete is called for an upload that already finished.

{
"message": "Upload has already been completed."
}

Validation Error (422 Unprocessable Entity)​

{
"message": "HEIC/HEIF is not supported. Please convert to JPG or PNG before uploading."
}

The same status covers a Content-Type or Content-Length that does not match the reservation, a size outside the upload limit, and bytes that cannot be hosted (including a downloadable sent with prepare type image). A prepare body that fails field validation uses the standard validation envelope (message plus errors).

Rate Limit Error (429 Too Many Requests)​

Returned when the store exceeds 60 completes in 60 seconds, or when one client IP address exceeds 20 byte PUTs in a minute. The two limits are counted separately. The Retry-After response header gives the number of seconds to wait.

{
"message": "Too many requests. Please retry after 60 seconds."
}