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
| Parameter | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | Original filename, including the extension. |
content_type | string | Yes | Media type, for example image/jpeg. The PUT must send the same value. |
size | integer | Yes | Exact size in bytes. The PUT Content-Length must equal this. |
type | string | No | image (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"
}
}
}
| Field | Type | Nullable | Description |
|---|---|---|---|
id | string | No | Upload id. Use it on the PUT path and on complete. |
expires_at | string | No | RFC 3339 timestamp. The reservation ends at this time. |
upload_request.method | string | No | Always PUT. |
upload_request.url | string | No | Where to send the bytes. |
upload_request.headers | object | No | Copy 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
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Upload 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
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Upload 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"
}
| Field | Type | Nullable | Description |
|---|---|---|---|
name | string | No | Original filename. |
file_url | string | No | Hosted location. Send this on the later write that should show the file. |
type | string | No | Sniffed 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."
}