Skip to main content

Products

The Products API lets you manage products in your store.

Overview

The Products API provides endpoints to:

Active plan required

Putting a product on sale requires the store to have an active Easytools plan. Without one, the store can still create, edit and keep drafts, but it cannot make anything live and cannot change anything that is already live.

Five calls are refused, each returning 422:

  • Create product with status: published — no product is created. status: draft is always accepted.
  • Publish product — the product stays a draft. Publishing a product that is already published is refused in the same way.
  • Update product when the product is currently published, because the change would go live immediately. Updating a draft is always accepted.
  • Create product variant when the parent product is currently published. Adding a variant to a draft is always accepted.
  • Update product variant when the parent product is currently published. Updating a variant of a draft is always accepted.

A refused call creates and changes nothing, and returns a body with a message and no errors key:

{
"message": "Product cannot be published without an active Easytools plan"
}

All five endpoints also return 422 for ordinary validation errors, with a field-keyed errors object next to the message. The presence of errors is what tells the two apart.

Repeating the request returns the same error until the store owner activates a plan; changing the request does not resolve it. Create products as drafts and publish them once the plan is active.

Nothing else is gated. Unpublish product works regardless, so a live product can always be taken down; every read endpoint and every write to a draft product keeps working too.

Downloadable files

A product can carry up to 5 files, delivered to the buyer after purchase. Each entry in files has a name, a url, and — on reads — a download_url.

url is the file's stored location, and the value to send back when writing files. For a file uploaded to your store it is not publicly readable: requesting it directly returns 403. For a file attached by link it is that URL exactly as it was provided.

To fetch a file's contents, follow its download_url. It is a temporary signed link that needs no Authorization header, so it can be opened straight in a browser. It expires about an hour after the read, so request the product again for a fresh one instead of storing it. Integrations that previously downloaded a file by fetching files[].url need to follow download_url instead. See Download Product File.

download_url appears only on reads. Write requests take name and url.

A variant can carry its own files, which replace the product's for buyers of that variant. They use the same file object — see Product Variants.

URL fields

Every URL a product or variant accepts — image_url, redirect_url, webhook_url, terms_url, policy_url, and each file's url — must start with http:// or https:// and include a host. Almost nothing else is restricted: percent-encoding, query strings, fragments, explicit ports and non-ASCII characters in the path are all accepted, so a presigned storage link or a link with encoded spaces can be sent exactly as it is:

https://cdn.example.com/files/my%20file.pdf
https://bucket.s3.eu-central-1.amazonaws.com/f.pdf?X-Amz-Signature=abc&X-Amz-Expires=900
https://example.com/thanks#top
https://example.com:8443/thanks
https://example.com/a?tags=a,b

Anything else — a different scheme, a protocol-relative URL, a bare https:// with no host — is rejected with 422. Spaces and the characters <, > and " must be percent-encoded rather than sent raw.

The message is the same in every case, keyed by the field that failed:

{
"message": "The given data was invalid.",
"errors": {
"redirect_url": [
"Enter a valid link starting with http:// or https://"
]
}
}

Maximum lengths differ per field and are listed with each field on Create product, Update product and Update product variant.

Product images

The image at image_url is downloaded and stored when a product is created or updated. The image_url returned in responses points at that stored copy, so reading a product back returns a different URL than the one that was sent.