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.
Summary
GET /data/analytics/summaryShop-wide roll-up for a date range. Matches the Analytics dashboard.
| Parameter | Type | Notes |
|---|---|---|
start, end | date | YYYY-MM-DD, inclusive. Max 90 days. Default: last 30 days |
Response
| Field | Type | Notes |
|---|---|---|
totalUpsoldValue | number | Upsold revenue in the range |
totalUpsoldOrders | number | Orders that accepted an upsell |
totalOrders | number | Every order your store took in the range |
upsellSuccessRate | number | totalUpsoldOrders ÷ totalOrders |
averageUpsoldValue | number | Upsold value per upsold order |
offerViews | number | Offer impressions across all four placements |
currency | string | |
appliedWindow | object | The 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/statsOne row per placement for a date range.
| Parameter | Type | Notes |
|---|---|---|
start, end | date | Max 90 days. Default: last 30 days |
type | string | One placement. Omit for all four |
Response
| Field | Type | Notes |
|---|---|---|
placements | array | placement, impressions, claims, and revenue |
shopOfferCount | integer | Offers in the store, any status |
currency | string | |
appliedWindow | object |
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 as0, which would read as a problem. shopOfferCountis 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/topOffers ranked by revenue earned in a date range.
| Parameter | Type | Notes |
|---|---|---|
start, end | date | Max 90 days. Default: last 30 days |
limit | integer | How many rows. Default 3 |
order | string | highest (default) or lowest |
Response
| Field | Type | Notes |
|---|---|---|
offers | array | Offer rows with revenue, claims, acceptanceRate |
shopOfferCount | integer | Offers in the store, any status |
currency | string | |
appliedWindow | object |
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: trueand nourl. claimsandacceptanceRateare facts about that row, not a second ranking.order=loweststill 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/coverageWhich placements have an active offer, and which have nothing running.
| Parameter | Type | Notes |
|---|---|---|
start, end | date | For the revenue column. Default: last 30 days |
Response
| Field | Type | Notes |
|---|---|---|
placements | array | placement, hasActive, activeCount, totalCount, recentRevenue |
currency | string | |
appliedWindow | object |
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"
}
}recentRevenue uses the range.Lifetime
GET /data/analytics/lifetimeAll-time totals. No parameters. Matches the Statistics card on Home.
| Field | Type | Notes |
|---|---|---|
totalRevenue | number | All-time upsold revenue |
offerViews | number | All-time impressions |
upsoldOrders | number | All-time orders that accepted an upsell |
conversionRate | number | upsoldOrders ÷ offerViews — measured against views |
revenuePerVisit | number | totalRevenue ÷ offerViews |
currency | string |
Because there is no range, this is the endpoint to cache most aggressively. It is also the only way to see beyond 90 days.