Put the verified offer in front of shoppers.

A verified offer reaches the page three ways: a theme block with no developer, your developers on the public resolve and cart API, or your agent. Every price comes from the server, and a visitor keeps the same arm when your page passes the same visitor id to the server and the cart.

Sell: put it in front of shoppersIncludes parts not in production yet

A buy box drawn from a real response

In production

This is OF_DEV_KITS as the dev API served it to a buy box: three kits, each to subscribe or to buy once, and two first-order gifts. Choose a kit or a way to buy. Every amount is one the API priced, and only options a real cart matched can be chosen.

OF_DEV_KITS · USD

How to buy

Subscribe
Then $80.00 every 30 days
$58.60first order
Buy once
One delivery, no renewal
$80.00once

Choose your kit

Single Bag
Then $40.00 every 30 days
$32.00Original price $50.00
Starter Kit
Then $60.00 every 30 days
$48.00Original price $80.00
Ritual Set
Then $80.00 every 30 days
$58.60Original price $125.00

First-order gifts

2 of 2 in this kit
First order$58.60
Free shippingThen $80.00 every 30 days
  • 1 × Ritual Setwas $80.00, now $58.60
  • 1 × Gift: Mugwas $18.00, now Free
  • 1 × Gift: FrotherFree

matched on a real cart, 2 October 2026

The resolve response behind itexcerpt: the default option, ritual-set (1 of 6 options)
{
  "code": "OF_DEV_KITS",
  "status": "live",
  "store": {
    "market": {
      "countryCode": "US",
      "currencyCode": "USD"
    },
    "myshopifyDomain": "offer-suite-demo.myshopify.com"
  },
  "tracking": {
    "campaignName": "cmp_dev_store",
    "experienceName": "choose-your-kit-v1",
    "presellPageName": null,
    "preservedCartAttributes": []
  },
  "defaultOptionKey": "ritual-set",
  "options": [
    {
      "key": "ritual-set",
      "display": {
        "title": "Ritual Set"
      },
      "visible": true,
      "axisValues": {},
      "unitCount": 4,
      "cardKey": "ritual-set",
      "lines": [
        {
          "role": "main",
          "quantity": 1,
          "required": true,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092669284719",
            "variantId": "gid://shopify/ProductVariant/53479763214703"
          },
          "purchase": {
            "type": "subscription",
            "intervalDays": 30,
            "sellingPlanId": "gid://shopify/SellingPlan/708367581551"
          },
          "catalogPrice": {
            "capturedAt": "2026-09-16T00:00:00Z",
            "amountCents": 8000,
            "compareAtAmountCents": 12500
          }
        },
        {
          "role": "gift",
          "quantity": 1,
          "required": false,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092670660975",
            "variantId": "gid://shopify/ProductVariant/53479764722031"
          },
          "purchase": {
            "type": "one_time"
          },
          "catalogPrice": {
            "capturedAt": "2026-09-16T00:00:00Z",
            "amountCents": 1800,
            "compareAtAmountCents": 1800
          },
          "claimedValueCents": 1800
        },
        {
          "role": "gift",
          "quantity": 1,
          "required": false,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092670529903",
            "variantId": "gid://shopify/ProductVariant/53479764623727"
          },
          "purchase": {
            "type": "one_time"
          },
          "catalogPrice": {
            "capturedAt": "2026-09-16T00:00:00Z",
            "amountCents": 0,
            "compareAtAmountCents": null
          },
          "claimedValueCents": 2500
        }
      ],
      "pricing": {
        "promise": {
          "firstChargeCents": 5860,
          "renewalChargeCents": 8000
        },
        "compareAtPrice": {
          "source": "variant",
          "amountCents": 12500
        },
        "shipping": {
          "type": "free",
          "rates": {
            "type": "cheapest"
          },
          "charges": "every"
        },
        "mechanisms": [
          {
            "type": "managed_discount",
            "charges": "first",
            "discount": {
              "per": "line",
              "type": "fixed_amount",
              "amountCents": 2140
            },
            "lineRoles": [
              "main"
            ]
          }
        ]
      },
      "prices": {
        "first": {
          "totalCents": 5860,
          "lines": [
            {
              "lineIndex": 0,
              "role": "main",
              "variantId": "gid://shopify/ProductVariant/53479763214703",
              "quantity": 1,
              "sellingPlanId": "gid://shopify/SellingPlan/708367581551",
              "unitCents": 8000,
              "grossCents": 8000,
              "discountCents": 2140,
              "discountBy": "managed_discount",
              "netCents": 5860
            },
            {
              "lineIndex": 1,
              "role": "gift",
              "variantId": "gid://shopify/ProductVariant/53479764722031",
              "quantity": 1,
              "unitCents": 1800,
              "grossCents": 1800,
              "discountCents": 1800,
              "discountBy": "gift",
              "netCents": 0
            },
            {
              "lineIndex": 2,
              "role": "gift",
              "variantId": "gid://shopify/ProductVariant/53479764623727",
              "quantity": 1,
              "unitCents": 0,
              "grossCents": 0,
              "discountCents": 0,
              "discountBy": null,
              "netCents": 0
            }
          ]
        },
        "renewal": {
          "totalCents": 8000,
          "lines": [
            {
              "lineIndex": 0,
              "role": "main",
              "variantId": "gid://shopify/ProductVariant/53479763214703",
              "quantity": 1,
              "sellingPlanId": "gid://shopify/SellingPlan/708367581551",
              "unitCents": 8000,
              "grossCents": 8000,
              "discountCents": 0,
              "discountBy": null,
              "netCents": 8000
            }
          ]
        },
        "addOns": []
      },
      "checkoutMode": "direct_to_checkout",
      "verification": {
        "status": "matched",
        "checkedAt": "2026-10-02T15:02:52.297Z"
      }
    }
  ],
  "catalog": {
    "gid://shopify/ProductVariant/53479764722031": {
      "productId": "gid://shopify/Product/15092670660975",
      "productTitle": "Gift: Mug",
      "variantTitle": "Default Title",
      "image": null,
      "description": null,
      "status": "active"
    },
    "gid://shopify/ProductVariant/53479763214703": {
      "productId": "gid://shopify/Product/15092669284719",
      "productTitle": "Ritual Set",
      "variantTitle": "Default Title",
      "image": null,
      "description": null,
      "status": "active"
    },
    "gid://shopify/ProductVariant/53479764623727": {
      "productId": "gid://shopify/Product/15092670529903",
      "productTitle": "Gift: Frother",
      "variantTitle": "Default Title",
      "image": null,
      "description": null,
      "status": "active"
    }
  }
}
Resolve response for OF_DEV_KITS, dev store, captured 2 October 2026. Every amount is one the API computed. The theme block draws the same data with its own markup. No order is placed.

Three ways to show it

Each way serves a visitor the same version at the same server-computed prices. Only who does the work changes.

Three ways to show a verified offer, from no code to your own agent
WayWho does itHow
The Verified offer theme blockIn productionThe merchant, in Shopify's theme editor. No code.App Home's setup guide opens the theme editor with the block placed on a product template. Paste the offer's id into the block's text setting and, if you like, pick an accent colour. The store's public key is written for you.
Your developersIn productionYour front-end team, in your own markup or a headless storefront.GET /offers/{id}/resolve to draw the offer, POST /offers/{id}/price for any other selection, then POST /offers/{id}/cart for a checkout URL, or GET /offers/{id}/cart-input to send the cart to Shopify yourself.
Your agentIn productionYour agent, over MCP, with your workspace's key.resolve_offer, price_offer_selection and create_offer_cart: the same three steps as the API, one tool each.

$58.60

A full paid order went through the Verified offer theme block on a Plus dev store (order #1033, $58.60). On a Basic-plan dev store the block's cart reached a checkout charging the $800.00 it showed; no order was placed there.

Dev store, 29 September 2026

What resolve returns

In production

One call with the store's public key, GET /offers/{id}/resolve, returns everything a buy box draws, for the version this visitor is served or, while an experiment runs, their arm. The collapsed excerpt under the buy box above is this response, cut from the same capture.

StructureAxes, pools, options and their lines, with Shopify variant and selling-plan ids, tracking labels and the checkout mode.
PricesEach line's unit, gross, discount and net price for the first order and for the steady-state renewal, compare-at prices, and gifts with their claimed value.
VerificationEach option's verification status and the time a real cart last checked it.
CatalogKeyed by variant: product and variant titles, an image, a description excerpt and the product's status (active, draft, archived or deleted), not stock levels.
CachingAn ETag that changes with the version and its content, 304 on If-None-Match, and a private browser cache of 60 seconds.
Never returnedCosts, Shopify admin discount ids and verification evidence stay on the server.

In the product, prices are never computed in the browser

In production

Resolve already carries the price of every option on its default selection and of each optional add-on. Any other selection, such as a pick from a pool, is priced by POST /offers/{id}/price for the version that visitor is served.

Both answers come from the shared line-pricing module the publish verifier uses, which applies Shopify's per-unit floor to percentage discounts from Offer Suite's discount function. The buy box computes no price of its own: it formats the figures the API priced and, when add-ons are chosen, sums their API-priced totals.

878 carts

The shared line-pricing module the verifier uses matched what Shopify charged on 878 real dev-store carts.

Dev store, 28 September 2026

Same arm everywhere

In production
  • Pass the visitor's id as visitorId. While an experiment runs it pins that visitor to one arm, and resolve, price, cart and cart input all choose the version through one serving path, read on every request.
  • For a page rendered on your server, keep the id in a first-party os_visitor cookie and hand the same id to the browser, so the page and the cart see the same arm.
  • Send the version the page rendered as shownVersion. If the visitor is served another version by then, cart, cart input and price answer 409 offer_changed with the offer to render instead.
  • The theme block sends it and re-shows the offer on its own. Raw API callers and agents (create_offer_cart, get_offer_cart_input, price_offer_selection) pass shownVersion themselves; without it the cart is built from whatever version is served now.

Carts

In production
  • POST /offers/{id}/cart builds the Shopify cart for the chosen option and returns its checkout URL. GET /offers/{id}/cart-input instead hands your code the exact CartInput the same builder makes for the publish verifier, to send to Shopify yourself.
  • Carts reach Shopify with a private Storefront token and the buyer's IP. Shopify's cart warnings come back unchanged, and a Storefront throttle returns 429 storefront_throttled with Retry-After.
  • The verifier proves each visible option's default cart, not every selection a shopper can make.

A public key gets 120 requests a minute for each connecting IP, so one busy visitor does not throttle the store; a server rendering the buy box with a secret key can name the shopper in Offer-Suite-Buyer-IP to give each shopper their own bucket (the header is ignored from a public key). The full table is under rate limits.

Speed

In production

The API runs beside its databases: Cloudflare placement is hinted to us-east-2, next to Postgres and ClickHouse in Ohio. Shoppers far from the US pay a longer network hop to reach it.

338 ms

Measured on the dev API from São Paulo, resolve fell from 1,389 ms to 338 ms p50 on an edge-cache miss once the API was placed beside its databases. Production latency has not been measured.

Dev API, 16 September 2026

What it does not do yet

  • The browser SDK is not published yet (no npm package or hosted script). Your developers call the HTTP API directly. Not available yet
  • The React bindings are not on npm either. Not available yet
  • The <os-offer> element ships only inside the theme block; it is not hosted as a script of its own. Partly available
  • The theme block has no offer picker: the merchant pastes the offer's id, and the dashboard does not yet show that id as text to copy (it is in the offer page's address).
  • Neither the theme block nor <os-offer> draws category builders (options with several category groups or a unit cap); price those through the API. Partly available
  • An offer is written and published through the API, MCP or an agent before any of these can show it; App Home does not author offers.

Questions

Do I need a developer?

Not to show an offer. A merchant adds the Verified offer theme block in the theme editor and pastes the offer's id; the store's key is written automatically. Writing and publishing the offer happens through the API, MCP or an agent. Developers come in when you want your own markup, a headless storefront or a category builder. Offer Suite is not open yet: no brand has been invited, and brands join the waitlist.

Does the price on the page match what checkout charges?

Before a version goes live, each visible option's default cart is built on Shopify and must charge what the offer promises. The page's prices come from the same line-pricing module that check uses, so they are the figures the verifier expects Shopify to charge.

Can I cache the resolve response?

A browser may keep it for 60 seconds, and its ETag lets the next request come back as a 304 when nothing changed. Send the version you rendered with the cart: if the visitor is served another version by then, the cart is refused with offer_changed and the offer to render, so no cart is built for a version the page did not show.