Skip to main content

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

EnvironmentHost
Productionhttps://n1-loyalty-core-redemption-api.n1co.com
Testinghttps://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

EndpointWhat it does
POST /api/v1/coupons/validateChecks that the code is usable, without consuming it.
POST /api/v1/coupons/redeemConsumes it for good.
Consumption is idempotent

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..." }
Branch on rejectionCode, not on message

rejectionCode 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

rejectionCodeWhat happened
CODE_NOT_FOUNDThe code does not exist. Usually a typing error.
COUPON_EXPIREDThe code expired.
COUPON_LIMIT_REACHEDIt was already redeemed.
COUPON_NOT_YET_ACTIVEIts validity has not started yet.
CODE_NOT_IN_TRIGGERED_CAMPAIGNThe code does not belong to your business.
COUPON_REJECTED_BY_CONDITIONA promotion condition blocked it.
UNKNOWN_REASONUnrecognised reason. Treat it as invalid.

Technical errors

These do use semantic HTTP status codes and a different body:

{ "error": { "code": "UNAUTHORIZED", "message": "Unauthorized" } }
HTTPerror.codeWhen
400INVALID_REQUESTcode is missing or empty.
401UNAUTHORIZEDX-Api-Key is missing or invalid.
415UNSUPPORTED_MEDIA_TYPEContent-Type is not application/json.
502UPSTREAM_ERRORTemporary service failure.
503SERVICE_UNAVAILABLEService unavailable.
On 502 or 503, do not treat the code as redeemed

These are temporary failures, not rejections. Retry: consumption is idempotent.

The flow at the register

  1. The cashier types or scans the code.
  2. Your system looks it up in your local database, using what you received with the purchase.
  3. You call validate to confirm validity.
  4. 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.
  5. You call redeem when confirming the sale.
If redeem is rejected, do not close the sale with that code

A 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.