Errors
The error envelope, every error code and the detail reasons the ShopAssist API returns.
Every non-2xx JSON response has the same shape:
{
"error": {
"code": "forbidden",
"message": "This API key lacks the required scope.",
"details": { "required_scope": "leads:read" },
"request_id": "req_01k6a8x3qv6f2m9c4j7t5r0bwe"
}
}codeis stable; branch on it, not onmessageor the HTTP status alone.detailsdepends on the code (for validation errors it lists the invalidfields).request_ididentifies the request in our logs.
Validation errors are always 422 validation_error, with details.fields as [{ loc, msg, type }]. Unknown request fields are rejected. Ignore unknown fields and unknown enum values in responses: they may be added at any time.
Error codes
| Code | Meaning |
|---|---|
bad_request | The request is malformed, for example invalid JSON or a missing required header. Fix the request before retrying. |
invalid_id | An id has the wrong prefix (for example `sto_` where `conv_` was expected) or cannot be decoded. |
invalid_cursor | The pagination cursor is invalid or has expired. Start again from the first page. |
unauthenticated | No valid credentials were sent. Send `Authorization: Bearer sa_live_…` (merchant API) or sign in again. |
session_invalid | The widget session is missing, expired or revoked. The widget creates a new session automatically. |
identity_required | The operation needs a verified shopper identity (signed in, or an order verified by email code). |
identity_invalid | The identity token could not be verified. `details.reason` says which check failed (signature, issuer, audience, expiry). |
mfa_required | Multi-factor authentication is required, or a recent sign-in is needed for a sensitive change. |
signature_invalid | A webhook, plugin or proxy signature did not verify. Check the secret and the server clock. |
forbidden | The credentials are valid but not allowed to do this, for example a missing API key scope or a test key calling a write endpoint. |
origin_not_allowed | The page's origin is not in the store's allowed domains. Add the domain in the dashboard. |
csrf_failed | The request's `Origin` header could not be verified. Browsers send it automatically; reload the page. |
email_not_verified | The account email must be verified before this action. |
plan_feature_unavailable | The feature is not in the current plan. `details.required_plan` names the lowest plan that has it. |
plan_limit_exceeded | A plan limit was reached. `details` holds `limit`, `value` and `max`. |
store_suspended | The store is suspended. Contact support. |
organization_suspended | The organization is suspended. Contact support. |
challenge_required | A bot-protection challenge must be solved before the request is accepted. |
verification_code_invalid | The one-time code is wrong. `details.attempts_remaining` says how many attempts are left. |
not_found | The resource does not exist or is not visible to these credentials. |
method_not_allowed | The HTTP method is not allowed on this path. |
conflict | The request conflicts with the current state. `details.reason` explains the conflict. |
response_in_progress | The assistant is still answering in this conversation. Wait for the current reply. |
conversation_closed | The conversation is closed. Start a new one. |
action_not_pending | The proposed action is no longer waiting for confirmation (already confirmed, declined or expired). |
sync_in_progress | A sync of this kind is already running for the store. |
idempotency_in_progress | A request with the same `Idempotency-Key` is still being processed. Retry after a moment. |
action_expired | The confirmation card expired before it was confirmed. |
verification_expired | The verification (for example an email code) expired. Request a new one. |
token_expired | The link or token has expired or was already used. |
version_conflict | The resource was changed by someone else since you read it. Reload and apply your change again. |
payload_too_large | The request body exceeds the size limit of the endpoint. |
message_too_long | The chat message exceeds the maximum length. |
file_too_large | The uploaded file exceeds the size limit. |
unsupported_media_type | The content type is not supported by this endpoint. |
validation_error | The request failed validation. `details.fields` lists each field with its location, and `details.reason` names rule failures. |
idempotency_key_reused | The `Idempotency-Key` was already used with a different request body. |
action_no_longer_eligible | The action is no longer possible, for example because the order shipped in the meantime. |
verification_locked | Too many wrong codes were entered. Request a new code later. |
rate_limited | Too many requests. Wait for the number of seconds in `Retry-After`; `details.scope` names the limit. |
quota_exceeded | The usage quota was reached. `details.reason` says which quota (for example `free_cap_reached`). |
internal_error | Something went wrong on our side. Retry with backoff and contact support with the `request_id` if it persists. |
not_implemented | The endpoint exists in the contract but is not available yet. |
platform_error | The store platform did not respond as expected. `details.retryable` says whether a retry can help. |
service_unavailable | ShopAssist is temporarily unavailable, for example during maintenance. Retry after `Retry-After`. |
Detail reasons
Some codes carry details.reason with a more specific cause:
unauthenticated
| Reason | Meaning |
|---|---|
code_invalid | The connection or pairing code is unknown, expired, already used or does not match the platform. |
identity_invalid
| Reason | Meaning |
|---|---|
malformed | The token is not a valid JWT. |
algorithm | The token is not signed with HS256. |
unknown_kid | The `kid` header does not match the store's current or previous identity key. |
signature | The signature does not verify with the identity secret. |
issuer | `iss` is not `generic:{store_id}` (or the platform issuer). |
audience | `aud` is not `shopassist`. |
session_mismatch | `sid` is not the widget session that requested the token. |
expired | The token has expired. |
not_yet_valid | `iat` or `nbf` lies in the future (check the server clock). |
lifetime_too_long | `exp − iat` is longer than 300 seconds. |
mfa_required
| Reason | Meaning |
|---|---|
mfa_pending | Sign-in is waiting for the second factor. |
recent_auth_required | This change needs a recent sign-in or a fresh MFA code. |
session_invalid
| Reason | Meaning |
|---|---|
missing | No session token was sent. |
malformed | The session token cannot be parsed. |
signature | The session token signature is invalid. |
store_mismatch | The session belongs to another store. |
origin_mismatch | The session was created on another origin. |
expired | The session expired. |
revoked | The session was revoked. |
signature_invalid
| Reason | Meaning |
|---|---|
headers_missing | Signature headers are missing. |
unknown_store | The store in the signature is unknown. |
unknown_key | The key id is not known for the store. |
timestamp | The timestamp is outside the allowed window; check the server clock. |
nonce | The nonce was already used. |
store_mismatch | The key belongs to a different store. |
signature | The signature does not match. |
forbidden
| Reason | Meaning |
|---|---|
abuse_blocked | The visitor or session is blocked by abuse protection. |
test_key | Test keys cannot call write endpoints. |
email_mismatch | The invitation is for a different email address. |
ip_not_allowed | The request IP is not allowed for this endpoint. |
support_access_not_granted | The merchant has not granted support access. |
operator_mfa_required | Operators need TOTP multi-factor authentication. |
feature_disabled | The feature is switched off for this store. |
not_generic | This endpoint only works for stores connected with the Generic connector. |
challenge_required
| Reason | Meaning |
|---|---|
invalid_solution | The challenge solution was wrong. |
expired | The challenge expired. |
conflict
| Reason | Meaning |
|---|---|
identity_changed | The signed-in shopper changed during the conversation. |
identity_required_for_conversation | This conversation belongs to a signed-in shopper. |
store_already_connected | The site is already connected to another ShopAssist store. |
clone_detected | The connection looks like a copy of another site (staging clone). |
feed_authoritative | A product feed owns this catalog; the Push API cannot write to it. |
generic_only | The operation is available only for Generic stores. |
no_deletion_scheduled | No deletion is scheduled that could be cancelled. |
scope_required | A platform scope is missing (`details.scope`). |
handoff_active | A handoff is already active. |
open_conversations_limit | Too many open conversations for this session. |
images_limit | The image limit of the conversation is reached. |
retry_not_allowed | The message cannot be retried. |
already_taken_over | Another agent already took over. |
not_assignee | Only the assigned agent can do this. |
mode_not_human | The conversation is not in human mode. |
billed_by_shopify | The account is billed by Shopify; change the plan in Shopify. |
manual_billing | The account is billed manually by COEC. |
contact_sales | This change needs a sales contact. |
subscription_exists | A subscription already exists. |
unpaid_invoices | There are unpaid invoices. |
currency_locked | The billing currency cannot change after the first invoice. |
no_subscription | There is no subscription. |
no_change | The requested plan is the current plan. |
payment_past_due | A payment is past due. |
payment_method_required | Add a payment method first. |
no_scheduled_change | No plan change is scheduled. |
no_customer | No billing customer exists yet. |
annual_cancel_in_dashboard | Cancel annual plans in the dashboard. |
scheduled_change_pending | A plan change is already scheduled. |
not_shopify | The account is not billed by Shopify. |
run_closed | The replace_all run is already finished or expired; start a new run. |
validation_error
| Reason | Meaning |
|---|---|
too_short | The password is too short. |
password_too_long | The password is too long. |
context_word | The password contains your name, email or the product name. |
common_password | The password is too common. |
breached_password | The password appeared in a data breach. |
password_required | A password is required. |
terms_version_outdated | The terms were updated; reload and accept the current version. |
https_required | The URL must use HTTPS. |
port_not_allowed | The URL uses a port that is not allowed. |
url_not_public | The URL does not resolve to a public internet address. |
ssrf_blocked | The URL points to a private or reserved network. |
base_url_forbidden | This base URL is not allowed. |
privacy_policy_url_origin | The privacy policy URL must be on the store's own domain. |
malware_detected | The file failed the malware scan. |
residency_ack_required | Confirm the BYOK data-processing attestation. |
invalid_key_format | The API key does not have the provider's format. |
key_provider_mismatch | The key belongs to a different provider. |
gemini_free_tier | Free-tier Gemini keys are not accepted; use a paid-tier key. |
unknown_model | The model is not known for this provider. |
api_key_required | An API key is required. |
rule_override | The text tries to override a fixed assistant rule. |
discount_code_not_registered | The text mentions a discount code that is not registered. |
ai_disclosure_conflict | The text would hide or deny that the assistant is an AI. |
persona_name_implies_human | The assistant name suggests a human. |
unfair_practice_lint | The text uses fake urgency or scarcity. |
consent_text_lint | The consent text is not specific enough. |
unknown_consent_text_version | The consent text version is unknown. |
cmp_category_map_required | Map your consent tool's categories first. |
attestation_required | An attestation must be confirmed first. |
version_not_current | The legal document version is not the current one. |
store_required | The attestation needs a store. |
site_mismatch | The ShopAssist store is connected to a different site. |
use_shopify_app_store | Shopify stores are installed from the Shopify App Store. |
signature_invalid_check_offloader_header | Magento could not verify our requests; set the offloader header. |
waf_blocked | A firewall blocked ShopAssist. |
basic_auth | The store is protected by HTTP Basic authentication. |
unreachable | ShopAssist could not reach the store. |
acl_missing | The Magento integration lacks permissions. |
rest_blocked | The WordPress REST API is blocked for our requests. |
signature_invalid | The connection signature could not be verified; check the server clock. |
not_purchasable | The product cannot be bought. |
out_of_stock | The variant is out of stock. |
excluded | The product is hidden from the assistant. |
max_qty | The quantity exceeds the maximum. |
max_extensions | The confirmation cannot be extended again. |
in_stock | The variant is in stock, so no back-in-stock alert is needed. |
consent_required | Consent is required first. |
credential_rejected | The integration rejected the credentials. |
currency_not_allowed | The currency is not offered. |
country_required | A billing country is required. |
trial_plan_invalid | Trials are only for the trial plan. |
trial_already_redeemed | A trial was already used for this store domain. |
store_not_connected | Connect the store first. |
currency_mismatch | The currency does not match the billing account. |
amount_out_of_range | The amount is outside the allowed range. |
url_not_allowed | The URL is not https on one of the store's origins or allowed link domains. |
template_invalid | The URL template is invalid (unknown placeholders, or {items} missing). |
mapping_invalid | The field mapping could not be applied to the feed. |
header_not_allowed | This HTTP header name may not be set on outgoing feed or RPC requests. |
run_fields_required | This request needs the run fields (run_id and mode) of an open import run. |
provider_priced | This billing account is priced by its provider (e.g. Shopify), so this override is not allowed. |
action_no_longer_eligible
| Reason | Meaning |
|---|---|
status_changed | The order status changed. |
already_shipped | The order has shipped. |
ownership | The order does not belong to the shopper. |
policy | The store's policy does not allow it. |
window_closed | The time window has closed. |
rate_limited
| Reason | Meaning |
|---|---|
auth_ip | Too many sign-in attempts from this address. |
auth_account | Too many sign-in attempts for this account. |
auth_lockout | Sign-in is temporarily locked after failed attempts. |
visitor_conversations | Too many new conversations from this visitor. |
live_streams | Too many open live streams. |
test_conversations | Too many test conversations. |
shopper_mail | Too many emails to this shopper. |
webhook_test | Too many webhook test deliveries. |
quota_exceeded
| Reason | Meaning |
|---|---|
test_daily_cap_reached | The daily limit of test AI replies is reached. |
free_cap_reached | The Free plan's monthly conversation cap is reached. |
trial_cap_reached | The trial's conversation cap is reached. |
overage_budget_reached | The overage budget is used up. |
subscription_inactive | The subscription is not active. |
managed_ai_not_included | Managed AI is not included in the plan. |
byok_cap_reached | The own-key conversation allowance is reached. |
plan_cap_reached | The plan's included conversations are used and overage is off. |
store_over_plan_limit | The store is over the plan's store limit. |
Retrying
Retry 429, 502 with details.retryable: true, 503 and network errors with exponential backoff, honouring Retry-After. Use an idempotency key on POST requests you retry.
Last updated on