Create Product Variant
Create a new product variant for a product in your store.
Adding a variant to a product that is currently published requires an active Easytools
plan, because the variant goes live immediately. If the store has none, the call returns
422 and no variant is created. Adding a variant to a draft product is always accepted.
See Active plan required.
Request
POST /products/{id}/variants
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Product UUID |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Variant type ("one_time" or "recurring") |
name | string | No | Variant name (max 255 characters) |
recurring_options | object | No | Recurring options (required if type is "recurring") |
recurring_options.interval | string | No | Recurring interval ("day", "week", "month", "year") |
recurring_options.interval_count | integer | No | Number of intervals between charges |
amount | integer | Yes | Price in minor units (minimum 0) |
trial_period_days | integer | No | Trial period in days (min: 0, recurring variants only) |
active_cycles | integer | No | Number of billing cycles (min: 1, recurring only) |
is_hidden | boolean | No | Hide variant from checkout (default: false) |
quantity | integer | No | Inventory limit (min: 0, null = unlimited) |
has_quantity | boolean | No | Enable quantity selector (default: false) |
old_price | integer | No | 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). See Price units. |
active_from | string | No | Start selling date (ISO 8601) |
active_until | string | No | End selling date (ISO 8601, must be >= active_from) |
active_days | integer | No | Days of access after purchase (relative-expiry mode, min: 1) |
access_until | string | No | Fixed access-expiry date (ISO 8601). Also ends the sale window on that date |
tax_behavior | string | No | Tax handling ("inclusive" or "exclusive") |
Example Request
curl -X POST "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb/variants" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "one_time",
"name": "250g Package",
"amount": 1999,
"old_price": 29,
"quantity": 100,
"has_quantity": true
}'
curl -X POST "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb/variants" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "recurring",
"name": "Monthly Subscription - 250g",
"recurring_options": {
"interval": "month",
"interval_count": 1
},
"amount": 1799,
"trial_period_days": 14,
"active_cycles": 12,
"tax_behavior": "inclusive"
}'
Response
Success Response (201)
Returns the created product variant.
{
"id": "d91c65aa-b221-4aa4-b626-cf0d8a198749",
"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"
}
{
"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"
}
Response Fields
| Field | Type | Nullable | Description |
|---|---|---|---|
id | string | No | Unique identifier for the product variant (UUID) |
type | string | No | Variant type ("one_time" or "recurring") |
recurring_options | object | Yes | Recurring options (null for one-time variants) |
recurring_options.interval | string | No | Recurring interval ("day", "week", "month", "year") |
recurring_options.interval_count | integer | No | Number of intervals between charges |
name | string | Yes | Variant name |
amount | integer | No | Price in minor units |
currency | string | No | Lowercase ISO currency code (inherited from the product) |
checkout_url | string | Yes | Direct checkout URL for this variant. Null until the variant is synced to Stripe. |
The response contains the full variant. See Get Product Variant for the complete list of fields; the examples above are abbreviated.
Error Responses
Product Not Found (404)
{
"message": "Product with ID 0632bef5-c308-42cd-9cba-89a7a4f722bb not found"
}
Validation Error (422 Unprocessable Entity)
{
"message": "The given data was invalid.",
"errors": {
"type": [
"The type field is required."
],
"recurring_options": [
"The recurring options field is required when type is recurring."
],
"amount": [
"The amount field is required."
]
}
}
Publishing Not Allowed (422 — No Active Plan)
Returned when the parent product is currently published and the store has no active Easytools plan. No variant is created. Activate a plan and retry — repeating the call as it stands returns the same error. Draft products 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"
}