Skip to main content

List Coupons

List the coupons issued under your merchant, newest first, with cursor pagination.

Endpoint

GET /v1/coupons

Headers

HeaderTypeDescriptionRequired
X-Client-IdStringYour client ID✅ Yes
X-Client-SecretStringYour client secret✅ Yes

Query Parameters

ParameterTypeDescriptionRequired
templateIdString (UUID)Return only coupons issued from this template❌ No
limitIntegerItems per page. Default 15, maximum 100, -1 for unlimited❌ No
nextCursorStringCursor from the previous page's pagination object❌ No

Example Request

curl --location 'https://stage-platform-exlr8-incentives.exlr8now.com/v1/coupons?limit=50' \
--header 'X-Client-Id: YOUR_CLIENT_ID' \
--header 'X-Client-Secret: YOUR_CLIENT_SECRET'

Filter by template

curl --location 'https://stage-platform-exlr8-incentives.exlr8now.com/v1/coupons?templateId=5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9&limit=50' \
--header 'X-Client-Id: YOUR_CLIENT_ID' \
--header 'X-Client-Secret: YOUR_CLIENT_SECRET'

This is the practical way to measure one campaign, rather than paging every coupon the merchant has issued.

Response

Successful Response — 200 OK

{
"success": true,
"data": [
{
"couponId": "7c8d9e0f-1a2b-4c3d-9e4f-5a6b7c8d9e0f",
"merchantId": "b1946ac9-2c3d-4e5f-8a7b-6c5d4e3f2a1b",
"programId": "3c6e0b8a-9c15-4f2b-8d1e-7a5b9c0d2e4f",
"couponTemplateId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"codeHash": "8d7e6f...",
"status": "ACTIVE",
"redemptionCount": 0,
"usageLimitTotal": 1,
"expiresAt": "2026-11-23T11:30:00Z",
"version": 1,
"createdAt": "2026-08-25T11:30:00Z",
"updatedAt": "2026-08-25T11:30:00Z"
}
],
"pagination": {
"nextCursor": "eyJpZCI6IjdjOGQ5ZTBmIn0",
"hasMore": true
}
}

Each element carries the same fields as Get Coupon By ID.

Pagination Fields

FieldTypeDescription
nextCursorStringOpaque cursor for the next page
hasMoreBooleanWhether further pages exist

Paging through results

async function listAllCoupons() {
const coupons = [];
let cursor = null;

do {
const url = new URL(`${baseUrl}/v1/coupons`);
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("nextCursor", cursor);

const res = await fetch(url, {
headers: {
"X-Client-Id": clientId,
"X-Client-Secret": clientSecret,
},
});

const body = await res.json();
coupons.push(...body.data);
cursor = body.pagination.hasMore ? body.pagination.nextCursor : null;
} while (cursor);

return coupons;
}

Treat the cursor as opaque. Results are already scoped to your merchant.

Campaign performance

redemptionCount and status across a campaign's coupons give you redemption rates without any additional endpoint:

const summary = coupons.reduce(
(acc, c) => {
acc.total += 1;
if (c.redemptionCount > 0) acc.redeemed += 1;
if (c.status === "EXPIRED") acc.expired += 1;
return acc;
},
{ total: 0, redeemed: 0, expired: 0 }
);

const redemptionRate = summary.redeemed / summary.total;

:::caution Not a real-time feed This is a snapshot for reporting, not a change stream. Coupons issued or redeemed while you are paging may not appear consistently. For an authoritative view of one coupon, fetch it directly. :::

Error Responses

Error codeHTTPCause
BAD_REQUEST400limit is not an integer, or the cursor is malformed