Error Catalog
What you'll learn
- The standard error response format used across all API endpoints
- Every error code the API can return, organized by domain
- HTTP status codes and when each is used
- Best practices for handling errors in your frontend
Prerequisites
- Familiarity with the General Concepts guide
- Understanding of JSON
error format - Completed the Quick Start
Overview
The Blaze ECOM Storefront API uses a consistent error format across all endpoints. Every error response follows the JSON
errors array.
Errors are identified by a machine-readable code field (e.g., empty_cart, invalid_token) which stays stable across API versions, making it safe to match against in your frontend logic. The detail field provides a human-readable message suitable for displaying to end users.
Error Response Format
All error responses return a JSON body with the following structure:
{
"errors": [
{
"code": "empty_cart",
"status": 400,
"detail": "The cart is empty.",
"fields": [],
"extra_info": {}
}
]
}
Error Object Fields
| Field | Type | Description |
|---|---|---|
code |
string | null |
Machine-readable error code (e.g., empty_cart, invalid_token). Stable across versions. May be null for generic validation errors. |
status |
integer |
HTTP status code for this error (e.g., 400, 401, 404). |
detail |
string |
Human-readable error message. Safe to display to end users. |
fields |
string[] |
List of field names related to the error (e.g., ["email"], ["phone_number"]). Empty array when not field-specific. |
extra_info |
object |
Additional context for the error. Structure varies by error type. Empty object when no extra context is available. |
Multiple Errors
A single response can contain multiple errors — for example, when an Ecto changeset validation fails on several fields:
{
"errors": [
{
"code": null,
"status": 422,
"detail": "can't be blank",
"fields": ["email"],
"extra_info": {}
},
{
"code": null,
"status": 422,
"detail": "can't be blank",
"fields": ["password"],
"extra_info": {}
}
]
}
Extra Info Examples
Some errors include extra_info with additional context:
Cart already submitted — includes the order UUID so the frontend can redirect:
{
"code": "cart_already_submitted",
"status": 400,
"detail": "This cart has already been submitted",
"fields": [],
"extra_info": {
"order": { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
}
}
Invalid promo code — echoes back the code that was rejected:
{
"code": "invalid_promo_code",
"status": 400,
"detail": "Coupon code does not exist.",
"fields": ["promo_code"],
"extra_info": {
"promo_code": "SUMMER20"
}
}
Invalid cart items — lists the items that are problematic:
{
"code": "invalid_cart",
"status": 400,
"detail": "Invalid items in the cart",
"fields": ["cart"],
"extra_info": {
"items": [{ "id": "item-uuid", "reason": "out_of_stock" }]
}
}
POS API error codes — external error codes from integrated POS systems:
{
"code": "bad_request",
"status": 400,
"detail": "Inventory type does not match",
"fields": [],
"extra_info": {
"api_error_code": "TZ00003"
}
}
HTTP Status Codes
| Status Code | Meaning | When It's Used |
|---|---|---|
400 |
Bad Request | Invalid input, business rule violations, missing fields, cart errors, payment issues. The most common error status. |
401 |
Unauthorized | Invalid or expired JWT token, wrong credentials, inactive user, invalid SSO token. |
403 |
Forbidden | User does not have permission to access the resource. |
404 |
Not Found | Resource does not exist (store, product, order, kiosk, etc.). |
422 |
Unprocessable Entity | Ecto changeset validation failures — field-level validation errors on create/update. |
429 |
Too Many Requests | Rate limit exceeded. See Rate Limiting. |
500 |
Internal Server Error | Unexpected server error. These are never intentional — report them to support. |
Error Codes by Domain
Authentication Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
bad_login |
401 | Wrong Credentials | Email/phone and password combination is incorrect. |
inactive_user |
401 | The user for the given credentials is inactive. | The account exists but has been deactivated. |
user_is_not_confirmed |
400 | User was not confirmed | User has not completed account confirmation (e.g., email/phone verification). Also returned when trying to reset password or update email for an unconfirmed user. |
phone_number_requires_confirmation |
400 | Phone number requires confirmation. | Login or registration requires phone verification before proceeding. |
email_requires_confirmation |
400 | Email requires confirmation. | Login or registration requires email verification before proceeding. |
invalid_verification_code |
400 | Invalid verification code | The verification code submitted during phone/email confirmation is wrong. |
verification_not_available |
400 | Verification is no longer available. Request another code. | The verification code has expired. A new one must be requested. |
failed_to_request_verification_code |
400 | Failed to request verification code. | The system was unable to send a verification code (SMS/email provider failure). |
verification_code_blocked |
400 | Failed to request verification code. | Too many verification code requests — the user is temporarily blocked. |
account_already_verified |
400 | There is a verified account with the phone number {phone}. | Attempting to verify an account when another verified account already uses that phone number. |
invalid_token |
400 | This token is no longer valid. Please request another link. | Password reset or email confirmation token has expired or already been used. |
invalid_credentials |
401 | Invalid user credentials. | Generic authentication failure — credentials do not match any account. |
invalid_sso_token |
401 | Invalid or expired SSO token | SSO token provided in the request is invalid or has expired. |
missing_authorization_header |
401 | Missing Authorization header | An authenticated endpoint was called without the Authorization: Bearer header. |
bad_current_password_match |
400 | Current password is wrong | Password change failed because the current password provided doesn't match. |
invalid_password |
400 | The password does not match with your existing account in the Blaze point of sales. | User tried to register with an email/phone that exists in the POS but provided the wrong POS password. |
pos_not_allowed |
401 | The user for the given credentials is not allowed access to the store's POS. | User is authenticated but not authorized for POS access. |
url_expired |
403 | URL expired | A time-limited URL (e.g., magic link) has expired. |
logout_unsuccessful |
400 | Logout was unsuccessful. | Server-side logout failed (token invalidation error). |
OAuth & SSO Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
oauth_login_missing_client |
400 | You must provide a client ID | OAuth login request is missing the client_id parameter. |
oauth_login_invalid_client |
400 | Invalid client ID | The client_id provided does not match a registered OAuth application. |
oauth_login_missing_store |
400 | You must provide a store ID | OAuth login request is missing the store identifier. |
oauth_login_invalid_store |
400 | Invalid store ID | The store ID in the OAuth request does not match a valid store. |
oauth_auth0_failed_to_register |
400 | Failed to register user with Auth0: {detail} | Auth0 provider rejected the user registration. |
authentication_oauth_invalid_state |
400 | Invalid state parameter | The OAuth state parameter doesn't match expected value (possible CSRF). |
authentication_oauth_missing_redirect_uri |
400 | Missing redirect URI | OAuth request is missing the required redirect_uri. |
authentication_oauth_invalid_redirect_uri |
400 | Invalid redirect URI | The redirect_uri does not match any registered redirect URIs. |
authentication_oauth_invalid_callback |
400 | Invalid callback parameters | OAuth callback received invalid or missing parameters. |
User & Account Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
invalid_user |
400 | Sorry, we couldn't find any user matching your information. | No user matches the provided fields (used during registration/lookup). |
email_already_exists |
400 | Email is already in use. Please contact Retailer Support. | Attempting to register or update with an email that another account already uses. |
phone_already_exists |
400 | Phone number is already in use. Please contact Retailer Support. | Attempting to register or update with a phone number that another account already uses. |
locked_verified_user_uploads |
400 | Need to update your ID/Medical Info? Please contact Support. | User tried to change ID/medical documents on a verified account. |
name_and_dob_update_not_alowed |
400 | Need to update your name or birthday info? Please contact Support. | User tried to update name or date of birth on a verified account. |
user_is_not_linked |
400 | User is not linked to any POS profile | Sync was requested but the user has no linked POS profile. |
non_customer_user |
400 | User is not a customer | An operation requiring a customer role was attempted by a non-customer user. |
invalid_phone_number |
400 | Invalid phone number | The phone number format is invalid. |
us_phone_number_required |
400 | US phone number is required. | The operation requires a US-formatted phone number. |
verified_phone_number_required |
400 | Phone number must be verified. | The operation requires a verified phone number. |
registration_with_email_is_required |
400 | Email is required for registration | Store settings require an email address during registration. |
registration_with_phone_is_required |
400 | Phone number is required for registration | Store settings require a phone number during registration. |
registration_with_drivers_license_id_is_required |
400 | Driver's License ID is required for registration | Store settings require a driver's license ID during registration. |
registration_with_state_residency_is_required |
400 | State residency is required for registration | Store settings require state residency information during registration. |
consumer_not_found |
404 | User not found | The referenced user/consumer does not exist. |
failed_to_update_email |
400 | The email could not be updated in the POS. Please contact us. | Email update was rejected by the POS system. |
failed_register |
400 | Oops, looks like this is not the phone number we have on file in the POS. Please try again or contact us to access your account. | Phone-based registration failed because the POS has a different phone number on file. |
expected_same_email_for_phone_number |
400 | A different email was found assigned to {phone}. Please contact us. | The phone number exists in the POS but is linked to a different email. |
expected_one_result_for_phone_number |
400 | Your phone is associated with multiple profiles in our different stores. Please contact us to setup your profile. | Phone number matches multiple POS profiles across stores. |
expected_one_result_for_email |
400 | Your email is associated with multiple profiles in our different stores. Please contact us to setup your profile. | Email matches multiple POS profiles across stores. |
expected_one_result_for_drivers_license |
400 | Your driver's license is associated with multiple profiles in our different stores. Please contact us to setup your profile. | Driver's license matches multiple POS profiles. |
no_matching_email_to_dl_match |
400 | POS profile unable to match email and ID, please contact us to update your profile. | Email and ID document don't match the same POS profile. |
no_matching_phone_to_dl_match |
400 | POS profile unable to match phone and ID, please contact us to update your profile. | Phone and ID document don't match the same POS profile. |
age_not_allowed |
400 | The allowed minimum age is {age} | User does not meet the store's minimum age requirement. |
dob_is_required |
400 | Please update your date of birth in your profile. | Date of birth is missing and required for the requested operation. |
user_already_verified |
400 | The identity of this user is already verified by {service} | Identity verification was requested but user is already verified. |
unique_email_already_confirmed |
400 | There's already a confirmed customer with the same email ({email}). You can't reset the password and activate this User | Cannot activate a user because another confirmed user has the same email. |
user_active_on_pos |
400 | Only customers deactivated on POS can be reset | Account reset is only available for POS-deactivated customers. |
not_a_member |
400 | The POS still has not accepted your membership | User's membership is pending POS acceptance. |
user_is_no_store_manager |
400 | User is no store manager. | Operation requires store manager role. |
group_mismatch |
400 | Customer and store groups do not match | Customer belongs to a different group than the requested store. |
resource_id_mismatch |
400 | Resource ID in the URL and data do not match | The id in the URL path doesn't match the id in the JSON
|
Cart Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
empty_cart |
400 | The cart is empty. | Attempting to submit or validate an empty cart. |
invalid_cart |
400 | Invalid items in the cart | Cart contains items that are invalid (unavailable, wrong inventory type, etc.). extra_info.items lists the problematic items. |
invalid_cart_item |
400 | Your order has one or more unavailable items | One or more cart items are no longer available for purchase. |
invalid_item |
404 | This product is no longer available | A specific product referenced in the cart no longer exists or is delisted. |
out_of_stock |
400 | This product is out of stock for your location | A cart item is out of stock at the relevant location/ZIP code. extra_info includes item details. |
invalid_promo_code |
400 | Coupon code does not exist. | The promotion/coupon code is invalid, expired, or not applicable. extra_info.promo_code echoes back the rejected code. |
duplicate_promo_code |
400 | Coupon already applied | The promo code has already been applied to this cart. |
cannabis_weight_limit_exceeded |
400 | Your order has exceeded the cannabis weight limit by {amount} {uom}. | Cart exceeds the legal cannabis weight limit for the jurisdiction. |
cart_already_submitted |
400 | This cart has already been submitted | Cart was already submitted as an order. extra_info.order.uuid contains the order ID. |
cart_already_processing |
400 | This cart is already being processed | A submission is already in progress for this cart. |
cart_total_changed |
400 | Your cart totals have changed, please confirm new values | Cart totals were recalculated and differ from what the user confirmed. Frontend should re-validate. |
under_order_minimum |
400 | Cart is under the minimum total ${amount}. | Cart total is below the store's minimum order amount. |
cart_data_required_for_customer |
400 | Additional shopping cart data is required for this payment processor | The payment processor requires additional cart data that is missing. |
invalid_cart_submission_state_transition |
400 | Cart submission status transition to '{next}' failed. | Cart submission state machine rejected the transition. |
failed_to_submit_cart |
400 | There was a unexpected problem submitting the cart. Please try again. | Server-side error during cart submission. |
Order Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
order_already_paid |
400 | This order has already been paid. | Payment was attempted on an order that is already paid. |
order_already_completed |
400 | This order has already been completed. | An action was attempted on an already-completed order. |
order_user_mismatch |
400 | Order belongs to another user. | User tried to access or modify an order that belongs to a different account. |
order_sync_limit_reached |
400 | This order has reached the limit of days to sync. | Order can no longer be synced with the POS because the time window has expired. |
related_order_not_found |
404 | Related order not found. | A referenced order (e.g., for reorder or tip) does not exist. |
already_tipped |
400 | Tip already processed. | A tip was already applied to this order. |
Payment Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
payment_failed |
400 | Payment authorization failed. | Payment authorization or capture was declined by the payment processor. extra_info may include order.uuid. |
missing_payment_source |
400 | Online payment requires a payment source. | No payment source (card, bank) was provided for an online payment. |
invalid_payment_source |
400 | Some required fields are missing for adding the payment source. | Payment source is missing required fields. fields lists the missing ones. |
missing_payment_source_identifier |
400 | Online payment requires a payment source id or token | Neither a payment source ID nor a tokenized card was provided. |
expired_payment_token |
400 | Online payment card has expired. Please try again. | The payment card token has expired. |
invalid_payment_token |
400 | Online payment card is invalid. Please try another one. | The payment card token is invalid or rejected. |
payment_source_not_found |
400 | Online payment source not found. | The referenced payment source does not exist. |
payment_customer_not_found |
400 | Online payment customer not found. | No matching customer record exists at the payment processor. |
payment_option_not_found |
400 | Online payment option not found. | The selected payment option is not configured for this store. |
charge_not_authorized |
400 | Payment not yet authorized. | Attempting to capture a payment that has not been authorized. |
charge_canceled |
400 | Payment canceled by {service}. | The payment was canceled by the payment service. |
no_match_for_payment |
400 | The given payment does not match the associated order. | Payment details don't match the order they're being applied to. |
no_match_for_payment_customer |
400 | Customer does not match the one from the external payment source data. | Customer on the order doesn't match the customer on the payment source. |
guest_cannot_add_source |
400 | Cannot add sources in guest checkout. | Guest checkout users cannot save payment sources. |
missing_billing_address |
400 | Billing address is required. | Payment processor requires a billing address. |
missing_payment_postal_code |
400 | Online payment requires a postal code. | Payment processor requires a postal code for card verification. |
missing_payment_cres |
400 | Invalid challenge result (cres) | 3D Secure challenge response is missing or invalid. |
missing_payment_cavv |
400 | Can't authenticate cardholder | 3D Secure CAVV (cardholder authentication) is missing. |
missing_cardholder_name |
400 | Missing cardholder name | Cardholder name is required by the payment processor. |
missing_transaction_id |
400 | Online payment requires a transaction ID. | Transaction ID is required but was not provided. |
missing_customer_signature |
400 | Missing customer signature | Customer signature is required for this payment method. |
missing_customer_auth_key |
400 | Missing customer auth key | Customer authentication key is required for bank account operations. |
missing_curstomer_return_url |
400 | Missing return URL for bank account connection | Return URL is required for bank account linking flow. |
multiple_bank_accounts |
400 | Only a single active bank account is supported for making payments | User has multiple active bank accounts but the processor only supports one. |
invalid_payment_secret_key |
400 | External payment secret key is invalid. | Payment provider secret key configuration is incorrect. |
invalid_payment_public_key |
400 | External payment public key is invalid. | Payment provider public key configuration is incorrect. |
not_found_payment_config |
404 | Payment configuration was not found. | No payment configuration exists for the store. |
invalid_payment_config |
400 | External payment configuration is invalid. | Payment configuration is malformed or incomplete. |
invalid_payment_account_config |
400 | External payment configurations in a group must be for the same payment service account. | Group stores have mismatched payment accounts. |
tips_not_allowed |
400 | Tips are not allowed for the selected payment option | The payment method does not support tipping. |
promotions_not_allowed |
400 | Promotions are not allowed for the selected payment option | The payment method does not support promotions. |
invalid_gift_card_amount |
400 | Invalid gift card amount. | Gift card amount is invalid (e.g., zero, negative, or exceeds limits). |
Delivery & Address Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
missing_zip_code |
400 | Zip code is required. | Delivery address is missing a ZIP/postal code. |
invalid_zip_code |
400 | Zip code is invalid for {country}. | ZIP/postal code format is invalid for the specified country. |
missing_address |
400 | Address is required. | Delivery address is required but not provided. |
geo_data_required |
400 | Delivery address geo location data is required for this delivery mode. | Delivery mode requires latitude/longitude coordinates. |
zip_code_required |
400 | Delivery address Zip Code is required for this delivery mode. | Delivery mode requires a ZIP code for zone-based delivery. |
no_deliveries_at_location |
400 | Sorry, we don't deliver to that location. | The delivery address is outside the store's delivery area. |
no_delivery_fee |
400 | We don't do deliveries to that location. | No delivery fee is configured for the given location (delivery unavailable). |
schedule_closed |
400 | Sorry, our schedule is closed for express delivery. | Express delivery is not available at the current time. |
Store & Configuration Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
bad_request |
400 | Missing X-Store header. | The required X-Store header was not included in the request. |
bad_request |
400 | Missing X-Group header. | The required X-Group header was not included (group endpoints). |
bad_request |
400 | Missing X-App-Mode header. | The required X-App-Mode header was not included. |
bad_request |
400 | Missing X-Kiosk header. | The required X-Kiosk header was not included (kiosk endpoints). |
not_found |
404 | Store does not exist. | The store UUID in X-Store doesn't match any store. |
not_found |
404 | Group does not exist. | The group UUID in X-Group doesn't match any group. |
not_active |
404 | Store is not active. | The store exists but is deactivated. |
bad_request |
404 | Group is inactive. | The group exists but is inactive. |
not_found |
404 | Kiosk does not exist. | The kiosk UUID in X-Kiosk doesn't match any kiosk. |
not_active |
400 | Kiosk is not active. | The kiosk exists but is deactivated. |
kiosk_not_from_store |
400 | Kiosk does not belong to the specified store. | The kiosk UUID doesn't belong to the store specified in X-Store. |
kiosk_not_allowed |
400 | This store does not allow Kiosks | Kiosk mode is not enabled for this store. |
developer_key_invalid |
400 | Developer Key is invalid. | The Blaze developer key is invalid. |
blaze_developer_key_missing |
400 | Blaze developer key is missing. | Blaze developer key was not provided for POS integration. |
invalid_pos_api_key |
400 | Online Store Code is invalid. | The POS API key/store code is invalid. |
invalid_integration |
400 | Invalid POS integration. | The POS integration type is not recognized. |
store_pos_config_mismatch |
400 | The shop for the given keys does not match. | POS configuration keys don't match the store. |
another_pos_is_configured |
400 | Store already has another POS configured. | Trying to add a POS when one is already configured. |
only_one_pos_allowed |
400 | Only one POS can be integrated with a store. | Multiple POS integrations are not supported. |
not_available_for_pos |
400 | {pos} POS does not support this action. | The requested action is not available for the store's POS type. |
missing_store_site |
400 | Store requires a site for integrations. | Store needs a site configured before integrations can be set up. |
page_exists |
400 | footer link already exists | A store page with the same name already exists. |
group_name_already_exists |
400 | The given group name already exists. | Group creation failed because the name is taken. |
missing_pos_integration_data |
400 | Missing POS integration data. | POS integration data is required but not provided. |
group_already_has_plan |
400 | The given group already has a Plan ({existing}), and it's different from the one specified ({given}). | Group already has a different plan assigned. |
deployment_integration_not_configured |
400 | There is no deployment integration configured. | Deployment action requires an integration that hasn't been set up. |
no_sms_provider |
400 | SMS provider not defined in the store | Store has no SMS provider configured for sending verification codes. |
inactive_service_config |
400 | {service} configuration is inactive. | A required third-party service configuration is inactive. |
missing_service_config |
400 | {service} configuration is missing. | A required third-party service configuration doesn't exist. |
another_rewards_system_is_configured |
400 | Please deactivate the currently active reward service. | Can't add a new rewards system while another is active. |
invalid_service_config |
400 | API Key configuration is invalid. | Third-party service API key is invalid. |
invalid_template_key |
400 | Berbix template key configuration is invalid. | Identity verification template key is misconfigured. |
sh_deferred_capture_is_on |
400 | This cannot be changed while the POS Split Payment setting is active. | Configuration change blocked because POS split payment is active. |
pos_not_allowed_for_reset |
400 | POS is not allowed for account reset | The store's POS type does not support account reset. |
Marketing & Promotions Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
invalid_marketing_source |
400 | The given marketing source is not valid for this store. | Marketing source value is not in the store's configured list. |
required_marketing_source |
400 | The marketing source is required in this store. | Store requires a marketing source but none was provided. |
marketing_source_not_allowed |
400 | Marketing source is not allowed in this store. | Marketing source feature is disabled for this store. |
invalid_update_campaign_status |
400 | Only draft or scheduled campaigns can be edited. | Attempting to edit a campaign that is already sent or in progress. |
target_stores_not_from_group |
400 | Target stores ({stores}) are not from this group. | Campaign targets stores outside the group. |
Loyalty & Rewards Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
no_loyalty |
400 | No Loyalty provider found for store. | Store has no loyalty program configured. |
no_growth |
400 | No Growth provider found for store. | Store has no Growth loyalty/rewards provider configured. |
multiple_matching_accounts |
400 | Multiple ECOM accounts found for matching member. Please contact support. | Loyalty member matches multiple ECOM accounts. |
member_is_banned |
400 | Unable to check in customer | Loyalty member is banned. extra_info.member_id is included. |
member_not_in_queue |
400 | Unable to check in customer | Loyalty member is not in the check-in queue. |
Identity Verification Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
missing_identity_verification_data |
400 | Missing identity verification data | Required identity verification fields are missing. |
invalid_document_type |
400 | Invalid document type | The uploaded document type is not accepted. |
invalid_file_format |
400 | Invalid file format | The uploaded file format is not supported. |
s3_key_length_exceeded |
400 | Upload failed. The URL is larger than what is currently allowed. | File upload URL exceeds length limits. |
General Errors
| Error Code | HTTP Status | Message | When It Occurs |
|---|---|---|---|
bad_request |
400 | Invalid request parameters | Generic invalid request — usually malformed JSON or missing required fields. |
not_found |
404 | Not found. | The requested resource does not exist. |
forbidden |
403 | Client not allowed to access the requested resource | User does not have permission for the requested action. |
required |
400 | Field is required. | A required field is missing. fields specifies which one. |
not_supported |
400 | Functionality not supported | The requested functionality is not available. |
invalid_value |
400 | Invalid value for {field}. | A field has an invalid value. fields specifies which field and valid choices may be listed. |
product_recommendations_not_allowed |
400 | Product recommendations are not allowed for this store. | Product recommendation feature is disabled. |
unsupported_geometry_type |
400 | Unsupported geometry type. | GeoJSON geometry type is not supported (delivery zones). |
cannot_update_keys |
400 | Cannot update payment configuration keys. | Payment config keys are locked and cannot be changed. |
not_implemented |
400 | Export is not implemented for {resource}. | Data export is not available for the requested resource type. |
invalid_data_for_webhook_url |
400 | Can't build the webhook URLs. | Webhook URL generation failed due to missing configuration. |
Changeset Validation Errors
Ecto changeset errors are returned with HTTP status 422 and a null code. These are field-level validation errors that occur on create and update operations.
{
"errors": [
{
"code": null,
"status": 422,
"detail": "can't be blank",
"fields": ["email"],
"extra_info": {}
}
]
}
Common changeset validation messages include:
| Message | When It Occurs |
|---|---|
can't be blank |
A required field was not provided. |
has already been taken |
A unique field (email, phone) already exists. |
is invalid |
Field value doesn't match the expected type or format. |
has invalid format |
Field value doesn't match the expected pattern (e.g., email format). |
must be at least N character(s) |
String field is shorter than the minimum length. |
must be at most N character(s) |
String field exceeds the maximum length. |
is not a valid email |
Email format validation failed. |
Handling Errors
General Strategy
- Check
statusfirst — Route to the appropriate handler based on HTTP status code. - Match on
code— Use the machine-readablecodefor specific error handling logic. - Display
detail— Show thedetailmessage to the user. - Use
extra_info— Leverage additional context when available (e.g., redirect to an order oncart_already_submitted).
JavaScript Example
async function handleApiResponse(response) {
if (response.ok) return response.json();
const body = await response.json();
const errors = body.errors || [];
const firstError = errors[0];
switch (response.status) {
case 401:
// Session expired or invalid credentials
if (firstError?.code === "inactive_user") {
showNotification("Your account has been deactivated.");
} else {
clearAuth();
redirectToLogin();
}
break;
case 403:
redirectToLogin();
break;
case 404:
showNotification("The requested resource was not found.");
break;
case 429:
const retryAfter = response.headers.get("Retry-After") || 30;
await delay(retryAfter * 1000);
// Retry the request
break;
default:
// Handle by error code
handleErrorByCode(firstError);
}
}
function handleErrorByCode(error) {
switch (error?.code) {
case "cart_already_submitted":
const orderUuid = error.extra_info?.order?.uuid;
if (orderUuid) redirectToOrder(orderUuid);
break;
case "cart_total_changed":
revalidateCart();
break;
case "phone_number_requires_confirmation":
case "email_requires_confirmation":
redirectToVerification();
break;
case "out_of_stock":
case "invalid_cart_item":
case "invalid_cart":
refreshCart();
showNotification(error.detail);
break;
default:
showNotification(error?.detail || "An error occurred.");
}
}
Error Code Constants
Define error codes as constants to avoid typos:
const ErrorCodes = {
EMPTY_CART: "empty_cart",
INVALID_CART: "invalid_cart",
INVALID_CART_ITEM: "invalid_cart_item",
OUT_OF_STOCK: "out_of_stock",
CART_ALREADY_SUBMITTED: "cart_already_submitted",
CART_TOTAL_CHANGED: "cart_total_changed",
INVALID_PROMO_CODE: "invalid_promo_code",
DUPLICATE_PROMO_CODE: "duplicate_promo_code",
PAYMENT_FAILED: "payment_failed",
PHONE_REQUIRES_CONFIRMATION: "phone_number_requires_confirmation",
EMAIL_REQUIRES_CONFIRMATION: "email_requires_confirmation",
INVALID_TOKEN: "invalid_token",
BAD_LOGIN: "bad_login",
INACTIVE_USER: "inactive_user",
MISSING_AUTHORIZATION: "missing_authorization_header",
NO_DELIVERIES_AT_LOCATION: "no_deliveries_at_location",
};
Best Practices
- Always check the
errorsarray — Never assume a single error. Multiple validation errors can occur simultaneously. - Use
codefor logic,detailfor display — Codes are stable; messages may change between versions. - Handle 401 globally — Set up a response interceptor that catches 401s and redirects to login.
- Handle
cart_already_submittedgracefully — Use theextra_info.order.uuidto redirect the user to their order instead of showing a raw error. - Handle
cart_total_changedby re-validating — When prices change between cart load and submission, re-fetch the cart and ask the user to confirm. - Don't ignore
fields— Whenfieldsis populated, highlight the relevant form fields for the user. - Log
extra_info.api_error_code— POS-originated errors include anapi_error_codethat can help your support team diagnose issues.