Skip to main content

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, wrong AuthorizationToken/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:

OperationAuthoritative success/failure signalDetail field
processQrCodeaccepted boolean; on rejection a single messages[] entry of type: error carries the numeric codemessages[].code (see Section 3 for the mapping)
orderClosed, refundOrderaccepted boolean — not errorCodeerrorText (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_CLOSED506, 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/refundOrder the authoritative signal is the boolean accepted field — not errorCode. errorCode is 0 in both the accepted and the rejected cases, so errorCode == 0 does not mean success. When accepted: false, the rejection reason is in errorText; errorCode stays 0. Always branch on accepted.

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:

CodeTypeMeaning
200infoGeneric information message
201infoBonus card was accepted
202infoReward was added
203info(reserved)
500errorGeneric error message
501errorQR code was generated for another partner
502errorQR code was generated for another profile
503errorQR code invalid
504errorQR code was already scanned
505errorQR code already validated
506errorOrder already closed
507errorDiscount already exists in the order
508errorReward was not added to the order
509errorPhone number has the wrong format
510errorThe phone / publicClientId flow is not available for this outlet
600warnGeneric 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 508 and 600, but the current spec Message schema lists 509 (PHONE_NUMBER_WRONG_FORMAT) and 510 (PHONE_FLOW_DISABLED) too; both are produced by the current service for the phone / publicClientId flows. Display the text the in-store API sends rather than hard-coding strings against the code — the text is 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 GlobalExceptionHandlerErrorCodeMapping). 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 codeNumeric codeMeaning
QR_EXPIRED503Mapping entry only — not currently emitted by the in-store API; QR-invalidity is reported via the other 503 codes below
QR_ALREADY_SCANNED504QR code already scanned (the qrCode already exists)
QR_CODE_ALREADY_VALIDATED505QR code already fully validated
ORDER_ALREADY_CLOSED506A QR was scanned (processQrCode) against an order that is already closed
QR_CODE_WRONG_FORMAT503QR string is malformed
QR_INVALID503QR code not recognized / invalid
QR_PARSE_ERROR503QR string could not be parsed
ESTABLISHMENT_NOT_FOUND500establishmentId not found
PHONE_FLOW_DISABLED510Phone / publicClientId flow not available for this outlet (flow disabled, or partner data missing)
PHONE_NUMBER_WRONG_FORMAT509Phone number not in the expected format
PARTNER_DATA_MISSING500Partner configuration incomplete on the LP side
CLIENT_SERVICE_ERROR500Downstream LP client service error
MISSING_REQUIRED_FIELD500A required field was absent
CUSTOMER_NOT_FOUND500(v1.7 publicClientId lookup) no such public ID — unmapped, falls through to default 500
CUSTOMER_WRONG_PARTNER500(v1.7 publicClientId lookup) client belongs to a different partner — unmapped, falls through to default 500
(any other / unmapped)500Default — generic error

Note: CUSTOMER_NOT_FOUND and CUSTOMER_WRONG_PARTNER (thrown on the v1.7 publicClientId lookup) have no dedicated entry in ErrorCodeMapping, so both fall through to the default 500. They are listed in the table above for completeness. Distinguish them by errorText/message text, not by the numeric 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.

CodesRetry?Vendor action
200, 201, 202NoThe scan/operation succeeded. Show message; apply any returned rewards, discounts, and payment.
600 (warn)NoA 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 codeShow 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)NoShow message; do not re-scan the same code into the same order. The first scan already counts.
506 (ORDER_ALREADY_CLOSED)NoSurfaces 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)NoShow message; reconcile the order contents — a discount/reward could not be applied as sent.
509 (PHONE_NUMBER_WRONG_FORMAT)No — fix inputShow 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)NoThe 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 transientShow 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 403NoAuth/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 400No — fix requestRead the JSON-Pointer paths, correct the listed fields (types/enums), and resend.
HTTP 503Yes — after backoffService 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 carry accepted: false with an error message. For orderClosed/refundOrder, a rejection is signalled by accepted: falsenot by errorCode, which is 0 on both success and rejection. All of these arrive as HTTP 200, so always inspect the body and branch on accepted.


Next: Integration Checklist →