Apply a coupon
Validate a customer-entered code and attach the resulting discount to a subscription.
Goal
Take a coupon ID (server-side) or a customer-typed promotion code (e.g. LAUNCH50), validate that it can be redeemed, and attach the resulting discount to a subscription. Coupons can also be scoped to specific products and to a single subscription item rather than the whole subscription.
Prerequisites
- A sandbox or production API key
@easylabs/nodeinstalled- Either a Coupon you've already created, or a Promotion code that points at one
- An existing Subscription to apply the discount to
Implementation
1. Create the coupon (one-time setup)
Coupons are reusable — create them once for each campaign or pricing rule. Pick percent_off or amount_off + currency, never both.
import { createClient } from "@easylabs/node";
const easy = await createClient({ apiKey: process.env.EASY_API_KEY! });
const { data: coupon } = await easy.createCoupon({
duration: "repeating",
duration_in_months: 3,
percent_off: 25,
name: "Launch month — 25% off for 3 months",
max_redemptions: 500,
});
// Optional: a customer-redeemable code that points at this coupon.
const { data: promo } = await easy.createPromotionCode({
coupon_id: coupon.id,
code: "LAUNCH25",
first_time_only: true,
minimum_amount: 1000,
valid_until: "2026-12-31T23:59:59Z",
});2. Validate a customer-entered code
Always validate before applying — the call returns a discount_preview you can render in your UI ("$12.25 off") and a valid: false reason ("expired", "minimum_not_met") when the code can't be used.
const { data: validation } = await easy.validatePromotionCode({
code: "LAUNCH25",
identity_id: "cus_01HXXXXXXXXXXX",
amount: 4900, // optional — checks minimum_amount if set
});
if (!validation.valid) {
throw new Error(validation.reason ?? "Invalid promo code");
}3. Apply the discount to the subscription
Pass exactly one of coupon_id or promotion_code. Optionally scope to a single subscription item by passing subscription_item_id (otherwise the discount applies to the subscription as a whole).
// Apply via promotion code (typical for customer-entered codes)
const { data: discount } = await easy.applySubscriptionDiscount(
"sub_01HXXXXXXXXXXX",
{ promotion_code: "LAUNCH25" },
);
// Apply via coupon directly (typical for server-side / programmatic discounts)
await easy.applySubscriptionDiscount(
"sub_01HXXXXXXXXXXX",
{ coupon_id: coupon.id, subscription_item_id: "si_01HXXXXXXXXXXX" },
);The discount is honored on the next invoice generated by the subscription engine. For repeating coupons, the discount lasts duration_in_months invoices and then drops off automatically.
4. Inspect or remove later
const { data: discounts } = await easy.listSubscriptionDiscounts("sub_01HXXXXXXXXXXX");
await easy.removeSubscriptionDiscount("sub_01HXXXXXXXXXXX", discounts[0].id);Tradeoffs
- Coupon vs. promotion code — apply a
coupon_iddirectly when you control the discount logic server-side (anniversary perks, retention saves, internal credits). Use acodewhen the customer types it in. - Subscription-level vs. item-level scope — leaving
subscription_item_idunset discounts the whole subscription. Set it to scope to a single line — useful when an add-on is the discounted product but the base plan is not. first_time_onlyis per-coupon, not per-product — once a customer redeems any code that maps to a given coupon, no other code mapping to that same coupon will validate for them withfirst_time_only: true.