Skip to content

Error Codes

Every non-2xx response carries a stable errorCode string. Handle these in code instead of parsing message (which is localized / may change).

Standard codes

HTTPerrorCodeWhen
400VALIDATION_ERRORRequest body/query failed DTO validation.
400BAD_REQUESTGeneric client error.
401UNAUTHORIZEDMissing, expired, or malformed bearer token.
403FORBIDDENAuthenticated, but lacks the required permission/role.
404NOT_FOUNDResource does not exist (or you can’t see it).
409CONFLICTDuplicate / state conflict (e.g. email already registered).
422UNPROCESSABLE_ENTITYSemantic validation error beyond DTO checks.
429RATE_LIMITEDToo many requests; retry after the header duration.
500INTERNAL_ERRORUnexpected server failure. Please report with X-Request-ID.

Domain-specific codes (selection)

errorCodeMeaning
INVALID_CREDENTIALSSign-in failed — wrong MSISDN or password.
OTP_EXPIREDOne-time password no longer valid.
OTP_MISMATCHSubmitted OTP doesn’t match.
PRODUCT_UNAVAILABLEProduct is out of stock or inactive.
VOUCHER_ALREADY_REDEEMEDThe voucher was already claimed.
PAYMENT_FAILEDUpstream payment gateway declined.
INSUFFICIENT_BALANCEWallet doesn’t cover the charge.
OUT_OF_STOCKAll inventory reserved by the time the order attempted to lock stock. Render “sold out” and re-fetch the product.
DISCOUNT_INVALIDDiscount code does not exist, is inactive, has expired, or has hit its usage limit — returned by both the validate endpoint and POST /orders.
DISCOUNT_NOT_STARTEDDiscount code’s start_date has not been reached yet.
DISCOUNT_EXPIREDDiscount code’s end_date has passed.
DISCOUNT_USAGE_LIMIT_REACHEDDiscount code has been fully consumed.
DISCOUNT_MIN_PURCHASE_NOT_METCart subtotal is below the code’s min_purchase threshold.

Admin fulfillment recovery codes

These codes are returned by the /admin/orders/:id/fulfillment/* endpoints.

HTTPerrorCodeWhen
400BAD_REQUESTOrder has no SUCCEEDED payment — refusing to dispatch a voucher.
404NOT_FOUNDOrder or order item does not exist.
409CONFLICTItem already fulfilled, a successful attempt already exists (idempotency guard), or the voucher code is already assigned to another item.

Handling errors

const res = await fetch(url, opts);
const body = await res.json();
if (!body.success) {
switch (body.errorCode) {
case 'UNAUTHORIZED':
return redirectToLogin();
case 'RATE_LIMITED':
return retryWithBackoff();
case 'VALIDATION_ERROR':
return showFieldErrors(body.errors);
default:
return toast(body.message);
}
}

Request tracing

Every response includes an X-Request-ID header. Include it when filing support tickets — it’s the fastest way to look up the corresponding server log.