🔌 Store API
Errors

Errors

Every failure comes back in the same envelope, with a stable code:

{"success": false, "data": null, "error": {"code": "INVALID_KEY", "message": "…"}}

Branch on error.code or on the HTTP status. Both are stable. The message is written for a human reading a log and may be reworded.

Every code

CodeHTTPCauseWhat to do
MISSING_KEY401No Authorization headerSend Authorization: Bearer aov_sk_…. Do not revoke your key — it is not the problem
INVALID_KEY401Unknown, revoked, or expired keyCreate a new key in Settings → Store API
WRONG_SURFACE401An aov_mcp_ key was used hereMint a Store API key; MCP keys work only at /mcp
NO_PLAN403Store is not on a paid planUpgrade, then create new keys — see below
APP_UNINSTALLED403Free Gift was uninstalled from the storeReinstall. New keys will not help
RATE_LIMIT429More than 60 requests in a minuteBack off about a minute
INVALID_INPUT400A parameter is wrongFix the parameter. Retrying unchanged will never succeed
NOT_FOUND404No such pathCheck the path against GET /store-api/v1
INTERNAL_ERROR500Something failed on the serverSafe to retry; the cause is in our logs, not the response

Which of these are worth retrying

Retry after a waitRATE_LIMIT, INTERNAL_ERROR
Never retry unchangedINVALID_INPUT, NOT_FOUND, WRONG_SURFACE
Stop and alert a humanMISSING_KEY, INVALID_KEY, NO_PLAN, APP_UNINSTALLED

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


NO_PLAN also revokes your keys

⚠️

When a request arrives from a store that is no longer on a paid plan, the response is NO_PLAN and every Store API key on that store is revoked. Restoring the plan does not restore the keys. Someone has to create new ones and update the integration.

Every other failure here is recoverable with the same key. This one is not, and an integration that retries through it will move from NO_PLAN to INVALID_KEY and stay there. Treat NO_PLAN as an alert, not as a backoff.

Nothing changes on the /mcp side: connections there are refused while the plan is gone and resume working when it returns.


MISSING_KEY and INVALID_KEY are deliberately separate

They look similar and their fixes are opposites.

MISSING_KEY means no header arrived. The key is fine; the request is not. Usually a header that was dropped by a proxy, a client library that needs headers passed differently, or a missing Bearer prefix.

INVALID_KEY means a key arrived and is not usable.

⚠️

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

An unknown key and a revoked key both return INVALID_KEY, on purpose. Distinguishing them would confirm to whoever is holding it that a key was once real.


WRONG_SURFACE

The key is valid — for the other surface. Free Gift issues aov_sk_ keys for this API and aov_mcp_ keys for MCP, and each is refused by the other.

This has its own code rather than being folded into INVALID_KEY because the fix is different: mint the right kind of key, rather than go looking for a typo in a key that has none.


INVALID_INPUT

A parameter the endpoint cannot use. The message names the parameter and the problem. Retrying an identical request cannot help — fix the parameter.

RequestWhy
/products with no handlesAt least one product handle is required
/products?handles=Same — nothing usable in the value
/products?handles=,,,No handle survives parsing

Other parameters are not currently rejected. An out-of-range ?days, an unrecognised ?status, or a timezone that does not exist are passed through rather than refused, so the response may be an empty result or a window you did not intend. Validate inputs on your side, and check period in the analytics response against what you asked for.


RATE_LIMIT

60 requests per minute, counted per key. Wait roughly a minute.

Requests are counted before the key is resolved, so a burst of bad keys is throttled the same way as a burst of good ones. The budget is per key, so one integration cannot spend another's.

If you are hitting this legitimately, batch instead: /products takes up to 50 handles in one call, and /campaigns returns every campaign in one response rather than one per id.


Empty results are not errors

Several endpoints answer "there is nothing here" with HTTP 200 and found: false:

  • /campaigns/{id} — no campaign with that id on this store
  • /widgets/{type} — this store never configured that widget type
  • /translations — nothing has been customised; the built-in wording is in use
  • /settings — no configuration saved yet

Treat found: false as an answer, not a failure. In particular, /translations returning found: false means the store uses default wording — not that the widget has no text.


Reporting a problem

Include the endpoint and full query string, the error.code, the HTTP status, the key's name from the connections list, and roughly when it happened.

Never send the key itself. Support cannot read it back either — the server holds only a fingerprint. If a key has appeared in a ticket, a screenshot, a log you shared, 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.