Skip to main content
Coupons are the core reward delivery mechanism in Zupy. When a customer redeems a reward, a coupon is created with a unique code (CZ-XXXXXXXX). The partner can then list, display, and validate (use) that coupon at the point of sale.
Prerequisites: Your API key (zupy_pk_*) and familiarity with the Authentication page. If your integration requires OTP for redemption or coupon usage, see the OTP Flow guide first.

Coupon Lifecycle Overview

Every coupon follows this flow:
Browse Rewards → Redeem Reward → Coupon Created (CZ-XXXXXXXX)

                  List Customer Coupons (active/used/expired)

                  Validate Coupon (atomically mark as used)
1

Browse Available Rewards

Query the reward catalog to see what the customer can redeem.
2

Redeem Reward

Create a coupon by redeeming a reward for a specific customer.
3

List Coupons

View the customer’s coupons, filtered by status.
4

Validate (Use) Coupon

Atomically mark the coupon as used at the point of sale.

Step 1: Browse Available Rewards

Query the reward catalog to see what rewards are available for a customer. The customer_id parameter enriches each reward with the customer’s redemption status.
GET /api/v2/rewards/?customer_id={id}
Key query parameters:
ParameterTypeDescription
customer_idstringCustomer KSUID — enriches response with already_redeemed flag
redeemablebooleanIf true, only returns rewards the customer can currently afford
Response fields:
FieldTypeDescription
idstringReward KSUID (use in redeem URL)
namestringReward display name
points_requiredintegerPoints needed to redeem
is_availablebooleanWhether the reward is currently active
already_redeemedbooleanWhether this customer already redeemed this reward
validity_daysintegerDays the coupon remains valid after redemption
curl -X GET "https://api.zupy.com/api/v2/rewards/?customer_id=2awTHloSJX7kGGprFerOOsvABcd" \
  -H "X-API-Key: zupy_pk_your_api_key_here"
Example response:
{
  "data": [
    {
      "id": "2bxRKmpWJY8lHHqsGfsQQtwCDef",
      "name": "Free Dessert",
      "points_required": 200,
      "is_available": true,
      "already_redeemed": false,
      "validity_days": 30
    },
    {
      "id": "2bxRKnqXKZ9mIIrtHgtRRuxEFgh",
      "name": "10% Off Next Order",
      "points_required": 500,
      "is_available": true,
      "already_redeemed": true,
      "validity_days": 15
    }
  ],
  "meta": {}
}

Step 2: Redeem Reward (Create Coupon)

Redeem a reward for a customer. This creates a coupon with a unique code and deducts points from the customer’s balance.
POST /api/v2/customers/{id}/rewards/{reward_id}/redeem/
Request body:
FieldTypeDefaultDescription
use_z_tokensbooleanfalseIf true, Ztokenscoveranypointsgap.See[Z tokens cover any points gap. See [Z Tokens guide](/guides/z-dollar-tokens)
Response fields:
FieldTypeDescription
customer_idstringCustomer KSUID
coupon_codestringUnique coupon code (format: CZ-XXXXXXXX)
points_usedintegerPoints deducted from balance
z_tokens_usedstringZ$ tokens used (null if use_z_tokens: false)
new_balanceintegerCustomer’s remaining points balance
new_z_balancestringCustomer’s remaining Z$ balance (null if use_z_tokens: false)
valid_untilstringISO 8601 expiration date
created_atstringISO 8601 coupon creation timestamp
curl -X POST "https://api.zupy.com/api/v2/customers/2awTHloSJX7kGGprFerOOsvABcd/rewards/2bxRKmpWJY8lHHqsGfsQQtwCDef/redeem/" \
  -H "X-API-Key: zupy_pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"use_z_tokens": false}'
Example response:
{
  "data": {
    "customer_id": "2awTHloSJX7kGGprFerOOsvABcd",
    "coupon_code": "CZ-A1B2C3D4",
    "points_used": 200,
    "z_tokens_used": "0.000000",
    "new_balance": 350,
    "new_z_balance": null,
    "valid_until": "2026-04-25T00:00:00Z",
    "created_at": "2026-03-25T14:30:00Z"
  },
  "meta": {}
}
If your integration’s OTP policy requires verification for redemption, include the X-OTP-Session header. See the OTP Flow guide.

Redemption Errors

StatusTypeWhen
403otp-requiredOTP verification required but X-OTP-Session not provided
404not-foundCustomer or reward not found
422validation-errorCustomer doesn’t have enough points, or reward already redeemed by this customer

Step 3: List Customer’s Coupons

Retrieve all coupons for a customer, optionally filtered by status.
GET /api/v2/rewards/coupons/?customer_id={id}
Key query parameters:
ParameterTypeDescription
customer_idstringRequired. Customer KSUID
statusstringFilter by status: active, used, expired, cancelled
Response fields:
FieldTypeDescription
idstringCoupon KSUID (use in validate URL)
coupon_codestringUnique coupon code (CZ-XXXXXXXX)
statusstringactive, used, expired, or cancelled
remaining_usagesintegerHow many times the coupon can still be used
total_usages_allowedintegerTotal uses allowed (1 for single-use coupons)
valid_untilstringISO 8601 expiration date
reward_namestringName of the redeemed reward
# All coupons for a customer
curl -X GET "https://api.zupy.com/api/v2/rewards/coupons/?customer_id=2awTHloSJX7kGGprFerOOsvABcd" \
  -H "X-API-Key: zupy_pk_your_api_key_here"

# Only active coupons
curl -X GET "https://api.zupy.com/api/v2/rewards/coupons/?customer_id=2awTHloSJX7kGGprFerOOsvABcd&status=active" \
  -H "X-API-Key: zupy_pk_your_api_key_here"
Example response:
{
  "data": [
    {
      "id": "2cxSLnqYLZ0nJJsuIhuSSvyFGij",
      "coupon_code": "CZ-A1B2C3D4",
      "status": "active",
      "remaining_usages": 1,
      "total_usages_allowed": 1,
      "valid_until": "2026-04-25T00:00:00Z",
      "reward_name": "Free Dessert"
    },
    {
      "id": "2cxSLoRZMA1oKKtvJivTTwzGHjk",
      "coupon_code": "CZ-E5F6G7H8",
      "status": "used",
      "remaining_usages": 0,
      "total_usages_allowed": 1,
      "valid_until": "2026-04-20T00:00:00Z",
      "reward_name": "10% Off Next Order"
    }
  ],
  "meta": {}
}

Step 4: Validate (Use) Coupon

Validate a coupon to mark it as used. This is an atomic operation — concurrent validate calls are race-safe (uses database-level locking).
POST /api/v2/customers/{id}/coupons/{coupon_id}/validate/
Send an empty POST body (no JSON needed). Response fields:
FieldTypeDescription
coupon_codestringThe validated coupon code
statusstring"validated" — confirms the coupon was successfully used
remaining_usagesintegerRemaining uses after validation
total_usages_allowedintegerTotal uses allowed for this coupon
validated_atstringISO 8601 timestamp of validation
This is an atomic operation. The backend uses select_for_update() to prevent race conditions. If two concurrent validate calls hit the same coupon, only one will succeed — the other returns 409 Conflict.
curl -X POST "https://api.zupy.com/api/v2/customers/2awTHloSJX7kGGprFerOOsvABcd/coupons/2cxSLnqYLZ0nJJsuIhuSSvyFGij/validate/" \
  -H "X-API-Key: zupy_pk_your_api_key_here"
Example response:
{
  "data": {
    "coupon_code": "CZ-A1B2C3D4",
    "status": "validated",
    "remaining_usages": 0,
    "total_usages_allowed": 1,
    "validated_at": "2026-03-25T14:30:00Z"
  },
  "meta": {}
}
If your integration’s OTP policy requires verification for coupon usage, include the X-OTP-Session header. See the OTP Flow guide.

Validation Errors

StatusTypeWhen
403otp-requiredOTP verification required but X-OTP-Session not provided
404not-foundCoupon not found or doesn’t belong to this customer
409conflictCoupon already fully used or expired

Multi-Use Coupons

Some rewards create coupons that can be used more than once. The remaining_usages and total_usages_allowed fields track usage:
Coupon Typetotal_usages_allowedBehavior
Single-use1After one validate call, remaining_usages becomes 0 and status becomes used
Multi-use2+Each validate call decrements remaining_usages. Status stays active until the last use, then becomes used
A “3x Free Coffee” reward creates a coupon with total_usages_allowed: 3:
// After redemption
{ "remaining_usages": 3, "total_usages_allowed": 3, "status": "active" }

// After 1st validate
{ "remaining_usages": 2, "total_usages_allowed": 3, "status": "active" }

// After 2nd validate
{ "remaining_usages": 1, "total_usages_allowed": 3, "status": "active" }

// After 3rd validate (final use)
{ "remaining_usages": 0, "total_usages_allowed": 3, "status": "used" }

Coupon Expiration

Coupons have a valid_until date set at redemption time, calculated as:
valid_until = redemption_time + reward.validity_days
  • Auto-expires: a validate call on an expired coupon returns 409 Conflict
  • Check before displaying: partners should compare valid_until with the current time before showing a coupon to the customer
  • Expired status: coupons past their valid_until date will show status: "expired" in list responses
Expired coupons cannot be reactivated. The customer must redeem the reward again to get a new coupon (if the reward allows re-redemption).

Full Example: Complete Coupon Flow

End-to-end flow: browse rewards, redeem, list coupons, and validate.
import requests

BASE_URL = "https://api.zupy.com/api/v2"
HEADERS = {"X-API-Key": "zupy_pk_your_api_key_here"}
customer_id = "2awTHloSJX7kGGprFerOOsvABcd"

# Step 1: Browse rewards the customer can afford
response = requests.get(
    f"{BASE_URL}/rewards/",
    params={"customer_id": customer_id, "redeemable": True},
    headers=HEADERS,
)
rewards = response.json()["data"]
print(f"Found {len(rewards)} redeemable rewards")

# Step 2: Redeem the first available reward
reward_id = rewards[0]["id"]
response = requests.post(
    f"{BASE_URL}/customers/{customer_id}/rewards/{reward_id}/redeem/",
    json={"use_z_tokens": False},
    headers=HEADERS,
)
coupon = response.json()["data"]
print(f"Coupon created: {coupon['coupon_code']}, valid until {coupon['valid_until']}")

# Step 3: List active coupons
response = requests.get(
    f"{BASE_URL}/rewards/coupons/",
    params={"customer_id": customer_id, "status": "active"},
    headers=HEADERS,
)
active_coupons = response.json()["data"]
print(f"{len(active_coupons)} active coupons")

# Step 4: Validate the coupon at the point of sale
coupon_id = active_coupons[0]["id"]
response = requests.post(
    f"{BASE_URL}/customers/{customer_id}/coupons/{coupon_id}/validate/",
    headers=HEADERS,
)
result = response.json()["data"]
print(f"Coupon {result['coupon_code']} used at {result['validated_at']}")

Next Steps

Z$ Tokens

Learn how Z$ tokens work and how they can cover points gaps in reward redemption

OTP Flow

Set up customer identity verification for sensitive operations

API Reference

Browse all endpoints with request/response schemas