Skip to main content

Update Product Variant

Update an existing product variant in your store. This endpoint uses PATCH semantics, meaning only the fields you include in the request body will be updated.

Updating a variant of 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 variant of a draft product is always accepted. See Active plan required.

When the product has has_draft: true, the call returns 409 and nothing is changed. Edit the overlay with Update Product Draft Variant, or discard the draft first. See Live writes while a draft exists.

amount, type, currency, tax_behavior, and recurring_options cannot be changed after create. Sending any of them returns 422 and nothing is changed. See Immutable price fields.

Sending custom_fields here is ignored. Use the nested Custom Fields endpoints.

Request​

PATCH /product-variants/{id}

Path Parameters​

ParameterTypeRequiredDescription
idstringYesProduct variant UUID

Request Body​

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

ParameterTypeDescription
namestringVariant display name (max 255 characters). Set to null to clear.
custom_idstringExternal identifier for integration with other systems (max 255 characters). Set to null to clear.
is_hiddenbooleanHide variant from checkout page
trial_period_daysintegerTrial period in days before first charge (recurring variants only, min 0). Set to null to disable.
active_cyclesintegerNumber of billing cycles for payment plans (min 1). Set to null for unlimited.
cancel_earlybooleanAllow customers to cancel subscription before active_cycles complete
keep_access_after_expiringbooleanKeep access to content after subscription ends
allow_multiple_subscriptionsbooleanAllow customers to have multiple concurrent subscriptions
active_daysintegerAccess duration in days after purchase, relative-expiry mode (min 1). Set to null for unlimited.
access_untilstringFixed access-expiry date (ISO 8601), fixed-expiry mode. The variant stops selling on this date at the latest — see Access expiry modes. Set to null to clear.
quantityintegerAvailable inventory limit (min 0). Set to null for unlimited.
has_quantitybooleanEnable quantity selector on checkout page
old_priceintegerStruck-through compare-at price in major currency units (79 = 79.00 PLN). Min: 0. Unlike amount, which is in minor units (e.g. cents). Set to null to clear. See Price units.
active_fromstringStart selling date (ISO 8601 format). Set to null to clear.
active_untilstringStop selling date (ISO 8601 format). Set to null to clear. With access_until set, it can't be later than access_until, and null resets it to access_until.
show_active_until_counterbooleanShow countdown timer on checkout page
refund_daysintegerRefund period in days, overrides product setting (min 0). Set to null to use product default.
redirect_urlstringPost-purchase redirect URL, overrides product setting (max 1000 characters). Set to null to use product default.
redirect_timeintegerRedirect delay in seconds (min 0). Set to null to use product default.
fileobjectSingle variant-specific downloadable file, overrides product files. Legacy shortcut equivalent to files: [file]. Set to null to remove. See File Object.
filesarrayUp to 5 variant-specific downloadable files, overriding the product's files. Each entry is a File Object. When provided, this supersedes file and replaces the variant's entire file set. Send [] to remove all variant files (falls back to the product's files).
invoice_remarksstringAdditional notes to include on invoices (max 250 characters). Set to null to clear.
has_shippingbooleanCollect a delivery address for this variant. Checkout ORs this with the product — see Checkout overrides.
delivery_enabledbooleanEnable parcel delivery on this variant. When true, delivery_service and a complete delivery_options object are required.
delivery_servicestringCarrier slug. See Checkout form. Set to null to clear the override only when the resulting delivery_enabled on this variant is false.
delivery_address_optionalbooleanWhether the delivery address is optional on this variant
delivery_optionsobjectParcel package. See Delivery Options Object. Replaces the stored package in full. Set to null to clear the override when the resulting delivery_enabled on this variant is false. Returns 422 while this variant still has delivery 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 inherit the product.
mandatory_consentstringMandatory checkout consent label (max 500 characters). Set to null to inherit the product.
optional_consentstringOptional checkout consent label (max 500 characters). Set to null to inherit the product.
digital_consentstringDigital-content consent label (max 500 characters). Set to null to inherit the product.
newsletter_consentstringNewsletter opt-in label (max 500 characters). Set to null to inherit the product.
newsletter_consent_requiredbooleanWhether the newsletter opt-in is required on this variant
newsletter_discountobjectDiscount offered with newsletter opt-in. See Newsletter Discount Object. Do not send currency. Set to null to inherit the product.
vat_ratestringVAT rate as a string (23, zw, np). Set to null to inherit the product (not 0%). See Checkout overrides.
gtu_codeintegerGTU code 1–13 as stored. Set to null to inherit the product.
flat_ratenumberFlat-rate tax percent from the allowed set. Set to null to inherit the product.
exempt_tax_kindstringExemption basis when VAT is zw. Set to null to inherit the product.
tax_categorystringTax category slug. Distinct from tax_behavior. Set to null to inherit the product.
dashboard_headerstringSelf-service portal header (max 250 characters). Set to null to inherit the product.
long_descriptionstringSelf-service portal body (max 500 characters). Set to null to inherit the product.
cta_textstringSelf-service portal call-to-action label (max 50 characters). Set to null to inherit the product.
cta_urlstringSelf-service portal call-to-action URL (max 255 characters). Set to null to inherit the product.
creditsintegerQR credits stored on this variant. Set to null to inherit the product, not zero. Minimum 0, maximum 4294967295 (422 if exceeded).
show_net_pricebooleanWhether checkout shows the net price.
pay_what_you_wantobjectPay-what-you-want settings. See Pay What You Want Object. Set to null to turn it off (does not clear purchase_button_text). Cannot be enabled on a recurring variant.
purchase_button_textstringCheckout purchase-button label (pay-what-you-want and lead magnet). Sibling of pay_what_you_want. Set to null to clear.
upsellsarraySame-product checkout upgrades. Replaces the list in full. Omit to leave unchanged; send [] to clear. See Upsells.

redirect_url, cta_url, and each file's url must start with http:// or https:// and include a host. See URL fields for what is accepted and what returns 422.

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 Response Fields. Requests take name and url only, so a file kept from an earlier read is sent back without it.

note

A variant can have up to 5 downloadable files. Variant files override the product's files: when a variant has any files, customers see only the variant's files; when it has none, they fall back to the product's files. Updating files (or the legacy file) replaces the variant's entire set.

Example Requests​

Update variant name and custom ID:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Premium Package",
"custom_id": "SKU-PREMIUM-001"
}'

Clear this variant's newsletter discount so checkout uses the product's:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"newsletter_discount": null
}'

Enable pay-what-you-want on a one-time variant:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pay_what_you_want": {
"enabled": true,
"suggested_amount": 4900,
"minimum_amount": 1000
},
"purchase_button_text": "Pay yours"
}'

Turn pay-what-you-want off. purchase_button_text is left unchanged:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pay_what_you_want": null
}'

Attach same-product upsells. The listed variant_id must be a published sibling of this product. Omit upsells to leave the list unchanged:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"upsells": [
{
"variant_id": "e903c6d6-163e-4080-a99a-4ee5a1e4f17a",
"text": "Switch to a monthly subscription"
}
]
}'

Clear every upsell:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"upsells": []
}'

Update availability settings with sale pricing:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-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,
"old_price": 29
}'

Update subscription settings:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/e903c6d6-163e-4080-a99a-4ee5a1e4f17a" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"trial_period_days": 14,
"active_cycles": 12,
"cancel_early": true,
"keep_access_after_expiring": false
}'

Update file attachment:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"file": {
"name": "Premium Ebook.pdf",
"url": "https://mysite.com/files/premium-ebook.pdf"
}
}'

Attach multiple variant-specific files:

curl -X PATCH "https://cart.easy.tools/api/v1/product-variants/d91c65aa-b221-4aa4-b626-cf0d8a198749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"files": [
{ "name": "Premium Ebook.pdf", "url": "https://mysite.com/files/premium-ebook.pdf" },
{ "name": "Workbook.pdf", "url": "https://mysite.com/files/workbook.pdf" }
]
}'

Response​

Success Response (200)​

Returns the updated product variant with all fields.

{
"id": "d91c65aa-b221-4aa4-b626-cf0d8a198749",
"status": "published",
"type": "one_time",
"recurring_options": null,
"name": "Premium Package",
"amount": 1999,
"currency": "usd",
"checkout_url": "https://cart.easy.tools/checkout/premium-coffee?plan=price_123",
"custom_id": "SKU-PREMIUM-001",
"is_hidden": false,
"trial_period_days": null,
"active_cycles": null,
"cancel_early": false,
"keep_access_after_expiring": false,
"allow_multiple_subscriptions": false,
"active_days": null,
"quantity": null,
"has_quantity": false,
"old_price": 29,
"active_from": "2026-02-01T00:00:00+00:00",
"active_until": "2026-03-01T00:00:00+00:00",
"access_until": null,
"show_active_until_counter": true,
"tax_behavior": "exclusive",
"refund_days": null,
"redirect_url": null,
"redirect_time": null,
"file": {
"name": "Premium Ebook.pdf",
"url": "https://mysite.com/files/premium-ebook.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/6a3f9d24-0b71-4c85-9e6a-1d8b2f45c703/download?expires=1749384000&signature=abc123"
},
"files": [
{
"name": "Premium Ebook.pdf",
"url": "https://mysite.com/files/premium-ebook.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/6a3f9d24-0b71-4c85-9e6a-1d8b2f45c703/download?expires=1749384000&signature=abc123"
},
{
"name": "Workbook.pdf",
"url": "https://mysite.com/files/workbook.pdf",
"download_url": "https://cart.easy.tools/api/v1/product-files/d1c8b573-4e29-4a06-8f7b-52e3a9d61c48/download?expires=1749384000&signature=abc123"
}
],
"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
}
{
"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": "https://cart.easy.tools/checkout/premium-coffee?plan=price_456",
"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
}

Response Fields​

See Get Product Variant for a complete list of response fields.

Error Responses​

Bad Request (400)​

{
"message": "Invalid product variant ID"
}

Not Found Error (404)​

{
"message": "Product variant with ID e1b6d3f8-7c2a-4f9e-b4d1-6a8c3e7b2f50 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)​

Returned when an immutable price field is sent — see Immutable price fields — or when another field fails validation. Sending an immutable field changes nothing, including other fields in the same body.

{
"message": "The amount field cannot be updated. Stripe does not allow changing the amount on a published price.",
"errors": {
"amount": [
"The amount field cannot be updated. Stripe does not allow changing the amount on a published price."
]
}
}
{
"message": "The given data was invalid.",
"errors": {
"active_until": [
"The active until field must be a date after or equal to active from."
]
}
}
{
"message": "The given data was invalid.",
"errors": {
"redirect_url": [
"Enter a valid link starting with http:// or https://"
],
"file.url": [
"Enter a valid link starting with http:// or https://"
]
}
}

Sellability fields use the same envelope. See Checkout form and Checkout overrides. Pay-what-you-want and tax fields use it too — see Pay What You Want Object. upsells uses it too — see Upsells:

{
"message": "The given data was invalid.",
"errors": {
"upsells.0.variant_id": [
"A variant cannot upsell to itself."
]
}
}

Publishing Not Allowed (422 — No Active Plan)​

Returned when the variant's 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. Variants of a draft product 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​

  • Immutable fields: amount, type, currency, tax_behavior, and recurring_options cannot be changed after create. Sending any of them returns 422 — see Immutable price fields.
  • Access expiry: active_days (relative — N days after purchase) and access_until (fixed calendar date) are the two access-expiry modes. If both are set, access_until takes precedence at fulfilment. For how access_until limits active_until, see Access expiry modes.