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.
Create a discount
Use the dashboard or API to create a percentage or fixed amount discount code.
Preview in your storefront
Call /api/v1/discounts/preview from your server when the buyer enters a code.
Apply to a cart
When checkout starts, apply the valid code to the Throttle cart before creating the checkout session.
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.
Use the SDK
@usethrottle/discounts is a server-side, headless SDK. It does not ship a coupon input UI; your checkout owns that field.
npm install @usethrottle/discountsimport { 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.
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.messageCustomer 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.
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.
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 totalMetadata
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.
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.
// 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 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.
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"
}'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.