Redeeming the code
The redemption API answers two questions at the point of sale: whether the code is still valid and the final consumption. It works the same for coupons and giftcards. For giftcards, the amount is not looked up here: you already received it when the purchase was registered.
Environments
| Environment | Host |
|---|---|
| Production | https://n1-loyalty-core-redemption-api.n1co.com |
| Testing | https://n1-loyalty-core-redemption-api.n1co.dev |
Every request to /api/v1/* requires the X-Api-Key header with the key n1co gives
you. Each environment has its own.
Endpoints
| Endpoint | What it does |
|---|---|
POST /api/v1/coupons/validate | Checks that the code is usable, without consuming it. |
POST /api/v1/coupons/redeem | Consumes it for good. |
Calling redeem twice with the same code does not deduct it twice: the second call
returns a rejection. Retrying after a network error is safe.
Request
POST /api/v1/coupons/validate
X-Api-Key: <your-key>
Content-Type: application/json
{ "code": "HPY-SOL-073" }
Response
Both endpoints return HTTP 200 whenever the request was processed, even when the code was rejected. A rejection is a business outcome, not a transport error.
// Accepted
{ "success": true, "rejectionCode": null, "message": "Valid coupon" }
// Rejected
{ "success": false, "rejectionCode": "COUPON_EXPIRED", "message": "The coupon expired on..." }
rejectionCode, not on messagerejectionCode is a stable, locale-independent identifier. message is English text
meant for diagnostics only; if you need to show something to the cashier in another
language, translate it on your side from the code.
Rejection codes
rejectionCode | What happened |
|---|---|
CODE_NOT_FOUND | The code does not exist. Usually a typing error. |
COUPON_EXPIRED | The code expired. |
COUPON_LIMIT_REACHED | It was already redeemed. |
COUPON_NOT_YET_ACTIVE | Its validity has not started yet. |
CODE_NOT_IN_TRIGGERED_CAMPAIGN | The code does not belong to your business. |
COUPON_REJECTED_BY_CONDITION | A promotion condition blocked it. |
UNKNOWN_REASON | Unrecognised reason. Treat it as invalid. |
Technical errors
These do use semantic HTTP status codes and a different body:
{ "error": { "code": "UNAUTHORIZED", "message": "Unauthorized" } }
| HTTP | error.code | When |
|---|---|---|
| 400 | INVALID_REQUEST | code is missing or empty. |
| 401 | UNAUTHORIZED | X-Api-Key is missing or invalid. |
| 415 | UNSUPPORTED_MEDIA_TYPE | Content-Type is not application/json. |
| 502 | UPSTREAM_ERROR | Temporary service failure. |
| 503 | SERVICE_UNAVAILABLE | Service unavailable. |
These are temporary failures, not rejections. Retry: consumption is idempotent.
The flow at the register
- The cashier types or scans the code.
- Your system looks it up in your local database, using what you received with the purchase.
- You call
validateto confirm validity. - Depending on the product:
- Coupon: you hand over the promotion it stands for.
- Giftcard: you select it as the means of payment and apply the amount you already stored. The cashier never types it, and you charge the remainder by another method if needed.
- You call
redeemwhen confirming the sale.
redeem is rejected, do not close the sale with that codeA rejection between steps 3 and 5 means the code was used in the meantime. Reverse the promotion or the discount and ask for another means of payment.
When to confirm the redemption
You can call redeem at the time of sale or in a batch at end of day.
Confirming at the time of sale is the safest: while a redemption lives only in your system, the same code can be used at two branches before either of them knows.
Batching works when your registers operate without a permanent connection, at the cost of accepting that window. If you choose it, coordinate the frequency with n1co.