Promotion codes

Discounts for Cart-Backed Checkout

Throttle supports percentage and fixed amount promotion codes. The payment processor does not validate or calculate codes; Throttle computes the discounted cart total before checkout hands the final amount to payment.

1

Create a discount

Use the dashboard or API to create a percentage or fixed amount discount code.

2

Preview in your storefront

Call /api/v1/discounts/preview from your server when the buyer enters a code.

3

Apply to a cart

When checkout starts, apply the valid code to the Throttle cart before creating the checkout session.

4

Lock totals

Run shipping/tax calculation and checkout_final against the discounted cart before payment.

Supported types

Percentage off

type: "percentage", with value: 25 for 25% off.

Fixed amount off

type: "fixed_amount", with value: 500 for $5.00 off in minor units.

Removed discount types
Free shipping and buy-X-get-Y are no longer active product surfaces. Existing historical rows may remain inactive, but new code should only create percentage or fixed amount discounts.

Use the SDK

@usethrottle/discounts is a server-side, headless SDK. It does not ship a coupon input UI; your checkout owns that field.

Install
npm install @usethrottle/discounts
Preview and apply
import { DiscountsClient } from '@usethrottle/discounts';

const discounts = new DiscountsClient({
  apiKey: process.env.THROTTLE_API_KEY!,
});

const preview = await discounts.preview({
  code: 'SUMMER25',
  currency: 'USD',
  subtotal: 10000,
  customerId,
});

if (preview.valid) {
  await discounts.applyToCart('cart_123', preview.code);
}

Preview without mutation

Use preview when the buyer types a code in your order summary. It returns the discount amount and total without creating or changing a cart. Include a customer ID when you want the preview to check the customer redemption limit.

POST /discounts/preview
const res = await fetch(`${process.env.THROTTLE_API_URL}/api/v1/discounts/preview`, {
  method: 'POST',
  headers: {
    'x-api-key': process.env.THROTTLE_API_KEY!,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    code: 'SUMMER25',
    currency: 'USD',
    subtotal: 10000,
    customerId: '00000000-0000-0000-0000-000000000000',
  }),
});

const { data } = await res.json();
// data.valid, data.discountTotal, data.total, data.message

Customer limits

New discounts default to one redemption per customer. Set maxRedemptionsPerCustomer to another positive integer for repeat use, or null for unlimited customer redemptions. Usage is recorded when a discounted cart becomes an order.

One code per cart
A cart can hold one applied code. Applying a valid new code replaces the previous one; invalid replacement attempts leave the existing code unchanged.

List and search codes

GET /api/v1/discounts uses cursor pagination and accepts q, type, isActive, code, and sort filters. Use meta.pagination.cursor for the next page until hasMore is false.

Cart and order records

Applied coupons are visible on cart reads and are preserved on the order snapshot. A cart response includes appliedDiscount with the code, discount ID, type, amount, currency, validity, and snapshot hash. When the cart becomes an order, the order stores discountId, discountCode, and metadata.appliedDiscount.

Redemption timing
Global and per-customer usage counts are recorded when the discounted cart is converted to an order. Preview does not mutate usage. Apply validates customer limits when the cart has a customer ID.

Payment flow

Throttle applies discounts before payment. The payment processor receives the final amount to authorize or capture; it does not receive a coupon code to validate.

sequenceDiagram
  participant B as Browser
  participant M as Merchant Backend
  participant T as Throttle API
  participant C as Checkout Iframe
  B->>M: buyer enters SUMMER25
  M->>T: POST /api/v1/discounts/preview
  T-->>M: valid + discountTotal + total
  M-->>B: show adjusted order summary
  B->>M: buyer starts checkout
  M->>T: POST /api/v1/carts/{id}/apply-discount
  M->>T: POST /api/v1/checkout/sessions
  M-->>B: sessionId
  B->>C: render checkout
  C->>T: complete payment with discounted total
Discount preview, cart apply, and checkout handoff.

Metadata

Discounts accept arbitrary JSON metadata for campaign IDs, channels, attribution, or merchant notes. Metadata is stored with the discount and returned by the REST API, GraphQL API, and SDK.

Discount metadata
await discounts.create({
  code: 'SUMMER25',
  name: 'Summer 25% off',
  type: 'percentage',
  value: 25,
  maxRedemptionsPerCustomer: 1,
  metadata: {
    campaign: 'summer-2026',
    channel: 'email',
  },
});

const discount = await discounts.get('disc_xxx');
// discount.metadata.campaign === 'summer-2026'

Apply to checkout

Cart application is the authoritative step. Apply the code to a native Throttle cart before you create or render the checkout session.

Cart mutation
// Server-side only. Never expose your sk_ key in the browser.
import { createDiscountsClient } from '@usethrottle/discounts';

const discounts = createDiscountsClient({
  apiKey: process.env.THROTTLE_API_KEY!,
});

await discounts.applyToCart(cartId, 'SUMMER25');

// Remove the active code:
await discounts.removeFromCart(cartId);
Storefront pattern
// Storefront checkout pattern
// 1. Buyer enters code in your order summary.
// 2. Your server previews it without mutating a cart.
const preview = await discounts.preview({ code, currency: 'USD', subtotal, customerId });

// 3. If valid, include the code when your server creates the Throttle cart.
const cart = await cartClient.carts.create({ applicationId, currency: 'USD' });
await Promise.all(items.map((item) => cartClient.items.add(cart.id, item)));
await discounts.applyToCart(cart.id, preview.code);

// 4. Create the checkout session using the discounted cart id.
await checkout.createSession({ applicationId, cartId: cart.id, returnUrl, cancelUrl });

Pre-applying at session create

POST /api/v1/checkout/sessions accepts a top-level discountCode: string field. When provided, Throttle validates the code synchronously against the session's cart and applies it before returning. The response cart total reflects the discount.

Pre-apply at session create
curl -X POST https://api.usethrottle.dev/api/v1/checkout/sessions \
  -H "X-API-Key: sk_production_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
    "externalCartId": "cart_abc",
    "amount": 2999,
    "currency": "USD",
    "discountCode": "FRIEND10"
  }'
Validation is synchronous
An invalid or expired code returns 422 discount_invalid. The session is not created. Your storefront should call /api/v1/discounts/preview first if you want to surface the error inline, then retry the session create with the validated code (or omit it). The preview endpoint computes the discounted total without mutating any cart or session.

Current boundaries

  • Promotion codes work on cart-backed checkout totals.
  • Discounts are order-level cart reductions, not item-level discounts.
  • Only one coupon can be attached to a cart; this is enforced by the API and database.
  • External/provider-owned carts must apply discounts upstream in the provider.
  • Recurring subscription discounts are not part of this v1 surface.
  • Keep secret API keys on your backend; browsers should call your server.
Related guides
Continue with Cart API for native carts and Embedded Checkout for the iframe handoff.