Skip to main content

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​

ParameterTypeRequiredDescription
idstringYesProduct UUID

Request Body​

All fields are optional. Only include the fields you want to update.

ParameterTypeDescription
namestringProduct name (max 255 characters)
descriptionstringProduct description (max 10,000 characters). Set to null to clear.
slugstringCustom URL slug. Must match pattern ^[a-z0-9-]+$ and be unique per store.
image_urlstringCover 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.
fileobjectSingle downloadable file. Legacy shortcut equivalent to files: [file]. Set to null to remove. See File Object.
filesarrayUp 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_fromstringStart selling date (ISO 8601 format). Set to null to clear.
active_untilstringStop selling date (ISO 8601 format). Set to null to clear.
show_active_until_counterbooleanShow countdown timer on checkout page
redirect_urlstringPost-purchase redirect URL (max 1000 characters). Set to null to clear.
redirect_timeintegerRedirect delay in seconds
add_redirect_paramsbooleanInclude order parameters in redirect URL
webhook_urlstringWebhook URL for order events (max 255 characters). Set to null to use global webhook.
terms_urlstringTerms & conditions URL (max 255 characters). Set to null to clear.
policy_urlstringPrivacy policy URL (max 255 characters). Set to null to clear.
contact_emailstringProduct contact email. Set to null to clear.
refund_daysintegerRefund period in days. Set to 0 to disable refunds.
delegablebooleanAllow order delegation/gifting
show_discount_fieldbooleanShow promo code field on checkout page
hide_tax_id_fieldbooleanHide tax ID field on checkout page
require_tax_idbooleanRequire tax ID on checkout page
photo_zoomablebooleanEnable image zoom on checkout page
show_confettibooleanShow confetti animation after purchase
generate_qr_codebooleanGenerate QR code for product
show_delegationbooleanShow the access-delegation control on checkout
delegation_labelstringLabel for the access-delegation control (max 250 characters). Set to null to clear.
delegable_if_multiplebooleanAllow delegation only when buying more than one
has_shippingbooleanCollect a delivery address at checkout. Distinct from delivery_enabled.
delivery_enabledbooleanEnable parcel delivery. When true, delivery_service and a complete delivery_options object are required.
delivery_servicestringCarrier slug. See Checkout form. Set to null to clear only when the resulting delivery_enabled is false.
delivery_address_optionalbooleanWhether the delivery address is optional
delivery_optionsobjectParcel 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_requiredbooleanRequire a phone number with the delivery address
phone_tooltipstringHelper text for the shipping phone field (max 250 characters). Set to null to clear.
mandatory_consentstringLabel for the mandatory checkout consent (max 500 characters). Set to null to turn it off.
optional_consentstringLabel for the optional checkout consent (max 500 characters). Set to null to turn it off.
digital_consentstringLabel for the digital-content consent (max 500 characters). Set to null to turn it off.
newsletter_consentstringLabel for the newsletter opt-in (max 500 characters). Set to null to turn it off.
newsletter_consent_requiredbooleanWhether the newsletter opt-in is required
newsletter_discountobjectDiscount offered with newsletter opt-in. See Newsletter Discount Object. Do not send currency. Set to null to clear.
invoice_remarksstringRemarks printed on invoices (max 250 characters). Set to null to clear.
vat_ratestringVAT rate as a string (23, zw, np). Set to null to clear. See Invoice, tax, portal, waitlist, and QR.
gtu_codeintegerGTU code 1–13 as stored, not a GTU_01 label. Set to null to clear.
flat_ratenumberFlat-rate tax percent: 17, 15, 14, 12.5, 12, 10, 8.5, 5.5, 3, or 2. Set to null to clear.
exempt_tax_kindstringExemption basis when VAT is zw (max 255 characters). Set to null to clear.
tax_categorystringTax category: saas, ebook, course, webinar, digital, consulting, physical, or exempt. Distinct from variant tax_behavior. Set to null to clear.
dashboard_headerstringSelf-service portal header (max 250 characters). Set to null to clear.
long_descriptionstringSelf-service portal body (max 25,000 characters). Distinct from description. Set to null to clear.
cta_textstringSelf-service portal call-to-action label (max 50 characters). Set to null to clear.
cta_urlstringSelf-service portal call-to-action URL (max 255 characters). Set to null to clear.
contact_phonestringProduct contact phone (max 50 characters). Set to null to clear.
contact_website_urlstringProduct contact website (max 255 characters). Distinct from the store website_url. Set to null to clear.
contact_additional_infostringExtra product contact copy (max 5,000 characters). Set to null to clear.
footer_textstringCheckout footer copy (max 500 characters). Set to null to clear.
waitlist_enabledbooleanWhether the waitlist is enabled.
waitlist_require_first_namebooleanWhether the waitlist requires a first name.
waitlist_require_last_namebooleanWhether the waitlist requires a last name.
qr_code_infostringExtra text shown with the QR code (max 250 characters). Set to null to clear.
creditsintegerQR credits. Set to null to turn credits off (not zero). Minimum 0, maximum 4294967295 (422 if exceeded).
reset_creditsbooleanWhether credits reset after use.
wall_idstringLove 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_idstringProduct 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.
playerobjectCourse 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.

ParameterTypeRequiredDescription
idstringYesCourse UUID. Required when player is sent. null returns 422.
redirectbooleanNoDefaults to true when disconnect is omitted or false.
disconnectbooleanNoDefaults 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​

ParameterTypeRequiredDescription
namestringYesDisplay name shown in the customer portal (max 100 characters)
urlstringYesThe 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.

note

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 currency field cannot be changed after a product is created. Create a new product to use a different currency.