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:(If required) Get an OTP session
Most companies enable
require_otp_for_redemption and require_otp_for_coupon_usage by default — without a valid X-OTP-Session header, the redeem and validate calls below return 403 otp-required. See OTP Flow for the full request → verify dance.Redeem Reward
Create a coupon by redeeming a reward for a specific customer. Returns the coupon’s KSUID
id and the customer-facing CZ- code.List Coupons
View the customer’s coupons, filtered by status. Use this to find the KSUID
id you’ll need for validation.Two identifiers, one coupon
Every coupon carries two identifiers:| Identifier | Format | Where it comes from |
|---|---|---|
id | KSUID (e.g. 16a33f27fbbc1801d63d56d2027) | Returned by GET /coupons/issued/ and POST /rewards/{id}/redeem/. Stable, opaque, never shown to customers. |
coupon_code | CZ-XXXXXXXX (e.g. CZ-914F15F3) | Customer-facing — printed on receipts, shown in wallet passes, spoken by the cashier. |
{coupon_id} path parameter on the validate endpoint (case-insensitive on the CZ-/CP- form). When a customer hands you a receipt at the POS, you can validate directly with the printed code — no extra lookup needed.
To go from CZ- code → full coupon record (for example, to check status before validating), use the dedicated lookup:
data array if not found) with its id, customer_id, status, valid_until, etc.
Step 1: Browse Available Rewards
Query the reward catalog to see what rewards are available for a customer. Thecustomer_id parameter enriches each reward with the customer’s redemption status.
| Parameter | Type | Description |
|---|---|---|
customer_id | string | Customer KSUID — enriches response with already_redeemed flag |
redeemable | boolean | If true, only returns rewards the customer can currently afford |
| Field | Type | Description |
|---|---|---|
id | string | Reward KSUID (use in redeem URL) |
name | string | Reward display name |
points_required | integer | Points needed to redeem |
is_available | boolean | Whether the reward is currently active |
already_redeemed | boolean | Whether this customer already redeemed this reward |
validity_days | integer | Days the coupon remains valid after redemption |
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.| Field | Type | Default | Description |
|---|---|---|---|
use_z_tokens | boolean | false | If true, Z$ tokens cover any points gap. See Z$ Tokens guide |
| Field | Type | Description |
|---|---|---|
customer_id | string | Customer KSUID |
coupon_code | string | Unique coupon code (format: CZ-XXXXXXXX). Display this to the customer for in-store redemption. |
points_used | integer | Points deducted from the customer’s balance (0 for marketing rewards) |
z_tokens_used | string | Z$ tokens consumed by this redemption — "0.000000" when use_z_tokens=false or the reward is purely points-based |
new_balance | integer | Customer’s remaining points balance after the redemption |
new_z_balance | string | Customer’s current Z$ balance after any token consumption. Always returned (not conditional on use_z_tokens) — partners use it to surface the live balance back to the customer. |
valid_until | string | ISO 8601 expiration date (server-computed from reward.validity_days) |
created_at | string | ISO 8601 coupon creation timestamp |
Redemption Errors
| Status | Type | When |
|---|---|---|
| 403 | otp-required | OTP policy is on but X-OTP-Session was not provided. See OTP Flow. |
| 404 | not-found | Customer or reward not found in your company |
| 422 | validation-error | Customer doesn’t have enough points — detail field explains which constraint failed |
| 422 | validation-error | Marketing reward already redeemed by this customer (free rewards have a 1-per-customer limit). errors[].detail = "Prêmio gratuito já resgatado por este cliente" |
Step 3: List Customer’s Coupons
Retrieve all coupons for a customer, optionally filtered by status.| Parameter | Type | Description |
|---|---|---|
customer_id | string | Filter to one customer (KSUID). Omit to list all issued coupons for the company |
status | string | Filter by status: active, used, expired, cancelled |
origin_type | string | Filter by provenance: loyalty_reward (redeemed with points) or marketing (claimed without points) |
reward_id | string | Filter to coupons issued from one reward |
code | string | Exact coupon_code lookup (case-insensitive) |
| Field | Type | Description |
|---|---|---|
id | string | Coupon KSUID (use in validate URL) |
coupon_code | string | Unique coupon code (CZ-XXXXXXXX) |
status | string | active, used, expired, or cancelled |
origin_type | string | loyalty_reward (redeemed by spending points) or marketing (claimed without points) |
usage_date | string | ISO 8601 timestamp when the coupon was used/validated (null while still active) |
remaining_usages | integer | How many times the coupon can still be used |
total_usages_allowed | integer | Total uses allowed (1 for single-use coupons) |
valid_until | string | ISO 8601 expiration date |
reward_name | string | Name of the redeemed reward |
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).| Field | Type | Description |
|---|---|---|
customer_id | string | Customer KSUID (matches the {id} in the URL path). |
coupon_code | string | The validated coupon code. |
status | string | "validated" — confirms the coupon was successfully used. |
remaining_usages | integer | Remaining uses after validation. |
total_usages_allowed | integer | Total uses allowed for this coupon. |
validated_at | string | ISO 8601 timestamp of validation. |
new_balance | integer | Customer’s points balance after validation. Validate itself debits 0 pts (points were spent at redemption time); this is the current balance, returned so partners don’t need a follow-up GET /customers/{id}/points/. |
new_z_balance | string | Customer’s current Z$ balance after validation (stringified Decimal with 6 places — same format as RewardRedeemResponse). |
Validation Errors
| Status | Type | When |
|---|---|---|
| 403 | otp-required | OTP verification required but X-OTP-Session not provided. See OTP Flow. |
| 404 | not-found | Coupon not found under this customer. Confirm: (a) the coupon belongs to the customer_id in the path, (b) the CZ-/CP- code (if you used one) is spelled correctly. |
| 409 | conflict | Coupon already fully consumed. detail: "Cupom já Used" (or "Expired" / "Cancelled"). Re-validating an already-used coupon is safe and idempotent — it returns 409, never double-applies the side effect. |
POS Quick Lookup — validate by receipt code
When a customer hands you a printed coupon code at the point of sale, you have two equivalent ways to validate it. Use whichever fits your flow.If you don’t know the
customer_id ahead of time (cashier scanning a printed code from a customer they don’t recognize), the lookup-first path tells you both customer_id and status so you can decide whether to proceed and which OTP session to attach.Multi-Use Coupons
Some rewards create coupons that can be used more than once. Theremaining_usages and total_usages_allowed fields track usage:
| Coupon Type | total_usages_allowed | Behavior |
|---|---|---|
| Single-use | 1 | After one validate call, remaining_usages becomes 0 and status becomes used |
| Multi-use | 2+ | Each validate call decrements remaining_usages. Status stays active until the last use, then becomes used |
Multi-use coupon example
Multi-use coupon example
A “3x Free Coffee” reward creates a coupon with
total_usages_allowed: 3:Coupon Expiration
Coupons have avalid_until date set at redemption time, calculated as:
- Auto-expires: a validate call on an expired coupon returns
409 Conflict - Check before displaying: partners should compare
valid_untilwith the current time before showing a coupon to the customer - Expired status: coupons past their
valid_untildate will showstatus: "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).
Reward & Coupon Stats
Aggregate how many coupons were issued (rewards redeemed) and how many were used — company-wide or per reward.| Parameter | Description |
|---|---|
reward_id | Per-reward stats — issued/used for one reward |
origin_type | loyalty_reward or marketing |
customer_id | Restrict to one customer |
code | Exact coupon code |
status filter is ignored here so the breakdown always spans every status.
Response
| Field | Description |
|---|---|
total_issued | Rewards redeemed = coupons issued |
used | Coupons validated/consumed |
usage_rate | used / total_issued * 100 |
by_status | Count per status |
by_origin | Count per provenance (loyalty vs marketing) |
Full Example: Complete Coupon Flow
End-to-end flow validated in production 2026-05-26: request OTP → verify → browse rewards → redeem → list coupons → validate. Skip the OTP block if your integration’s policy disablesrequire_otp_for_redemption and require_otp_for_coupon_usage.
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