Skip to main content

Create Promotion Code

Create a new promotion code in your store.

The store must be connected to Stripe; this endpoint returns 422 until the connection is in place. After creation, only active, name, and price_uuids can be changed — see Most fields are immutable after create for the full list of frozen fields.

Request

POST /promotion-codes

Request Body

ParameterTypeRequiredDescription
codestringYesThe redemption string. Alphanumeric and hyphen only (^[a-zA-Z0-9-]+$), max 255 characters, unique per store.
discount_typestringYesamount_off (fixed amount) or percent_off (percentage). Discriminator for amount_off / percent_off below.
amount_offintegerWhen discount_type=amount_offDiscount in minor units (e.g. cents/grosze), minimum 1.
percent_offnumberWhen discount_type=percent_offPercentage discount, 1100.
currencystringWhen amount_off is set, and when minimum_amount is set on a global codeThree-letter ISO currency code (lowercase).
durationstringYesHow long the discount applies: once (first invoice), repeating (for duration_in_months invoices), or forever (every invoice).
duration_in_monthsintegerWhen duration=repeatingMonths the discount applies for, minimum 1.
namestringNoOptional internal name shown in dashboards, max 40 characters.
max_redemptionsintegerNoMaximum total redemptions across all customers, minimum 1.
expires_atstringNoWhen the code stops being redeemable (ISO 8601 format). Must be in the future.
first_time_transactionbooleanNoWhether the code is restricted to a customer's first purchase. Default false.
minimum_amountintegerNoMinimum transaction amount in minor units required for redemption, minimum 1.
product_idstringNoProduct UUID. When set, the code is product-scoped; otherwise it is global.
price_uuidsarrayWhen set, requires product_idArray of variant UUIDs. Narrows a product-scoped code to specific variants of product_id. Pass null or omit to apply to every price.

amount_off cannot be combined with duration=forever — a fixed-amount discount cannot apply indefinitely against future renewals. Use duration=once, or duration=repeating with duration_in_months. The combination returns 422.

Example Request — global percent_off code

curl -X POST "https://cart.easy.tools/api/v1/promotion-codes" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "BLACKFRIDAY20",
"name": "Black Friday 2026",
"discount_type": "percent_off",
"percent_off": 20,
"duration": "once",
"max_redemptions": 100,
"expires_at": "2026-12-31T23:59:59+00:00"
}'

Example Request — fixed-amount code for a specific product variant

curl -X POST "https://cart.easy.tools/api/v1/promotion-codes" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "LAUNCH10",
"discount_type": "amount_off",
"amount_off": 1000,
"currency": "pln",
"duration": "once",
"first_time_transaction": true,
"minimum_amount": 5000,
"product_id": "550e8400-e29b-41d4-a716-446655440000",
"price_uuids": [
"550e8400-e29b-41d4-a716-446655440001"
]
}'

Example Request — recurring discount for the next 3 invoices

curl -X POST "https://cart.easy.tools/api/v1/promotion-codes" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "THREE-MONTHS-FREE-50",
"discount_type": "percent_off",
"percent_off": 50,
"duration": "repeating",
"duration_in_months": 3
}'

Response

Success Response (201)

Returns the newly created promotion code.

{
"id": "550e8400-e29b-41d4-a716-446655440030",
"code": "BLACKFRIDAY20",
"name": "Black Friday 2026",
"discount_type": "percent_off",
"amount_off": null,
"percent_off": 20,
"currency": null,
"duration": "once",
"duration_in_months": null,
"max_redemptions": 100,
"times_redeemed": 0,
"expires_at": "2026-12-31T23:59:59+00:00",
"first_time_transaction": false,
"minimum_amount": null,
"minimum_amount_currency": null,
"scope": {
"type": "global"
},
"status": "active",
"created_at": "2026-05-28T11:00:00+00:00",
"updated_at": "2026-05-28T11:00:00+00:00"
}

See the Get Promotion Codes reference for a full description of each field.

Error Responses

Validation Error (422 Unprocessable Entity)

Returned for missing/invalid fields and for business-rule failures: Stripe not connected, amount_off + duration=forever, duplicate code, or price_uuids without product_id.

{
"message": "The given data was invalid.",
"errors": {
"code": [
"The code field is required."
],
"discount_type": [
"The discount type field is required."
]
}
}
{
"message": "Account is not connected to Stripe"
}
{
"message": "`forever` duration is not allowed with a fixed amount discount"
}
{
"message": "Promotion code \"BLACKFRIDAY20\" is already taken"
}
{
"message": "`price_uuids` requires `product_id`"
}

Unauthorized (401)

{
"message": "Unauthenticated."
}