Resources
Developer docs
Integration guide for POS systems connecting checkout validation, redemption recording, and void handling to SponsorPay.
Authentication
API keys
All POS endpoints use an API key in the request header. No login session is needed. The key is tied to your store — you never send a storeId.
Generate keys in the store portal under API Keys. The raw key is shown only once on creation — store it securely. Send it on every POS request:
x-api-key: YOUR_KEY401 on any call
Means the key is missing, wrong, or deactivated. Check the header name and value first. If a key was regenerated, the old one is immediately invalidated.
Response format
Always check ok, not the HTTP status
Every response includes ok: true on success or ok: false on failure. Redemption endpoints return HTTP 200 even on business failures — an invalid coupon, a wrong phone number, and an empty wallet all come back as HTTP 200 with ok: false. Always check the ok field.
Success
{ "ok": true, ...data }Failure
{ "ok": false, "error": "..." }Decimal / money values are returned as strings ("30.00"). Dates are UTC ISO strings.
The checkout flow
3 steps at the register
Scan the barcode
GET /api/store/barcode/:code
Call this when the cashier scans a barcode. It's read-only — nothing is charged. Returns the coupon type, whether it's active, the POS discount ID to apply, and the discount percentages.
Apply in your POS
Use posDiscountId
Take the posDiscountId from the scan response and use it to auto-apply the matching promotion already configured in your POS system. Your POS handles item and category eligibility.
Record the redemption
POST /api/store/coupon/redeem
After the sale is finalized, call redeem with the sale totals. This commits the redemption, charges the sponsor's wallet, and credits your store wallet. Save the redemption ID in case you need to void.
posDiscountId — the key to automation
When a store creates a Discount Option, they enter the ID of a promotion already configured in their POS (a Clover Promotion, Square Discount, Toast Applied Discount, etc.). That promotion has all item and category rules baked in. When you scan a barcode and get back posDiscountId, auto-apply that promotion in your POS. Your POS handles eligibility. SponsorPay only sees the final dollar amounts you report.
Endpoint
Scan a barcode
GET /api/store/barcode/:code
Call this when the cashier scans a barcode or types in a code. Safe to call multiple times — nothing is charged. Returns the type (coupon or giftCard), whether the item is currently redeemable, and the data your POS needs to apply the discount.
Response — active coupon
{
"ok": true,
"found": true,
"type": "coupon",
"active": true,
"data": {
"couponId": 88,
"code": "ABC123",
"posDiscountId": "CLOVER-PROMO-47",
"totalPercent": 30,
"sponsorPercent": 15,
"storePercent": 15,
"requirePhone": false,
"phoneLast3": null,
"maxAmountDiscountApplies": null,
"maxDiscountThisSale": null,
"sponsorRemaining": null
}
}Response — active gift card
{
"ok": true,
"found": true,
"type": "giftCard",
"active": true,
"data": {
"giftCardId": 55,
"code": "GC-123",
"amount": 25.00,
"requirePhone": false,
"phoneLast3": null
}
}amount is the remaining balance — the maximum redeemable.
Response fields
| Field | Description |
|---|---|
posDiscountId | ID of the matching promotion in your POS. Auto-apply this. null if not configured. |
totalPercent | Total discount % to apply at checkout. |
sponsorPercent / storePercent | How the % is split. For reporting only — apply totalPercent. |
requirePhone | If true, prompt the customer for their phone number before completing the sale. |
phoneLast3 | Last 3 digits of the registered phone. Show in your UI so the customer can confirm it's theirs. null if none on file. |
maxAmountDiscountApplies | Cap on how much of the sale the discount can apply to. Limit your totalAmountDiscountApplies to this. null = no cap. |
maxDiscountThisSale | Cap on total discount dollar amount for this sale. null = no cap. |
sponsorRemaining | Remaining sponsor budget on this coupon. null = no cap. |
Response — coupon not redeemable
{
"ok": true,
"found": true,
"type": "coupon",
"active": false,
"reason": "COUPON_NOT_ACTIVE"
}Reason codes
| Reason | Meaning |
|---|---|
COUPON_NOT_ACTIVE | Already used or voided |
COUPON_REDEMPTION_LIMIT_REACHED | Used the max number of times |
COUPON_AMOUNT_LIMIT_REACHED | Sponsor budget exhausted |
COUPON_SALE_LIMIT_REACHED | Cumulative sale amount limit reached |
COUPON_DISCOUNT_LIMIT_REACHED | Cumulative discount dollar limit reached |
COUPON_STORE_LIMIT_REACHED | Single-use per store; already used here |
DISCOUNT_NOT_ACTIVE | Campaign not active |
DISCOUNT_EXPIRED | Campaign expired |
DISCOUNT_OPTION_INACTIVE | Store's discount option is off or not approved |
DISCOUNT_OPTION_EXPIRED | Store's discount option expired |
SPONSOR_NOT_ACTIVE | Sponsor account inactive |
STORE_INACTIVE | Your store account is inactive |
Endpoint
Record a coupon redemption
POST /api/store/coupon/redeem
Call this after the sale is finalized in your POS. This commits the redemption, charges the sponsor's wallet, and credits your store wallet. Save the redemption.id from the response — you'll need it to void.
Request body
{
"code": "ABC123",
"saleId": "TXN-48291",
"totalSaleAmount": 100.00,
"totalItems": 3,
"totalAmountDiscountApplies": 100.00,
"totalDiscount": 30.00,
"roundedDiscount": false,
"phone": "2125551111",
"storeDiscountAdjustmentPercent": 0,
"storeDiscountAdjustmentReason": null,
"registerId": "REG-1",
"cashierId": "CASHIER-7",
"metadata1": null,
"metadata2": null,
"metadata3": null
}Success response
{
"ok": true,
"redemption": {
"id": 1001,
"status": "COMMITTED",
"saleId": "TXN-48291",
"discountAmount": "30.00",
"sponsorDiscountAmount": "15.00",
"storeDiscountAmount": "15.00",
"createdAt": "2026-05-07T20:02:04.237Z"
},
"totals": {
"discountAmount": "30.00",
"sponsorDiscountAmount": "15.00",
"storeDiscountAmount": "15.00"
}
}Save the redemption ID
Store redemption.id in your POS transaction record. You need it if the cashier voids the sale.
Request fields
| Field | Type | Required | Notes |
|---|---|---|---|
code | string | Yes | The scanned coupon barcode |
saleId | string | Yes | Your POS's unique sale ID. Used to prevent duplicate redemptions. |
totalSaleAmount | number | Yes | Full sale total before discount |
totalItems | integer | Yes | Number of items in the sale |
totalAmountDiscountApplies | number | Yes | The portion of the sale your POS promotion applied the discount to. Do not exceed maxAmountDiscountApplies from the scan. |
totalDiscount | number | Yes | Actual total discount dollar amount your POS applied. Server validates this matches its calculation. |
roundedDiscount | boolean | Yes | true if your POS rounded the discount to a whole dollar. Affects server match tolerance. Default false. |
phone | string | No | 10 digits. Required when requirePhone: true. Must match the registered phone. |
storeDiscountAdjustmentPercent | integer | No | Negative integer only. Reduces the store's % contribution (e.g. -5). Default 0. |
storeDiscountAdjustmentReason | string | No | Required when adjustment is non-zero. Values: "PAID_CC" | "OTHER" |
registerId | string | No | Your terminal/register ID. For reporting. |
cashierId | string | No | Cashier ID. For reporting. |
metadata1–3 | string | No | Free-form reference fields. |
Unknown fields are rejected.
Error responses (ok: false, HTTP 200)
| Error message | Meaning |
|---|---|
Coupon not found. | Barcode not valid at this store |
Coupon is not active. | Coupon already used or voided |
Discount is not active. / Discount is expired. | Campaign issue on sponsor side |
Discount option is not active. / ...expired. / ...not approved. | Store option issue |
Phone is required. | requirePhone was true; phone field not sent |
Phone does not match. | Wrong phone number provided |
Duplicate sale. | saleId already redeemed at this store — usually means it worked first time |
Coupon redemption limit reached. | Too many uses |
Coupon amount limit reached. | Sponsor budget exhausted |
Coupon total sale limit reached. | Cumulative sale cap hit |
Coupon total discount limit reached. | Cumulative discount cap hit |
Coupon has already been used at this store. | Single-use per store |
Discount redemption limit reached. | Campaign-level count cap hit |
Discount amount limit reached. | Campaign-level dollar cap hit |
Total discount does not match. | totalDiscount doesn't match server calculation |
Insufficient funds. | Sponsor wallet is empty |
Endpoint
Void a redemption
POST /api/store/redemption/discount/:id/void
Call this when the cashier voids the sale in your POS. Must be done within 24 hours. The sponsor's wallet is refunded and the store's pending wallet credit is cancelled.
Request
POST /api/store/redemption/discount/1001/void
{ "reason": "Sale voided" }Path param id is the redemption.id from the redeem response.
Success response
{
"ok": true,
"redemption": {
"id": 1001,
"status": "VOIDED",
"voidedAt": "2026-05-07T21:30:00.000Z",
"voidReason": "Sale voided"
}
}Error responses
| Error | Meaning |
|---|---|
Discount redemption not found. | ID doesn't exist or belongs to a different store |
Only committed redemptions can be voided. | Already voided or failed |
Redemption can only be voided within 24 hours. | Void window has closed |
Redemption can no longer be voided. | Wallet credit already settled |
Endpoint
Redeem a gift card
POST /api/store/giftCard/redeem
Gift cards work like coupons but carry a dollar balance instead of a percentage. Scan the gift card barcode first with GET /api/store/barcode/:code to confirm it's active and check the remaining balance, then call this endpoint after the sale is finalized. The redemption amount must not exceed the remaining balance from the scan.
Request body
{
"code": "GC-123",
"amount": 20.00,
"saleId": "TXN-48292",
"phone": "2125551111",
"registerId": "REG-1",
"cashierId": "CASHIER-7"
}Success response
{
"ok": true,
"redemption": {
"id": 901,
"status": "COMMITTED",
"amount": "20",
"saleId": "TXN-48292",
"createdAt": "2026-05-07T20:02:04.237Z"
},
"totals": { "amount": "20" }
}Request fields
| Field | Required | Notes |
|---|---|---|
code | Yes | Gift card code |
amount | Yes | Amount to redeem. Min 0.01. Must not exceed remaining balance from scan. |
saleId | Yes | Unique sale ID for duplicate prevention |
phone | No | 10 digits. Required when requirePhone: true. |
registerId | No | For reporting |
cashierId | No | For reporting |
Unknown fields are rejected.
Error responses
| Error message | Meaning |
|---|---|
Gift card not found. | Code not valid at this store |
Gift card is not active. | Already fully redeemed or voided |
Gift card is expired. | Past expiry date |
Phone is required. / Phone does not match. | Phone verification failed |
Duplicate sale. | saleId already used |
Gift card amount limit reached. | Requested amount exceeds remaining balance |
Insufficient funds. | Sponsor wallet empty |
Retry safety
Handling timeouts
If a redeem request times out with no response, retry with the exact same saleId.
Original succeeded
Retry returns "Duplicate sale." — meaning it worked the first time. Treat this as a successful redemption.
Original truly failed
The retry processes normally as if it's a new request.
Never retry ok: false errors
Business errors (invalid coupon, wrong phone, empty wallet) are deterministic. Retrying them will not change the outcome.
Automation checklist
What your POS handles
Barcode scanned
Call GET /api/store/barcode/:code
Coupon active
Look up posDiscountId → apply that promotion in POS
requirePhone: true
Show phone entry prompt to customer (not cashier)
Eligible items determined
Your POS promotion handles this; report result as totalAmountDiscountApplies
Sale complete
Call POST /api/store/coupon/redeem or POST /api/store/giftCard/redeem
Sale voided
Call POST /api/store/redemption/discount/:id/void
ok: false on redeem
Show error to customer; do not apply discount; do not retry
Need help?
Contact us for API keys and POS setup
For endpoint questions, API key provisioning, or help matching the integration to your specific POS system, reach out and we'll get you set up.
Contact SponsorPay