Update Product
Update an existing product in your store. This endpoint uses PATCH semantics, meaning only the fields you include in the request body will be updated.
Updating a product that is currently published requires an active Easytools plan, because
the change goes live immediately. If the store has none, the call returns 422 and
nothing is changed. Updating a draft is always accepted. See
Active plan required.
When has_draft is true, the call returns 409 and nothing is changed. Edit the overlay
with Update Product Draft, or
discard the draft first. See
Live writes while a draft exists.
Checkout appearance is not on this body — use
Update Product Styles,
Update Product Settings, and
Update Product Media. Sending styles, settings, or
media here is ignored. image_url still updates the cover only.
Checkout and waitlist field definitions are nested — see
Custom Fields. Sending custom_fields or
waitlist_custom_fields here is ignored. Checkout order bumps are nested —
see Order Bumps. Sending order_bumps here is ignored.
After-purchase cross-sells are nested — see Cross-sells.
Sending cross_sells here is ignored.
Request
PATCH /products/{id}
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Product UUID |
Request Body
All fields are optional. Only include the fields you want to update.
| Parameter | Type | Description |
|---|---|---|
name | string | Product name (max 255 characters) |
description | string | Product description (max 10,000 characters). Set to null to clear. |
slug | string | Custom URL slug. Must match pattern ^[a-z0-9-]+$ and be unique per store. |
image_url | string | Cover image URL (max 2048 characters). Updates the cover only — other images on the product are left unchanged. The image is downloaded and stored — see Product images. Set to null to clear the cover only. |
file | object | Single downloadable file. Legacy shortcut equivalent to files: [file]. Set to null to remove. See File Object. |
files | array | Up to 5 downloadable files. Each entry is a File Object. When provided, this supersedes file and replaces the product's entire file set. Send [] to remove all files. |
active_from | string | Start selling date (ISO 8601 format). Set to null to clear. |
active_until | string | Stop selling date (ISO 8601 format). Set to null to clear. |
show_active_until_counter | boolean | Show countdown timer on checkout page |
redirect_url | string | Post-purchase redirect URL (max 1000 characters). Set to null to clear. |
redirect_time | integer | Redirect delay in seconds |
add_redirect_params | boolean | Include order parameters in redirect URL |
webhook_url | string | Webhook URL for order events (max 255 characters). Set to null to use global webhook. |
terms_url | string | Terms & conditions URL (max 255 characters). Set to null to clear. |
policy_url | string | Privacy policy URL (max 255 characters). Set to null to clear. |
contact_email | string | Product contact email. Set to null to clear. |
refund_days | integer | Refund period in days. Set to 0 to disable refunds. |
delegable | boolean | Allow order delegation/gifting |
show_discount_field | boolean | Show promo code field on checkout page |
hide_tax_id_field | boolean | Hide tax ID field on checkout page |
require_tax_id | boolean | Require tax ID on checkout page |
photo_zoomable | boolean | Enable image zoom on checkout page |
show_confetti | boolean | Show confetti animation after purchase |
generate_qr_code | boolean | Generate QR code for product |
show_delegation | boolean | Show the access-delegation control on checkout |
delegation_label | string | Label for the access-delegation control (max 250 characters). Set to null to clear. |
delegable_if_multiple | boolean | Allow delegation only when buying more than one |
has_shipping | boolean | Collect a delivery address at checkout. Distinct from delivery_enabled. |
delivery_enabled | boolean | Enable parcel delivery. When true, delivery_service and a complete delivery_options object are required. |
delivery_service | string | Carrier slug. See Checkout form. Set to null to clear only when the resulting delivery_enabled is false. |
delivery_address_optional | boolean | Whether the delivery address is optional |
delivery_options | object | Parcel package. See Delivery Options Object. Replaces the stored package in full. Set to null to delete the stored package when the resulting delivery_enabled is false. Returns 422 while delivery stays on. |
shipping_phone_required | boolean | Require a phone number with the delivery address |
phone_tooltip | string | Helper text for the shipping phone field (max 250 characters). Set to null to clear. |
mandatory_consent | string | Label for the mandatory checkout consent (max 500 characters). Set to null to turn it off. |
optional_consent | string | Label for the optional checkout consent (max 500 characters). Set to null to turn it off. |
digital_consent | string | Label for the digital-content consent (max 500 characters). Set to null to turn it off. |
newsletter_consent | string | Label for the newsletter opt-in (max 500 characters). Set to null to turn it off. |
newsletter_consent_required | boolean | Whether the newsletter opt-in is required |
newsletter_discount | object | Discount offered with newsletter opt-in. See Newsletter Discount Object. Do not send currency. Set to null to clear. |
invoice_remarks | string | Remarks printed on invoices (max 250 characters). Set to null to clear. |
vat_rate | string | VAT rate as a string (23, zw, np). Set to null to clear. See Invoice, tax, portal, waitlist, and QR. |
gtu_code | integer | GTU code 1–13 as stored, not a GTU_01 label. Set to null to clear. |
flat_rate | number | Flat-rate tax percent: 17, 15, 14, 12.5, 12, 10, 8.5, 5.5, 3, or 2. Set to null to clear. |
exempt_tax_kind | string | Exemption basis when VAT is zw (max 255 characters). Set to null to clear. |
tax_category | string | Tax category: saas, ebook, course, webinar, digital, consulting, physical, or exempt. Distinct from variant tax_behavior. Set to null to clear. |
dashboard_header | string | Self-service portal header (max 250 characters). Set to null to clear. |
long_description | string | Self-service portal body (max 25,000 characters). Distinct from description. Set to null to clear. |
cta_text | string | Self-service portal call-to-action label (max 50 characters). Set to null to clear. |
cta_url | string | Self-service portal call-to-action URL (max 255 characters). Set to null to clear. |
contact_phone | string | Product contact phone (max 50 characters). Set to null to clear. |
contact_website_url | string | Product contact website (max 255 characters). Distinct from the store website_url. Set to null to clear. |
contact_additional_info | string | Extra product contact copy (max 5,000 characters). Set to null to clear. |
footer_text | string | Checkout footer copy (max 500 characters). Set to null to clear. |
waitlist_enabled | boolean | Whether the waitlist is enabled. |
waitlist_require_first_name | boolean | Whether the waitlist requires a first name. |
waitlist_require_last_name | boolean | Whether the waitlist requires a last name. |
qr_code_info | string | Extra text shown with the QR code (max 250 characters). Set to null to clear. |
credits | integer | QR credits. Set to null to turn credits off (not zero). Minimum 0, maximum 4294967295 (422 if exceeded). |
reset_credits | boolean | Whether credits reset after use. |
wall_id | string | Love wall UUID. Omit to leave the current wall. null clears it. Omitted from this response. Get Product and Get Product Draft return it only with include=wall_id. A value that is not a UUID, an unknown project, or a project that is not a Love wall on your account returns 422 and the patch is not saved. |
footer_id | string | Product legal footer UUID. Omit to leave the current footer. Setting a UUID turns the product legal footer on. null clears it and turns that footer off. Omitted from this response. Get Product and Get Product Draft return it only with include=footer_id. A value that is not a UUID, an unknown project, or a project that is not a legal footer on your account returns 422 and the patch is not saved. |
player | object | Course attach. See Player Object. Omit to leave connected courses and the thank-you redirect unchanged. null returns 422. Omitted from this response. Get Product and Get Product Draft return the read object only with include=player. |
Every URL in the body must start with http:// or https:// and include a host. See
URL fields for what is accepted and what returns 422.
Player Object
player connects or disconnects one course and can set the thank-you redirect. Other connected courses stay. Omit player to leave both unchanged. player: null returns 422 and the patch is not saved.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Course UUID. Required when player is sent. null returns 422. |
redirect | boolean | No | Defaults to true when disconnect is omitted or false. |
disconnect | boolean | No | Defaults to false. true removes that course. If it was the thank-you course, redirecting stops too. |
redirect: true connects the course if it is not already connected and makes it the thank-you redirect.
redirect: false connects the course or leaves it connected. It does not disconnect it. If this course is the current thank-you redirect, redirecting stops. If it is not, the current redirect stays.
redirect: true with disconnect: true returns 422 and the patch is not saved.
A value that is not a UUID, an unknown project, or a project that is not a course on your account returns 422 and the patch is not saved.
wall_id, footer_id, and player are omitted from this response. Get Product and Get Product Draft return them only when include names that field. On a draft, wall_id and footer_id wait until publish, and a course connect or disconnect waits until publish too. Publish applies only the course connects and disconnects that draft asked for. A draft that did not send player changes no courses. See Update Product Draft. This request connects or disconnects immediately when the product has no draft. Easytools unavailable returns 502. An account not connected to Easytools returns 422. Neither reports the field as invalid.
File Object
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name shown in the customer portal (max 100 characters) |
url | string | Yes | The file's location (max 2048 characters): a URL to attach the file by link, or the url of a file read back from the API |
Reads add a read-only third property, download_url — see
File Object. Requests take name and url only, so a file
kept from an earlier read is sent back without it.
A product can have up to 5 downloadable files. Updating files (or the legacy file) replaces the product's entire file set — include every file you want to keep. Send files: [] (or file: null) to remove all files.
Example Request
Update product name and description:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Product Name",
"description": "New and improved description"
}'
Update availability settings:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"active_from": "2026-02-01T00:00:00Z",
"active_until": "2026-03-01T00:00:00Z",
"show_active_until_counter": true
}'
Update checkout options:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"redirect_url": "https://mysite.com/thank-you",
"redirect_time": 5,
"add_redirect_params": true,
"show_discount_field": true,
"show_confetti": true
}'
Collect a delivery address and set a newsletter discount:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"has_shipping": true,
"newsletter_discount": {
"amount": 500,
"duration": "once"
}
}'
Clear the stored package and newsletter discount. Sending delivery_options: null while
delivery_enabled stays true returns 422, so turn delivery off in the same request
when it is currently on:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"delivery_enabled": false,
"delivery_options": null,
"newsletter_discount": null
}'
Set invoice remarks, VAT, and waitlist names:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"invoice_remarks": "Thank you for your purchase!",
"vat_rate": "23",
"tax_category": "digital",
"waitlist_require_first_name": true,
"credits": 10
}'
Turn credits off:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credits": null
}'
Attach a Love wall, a product legal footer, and a course. redirect is omitted, so it defaults to true: the course is connected if needed and becomes the thank-you redirect.
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"wall_id": "8c2e1a40-6f5b-4d83-a917-3b0e6c4d2f18",
"footer_id": "5d9a7c31-2e84-4b16-8f0a-6c3d1e9b4a27",
"player": {
"id": "a4e8c1d2-7b39-4f6a-9c05-1e2d8b4a6f70"
}
}'
Disconnect that course. If it was the thank-you course, redirecting stops too:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"player": {
"id": "a4e8c1d2-7b39-4f6a-9c05-1e2d8b4a6f70",
"disconnect": true
}
}'
Replace the product's downloadable files with multiple files:
curl -X PATCH "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"files": [
{ "name": "My ebook.pdf", "url": "https://mysite.com/ebook.pdf" },
{ "name": "Bonus chapter.pdf", "url": "https://mysite.com/bonus-chapter.pdf" }
]
}'
Response
Success Response (200)
Returns the updated product. wall_id, footer_id, and player are omitted from this response.
{
"id": "0632bef5-c308-42cd-9cba-89a7a4f722bb",
"status": "published",
"name": "Updated Product Name",
"description": "New and improved description",
"slug": "premium-coffee-beans",
"image_url": "https://example.com/images/coffee-beans.jpg",
"checkout_url": "https://cart.easy.tools/checkout/0632bef5-c308-42cd-9cba-89a7a4f722bb",
"currency": "usd",
"file": {
"name": "My ebook.pdf",
"url": "https://mysite.com/ebook.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/9c1e7f42-3b8a-4d6e-9f10-2a5c7d3e8b41/download?expires=1749384000&signature=abc123"
},
"files": [
{
"name": "My ebook.pdf",
"url": "https://mysite.com/ebook.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/9c1e7f42-3b8a-4d6e-9f10-2a5c7d3e8b41/download?expires=1749384000&signature=abc123"
},
{
"name": "Bonus chapter.pdf",
"url": "https://mysite.com/bonus-chapter.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/4f7a2d18-6c93-4e5b-8a21-0d9e3f6b7c52/download?expires=1749384000&signature=abc123"
}
],
"active_from": "2026-02-01T00:00:00+00:00",
"active_until": "2026-03-01T00:00:00+00:00",
"show_active_until_counter": true,
"redirect_url": "https://mysite.com/thank-you",
"redirect_time": 5,
"add_redirect_params": true,
"webhook_url": null,
"terms_url": null,
"policy_url": null,
"contact_email": null,
"refund_days": 14,
"delegable": false,
"show_discount_field": true,
"hide_tax_id_field": false,
"require_tax_id": false,
"photo_zoomable": false,
"show_confetti": true,
"generate_qr_code": false,
"show_delegation": false,
"delegation_label": null,
"delegable_if_multiple": false,
"has_shipping": false,
"delivery_enabled": false,
"delivery_service": null,
"delivery_address_optional": true,
"delivery_options": null,
"shipping_phone_required": false,
"phone_tooltip": null,
"mandatory_consent": null,
"optional_consent": null,
"digital_consent": null,
"newsletter_consent": null,
"newsletter_consent_required": false,
"newsletter_discount": null,
"invoice_remarks": null,
"vat_rate": null,
"gtu_code": null,
"flat_rate": null,
"exempt_tax_kind": null,
"tax_category": null,
"dashboard_header": null,
"long_description": null,
"cta_text": null,
"cta_url": null,
"contact_phone": null,
"contact_website_url": null,
"contact_additional_info": null,
"footer_text": "",
"waitlist_enabled": false,
"waitlist_require_first_name": false,
"waitlist_require_last_name": false,
"qr_code_info": null,
"credits": null,
"reset_credits": false,
"variants": [
{
"id": "d91c65aa-b221-4aa4-b626-cf0d8a198749",
"status": "published",
"type": "one_time",
"recurring_options": null,
"name": "250g Package",
"amount": 1999,
"currency": "usd",
"checkout_url": "https://cart.easy.tools/checkout/premium-coffee?plan=price_123",
"custom_id": "SKU-250G",
"is_hidden": false,
"trial_period_days": null,
"active_cycles": null,
"cancel_early": false,
"keep_access_after_expiring": false,
"allow_multiple_subscriptions": false,
"active_days": 365,
"quantity": 100,
"has_quantity": true,
"old_price": 29,
"active_from": "2026-01-01T00:00:00+00:00",
"active_until": "2026-12-31T23:59:59+00:00",
"access_until": null,
"show_active_until_counter": true,
"tax_behavior": "exclusive",
"refund_days": 14,
"redirect_url": "https://mysite.com/thank-you",
"redirect_time": 5,
"file": {
"name": "Product Guide.pdf",
"url": "https://mysite.com/files/guide.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/b8d4e6a2-51c7-4f39-9e0b-3a7c2d18f5e6/download?expires=1749384000&signature=abc123"
},
"files": [
{
"name": "Product Guide.pdf",
"url": "https://mysite.com/files/guide.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/b8d4e6a2-51c7-4f39-9e0b-3a7c2d18f5e6/download?expires=1749384000&signature=abc123"
},
{
"name": "Quick Start.pdf",
"url": "https://mysite.com/files/quick-start.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/2e9b5c71-8d40-4a6f-b312-7f5a9c04d8e3/download?expires=1749384000&signature=abc123"
}
],
"invoice_remarks": "Thank you for your purchase!",
"has_shipping": false,
"delivery_enabled": false,
"delivery_service": null,
"delivery_address_optional": true,
"delivery_options": null,
"shipping_phone_required": false,
"phone_tooltip": null,
"mandatory_consent": null,
"optional_consent": null,
"digital_consent": null,
"newsletter_consent": null,
"newsletter_consent_required": false,
"newsletter_discount": null,
"vat_rate": null,
"gtu_code": null,
"flat_rate": null,
"exempt_tax_kind": null,
"tax_category": null,
"dashboard_header": null,
"long_description": null,
"cta_text": null,
"cta_url": null,
"credits": null,
"show_net_price": false,
"pay_what_you_want": null,
"purchase_button_text": null,
"upsells": [],
"custom_fields": null
},
{
"id": "e903c6d6-163e-4080-a99a-4ee5a1e4f17a",
"status": "published",
"type": "recurring",
"recurring_options": {
"interval": "month",
"interval_count": 1
},
"name": "Monthly Subscription - 250g",
"amount": 1799,
"currency": "usd",
"checkout_url": null,
"custom_id": null,
"is_hidden": false,
"trial_period_days": 14,
"active_cycles": 12,
"cancel_early": true,
"keep_access_after_expiring": false,
"allow_multiple_subscriptions": false,
"active_days": null,
"quantity": null,
"has_quantity": false,
"old_price": null,
"active_from": null,
"active_until": null,
"access_until": null,
"show_active_until_counter": null,
"tax_behavior": "exclusive",
"refund_days": null,
"redirect_url": null,
"redirect_time": null,
"file": null,
"files": [],
"invoice_remarks": null,
"has_shipping": false,
"delivery_enabled": false,
"delivery_service": null,
"delivery_address_optional": true,
"delivery_options": null,
"shipping_phone_required": false,
"phone_tooltip": null,
"mandatory_consent": null,
"optional_consent": null,
"digital_consent": null,
"newsletter_consent": null,
"newsletter_consent_required": false,
"newsletter_discount": null,
"vat_rate": null,
"gtu_code": null,
"flat_rate": null,
"exempt_tax_kind": null,
"tax_category": null,
"dashboard_header": null,
"long_description": null,
"cta_text": null,
"cta_url": null,
"credits": null,
"show_net_price": false,
"pay_what_you_want": null,
"purchase_button_text": null,
"upsells": [],
"custom_fields": null
}
],
"custom_fields": null,
"waitlist_custom_fields": null,
"styles": null,
"settings": null,
"media": null,
"has_draft": false
}
Response Fields
See Get Product for a complete list of response fields.
Error Responses
Bad Request (400)
{
"message": "Invalid product ID"
}
Not Found Error (404)
Returned when the UUID does not exist, belongs to another store, or points at an archived
product. Only draft and published products can be updated.
{
"message": "Product with ID b2c8e4a1-9d3f-4e7b-a5c2-1f6d8b3e9a47 not found"
}
Conflict Error (409 — Unpublished Draft)
Returned when an unpublished overlay exists. Nothing is changed. Edit the overlay, or discard the draft first.
{
"message": "This product has an unpublished draft. Publish, unpublish, or delete the draft before making live changes."
}
Validation Error (422 Unprocessable Entity)
{
"message": "The given data was invalid.",
"errors": {
"slug": [
"The slug 'existing-slug' is already in use by another product."
]
}
}
{
"message": "The given data was invalid.",
"errors": {
"slug": [
"The slug field format is invalid."
],
"active_until": [
"The active until field must be a date after or equal to active from."
]
}
}
wall_id, footer_id, and player use the same 422 envelope, and the patch is not saved. That includes a value that is not a UUID, player: null, player.id: null, redirect: true with disconnect: true, an unknown project, or a project that is not a Love wall, a legal footer, or a course on your account.
{
"message": "The given data was invalid.",
"errors": {
"player.redirect": [
"The player.redirect field is prohibited when player.disconnect is true."
]
}
}
Checkout-form fields use the same 422 envelope. See
Checkout form for the full matrix. Invoice, tax, portal,
waitlist, and QR fields use it too — see
Invoice, tax, portal, waitlist, and QR:
{
"message": "The given data was invalid.",
"errors": {
"newsletter_discount": [
"Provide either amount or percent, not both."
]
}
}
Service Unavailable (502)
Returned when Easytools is temporarily unavailable while attaching a Love wall, legal footer, or course. Retry shortly. The identifier was not rejected. Nothing in the patch is saved.
{
"message": "The Easytools service is temporarily unavailable. Please retry."
}
Account Not Connected (422)
Returned when the account is not connected to Easytools, so a wall, footer, or course cannot be attached. This is not an invalid field. Nothing in the patch is saved. The body has a message and no errors key.
{
"message": "Account is not connected to Easytools"
}
Publishing Not Allowed (422 — No Active Plan)
Returned when the product is currently published and the store has no active Easytools plan. Nothing is changed. Activate a plan and retry — repeating the call as it stands returns the same error. Drafts are unaffected.
This body carries a message and no errors key, so a 422 from this endpoint is
distinguished by whether errors is present.
{
"message": "Product cannot be published without an active Easytools plan"
}
Notes
- Currency: The
currencyfield cannot be changed after a product is created. Create a new product to use a different currency.