Discount codes
A discount code is a coupon, voucher, or promo entered at checkout to reduce the order total. Storlaunch's discount-code surface is a thin proxy to Ripllo, the marketing-orchestration product in the Forjio family. Ripllo owns the table, the validation rules, and the usage-tracking; Storlaunch just exposes it under your auth.
All endpoints require an sk_* key.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/discount-codes |
Create a discount code |
GET |
/v1/discount-codes |
List discount codes |
GET |
/v1/discount-codes/:id |
Retrieve one discount code |
PATCH |
/v1/discount-codes/:id |
Update a code |
DELETE |
/v1/discount-codes/:id |
Archive a code |
For the unauthenticated validate endpoint (used by the public storefront to live-check a code in a cart), see POST /v1/storefront/public/validate-discount.
Create
POST /v1/discount-codes
Request body
| Field | Type | Required | Description |
|---|---|---|---|
code |
string (1–50) | yes | The code the buyer types (LAUNCH10, SUMMER25). Case-insensitive at validation time; stored as-typed. Must be unique per workspace. |
description |
string (≤500) | no | Internal label. |
type |
enum | yes | percent — percent off subtotal. fixed — flat amount off. shipping_percent — percent off shipping. shipping_fixed — flat amount off shipping. |
value |
integer | yes | For percent types: 1–100 (treated as percent). For fixed types: smallest currency unit. |
currency |
string (3) | yes | ISO 4217. |
scope |
enum | no | cart (default, applies to whole cart), products (only to specific products), tags (only to products carrying matching tags). |
productIds |
array of strings | conditional | Required when scope: products. |
tagFilter |
array of strings | conditional | Required when scope: tags. Match-any semantics. |
minPurchaseAmount |
integer | no | Smallest-unit. Below this, the code is rejected with MIN_PURCHASE_NOT_MET. |
maxUsesTotal |
integer | no | Cap across all buyers. null = unlimited. |
maxUsesPerCustomer |
integer | no | Cap per buyer. |
startsAt / expiresAt |
ISO 8601 string | no | Validity window. Outside it, the code is rejected with EXPIRED (or NOT_YET_VALID before startsAt). |
active |
boolean | no | Defaults true. |
public |
boolean | no | If true, the code appears in /v1/storefront/public/:merchantSlug/discount-codes so storefronts can list "active promos". |
Response — 201 Created. Returns the discount-code object.
Errors specific to this endpoint
| Status | error.code |
When |
|---|---|---|
409 |
CODE_EXISTS |
The code already exists in this workspace. |
400 |
VALIDATION_ERROR |
Required field missing or shape wrong. |
curl -X POST https://storlaunch.forjio.com/api/v1/discount-codes \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"code":"LAUNCH10",
"type":"percent",
"value":10,
"currency":"IDR",
"minPurchaseAmount":100000,
"maxUsesTotal":500,
"expiresAt":"2026-06-01T00:00:00Z",
"public":true
}'
// Node
const code = await client.discountCodes.create({
code: 'LAUNCH10',
type: 'percent',
value: 10,
currency: 'IDR',
maxUsesTotal: 500,
});
List
GET /v1/discount-codes?limit=50&cursor=…&active=true
Cursor-paginated. Filter by active for show-only-current view.
Retrieve / update / archive
Standard. The code field is not patchable — rename means delete and recreate. DELETE is a soft-archive (sets active: false and excludes from /storefront/public/.../discount-codes).
The discount-code object
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
string | no | disc_ + ULID. |
code |
string | no | The code itself. |
description |
string | yes | Internal label. |
type |
enum | no | percent / fixed / shipping_percent / shipping_fixed. |
value |
integer | no | Percent (1–100) or smallest-unit amount. |
currency |
string | no | ISO 4217. |
scope |
enum | no | cart / products / tags. |
productIds |
array | yes | When scope: products. |
tagFilter |
array | yes | When scope: tags. |
minPurchaseAmount |
integer | yes | Floor for the cart subtotal. |
maxUsesTotal |
integer | yes | Global cap. |
maxUsesPerCustomer |
integer | yes | Per-buyer cap. |
usesTotal |
integer | no | Counter, incremented at successful checkout completion. |
startsAt |
string (ISO 8601) | yes | Earliest valid moment. |
expiresAt |
string (ISO 8601) | yes | Last valid moment. |
active |
boolean | no | If false, validation always fails. |
public |
boolean | no | Listed on the public storefront. |
createdAt / updatedAt |
string | no | Timestamps. |
Validation semantics
When a code is applied at checkout, Ripllo applies these checks in order:
activeistrue.- Now >=
startsAtand now <expiresAt. usesTotal<maxUsesTotal(when set).- Customer's prior uses <
maxUsesPerCustomer(when set). - Cart subtotal ≥
minPurchaseAmount. - For
scope: products, at least one cart line item is inproductIds. - For
scope: tags, at least one cart product carries a tag intagFilter.
Each failure returns a specific reason: INACTIVE, NOT_YET_VALID, EXPIRED, MAX_USES_REACHED, CUSTOMER_LIMIT_REACHED, MIN_PURCHASE_NOT_MET, SCOPE_MISMATCH. The public validate-discount endpoint surfaces these directly.
Events
Storlaunch does not emit dedicated discount-code events. Use the validate endpoint or polling.
If you need usage events ("a code was redeemed"), subscribe on the Ripllo side — Ripllo emits discount_code.applied and discount_code.redeemed. Those are routed to Ripllo-side endpoints, not Storlaunch's.
Next
- Public storefront — the live-validate endpoint used in the cart UI.
- Products — tags here drive
scope: tagslookups.