Failed requests return a 4xx or 5xx HTTP status code and a JSON error envelope.
{
"error": "Conflict",
"code": "POINTS_ALREADY_SPENT",
"message": "These points were already spent."
}
| Field | Description |
|---|
error | HTTP status name (Bad Request, Conflict, …) |
code | Stable, machine-readable error code. Branch your application logic on code. |
message | Human-readable English explanation. Wording may change over time; do not parse it |
Validation errors do not include code
Requests with an invalid body, query, or header return 400 without a code field. message identifies which field failed validation:
{ "error": "Bad Request", "message": "idempotency-key: Idempotency-Key header is required" }
| Status | Meaning |
|---|
200 / 201 | Success. 201 is a newly created entry; 200 is an idempotent replay (replayed: true) |
400 | Bad request: missing field, invalid QR token |
401 | Missing or invalid API key |
402 | Business subscription (expired trial or suspended) blocks till transactions |
403 | Forbidden: another cashier's entry, or the 15-minute void window has passed |
404 | Resource not found |
409 | Conflict: already-used QR, insufficient balance, duplicate receipt or request |
413 | Request body exceeds 1 MB (PAYLOAD_TOO_LARGE) |
422 | Amount or stamp count violates business rules |
429 | Rate limit exceeded or daily earn/redeem cap reached |
5xx | Server error on our side. Retry safely with the same Idempotency-Key |
| Code | Status | Meaning |
|---|
UNAUTHORIZED | 401 | API key is missing, malformed, or revoked |
QR_INVALID | 400 | QR token is invalid or belongs to another business |
QR_EXPIRED | 400 | 30-second QR token has expired; customer must refresh |
QR_ALREADY_USED | 409 | This QR token was already scanned |
SCAN_NOT_FOUND | 404 | scanId was not found; scan the QR again |
SCAN_INVALID | 409 | Scan was already consumed or its 5-minute window expired |
MEMBERSHIP_NOT_FOUND | 404 | Member not found |
MEMBERSHIP_BLOCKED | 403 | Membership is blocked |
| Code | Status | Meaning |
|---|
INVALID_AMOUNT | 400 | Purchase amount must be greater than zero |
INVALID_STAMPS | 400 | Stamp count must be at least 1 (or 0 in a hybrid program) |
TOO_MANY_STAMPS | 422 | Stamp count exceeds the per-visit maximum |
NOTHING_TO_EARN | 400 | Neither stamps nor purchase amount was provided in a hybrid program |
AMOUNT_TOO_HIGH | 422 | Purchase amount exceeds the business's per-transaction ceiling |
DAILY_EARN_LIMIT | 429 | Member has reached their daily earn limit |
CASHIER_DAILY_LIMIT | 429 | This API key/cashier has reached its daily earn limit |
DUPLICATE_RECEIPT | 409 | This receiptNo has already been recorded |
DUPLICATE_REQUEST | 409 | This Idempotency-Key was already used for a different request |
NO_PROGRAM | 409 | Business has not configured a loyalty program yet |
| Code | Status | Meaning |
|---|
REWARD_REQUIRED | 400 | No reward selected (rewardId is required for standard QR scans) |
REWARD_MISMATCH | 400 | rewardId does not match the reward encoded in the customer's QR |
CASHIER_DAILY_LIMIT | 429 | This cashier or API key has reached its daily redemption limit |
INSUFFICIENT_BALANCE | 409 | Member does not have enough points or stamps |
REWARD_NOT_FOUND | 404 | Reward not found |
REWARD_INACTIVE | 409 | Reward is currently inactive |
REWARD_OUT_OF_STOCK | 409 | Reward is out of stock |
REWARD_EXPIRED | 409 | Reward validity period has ended |
REWARD_NOT_STARTED | 409 | Reward validity period has not started yet |
REWARD_LIMIT_REACHED | 409 | Member has reached the per-member redemption limit for this reward |
| Code | Status | Meaning |
|---|
ENTRY_NOT_FOUND | 404 | Ledger entry not found |
NOT_VOIDABLE | 409 | Only earn and redeem entries can be voided |
ALREADY_VOIDED | 409 | Entry has already been voided |
POINTS_ALREADY_SPENT | 409 | Points earned in this entry have already been spent |
FORBIDDEN | 403 | An API key can only void its own entries |
VOID_WINDOW_PASSED | 403 | 15-minute void window has elapsed; a manager must void it |
| Code | Status | Meaning |
|---|
TRIAL_EXPIRED | 402 | Business's free trial has expired |
SUBSCRIPTION_SUSPENDED | 402 | Business's subscription is suspended |
LOCATION_REQUIRED | 400 | No location specified (does not happen for single-branch keys) |
LOCATION_FORBIDDEN | 403 | Key is not permitted to operate at this location |
LOCATION_NOT_FOUND | 404 | Location was not found or has been archived |
RATE_LIMITED | 429 | Rate limit exceeded |
PAYLOAD_TOO_LARGE | 413 | Request body exceeds 1 MB |
For 402 errors, the business owner must renew their subscription or contact Mekante support; no action can resolve it from the POS side.