Store API
A read-only REST API over this store's bundle data, for your own scripts, dashboards and
integrations. Every endpoint a merchant token can reach is a GET, and each token belongs to one
store.
If what you want is an AI assistant that answers questions about the store, you want MCP instead. This surface is for code you write.
| Base URL | https://avada-bundle-upsell.firebaseapp.com/public-api/shop/YOUR-STORE.myshopify.com |
| Methods | GET only |
| Credential | A token beginning aovmcp_ |
| Sent as | Authorization: Bearer aovmcp_… |
| Rate limit | 60 requests per minute, per token |
Copy the Public API base URL from the app rather than assembling the one above. It is shown in Settings → Integrations, on the Connect to AI and the API card, with your store's domain already filled in.
The same token as MCP
This is the one place this API differs from what you may expect: there is no separate API key. The token you create for an AI client is the same token you use here, and it grants exactly the same thing on both surfaces.
| Surface | For |
|---|---|
/mcp | AI clients |
/public-api | Your own scripts |
Create one in Settings → Integrations — see Authentication. Revoking a connection stops both at once.
A connection created by signing in through an AI client works here too. There is nothing wrong with that, but you will not have the token — the client fetched it and kept it. For a script, create a token by hand.
Your first request
curl "https://avada-bundle-upsell.firebaseapp.com/public-api/shop/YOUR-STORE.myshopify.com/status" \
-H "Authorization: Bearer aovmcp_YOUR_TOKEN"const base =
'https://avada-bundle-upsell.firebaseapp.com/public-api/shop/YOUR-STORE.myshopify.com';
const res = await fetch(`${base}/offers?status=active&limit=50`, {
headers: {Authorization: 'Bearer aovmcp_YOUR_TOKEN'}
});
const {success, data, message} = await res.json();The response shape
{"success": true, "data": { }}On failure, success is false and message carries a human-readable reason:
{"success": false, "message": "This token is read-only"}Branch on success and the HTTP status, not on message. There are no stable machine-readable
error codes on this surface — the message is written for a person reading a log and may be
reworded. success === false plus the status is the contract; the wording is not.
This differs from the Store API in AOV.ai's other apps, which return an error.code. Do not port
code that switches on one.
success: true does not always mean the resource exists. GET /offer/:id for an unknown id
answers 200 with success: true and data: undefined rather than a 404. Check that data is
present before using it.
Read-only, enforced by method
The rule is the HTTP method itself: a merchant token is refused on anything that is not a GET,
with 403 This token is read-only. There is no scope, no permission, and nothing you can be granted
that lifts it — so a token in a script cannot create, change or delete an offer even by accident.
Never call this from a browser
The API sends permissive CORS headers, so a request from browser JavaScript will succeed. That is there for server-side tools and local development, not for your storefront.
A token in browser code is a token you have published. Anything running in a shopper's browser is readable by that shopper; bundling or minifying changes nothing. Call this API from your server, and let the browser talk to your server.
If a token has ever shipped to a browser, revoke it in Settings → Integrations and create a new one. Removing the code does not un-publish what was already served.
Limits
| Requests | 60 per minute, per token |
| Rate-limit headers | X-RateLimit-Limit, X-RateLimit-Remaining on every response |
| Over the limit | 429, with Retry-After in seconds |
Requests are counted before the token is resolved, so a flood of bad tokens is throttled too. The budget is per token, so one integration cannot spend another's — give each script its own.
Read X-RateLimit-Remaining rather than counting requests yourself. It is on every response,
including successful ones.
What is never returned
- No customer data. No names, emails, addresses, or individual orders. Analytics are aggregates over a date range.
- No credentials. The Shopify access token and its hash are stripped from every response.
- No support-session or contact fields.
GET /statusreturns a fixed allowlist of fields, so a field added to the store record later does not start appearing here on its own. - No theme source. Reading theme files is not available to a merchant token.
- No write access. Every non-
GETrequest is refused.
Where next
- Endpoints — everything a token can call
- Errors — every refusal, and what to do about it
- MCP tool guide — what the fields mean, in more depth