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

Products

One endpoint: upsell suggestions, ranked by real bought-together history from your own orders and padded with same-category best sellers when that history is thin.

This is the same engine the offer-creation modal uses, so the API and the app never show different lists.

Recommendations

GET /data/products/recommendations
ParameterTypeNotes
offerIdstringPreferred. The app reads that offer's own trigger products, so you name nothing
productIdstringA Shopify product gid — gid://shopify/Product/...
productNamestringA product title, resolved on the server

Pass none of them and you get the store's best sellers.

Four possible shapes

The reply states its own cause in fields rather than by absence. Branch on which key is present.

Bought-together suggestions

The normal answer.

FieldNotes
triggersOne entry per trigger product: id, title, plus the two lists below
triggers[].boughtTogetherReal co-purchase evidence. Each row carries id, title, price, coPurchasedOrders, pastRevenue
triggers[].sameCategoryPicksPadding: same product type, ranked by store-wide orders. Carries id, title, price — and deliberately no co-purchase numbers

At most 6 suggestions come back per trigger, counted across both lists together — co-purchase rows first, so padding is what gets dropped when the cap bites. | omittedTriggers | Present when the offer had more trigger products than were looked up | | deletedTriggers | Present when some of the offer's trigger products no longer exist in Shopify | | currency | |

The two lists are separate keys, not one list with a flag, because a flag gets ignored — and a padded pick read as bought-together evidence is a recommendation resting on nothing. An empty boughtTogether is a real answer: this store has no co-purchase history for that product yet. It never quietly becomes a best-seller list.

Best sellers only

FieldNotes
shopBestSellersid, title, price. At most 5
bestSellersOnlytrue

This is a real answer, not an error — and the cause is always stated as a field, so you never have to guess why. Exactly one of these rides along:

Cause fieldWhat happened
nothingNamed: trueYou sent no offerId, productId or productName
hasProductTrigger: falseThe offer's triggers name collections, tags or an order value — nothing bought-together history can answer
hasProductTrigger: true with deletedTriggersEvery one of the offer's trigger products has been deleted from Shopify, so the offer cannot fire at all
namedProduct: "deleted"The productId you sent resolves to nothing
namedProduct: "no_match"No product goes by the productName you sent
namedProduct separates two facts that send you looking in different places: an id that no longer resolves is a deleted product, while a title nothing matches is usually a typo or a renamed product.

Ambiguous product name

FieldNotes
candidatesProducts matching the title you sent

Pick one and call again with its productId.

No product slot

FieldNotes
holdsUpsellProductsfalse
couponOffertrue

A Coupon offer has no product slot to fill, so this shape carries no product list at all — not even best sellers, because a list on screen is itself the claim that it can be used here.

Example

curl -H "Authorization: Bearer $AOV_TOKEN" \
  "https://aov-post-purchase.firebaseapp.com/public-api/v1/data/products/recommendations?offerId=8Kd2mQ"
{
  "success": true,
  "data": {
    "triggers": [
      {
        "id": "gid://shopify/Product/771",
        "title": "Trail Runner GTX",
        "boughtTogether": [
          { "id": "gid://shopify/Product/902", "title": "Merino Sock 3-pack", "price": 24, "coPurchasedOrders": 118, "pastRevenue": 2832 }
        ],
        "sameCategoryPicks": [
          { "id": "gid://shopify/Product/915", "title": "Gaiters", "price": 32 }
        ]
      }
    ],
    "currency": "USD",
    "shopDomain": "acme.myshopify.com"
  }
}

Related

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