Coupons
Create coupons and manage their pools of unique codes for emails.
Create a coupon
Create a coupon. Put it in an email with {{coupon "key"}}: when the email is rendered, each recipient gets a code. Requires an API key with the full scope.
- A unique coupon holds a pool of single-use codes. Each recipient is assigned the oldest free code, one per campaign or automation, and gets the same code again whenever that email is rendered for them. Upload your shop’s codes here or with Add coupon codes, or generate codes in Emailit and import them into your shop.
- A static coupon has one code,
static_code, for everyone.
When an order lists a code in coupon_codes, the code is marked redeemed. See Products, coupons and the cart.
/couponsRequest Body
keystringRequiredThe name you use in content, unique in the workspace. 1 to 64 lowercase letters, digits, - or _, starting with a letter or digit. Uppercase letters are lowercased.
namestringRequiredCoupon name shown in the dashboard, up to 191 characters.
descriptionstring | nullNote for your team. Longer text is cut to 1,000 characters.
kindstringunique (default) or static.
static_codestring | nullUp to 191 characters. Required for a static coupon: the code everyone gets. For a unique coupon, the text recipients get when the pool is empty or the coupon has expired. Without it, they get an empty string.
discount_labelstring | nullShort description of the discount, such as 10% off. Longer text is cut to 191 characters.
expires_atstring | nullISO 8601 date-time. After it, recipients of a unique coupon get static_code instead of a code, also in emails rendered again. Emailit doesn’t enforce the expiry in your shop.
codesstring[] | stringCodes to add to the pool: an array, or text with one code per line. For CSV text, the first column is read and a header such as code or coupon_code is skipped. A code is 2 to 64 letters, digits, -, _ or ., starting with a letter or digit. Invalid codes are left out and duplicates are added once. Up to 10,000 codes.
Returns
201 Created with the coupon object. Returns 422 when a field is invalid or another coupon already has the key.
{
"object": "coupon",
"id": "cpn_3sMKUvFbx63T9hhDoNea7fmo1fQ",
"key": "welcome-10",
"name": "Welcome 10% off",
"description": "First-order discount for new subscribers.",
"kind": "unique",
"static_code": "WELCOME10",
"discount_label": "10% off",
"expires_at": "2026-12-31T23:59:59.000Z",
"codes_total": 3,
"codes_available": 3,
"codes_assigned": 0,
"codes_redeemed": 0,
"created_at": "2026-10-01T09:00:12.304Z",
"updated_at": "2026-10-01T09:00:12.304Z"
}{
"message": "Validation failed. key: A coupon with this key already exists.",
"errors": {
"key": [
"A coupon with this key already exists."
]
}
}Retrieve a coupon
Retrieve a coupon by its ID or its key, with counts of its codes. Requires an API key with the full scope.
/coupons/{id}Path Parameters
idstringRequiredThe coupon ID (cpn_...) or the coupon’s key.
The coupon object
objectstringAlways coupon.
idstringCoupon ID (cpn_...).
keystringThe name you use in email content: {{coupon "welcome-10"}}. See Products, coupons and the cart.
namestringCoupon name shown in the dashboard.
descriptionstring | nullNote for your team.
kindstring-
unique: a pool of single-use codes. Each recipient gets their own code, one per campaign or automation, and the same code again when the email is rendered again. -
static: one code,static_code, for everyone.
static_codestring | nullThe shared code of a static coupon. For a unique coupon, the text recipients get when no code is left or the coupon has expired. Without it, they get an empty string.
discount_labelstring | nullShort description of the discount, such as 10% off.
expires_atstring | nullAfter this time, recipients of a unique coupon get static_code instead of a code.
codes_totalintegerCodes in the pool.
codes_availableintegerCodes that can still be handed out: not assigned to a contact and not used in an order.
codes_assignedintegerCodes assigned to a contact, including the ones already redeemed.
codes_redeemedintegerCodes used in an order (coupon_codes).
created_at, updated_atstringWhen the coupon was created and last changed, in ISO 8601 UTC.
{
"object": "coupon",
"id": "cpn_3sMKUvFbx63T9hhDoNea7fmo1fQ",
"key": "welcome-10",
"name": "Welcome 10% off",
"description": "First-order discount for new subscribers.",
"kind": "unique",
"static_code": "WELCOME10",
"discount_label": "10% off",
"expires_at": "2026-12-31T23:59:59.000Z",
"codes_total": 5000,
"codes_available": 3812,
"codes_assigned": 1188,
"codes_redeemed": 143,
"created_at": "2026-10-01T09:00:12.304Z",
"updated_at": "2026-10-01T09:00:12.304Z"
}{
"error": "Coupon not found",
"message": "Coupon not found."
}Update a coupon
Update a coupon. Only the fields you send change. To add codes, use Add coupon codes: codes in this request is ignored. Requires an API key with the full scope.
If you change the key, update the {{coupon "key"}} tags in your emails too: Emailit looks coupons up by the key in the content.
/coupons/{id}Path Parameters
idstringRequiredThe coupon ID (cpn_...) or the coupon’s current key.
Request Body
Any of key, name, description, kind, static_code, discount_label and expires_at, with the rules of Create a coupon. Send null for description, static_code, discount_label or expires_at to clear it. A coupon that is or becomes static must keep a static_code.
Returns
200 OK with the updated coupon object. Returns 404 if no coupon matches id and 422 when a field is invalid or the new key is taken.
{
"object": "coupon",
"id": "cpn_3sMKUvFbx63T9hhDoNea7fmo1fQ",
"key": "welcome-10",
"name": "Welcome 10% off",
"description": "First-order discount for new subscribers.",
"kind": "unique",
"static_code": "WELCOME10",
"discount_label": "10% off",
"expires_at": "2027-01-31T23:59:59.000Z",
"codes_total": 5000,
"codes_available": 3812,
"codes_assigned": 1188,
"codes_redeemed": 143,
"created_at": "2026-10-01T09:00:12.304Z",
"updated_at": "2026-10-09T15:47:20.061Z"
}{
"error": "Coupon not found",
"message": "Coupon not found."
}{
"message": "Validation failed. static_code: A static coupon needs its code.",
"errors": {
"static_code": [
"A static coupon needs its code."
]
}
}List coupons
List the coupons in your workspace, newest first, with counts of their codes. Requires an API key with the full scope.
/couponsQuery Parameters
pageintegerPage number for pagination (minimum: 1, default: 1).
limitintegerNumber of coupons to return (minimum: 1, maximum: 100, default: 25).
searchstringCase-insensitive match on the coupon name or key.
matchstringall (default) requires every filter. or matches any filter. See Filtering.
orderstringSort key for this list. See the sort keys below.
directionstringasc or desc.
Filters and sort
List filters are one layer of key.condition=value query parameters. See Filtering for match, order, direction and the condition list per type.
Filter keys
| Key | Type | Conditions | Notes |
|---|---|---|---|
key | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
name | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
kind | enum | exact, not_exact | Values: unique, static |
expires_at | date | exact, before, after, empty, not_empty | |
created_at | date | exact, before, after, empty, not_empty |
Sort keys
Pass order as one of these keys and direction as asc or desc: key, name, kind, expires_at, created_at
Returns
A page of coupon objects in data, with total_records, next_page_url and previous_page_url. The page URLs carry only page and limit, so add your search and filters again when you follow them.
{
"data": [
{
"object": "coupon",
"id": "cpn_3sMKUvFbx63T9hhDoNea7fmo1fQ",
"key": "welcome-10",
"name": "Welcome 10% off",
"description": "First-order discount for new subscribers.",
"kind": "unique",
"static_code": "WELCOME10",
"discount_label": "10% off",
"expires_at": "2026-12-31T23:59:59.000Z",
"codes_total": 5000,
"codes_available": 3812,
"codes_assigned": 1188,
"codes_redeemed": 143,
"created_at": "2026-10-01T09:00:12.304Z",
"updated_at": "2026-10-01T09:00:12.304Z"
}
],
"total_records": 1,
"next_page_url": null,
"previous_page_url": null
}Delete a coupon
Delete a coupon together with all of its codes, including assigned and redeemed ones. Orders keep the codes in their coupon_codes. Requires an API key with the full scope.
/coupons/{id}Path Parameters
idstringRequiredThe coupon ID (cpn_...) or the coupon’s key.
{
"object": "coupon",
"id": "cpn_3sMKUvFbx63T9hhDoNea7fmo1fQ",
"deleted": true
}{
"error": "Coupon not found",
"message": "Coupon not found."
}List coupon codes
List the codes of a coupon, oldest first: the order in which they’re handed out. Use format=csv to export them, for example to import generated codes into your shop. Requires an API key with the full scope.
/coupons/{id}/codesPath Parameters
idstringRequiredThe coupon ID (cpn_...) or the coupon’s key.
Query Parameters
pageintegerPage number for pagination (minimum: 1, default: 1).
limitintegerNumber of codes to return (minimum: 1, maximum: 100, default: 50).
statusstring-
available: not assigned to a contact and not used in an order, so it can still be handed out. -
assigned: assigned to a contact and not redeemed. -
redeemed: used in an order.
formatstringjson (default) or csv. CSV returns every code that matches status in one file, without paging, with the columns code, status, email, assigned_at and redeemed_at. key.condition filters and sorting don’t apply to the CSV.
matchstringall (default) requires every filter. or matches any filter. See Filtering.
orderstringSort key for this list. See the sort keys below.
directionstringasc or desc.
Filters and sort
List filters are one layer of key.condition=value query parameters. See Filtering for match, order, direction and the condition list per type.
Filter keys
| Key | Type | Conditions | Notes |
|---|---|---|---|
code | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
email | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
assigned_at | date | exact, before, after, empty, not_empty | |
redeemed_at | date | exact, before, after, empty, not_empty | |
created_at | date | exact, before, after, empty, not_empty |
Sort keys
Pass order as one of these keys and direction as asc or desc: code, email, assigned_at, redeemed_at, created_at
Returns
A page of coupon codes in data, with total_records, next_page_url and previous_page_url. Each code has:
objectstringAlways coupon_code.
idstringCode ID (cpc_...).
codestringThe code.
contact_id, emailstring | nullThe contact the code is assigned to, or null.
source_idstring | nullThe campaign (cmp_...) or automation (aut_...) whose email got the code.
assigned_atstring | nullWhen the code was assigned.
redeemed_at, redeemed_order_idstring | nullWhen the code was redeemed and by which order (ord_...).
created_atstringWhen the code was added to the pool.
{
"data": [
{
"object": "coupon_code",
"id": "cpc_3s6oCjlRbLFujYObEAcuiZmGKqu",
"code": "WELCOME-H4TZ8RCN",
"contact_id": "con_3MjLVacsYA6keJucAbOs3o3XhyA",
"email": "grace@example.com",
"source_id": "cmp_3zNrBaNjAoPK1l0IL2GQ3pDkubN",
"assigned_at": "2026-10-08T09:00:41.773Z",
"redeemed_at": null,
"redeemed_order_id": null,
"created_at": "2026-10-01T09:00:12.304Z"
},
{
"object": "coupon_code",
"id": "cpc_3u9TrZTopqOgaqT9FcZFareUFGj",
"code": "WELCOME-P2WD6YFE",
"contact_id": "con_3EwrXnu9OaQWlfI1AyPUUEoYjQs",
"email": "ada@example.com",
"source_id": "cmp_3zNrBaNjAoPK1l0IL2GQ3pDkubN",
"assigned_at": "2026-10-08T09:00:41.802Z",
"redeemed_at": null,
"redeemed_order_id": null,
"created_at": "2026-10-01T09:00:12.304Z"
}
],
"total_records": 1045,
"next_page_url": "/v2/coupons/cpn_3sMKUvFbx63T9hhDoNea7fmo1fQ/codes?page=2&limit=2",
"previous_page_url": null
}code,status,email,assigned_at,redeemed_at
WELCOME-7KQ2M9XA,redeemed,ada@example.com,2026-10-08T09:00:41.751Z,2026-10-08T14:32:10.000Z
WELCOME-H4TZ8RCN,assigned,grace@example.com,2026-10-08T09:00:41.773Z,
WELCOME-QX3B9NMV,available,,,{
"error": "Coupon not found",
"message": "Coupon not found."
}Add coupon codes
Add codes to the pool of a unique coupon. Either upload codes that exist in your shop with codes, or let Emailit create random codes with generate and then import them into your shop with List coupon codes (format=csv). Codes already in the pool are skipped. Static coupons have one code and return 422. Requires an API key with the full scope.
/coupons/{id}/codesPath Parameters
idstringRequiredThe coupon ID (cpn_...) or the coupon’s key.
Request Body
Send codes or generate. When you send both, generate is used.
codesstring[] | stringUp to 10,000 codes: an array, or text with one code per line. For CSV text, the first column is read and a header such as code or coupon_code is skipped. A code is 2 to 64 letters, digits, -, _ or ., starting with a letter or digit. Codes that don’t match are returned in invalid.
generateobjectCreate random codes from uppercase letters and digits, without 0, O, 1 and I, which are easy to mix up.
-
count(integer, required): 1 to 10,000 codes. -
prefix(string): up to 20 letters, digits,-or_, put in front of every code. For exampleWELCOME-. -
length(integer): random characters after the prefix, from 4 to 32. Defaults to 8. Values outside the range are clamped.
Returns
addedintegerCodes added to the pool.
skippedintegerCodes that were already in the pool.
invalidstring[]Uploaded values that aren’t valid codes and weren’t added.
couponobjectThe coupon object with the new counts.
Returns 404 if no coupon matches id, and 422 for a static coupon, when no valid code was sent, when more than 10,000 codes were sent, or when generate.count or generate.prefix is out of range.
{
"added": 5000,
"skipped": 0,
"invalid": [],
"coupon": {
"object": "coupon",
"id": "cpn_3sMKUvFbx63T9hhDoNea7fmo1fQ",
"key": "welcome-10",
"name": "Welcome 10% off",
"description": "First-order discount for new subscribers.",
"kind": "unique",
"static_code": "WELCOME10",
"discount_label": "10% off",
"expires_at": "2026-12-31T23:59:59.000Z",
"codes_total": 5003,
"codes_available": 5003,
"codes_assigned": 0,
"codes_redeemed": 0,
"created_at": "2026-10-01T09:00:12.304Z",
"updated_at": "2026-10-01T09:00:12.304Z"
}
}{
"message": "Validation failed. codes: Static coupons have one code; set static_code instead.",
"errors": {
"codes": [
"Static coupons have one code; set static_code instead."
]
}
}