Errors
The error envelope, request ids, every error code and which ones are safe to retry.
Both APIs answer success and failure the same way.
Envelope
Success is { "data": ... }. Failure is:
{
"error": {
"code": "validation_error",
"message": "A name is required.",
"requestId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
}| Field | Meaning |
|---|---|
code | A stable, machine-readable code. Branch on this |
message | Written for a person. It may be reworded, so do not branch on it. On the Merchant API it is safe to show to the merchant |
requestId | The id of this request. Always present. The same as the X-Request-Id response header |
details | Optional machine-readable context. For example retryAfter on rate_limited, or action and methods on step_up_required |
Each code has exactly one HTTP status. The list of codes can grow within v1, so treat a code you do not know as its status class: a 4xx you do not know is your request's fault, a 5xx is ours.
Unexpected failures are 500 internal_error. They never carry a stack trace or any internals.
Request ids
Every response, success or error, carries an X-Request-Id header. Send your own, 8 to 128 letters, digits, ., _ or -, and it is echoed back. Otherwise ShopEazy makes one.
It is the id in ShopEazy's logs and error reports. When you ask for help at shopeazy.co/help, quote it. A browser site on the Storefront API may send and read the header.
Retrying
The clients never retry on their own.
- A network failure or timeout. Retry. For a write, send the same
Idempotency-Keyagain, so the work happens once. See Idempotency. 429 rate_limited. Wait forRetry-Afterseconds, then send the same call again. A refused call did nothing and used nothing. See Rate limits.409 in_flightand503 idempotency_unavailable. Retry with the same key after a moment. Nothing was processed.500 internal_error. Retry with backoff. A read is always safe. A write is safe only with anIdempotency-Key.generation_failed,send_failedandlookup_failed. Retry. They are 502 answers where an outside service did not answer.- Any other 4xx. Do not send it again unchanged. The request, the credential, the plan or the state has to change first.
The last column of each table says which codes are safe to retry.
Codes
The same catalogue serves both APIs. The API column says which can return a code.
Credentials and access
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
missing_token | 401 | Both | No credential was sent. Send a Bearer token (a person’s token on the Merchant API, a store key on the Storefront API). | No, fix the cause first |
invalid_token | 401 | Both | The credential did not verify: unknown, expired or malformed. Sign in again, or check the key. | No, fix the cause first |
revoked_token | 401 | Storefront | The store key was revoked in the console. Issue a new one. | No, fix the cause first |
insufficient_scope | 403 | Storefront | The store key does not carry the scope this call needs. Scopes are fixed when a key is issued: reissue it. | No, fix the cause first |
origin_not_allowed | 403 | Storefront | A publishable key was used from a website the store does not allow. The store owner adds it under Allowed websites. | No, fix the cause first |
step_up_required | 403 | Merchant | A risky change (new bank details, payment keys, a sign-in email, an admin, closing the account) needs a fresh check by the person. details carries action and the allowed methods. Start one with POST /v1/merchant/step-up, verify it, and send the proof in Step-Up-Proof. | After a fresh check |
cashier_required | 403 | Merchant | The store uses till PINs, so a register, a void or a Scan to pay needs the cashier at the till. Take over with POST /v1/merchant/stores/{storeId}/till/cashier and send the ticket as cashierTicket. | No, fix the cause first |
forbidden | 403 | Merchant | The credential, scope or the person’s role does not allow this call, or only the ShopEazy app may do it. | No, fix the cause first |
shopper_required | 401 | Storefront | This call needs a signed-in shopper (X-Shopper-Session or X-Shopper-Token) and none was sent. | No, fix the cause first |
invalid_shopper_token | 401 | Storefront | The shopper’s token or session is not valid or has expired. Sign the shopper in again. | No, fix the cause first |
order_access_required | 401 | Storefront | Neither X-Order-Token nor a shopper credential was sent for this order. | No, fix the cause first |
email_unverified | 403 | Both | The email address has not been verified yet. | No, fix the cause first |
not_a_member | 403 | Storefront | A members-only store, and this shopper is not an active member. | No, fix the cause first |
verification_required | 422 | Storefront | The application needs a verified selfie and ID document before it is accepted. | No, fix the cause first |
Store and plan
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
store_not_found | 404 | Both | The store does not exist, or (Merchant API) the person cannot reach it. Never a 403, so it does not confirm the id. | No, fix the cause first |
no_account | 404 | Merchant | This login has no business on ShopEazy yet. | No, fix the cause first |
store_suspended | 403 | Storefront | The store is suspended. Show "shop closed". | No, fix the cause first |
store_closed | 403 | Storefront | The store has closed. Show "shop closed". | No, fix the cause first |
store_inactive | 403 | Storefront | The store’s plan is not active. Show "shop closed". | No, fix the cause first |
api_channel_off | 403 | Storefront | The merchant switched the API channel off for this store. | No, fix the cause first |
plan_limit | 403 | Both | The plan does not include this, or its cap is reached. The message names the plan to move to. Nothing is lost: upgrading restores access. | No, fix the cause first |
subscription_locked | 403 | Merchant | The plan is not active, so the store is read-only. Reads still work; pay or reactivate to write. | No, fix the cause first |
rate_limited | 429 | Both | Too many calls, or a daily cap reached. Wait, then retry. details.retryAfter says how long when known. | Yes, after the wait |
Requests
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
bad_request | 400 | Merchant | The request is malformed: a missing, mistyped or out-of-range field. The message names it. | No, fix the cause first |
invalid_parameter | 400 | Storefront | A query parameter is malformed or not one of its allowed values. The message names it. | No, fix the cause first |
validation_error | 422 | Both | Well-formed, but it breaks a rule. The message says which, and is safe to show. | No, fix the cause first |
not_found | 404 | Both | The thing asked for does not exist (in this store). | No, fix the cause first |
in_flight | 409 | Both | The same Idempotency-Key is still being processed. Wait a moment and retry with the same key. | Yes, same key, after a moment |
idempotency_key_reused | 409 | Both | This Idempotency-Key was already used for a different request (another method, path or body). A retry must send exactly the same request; a new request needs a new key. | No, fix the cause first |
idempotency_unavailable | 503 | Both | The Idempotency-Key could not be checked, so nothing was processed. Retry with the same key in a moment. | Yes, same key, after a moment |
internal_error | 500 | Both | Ours. Never carries internals. Quote the requestId when you ask for help. | Reads yes. Writes only with the same Idempotency-Key |
not_available | 501 | Merchant | Not available here (for example photo upload links). The message says what to do instead. | No |
not_configured | 503 | Merchant | This deployment is not set up for it. The message says what is missing. | No |
unknown_action | 400 | Merchant | The action asked for is not one the call offers. | No, fix the cause first |
Uploads
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
file_too_large | 413 | Storefront | The file is over the size limit. | No, fix the cause first |
too_large | 413 | Merchant | The upload is over the size limit. | No, fix the cause first |
bad_image | 415 | Merchant | The file is not an image of a type we accept. | No, fix the cause first |
bad_source | 400 | Merchant | The photo source could not be read: send a link, base64 data or an upload id. | No, fix the cause first |
Orders and payments
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
already_paid | 409 | Storefront | The order is already paid. | No, fix the cause first |
payment_unavailable | 409 | Storefront | No chargeable payment provider is connected, or the provider refused to open a checkout. The message says which. | No, fix the cause first |
delivery_changed | 422 | Storefront | The delivery choice or price changed since the quote. Quote again and confirm the new delivery. | After you quote again |
no_payment | 422 | Storefront | No payment was started for this order, so there is nothing to confirm. | No, fix the cause first |
needs_browser | 422 | Merchant | This needs the merchant in a browser or the app (for example entering payment credentials), not a connected AI app. | No, fix the cause first |
no_card | 400 | Merchant | No card on file. Add one first. | No, fix the cause first |
payment_failed | 400 | Merchant | The card did not go through. | No, fix the cause first |
change_failed | 400 | Merchant | The plan, add-on or extra change did not go through. The message says why. | No, fix the cause first |
out_of_stock | 409 | Merchant | Not enough stock to do that. | No, fix the cause first |
provider_error | 422 | Merchant | A courier, payment provider or Meta refused. Their reason, said plainly. | No, fix the cause first |
Delivery
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
no_quotes | 422 | Merchant | No courier could quote for this order. | No, fix the cause first |
booking_failed | 422 | Merchant | The courier would not take the booking. | No, fix the cause first |
no_shipment | 422 | Merchant | Nothing has been booked for this order yet. | No, fix the cause first |
quote_expired | 422 | Merchant | The courier quote has expired. Quote again. | After you quote again |
confirm_different | 409 | Merchant | The price changed since the quote. Confirm the new price to go ahead. | No, fix the cause first |
over_courier_limit | 403 | Merchant | The courier price is over what this connection may book without asking the merchant. | No, fix the cause first |
not_ready | 422 | Merchant | The order is not ready for that step. | No, fix the cause first |
not_tracked | 422 | Merchant | Stock is not tracked for this product. | No, fix the cause first |
Catalogue and shop
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
kit_not_found | 404 | Merchant | The product group does not exist. | No, fix the cause first |
kit_archived | 409 | Merchant | The product group is archived. | No, fix the cause first |
barcode_not_found | 404 | Merchant | No product matched that barcode. | No, fix the cause first |
code_taken | 409 | Merchant | That discount code is already in use. | No, fix the cause first |
needs_category | 422 | Merchant | Choose a business category before switching the storefront on. | No, fix the cause first |
needs_terms | 422 | Merchant | Accept the current merchant agreement before switching the storefront on. | No, fix the cause first |
needs_whatsapp | 422 | Merchant | Connect a WhatsApp number before switching that channel on. | No, fix the cause first |
facebook_not_connected | 409 | Merchant | Facebook is not connected for this store. | No, fix the cause first |
facebook_connect_expired | 409 | Merchant | The Facebook connect this call continues is gone: nothing was started, or ten minutes passed. Start again with POST /v1/merchant/stores/{storeId}/social/facebook/connect. | No, fix the cause first |
Studio and AI
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
insufficient_credits | 403 | Merchant | Not enough ShopEazy credits. Buy more, or add them in the console. | No, fix the cause first |
ai_not_configured | 403 | Merchant | AI is not set up for this store or deployment. | No, fix the cause first |
over_credit_limit | 403 | Merchant | This connection has used its monthly AI credit limit (the merchant sets it in Settings, AI apps). Nothing was spent. The business may still have credits; the merchant can raise the limit. | No, fix the cause first |
generation_failed | 502 | Merchant | The AI provider could not produce a result. Credits are only charged for a result. | Yes |
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
whatsapp_not_connected | 409 | Merchant | No WhatsApp number is connected. | No, fix the cause first |
whatsapp_reconnect_required | 409 | Merchant | The WhatsApp connection expired. Reconnect it. | No, fix the cause first |
whatsapp_not_configured | 503 | Merchant | WhatsApp is not set up on this deployment. | No, fix the cause first |
service_window_closed | 409 | Merchant | The 24 hour customer-service window is closed: only a template message can be sent. | No, fix the cause first |
whatsapp_rejected | 502 | Merchant | Meta refused the message. Its reason is in the message. | No, fix the cause first |
Sign-up and account
| Code | Status | API | Meaning | Retry |
|---|---|---|---|---|
already_onboarded | 409 | Merchant | This person already has a business on ShopEazy. | No, fix the cause first |
owns_business | 409 | Merchant | Close the businesses this person owns before closing the account. | No, fix the cause first |
terms_required | 400 | Merchant | The merchant terms and fee schedule must be accepted. | No, fix the cause first |
bad_code | 400 | Merchant | The verification code is wrong or expired. | No, fix the cause first |
disposable_email | 422 | Merchant | Disposable email addresses are not accepted. | No, fix the cause first |
send_failed | 502 | Merchant | We could not send the message (for example the verification code). Try again. | Yes |
lookup_failed | 502 | Merchant | The address lookup service did not answer. Try again. | Yes |
Codes on every operation
These can come back from any operation: missing_token, invalid_token, store_not_found, plan_limit, rate_limited, internal_error, and on the Merchant API subscription_locked and forbidden. A write can also return in_flight, idempotency_key_reused and idempotency_unavailable.