aov-post-purchase-upsell
🔌 Store API
Resources
Analytics

Analytics

Five endpoints: one shop-wide roll-up, one per-placement breakdown, one revenue ranking, one coverage check, and one all-time total.

All paths are relative to https://aov-post-purchase.firebaseapp.com/public-api/v1. Four of the five take start and end — see Date ranges.

⚠️
These five measure different things and are not interchangeable. The two mistakes that produce wrong numbers are adding per-placement rows into a shop total, and comparing an all-time figure with a dated one. Each section below says which is which.

Summary

GET /data/analytics/summary

Shop-wide roll-up for a date range. Matches the Analytics dashboard.

ParameterTypeNotes
start, enddateYYYY-MM-DD, inclusive. Max 90 days. Default: last 30 days

Response

FieldTypeNotes
totalUpsoldValuenumberUpsold revenue in the range
totalUpsoldOrdersnumberOrders that accepted an upsell
totalOrdersnumberEvery order your store took in the range
upsellSuccessRatenumbertotalUpsoldOrders ÷ totalOrders
averageUpsoldValuenumberUpsold value per upsold order
offerViewsnumberOffer impressions across all four placements
currencystring
appliedWindowobjectThe window actually used, and whether you chose it
upsellSuccessRate is a share of all orders, not a conversion rate against views. For conversion measured against impressions, use conversionRate from Lifetime, or an offer's acceptanceRate from Top offers.
curl -H "Authorization: Bearer $AOV_TOKEN" \
  "https://aov-post-purchase.firebaseapp.com/public-api/v1/data/analytics/summary?start=2026-08-01&end=2026-08-26"
{
  "success": true,
  "data": {
    "totalUpsoldValue": 12480.75,
    "totalUpsoldOrders": 214,
    "totalOrders": 3180,
    "upsellSuccessRate": 6.73,
    "averageUpsoldValue": 58.32,
    "offerViews": 2905,
    "currency": "USD",
    "appliedWindow": { "start": "2026-08-01", "end": "2026-08-26", "source": "merchant" },
    "shopDomain": "acme.myshopify.com"
  }
}

Stats

GET /data/analytics/stats

One row per placement for a date range.

ParameterTypeNotes
start, enddateMax 90 days. Default: last 30 days
typestringOne placement. Omit for all four

Response

FieldTypeNotes
placementsarrayplacement, impressions, claims, and revenue
shopOfferCountintegerOffers in the store, any status
currencystring
appliedWindowobject
⚠️
Do not add these rows into a shop total. They are separate measurements. Post-purchase counts impressions and claims on its first upsell only, while its revenue spans every slot — the columns do not share a denominator. For a shop-wide figure use Summary.

Two absences are deliberate:

  • Coupon offer rows carry no revenue. A coupon rewards a future order the app cannot attribute back, so the field is omitted rather than reported as 0, which would read as a problem.
  • shopOfferCount is there to disambiguate zeroes. All-zero rows in a store with 12 offers mean something very different from all-zero rows in a store that has never created one.

Top offers

GET /data/analytics/top

Offers ranked by revenue earned in a date range.

ParameterTypeNotes
start, enddateMax 90 days. Default: last 30 days
limitintegerHow many rows. Default 3
orderstringhighest (default) or lowest

Response

FieldTypeNotes
offersarrayOffer rows with revenue, claims, acceptanceRate
shopOfferCountintegerOffers in the store, any status
currencystring
appliedWindowobject

Three things follow from this being built on claim history rather than the offer list:

  • An offer that earned nothing in the range is absent, not ranked last. To reach those, call /data/offers?sort=revenue_low.
  • A top earner can be an already-deleted offer. Those rows carry isDeleted: true and no url.
  • claims and acceptanceRate are facts about that row, not a second ranking. order=lowest still ranks by revenue — and only among offers that earned something.
curl -H "Authorization: Bearer $AOV_TOKEN" \
  "https://aov-post-purchase.firebaseapp.com/public-api/v1/data/analytics/top?limit=5&order=highest"

Coverage

GET /data/analytics/coverage

Which placements have an active offer, and which have nothing running.

ParameterTypeNotes
start, enddateFor the revenue column. Default: last 30 days

Response

FieldTypeNotes
placementsarrayplacement, hasActive, activeCount, totalCount, recentRevenue
currencystring
appliedWindowobject

totalCount alongside activeCount is what separates "nothing created here" from "offers exist but none are active"activeCount is 0 in both, and they call for completely different actions.

Coupon offer rows carry no recentRevenue, for the same reason as in Stats.

{
  "success": true,
  "data": {
    "placements": [
      { "placement": "Post-purchase upsell", "hasActive": true, "activeCount": 4, "totalCount": 7, "recentRevenue": 8210.4 },
      { "placement": "Order status page upsell", "hasActive": false, "activeCount": 0, "totalCount": 0, "recentRevenue": 0 }
    ],
    "currency": "USD",
    "shopDomain": "acme.myshopify.com"
  }
}
This endpoint does not enforce the 90-day cap, because its counts are current-state rather than windowed. Only recentRevenue uses the range.

Lifetime

GET /data/analytics/lifetime

All-time totals. No parameters. Matches the Statistics card on Home.

FieldTypeNotes
totalRevenuenumberAll-time upsold revenue
offerViewsnumberAll-time impressions
upsoldOrdersnumberAll-time orders that accepted an upsell
conversionRatenumberupsoldOrders ÷ offerViews — measured against views
revenuePerVisitnumbertotalRevenue ÷ offerViews
currencystring
⚠️
These are all-time figures with no window. Never subtract them from a dated result, and never label them as a month or a quarter — the difference between an all-time total and a 30-day total is not "the rest of the time", it is two different measurements.

Because there is no range, this is the endpoint to cache most aggressively. It is also the only way to see beyond 90 days.

Related

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