Public Alpha. Easel is in early development, so expect rough edges.

Easel

← API Documentation

Product Images API

Upload, list, describe, and delete images for a product. Image uploads use
multipart/form-data.

Endpoints


List Product Images

Retrieve all images for a product. This endpoint is not paginated.

Endpoint: GET /api/v1/products/{product_id}/images/

Scope Required: products:read

Request

curl -X GET "https://studio.easel.engineering/api/v1/products/pro_d0b6fmv28q6vn14peun0/images/" \
  -H "Authorization: Bearer your_api_key_here"

Response

Status: 200 OK

{
  "data": [
    {
      "id": "img_d0b6fmv28q6vn14peun0",
      "url": "/images/img_d0b6fmv28q6vn14peun0-large.webp",
      "alt_text": "Front of the t-shirt",
      "width": 1200,
      "height": 1200,
      "format": "webp",
      "created_at": "2023-01-01T10:00:00Z",
      "external_source": null,
      "external_id": null,
      "taken_down_at": null
    }
  ]
}

The url is relative to the storefront origin.


Upload Product Images

Upload one or more images for a product. Send the images as files in a
multipart/form-data body. Every file part is stored, so you can include
several images in a single request.

Endpoint: POST /api/v1/products/{product_id}/images/

Scope Required: products:write

Limits

  • The total request body must be 50 MiB or smaller.
  • Images must be no larger than 8192 pixels in either dimension. Larger
    images are rejected.
  • Supported formats depend on the image decoder. Common web formats such as
    JPEG, PNG, GIF, and WebP are accepted.

Uploaded images are downscaled and re-encoded as WebP, which keeps
transparency, and report a format of webp. The derived files are named
<image_id>-<size>.webp, for example img_d0b6fmv28q6vn14peun0-large.webp.
Each image also has a JPEG copy, <image_id>-share.jpeg, up to 1200 pixels on
its longest side, for email clients and link previews that can't show WebP.
Images uploaded before WebP report jpeg or png until Easel converts them.
Photos are turned upright using their EXIF orientation, so a phone photo taken
sideways is stored the right way up.

Form Fields

Field Required Description
(any name) Yes An image file; every file part is stored
external_source No System the image was imported from; requires external_id and exactly one file (see External References)
external_id No Image's ID in that system; requires external_source

Request

curl -X POST "https://studio.easel.engineering/api/v1/products/pro_d0b6fmv28q6vn14peun0/images/" \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Idempotency-Key: unique-request-id-12345" \
  -F "file=@front.jpg" \
  -F "file=@back.jpg"

Response

Status: 201 Created

{
  "data": [
    {
      "id": "img_d0b6fmv28q6vn14peun0",
      "url": "/images/img_d0b6fmv28q6vn14peun0-large.webp",
      "alt_text": "",
      "width": 1200,
      "height": 1200,
      "format": "webp",
      "created_at": "2023-01-01T10:00:00Z",
      "external_source": null,
      "external_id": null,
      "taken_down_at": null
    }
  ]
}

Error Responses

Invalid multipart form

Status: 400 Bad Request

{
  "error": "Invalid input",
  "code": "INVALID_FIELD"
}

Request too large

Status: 413 Request Entity Too Large

{
  "error": "Request body is too large",
  "code": "REQUEST_TOO_LARGE"
}

Image refused

A file Easel removed as illegal can't be uploaded to any store.

Status: 422 Unprocessable Entity

{
  "error": "The image can't be uploaded",
  "code": "INVALID_FIELD"
}

Update Product Image

Set an image's alt text, which describes it for screen readers and search
engines. The storefront's product page uses it, and falls back to the
product's name for an image without any. Translate it into the site's other
languages with the Translations API.

Endpoint: PUT /api/v1/products/{product_id}/images/{image_id}/

Scope Required: products:write

Request Body

Field Type Required Description
alt_text string Yes Up to 512 characters, trimmed; an empty string removes it

Request

curl -X PUT "https://studio.easel.engineering/api/v1/products/pro_d0b6fmv28q6vn14peun0/images/img_d0b6fmv28q6vn14peun0/" \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"alt_text": "Front of the t-shirt"}'

Response

Status: 200 OK

The updated image.

{
  "id": "img_d0b6fmv28q6vn14peun0",
  "url": "/images/img_d0b6fmv28q6vn14peun0-large.webp",
  "alt_text": "Front of the t-shirt",
  "width": 1200,
  "height": 1200,
  "format": "webp",
  "created_at": "2023-01-01T10:00:00Z",
  "external_source": null,
  "external_id": null,
  "taken_down_at": null
}

Error Responses

Alt text too long

Status: 400 Bad Request

{
  "error": "Invalid input",
  "code": "INVALID_FIELD"
}

Image not found, or not on this product

Status: 404 Not Found

{
  "error": "Resource not found",
  "code": "NOT_FOUND"
}

Delete Product Image

Delete an image from a product.

Endpoint: DELETE /api/v1/products/{product_id}/images/{image_id}/

Scope Required: products:write

Request

curl -X DELETE "https://studio.easel.engineering/api/v1/products/pro_d0b6fmv28q6vn14peun0/images/img_d0b6fmv28q6vn14peun0/" \
  -H "Authorization: Bearer your_api_key_here"

Response

Status: 204 No Content

Error Responses

Image not found

Status: 404 Not Found

{
  "error": "Resource not found",
  "code": "NOT_FOUND"
}

Image Object

Fields

Field Type Description
id string Image public ID
url string Relative URL to the large image
alt_text string Alt text; empty when the image has none
width integer Width in pixels of the image at url
height integer Height in pixels of the image at url
format string Image format: webp, or jpeg or png for an image uploaded before WebP that is not yet converted
created_at string ISO 8601 timestamp
external_source string|null System the record was imported from (see External References)
external_id string|null Record's ID in that system
taken_down_at string|null ISO 8601 timestamp of when Easel took the image down, or null while it hasn't. Read-only. See Takedowns

Takedowns

Easel hosts your store's content, so under the EU Digital Services Act its
staff may take an image down that's illegal or against Easel's terms (see
Moderation). A taken-down image stays on
its product, with taken_down_at set, but isn't served at any size, so
its url stops loading, on the storefront too. Delete it, or upload
another in its place. The statement of reasons was emailed to the
store's owners, and the dashboard keeps it under Settings › Activity ›
Easel decisions; the activity log's taken_down event, on the store with
part image, names the category in reason.