Endpoints
Every path below is relative to your store's base URL:
https://avada-bundle-upsell.firebaseapp.com/public-api/shop/YOUR-STORE.myshopify.comAll are GET. Anything else is refused — see Errors.
Everything a token can call
| Endpoint | Answers |
|---|---|
/status | The store record — plan, install date, revenue used this cycle |
/subscription | Plan and app plan, and nothing else |
/eligibility | Whether the store is on a paid plan |
/offers | List and search offers |
/offer/:id | One offer, in full |
/offers/count | Offer counts by type |
/analytics | Revenue, orders and conversion, with a comparison period |
/settings | The store's widget settings |
/diagnostic | Why the widget is or is not live — the one that reaches a verdict |
/app-status | Theme embed state, per offer-type block |
/themes | The store's themes |
/themes/check | Embed and theme support, in one small response |
/languages | Published storefront languages |
/discounts | Active app discounts in Shopify |
/installed-apps | Which other AOV.ai apps are on the store |
/diagnostic
Start here. It reaches a conclusion rather than handing back raw data, and it is the same data
the get_setup_status MCP tool returns.
Returns: the store's identity and install date, the theme embed state, whether settings have
been saved, offer counts by type and status, and issues — a list of codes in the order worth
acting on.
| Code | Means |
|---|---|
APP_EMBED_DISABLED | The app embed is off on the live theme |
NO_BLOCK_ADDED | The theme supports app blocks and none of ours is placed |
NO_ACTIVE_OFFERS | No offer is currently active |
THEME_NOT_OS2 | The live theme does not support app blocks |
NO_SETTINGS | No widget settings have been saved |
The order is the point. Act on issues[0] and re-check; the rest are usually consequences of
it. A monitor that alerts on every code will page you five times for one cause.
This endpoint calls Shopify to inspect the live theme, so it is slower than the rest. Poll it on the order of minutes, not seconds.
/offers
Query parameters:
| Name | Notes |
|---|---|
name | Search text. Present and non-empty switches to search mode |
type | One offer type — see below |
status | active, scheduled, expired, draft, all |
page | Defaults to 1 |
limit | Defaults to 20 |
Offer types: fbt, volume-discount, mix-match-bundle, bundle-builder,
product-fixed-bundle, upsell. The last is what the app calls Product Recommendation.
Returns: {offers: [...], pagination: {...}}.
The default limit here is 20; the MCP tool's is 10. The two surfaces do not share a default, so
a script and an assistant asked the same question can report different counts. Always send an
explicit limit when the total matters.
/offer/:id
Returns: the full offer record — every field, not the trimmed shape the MCP tool returns.
An unknown id answers 200 with success: true and no data, not a 404. Check for data
before using it.
/offers/count
Returns: a count per type plus total:
{"success": true, "data": {
"fbt": 3, "volume-discount": 12, "mix-match-bundle": 0,
"bundle-builder": 1, "product-fixed-bundle": 0, "upsell": 4,
"total": 20
}}Cheaper than listing offers to count them, and the right call for a dashboard tile.
On an internal failure this endpoint answers success: false with data: {total: 0}. A client
that reads data.total without checking success will render a confident zero. Check success.
/analytics
Query parameters:
| Name | Notes |
|---|---|
from / to | YYYY-MM-DD. Preferred |
start / end | Accepted as aliases for the same thing |
offerId | Narrow to one offer |
timezone | Defaults to Asia/Bangkok |
firstRequest | true also returns the offer list, for populating a filter |
Returns: additionalRevenue, bundleOrders, totalOrders, avgExtraRevenue, conversion,
ordersByType, and the range actually used.
Each metric comes back with a comparison against the immediately preceding period of equal length, so a 30-day window is compared against the 30 days before it. That comparison is computed whether you want it or not — it is not something you request.
Send timezone explicitly. The default is Asia/Bangkok, which is almost certainly not your
store's. A day boundary in the wrong timezone silently shifts revenue between days, and nothing in
the response flags it.
An invalid range answers success: false with Invalid date range. A valid-looking but impossible
date (2026-02-31) may be accepted and normalised instead — compare the range in the response
against what you sent.
/status
Returns: the store record, restricted to a fixed allowlist: shopifyDomain, name, plan,
appPlan, appStatus, isInstalled, shopifyPlan, shopifyPlanName, shopCountry,
installedAt, createdAt, updatedAt, pricingVersion, pricingAdditionalRevenue,
pricingResetDate, plus the store's feature flags.
pricingAdditionalRevenue and pricingResetDate are the useful pair: revenue used this billing
cycle, and when it resets. Both are USD.
Feature flags are always present, defaulting to false, even on a store where they have never been
set. You can test === false safely.
/subscription
Returns: shopId, domain, plan, appPlan. Nothing else.
Use it when a plan check is all you need — /status returns far more for the same cost.
/eligibility
Returns: shopDomain, isPaid, and a features map.
The features map is a capability list that today reports a single always-true entry. Treat it as
forward-looking: read a named key if you need one, do not assume the set is stable.
/settings
Returns: the store's saved widget settings, or {} on a store that has never saved any.
An empty object here is exactly what NO_SETTINGS in /diagnostic is reporting.
/app-status
Returns: embedEnabled, isSupportThemeOS, themeId, and blockStatuses — a flag per offer
type saying whether that type's app block has been placed in the theme.
This is the endpoint that answers "is the Volume Discount block actually on the page", per type,
which /diagnostic only summarises.
Calls Shopify. Slower than the Firestore-backed endpoints.
/themes
Returns: every theme on the store, as id, name and role. The live one has
role: "main".
Read-only, and deliberately so: nothing on this API can write to a theme. Enabling the app embed happens in the merchant's own theme editor.
/themes/check
Returns: embedEnabled, isSupportThemeOS, themeId.
The small, fast version of /app-status when you only need to know whether the embed is on.
/languages
Returns: the store's published storefront languages, each with locale, name, primary and
published.
/discounts
Returns: up to 50 active app discounts from Shopify — id, title, status, type
(DiscountCodeApp or DiscountAutomaticApp) and code where there is one.
Only active discounts, and only the first 50. There is no pagination, so on a store with many discounts this is a sample, not an inventory.
/installed-apps
Returns: which other AOV.ai apps are installed on the store. Useful for deciding whether a cross-app integration is available before offering it.
Not available to a merchant token
Some routes under this prefix exist for internal support tooling and answer
403 This endpoint requires an internal API key. Reading theme file contents is among them —
/diagnostic and /app-status inspect the theme server-side and return conclusions, which is what
an integration actually needs.
Every write route — creating, updating, deleting or toggling offers, and updating settings — is refused for a different reason: the token is read-only regardless of the route.
Related
- Overview
- Errors
- MCP tool guide — what these fields mean, in more depth