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.
Request
PATCH /product-variants/{id}
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Product variant UUID |
Request Body
All fields are optional. Only include the fields you want to update.
| Parameter | Type | Description |
|---|---|---|
name | string | Variant display name (max 255 characters). Set to null to clear. |
custom_id | string | External identifier for integration with other systems (max 255 characters). Set to null to clear. |
is_hidden | boolean | Hide variant from checkout page |
trial_period_days | integer | Trial period in days before first charge (recurring variants only, min 0). Set to null to disable. |
active_cycles | integer | Number of billing cycles for payment plans (min 1). Set to null for unlimited. |
cancel_early | boolean | Allow customers to cancel subscription before active_cycles complete |
keep_access_after_expiring | boolean | Keep access to content after subscription ends |
allow_multiple_subscriptions | boolean | Allow customers to have multiple concurrent subscriptions |
active_days | integer | Access duration in days after purchase, relative-expiry mode (min 1). Set to null for unlimited. |
access_until | string | Fixed access-expiry date (ISO 8601), fixed-expiry mode. Setting it also ends the sale window (active_until is set to the same date). Set to null to clear. |
quantity | integer | Available inventory limit (min 0). Set to null for unlimited. |
has_quantity | boolean | Enable quantity selector on checkout page |
old_price | integer | Struck-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_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 |
refund_days | integer | Refund period in days, overrides product setting (min 0). Set to null to use product default. |
redirect_url | string | Post-purchase redirect URL, overrides product setting (max 1000 characters). Set to null to use product default. |
redirect_time | integer | Redirect delay in seconds (min 0). Set to null to use product default. |
file | object | Single variant-specific downloadable file, overrides product files. Legacy shortcut equivalent to files: [file]. Set to null to remove. See File Object. |
files | array | Up 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_remarks | string | Additional notes to include on invoices (max 250 characters). Set to null to clear. |
redirect_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
| 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
Response Fields. Requests take name and url
only, so a file kept from an earlier read is sent back without it.
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"
}'
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",
"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
}
{
"id": "e903c6d6-163e-4080-a99a-4ee5a1e4f17a",
"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
}
Response Fields
See Get Product Variant for a complete list of response fields.
Error Responses
Bad Request (400)
{
"message": "Invalid product variant ID"
}
Product Variant Not Found (404)
{
"message": "Product variant with ID e1b6d3f8-7c2a-4f9e-b4d1-6a8c3e7b2f50 not found"
}
Validation Error (422 Unprocessable Entity)
{
"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://"
]
}
}
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,interval,interval_count, andtax_behaviorcannot be changed after creation and are not accepted in the request body. - Access expiry:
active_days(relative — N days after purchase) andaccess_until(fixed calendar date) are the two access-expiry modes. Settingaccess_untilalso ends the sale window —active_untilis set to the same date. If both are set,access_untiltakes precedence at fulfilment.