aov-bundle-upsell
🔌 Store API
Errors

Errors

Every failure comes back in the same envelope:

{"success": false, "message": "Invalid token"}
⚠️

Branch on the HTTP status and success, never on message. This surface has no stable machine-readable error codes — the message is written for a person reading a log and may be reworded at any time. If you have integration code from another AOV.ai app that switches on error.code, it will not work here.

Every refusal

MessageHTTPCauseWhat to do
Missing credentials401No Authorization header arrivedSend Authorization: Bearer aovmcp_…. Do not revoke your token — it is not the problem
Invalid token401Unknown token, revoked token, or the app is uninstalledSee below before creating a new one
Unauthorized401The token could not be checkedRetry once; if it persists, treat as Invalid token
This token is read-only403The request was not a GETUse GET. No token can be granted more
Token is not authorized for this shop403The domain in the URL is not this token's storeFix the base URL — see below
This endpoint requires an internal API key403An internal support routeNot available to merchants
Missing shop domain400The URL has no /shop/<domain>/ segmentYou are calling the prefix, not an endpoint
Rate limit exceeded429More than 60 requests in a minuteBack off; read Retry-After
(varies)500Something failed on the serverSafe to retry; the cause is in our logs, not the response

Which are worth retrying

Retry after a wait429, 500
Never retry unchanged403 — neither the method nor the token's permissions will change
Stop and alert a humanMissing credentials, Invalid token, Token is not authorized for this shop

A retry loop that treats every failure the same will hammer the API for an hour on a wrong method and hide the one error that needed someone's attention.


Missing credentials and Invalid token are deliberately separate

Missing credentials means no header arrived. The token is fine; the request is not — usually a header dropped by a proxy, an HTTP client that passes headers differently, or a missing Bearer prefix and its single space.

Invalid token means a token arrived and is not usable.

⚠️

Revoking is not reversible. Reading Missing credentials as "bad token" and revoking destroys a working credential while leaving the real problem — the missing header — exactly where it was.

An unknown token and a revoked one both return Invalid token, on purpose. Distinguishing them would confirm to whoever holds a token that it was once real.

Invalid token also covers an uninstalled app. Before creating a replacement, check in this order:

  1. Is the header present, with Bearer and one space?
  2. Is AOV.ai Bundle Upsell still installed on the store? Uninstalling revokes every token, and reinstalling does not restore them.
  3. Was the connection revoked in Settings → Integrations?

Token is not authorized for this shop

The token is valid; the domain in your base URL is not the store it belongs to. A token carries its own store, and the URL may only confirm that store, never select a different one — which is why editing the domain cannot be used to read someone else's data.

In practice this is a copy-paste error: a base URL kept from another store, or a custom domain used where the .myshopify.com one belongs. Copy the Public API base URL from Settings → Integrations, which already has the right domain in it.

The comparison ignores case, so MyStore.myshopify.com and mystore.myshopify.com both work. If you are seeing this error, the domain is genuinely a different store.


This token is read-only

The request used a method other than GET. Every write on this API needs an internal service key, and there is no merchant-facing scope that grants one.

This is checked on the method, before the route. So a POST to a path that does not exist answers 403 This token is read-only rather than a 404 — the message is about your method, not about the path being wrong.


Rate limit exceeded

Sixty requests per minute, counted per token, with Retry-After giving the seconds to wait.

Every response — including successful ones — carries X-RateLimit-Limit and X-RateLimit-Remaining. Read X-RateLimit-Remaining and slow down before you hit zero, rather than discovering the limit by tripping it.

Requests are counted before the token is resolved, so a burst of bad tokens is throttled the same way as a burst of good ones.


Successes that are not what they look like

Three cases return 200 and still do not mean what a naive client will assume:

RequestAnswerWhy it matters
GET /offer/:id with an unknown idsuccess: true, no dataNot a 404. Check data exists
GET /offers/count after an internal failuresuccess: false, data: {total: 0}A client reading data.total without checking success renders a confident zero
GET /settings on a store that never saved anysuccess: true, data: {}An empty object is the real answer, not an error
⚠️

Check success before reading data, on every call. The /offers/count case is the one that bites: it is the only endpoint that ships a plausible-looking payload alongside success: false.


Empty results are not errors

An endpoint answering "there is nothing here" — no offers matching a filter, no published languages beyond the primary one, no active discounts — returns 200 with an empty list. Treat that as an answer.


Reporting a problem

Include the endpoint and full query string, the HTTP status, the message, the connection's name from Settings → Integrations, and roughly when it happened.

Never send the token itself. Support cannot read it back either — the server holds only a fingerprint. If a token has appeared in a ticket, a screenshot, a shared log, or any browser bundle, revoke it and create a new one.

Related

Product
Install AppWebsiteAvada Apps
Resources
DocumentationFAQPrivacy Policy
Company
Avada GroupContact
© 2026 Avada Group. All rights reserved.