Message & Error Codes
Read first: QR Code Loyalty Guide
This is the full catalog of the codes the In-Store Loyalty API returns: the message codes your integration must display to the cashier, and the business error codes it must handle. Field-level definitions live in the spec — the Message schema and the per-operation response schemas in In-Store Loyalty API specification — this page adds the meaning and the recommended handling.
1. The golden rule
A non-200 HTTP status is not how the in-store API tells you a loyalty operation failed. Business outcomes — "QR already scanned", "order already closed", "phone flow disabled" — are carried inside the response body, and they arrive with HTTP 200. Non-200 statuses are reserved for transport-, auth-, and field-validation-level problems:
- HTTP 403 — authentication failed (bad/missing
SaltId, reused salt, wrongAuthorizationToken/Signature). The body is{"message": "..."}, not a loyalty result. The auth layer emits this before the request reaches any business logic. - HTTP 400 — request-body validation failure (v1.4+). The body is a JSON object mapping each invalid field's JSON-Pointer path to a description, e.g.
{"/order/menuItems/0/itemId": "instance type (integer) does not match ..."}. Fix the request and resend. - HTTP 503 — service temporarily unavailable (a downstream dependency such as Redis is unreachable). The body is an
ErrorResponse. Retry after a short backoff.
Everything else is a business outcome delivered in the 200 body. How you read the outcome depends on the operation:
| Operation | Authoritative success/failure signal | Detail field |
|---|---|---|
processQrCode | accepted boolean; on rejection a single messages[] entry of type: error carries the numeric code | messages[].code (see Section 3 for the mapping) |
orderClosed, refundOrder | accepted boolean — not errorCode | errorText (human-readable rejection reason) |
The numeric messages[].code values returned by processQrCode follow the monolith's message-code numbering, so a POS that already knows those codes can reuse its display logic (ORDER_ALREADY_CLOSED → 506, the same 506 listed on the Message schema). This numbering applies to processQrCode only; orderClosed/refundOrder do not return these numeric codes (see the Note below).
Note: For
orderClosed/refundOrderthe authoritative signal is the booleanacceptedfield — noterrorCode.errorCodeis0in both the accepted and the rejected cases, soerrorCode == 0does not mean success. Whenaccepted: false, the rejection reason is inerrorText;errorCodestays0. Always branch onaccepted.
2. Cashier-facing messages
Each entry in the processQrCode response messages[] array has a type (info, warn, error), a numeric code, and a human-readable text. Every message must be shown — rendered on the self-service kiosk screen for the customer, or shown to the cashier at the cash register / POS terminal. That is how the customer learns the scan succeeded, a reward was added, or the code was rejected.
The full message-code catalog, as defined on the Message schema:
| Code | Type | Meaning |
|---|---|---|
| 200 | info | Generic information message |
| 201 | info | Bonus card was accepted |
| 202 | info | Reward was added |
| 203 | info | (reserved) |
| 500 | error | Generic error message |
| 501 | error | QR code was generated for another partner |
| 502 | error | QR code was generated for another profile |
| 503 | error | QR code invalid |
| 504 | error | QR code was already scanned |
| 505 | error | QR code already validated |
| 506 | error | Order already closed |
| 507 | error | Discount already exists in the order |
| 508 | error | Reward was not added to the order |
| 509 | error | Phone number has the wrong format |
| 510 | error | The phone / publicClientId flow is not available for this outlet |
| 600 | warn | Generic warning message |
Codes 200–203, 500–510, and 600 are all defined on the spec Message schema; codes 509 and 510 are additionally produced by the service's ErrorCodeMapping.
Note: The legacy public message-codes page stops at
508and600, but the current specMessageschema lists509(PHONE_NUMBER_WRONG_FORMAT) and510(PHONE_FLOW_DISABLED) too; both are produced by the current service for the phone /publicClientIdflows. Display thetextthe in-store API sends rather than hard-coding strings against the code — thetextis localized and authoritative.
3. Error codes
This mapping applies to processQrCode. When a business rule blocks a scan, the in-store API raises an internal error code and maps it to one of the numeric messages[].code values above (via GlobalExceptionHandler → ErrorCodeMapping). It does not populate the errorCode field of orderClosed/refundOrder — those report a business rejection as accepted: false with errorCode: 0 (see Section 1). The table below lists the error codes ErrorCodeMapping defines, plus two error codes that are thrown but unmapped (the last two rows):
| Internal error code | Numeric code | Meaning |
|---|---|---|
QR_EXPIRED | 503 | Mapping entry only — not currently emitted by the in-store API; QR-invalidity is reported via the other 503 codes below |
QR_ALREADY_SCANNED | 504 | QR code already scanned (the qrCode already exists) |
QR_CODE_ALREADY_VALIDATED | 505 | QR code already fully validated |
ORDER_ALREADY_CLOSED | 506 | A QR was scanned (processQrCode) against an order that is already closed |
QR_CODE_WRONG_FORMAT | 503 | QR string is malformed |
QR_INVALID | 503 | QR code not recognized / invalid |
QR_PARSE_ERROR | 503 | QR string could not be parsed |
ESTABLISHMENT_NOT_FOUND | 500 | establishmentId not found |
PHONE_FLOW_DISABLED | 510 | Phone / publicClientId flow not available for this outlet (flow disabled, or partner data missing) |
PHONE_NUMBER_WRONG_FORMAT | 509 | Phone number not in the expected format |
PARTNER_DATA_MISSING | 500 | Partner configuration incomplete on the LP side |
CLIENT_SERVICE_ERROR | 500 | Downstream LP client service error |
MISSING_REQUIRED_FIELD | 500 | A required field was absent |
CUSTOMER_NOT_FOUND | 500 | (v1.7 publicClientId lookup) no such public ID — unmapped, falls through to default 500 |
CUSTOMER_WRONG_PARTNER | 500 | (v1.7 publicClientId lookup) client belongs to a different partner — unmapped, falls through to default 500 |
| (any other / unmapped) | 500 | Default — generic error |
Note:
CUSTOMER_NOT_FOUNDandCUSTOMER_WRONG_PARTNER(thrown on the v1.7publicClientIdlookup) have no dedicated entry inErrorCodeMapping, so both fall through to the default500. They are listed in the table above for completeness. Distinguish them byerrorText/messagetext, not by the numeric code.
4. Recommended vendor handling per code
The table groups the outcomes by the action your integration should take. "Show message" always means: display the text the in-store API returned.
| Codes | Retry? | Vendor action |
|---|---|---|
| 200, 201, 202 | No | The scan/operation succeeded. Show message; apply any returned rewards, discounts, and payment. |
600 (warn) | No | A warning — it does not by itself assert the operation succeeded. Show message; check accepted (and messages[]) to decide whether to apply anything. |
503 (QR_INVALID/QR_*_FORMAT/QR_PARSE_ERROR) | No — needs new code | Show message; ask the customer to refresh the app and present a fresh QR code — to scan again at the self-service kiosk, or to have the cashier re-scan it at the cash register / POS terminal (per spec, codes are single use and intended to be short-lived). |
504 (QR_ALREADY_SCANNED), 505 (QR_CODE_ALREADY_VALIDATED) | No | Show message; do not re-scan the same code into the same order. The first scan already counts. |
506 (ORDER_ALREADY_CLOSED) | No | Surfaces on processQrCode when a QR is scanned against an already-closed order. Show message; do not re-scan into a closed order. (A repeated orderClosed call does not return 506 — it returns accepted: false with errorCode: 0; branch on accepted there.) |
| 507 (discount exists), 508 (reward not added) | No | Show message; reconcile the order contents — a discount/reward could not be applied as sent. |
509 (PHONE_NUMBER_WRONG_FORMAT) | No — fix input | Show message; re-prompt for a valid international phone number — the customer re-enters it on the self-service kiosk, or the cashier re-enters it at the POS / cash register. |
510 (PHONE_FLOW_DISABLED) | No | The phone number-based flow is off for this outlet. Do not retry; fall back to the QR code flow — the customer scans their QR code at the self-service kiosk, or the cashier scans it at the cash register / POS terminal — and contact LoyaltyPlant if it should be enabled. |
500 (ESTABLISHMENT_NOT_FOUND, PARTNER_DATA_MISSING, CLIENT_SERVICE_ERROR, MISSING_REQUIRED_FIELD, default; v1.7 CUSTOMER_NOT_FOUND/CUSTOMER_WRONG_PARTNER) | Only transient | Show message. Config/data errors (wrong establishmentId, missing partner data) need a fix or LP escalation, not a retry. A transient CLIENT_SERVICE_ERROR may be retried once with a fresh salt. |
| HTTP 403 | No | Auth/transport failure, not a loyalty outcome. Recompute AuthorizationToken with a fresh salt; never reuse a salt. See the QR Code Loyalty Guide troubleshooting table. |
| HTTP 400 | No — fix request | Read the JSON-Pointer paths, correct the listed fields (types/enums), and resend. |
| HTTP 503 | Yes — after backoff | Service temporarily unavailable (e.g. Redis down). Retry the request after a short backoff with a fresh salt. |
Warning: Never treat HTTP 200 as unconditional success. For
processQrCode, a response can carryaccepted: falsewith anerrormessage. FororderClosed/refundOrder, a rejection is signalled byaccepted: false— not byerrorCode, which is0on both success and rejection. All of these arrive as HTTP 200, so always inspect the body and branch onaccepted.
Next: Integration Checklist →