One document for every offer.

Your agent writes each offer as one document that the schema validates field by field. It covers every structure through the same few primitives, and nothing reaches Shopify until a publish.

Author: your agent writes the offerIn production

One document, every structure

In production

An offer is one strict JSON document in the offer/1 format. There is no list of offer types to pick from: a bundle, a subscription, a gift ladder and a build-your-own box are the same few primitives arranged differently. Options say what a buyer can choose, lines with a main, add-on or gift role say what goes in the cart, and each option carries its own pricing and shipping.

Your agent writes the document through the API (POST /offers, PUT /offers/{id}) or the MCP tools built from it, create_offer and replace_offer.

StructureWhat it doesDocument key
Quantity tiersOne option per tier (1 bag, 2 bags, 4 bags), each with the price it promises the buyer.options, unitCount, pricing.promise
Per-unit tiersIn a category builder, a group's first-order unit price falls as the cart holds more units of it; the discount function charges it at checkout.unit_price_tiers
Subscribe and saveLines bought on the selling plans you already sell through, which can share a card with a one-time twin (cardKey). In a category builder, a group's plan can step by its units (purchase.plans).purchase.sellingPlanId, purchase.plans, cardKey
Build your ownThe buyer picks from a minimum to a maximum number of units from a pool of variants, in any mix, with repeats allowed or not.pools, variantSource.type: pool, quantity, maxQuantity
Add-onsOptional lines a cart holds only when the request names their key.role: add_on, required: false, key
Free gifts and gift laddersGift lines are real products the discount function prices to zero, only up to the units the option includes. Each option carries its own gifts, so a buy box can show a larger option's gifts as the locked next step.role: gift
AxesFlavour, size or grind: each option names its value on each axis, and a line's variant follows those values.axes, axisValues, variantSource.type: axis_map
Free shippingPer option: the cheapest rate, every rate, or rates named by title, on the first order only or on every renewal too.pricing.shipping, rates, charges
Checkout destinationWhere the page sends the buyer after the choice: the cart page, a cart drawer, straight to checkout, a cart on the landing page, or an external flow.checkoutMode
Value claimsBonuses and stated values the page shows. They are copy only and never enter the cart.valueClaims
Every structure and the keys that hold it, spelled as the schema spells them.
OF_DEV_KITS, the dev store's offeroffer/1, six options
{
  "schemaVersion": "offer/1",
  "code": "OF_DEV_KITS",
  "tracking": {
    "experienceName": "choose-your-kit-v1",
    "campaignName": "cmp_dev_store",
    "presellPageName": null,
    "preservedCartAttributes": []
  },
  "store": {
    "myshopifyDomain": "offer-suite-demo.myshopify.com",
    "market": {
      "countryCode": "US",
      "currencyCode": "USD"
    }
  },
  "axes": [],
  "pools": [],
  "defaultOptionKey": "ritual-set",
  "options": [
    {
      "key": "single-bag",
      "display": {
        "title": "Single Bag"
      },
      "visible": true,
      "axisValues": {},
      "unitCount": 1,
      "cardKey": "single-bag",
      "lines": [
        {
          "role": "main",
          "quantity": 1,
          "required": true,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092668957039",
            "variantId": "gid://shopify/ProductVariant/53479762788719"
          },
          "purchase": {
            "type": "subscription",
            "sellingPlanId": "gid://shopify/SellingPlan/708367581551",
            "intervalDays": 30
          },
          "catalogPrice": {
            "amountCents": 4000,
            "compareAtAmountCents": 5000,
            "capturedAt": "2026-09-16T00:00:00Z"
          }
        }
      ],
      "pricing": {
        "promise": {
          "firstChargeCents": 3200,
          "renewalChargeCents": 4000
        },
        "compareAtPrice": {
          "amountCents": 5000,
          "source": "variant"
        },
        "shipping": {
          "type": "free",
          "rates": {
            "type": "cheapest"
          },
          "charges": "every"
        },
        "mechanisms": [
          {
            "type": "managed_discount",
            "lineRoles": [
              "main"
            ],
            "discount": {
              "type": "fixed_amount",
              "amountCents": 800,
              "per": "line"
            },
            "charges": "first"
          }
        ]
      },
      "checkoutMode": "direct_to_checkout"
    },
    {
      "key": "single-bag-once",
      "display": {
        "title": "Single Bag"
      },
      "visible": true,
      "axisValues": {},
      "unitCount": 1,
      "cardKey": "single-bag",
      "lines": [
        {
          "role": "main",
          "quantity": 1,
          "required": true,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092668957039",
            "variantId": "gid://shopify/ProductVariant/53479762788719"
          },
          "purchase": {
            "type": "one_time"
          },
          "catalogPrice": {
            "amountCents": 4000,
            "compareAtAmountCents": 5000,
            "capturedAt": "2026-09-16T00:00:00Z"
          }
        }
      ],
      "pricing": {
        "promise": {
          "firstChargeCents": 4000,
          "renewalChargeCents": null
        },
        "compareAtPrice": {
          "amountCents": 5000,
          "source": "variant"
        },
        "shipping": {
          "type": "free",
          "rates": {
            "type": "cheapest"
          },
          "charges": "every"
        },
        "mechanisms": [
          {
            "type": "catalog_price"
          }
        ]
      },
      "checkoutMode": "direct_to_checkout"
    },
    {
      "key": "starter-kit",
      "display": {
        "title": "Starter Kit"
      },
      "visible": true,
      "axisValues": {},
      "unitCount": 2,
      "cardKey": "starter-kit",
      "lines": [
        {
          "role": "main",
          "quantity": 1,
          "required": true,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092669088111",
            "variantId": "gid://shopify/ProductVariant/53479763018095"
          },
          "purchase": {
            "type": "subscription",
            "sellingPlanId": "gid://shopify/SellingPlan/708367581551",
            "intervalDays": 30
          },
          "catalogPrice": {
            "amountCents": 6000,
            "compareAtAmountCents": 8000,
            "capturedAt": "2026-09-16T00:00:00Z"
          }
        },
        {
          "role": "gift",
          "quantity": 1,
          "required": false,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092670660975",
            "variantId": "gid://shopify/ProductVariant/53479764722031"
          },
          "purchase": {
            "type": "one_time"
          },
          "catalogPrice": {
            "amountCents": 1800,
            "compareAtAmountCents": 1800,
            "capturedAt": "2026-09-16T00:00:00Z"
          },
          "claimedValueCents": 1800
        }
      ],
      "pricing": {
        "promise": {
          "firstChargeCents": 4800,
          "renewalChargeCents": 6000
        },
        "compareAtPrice": {
          "amountCents": 8000,
          "source": "variant"
        },
        "shipping": {
          "type": "free",
          "rates": {
            "type": "cheapest"
          },
          "charges": "every"
        },
        "mechanisms": [
          {
            "type": "managed_discount",
            "lineRoles": [
              "main"
            ],
            "discount": {
              "type": "fixed_amount",
              "amountCents": 1200,
              "per": "line"
            },
            "charges": "first"
          }
        ]
      },
      "checkoutMode": "direct_to_checkout"
    },
    {
      "key": "starter-kit-once",
      "display": {
        "title": "Starter Kit"
      },
      "visible": true,
      "axisValues": {},
      "unitCount": 2,
      "cardKey": "starter-kit",
      "lines": [
        {
          "role": "main",
          "quantity": 1,
          "required": true,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092669088111",
            "variantId": "gid://shopify/ProductVariant/53479763018095"
          },
          "purchase": {
            "type": "one_time"
          },
          "catalogPrice": {
            "amountCents": 6000,
            "compareAtAmountCents": 8000,
            "capturedAt": "2026-09-16T00:00:00Z"
          }
        }
      ],
      "pricing": {
        "promise": {
          "firstChargeCents": 6000,
          "renewalChargeCents": null
        },
        "compareAtPrice": {
          "amountCents": 8000,
          "source": "variant"
        },
        "shipping": {
          "type": "free",
          "rates": {
            "type": "cheapest"
          },
          "charges": "every"
        },
        "mechanisms": [
          {
            "type": "catalog_price"
          }
        ]
      },
      "checkoutMode": "direct_to_checkout"
    },
    {
      "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",
            "sellingPlanId": "gid://shopify/SellingPlan/708367581551",
            "intervalDays": 30
          },
          "catalogPrice": {
            "amountCents": 8000,
            "compareAtAmountCents": 12500,
            "capturedAt": "2026-09-16T00:00:00Z"
          }
        },
        {
          "role": "gift",
          "quantity": 1,
          "required": false,
          "variantSource": {
            "type": "fixed",
            "productId": "gid://shopify/Product/15092670660975",
            "variantId": "gid://shopify/ProductVariant/53479764722031"
          },
          "purchase": {
            "type": "one_time"
          },
          "catalogPrice": {
            "amountCents": 1800,
            "compareAtAmountCents": 1800,
            "capturedAt": "2026-09-16T00:00:00Z"
          },
          "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": {
            "amountCents": 0,
            "compareAtAmountCents": null,
            "capturedAt": "2026-09-16T00:00:00Z"
          },
          "claimedValueCents": 2500
        }
      ],
      "pricing": {
        "promise": {
          "firstChargeCents": 5860,
          "renewalChargeCents": 8000
        },
        "compareAtPrice": {
          "amountCents": 12500,
          "source": "variant"
        },
        "shipping": {
          "type": "free",
          "rates": {
            "type": "cheapest"
          },
          "charges": "every"
        },
        "mechanisms": [
          {
            "type": "managed_discount",
            "lineRoles": [
              "main"
            ],
            "discount": {
              "type": "fixed_amount",
              "amountCents": 2140,
              "per": "line"
            },
            "charges": "first"
          }
        ]
      },
      "checkoutMode": "direct_to_checkout"
    },
    {
      "key": "ritual-set-once",
      "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": "one_time"
          },
          "catalogPrice": {
            "amountCents": 8000,
            "compareAtAmountCents": 12500,
            "capturedAt": "2026-09-16T00:00:00Z"
          }
        }
      ],
      "pricing": {
        "promise": {
          "firstChargeCents": 8000,
          "renewalChargeCents": null
        },
        "compareAtPrice": {
          "amountCents": 12500,
          "source": "variant"
        },
        "shipping": {
          "type": "free",
          "rates": {
            "type": "cheapest"
          },
          "charges": "every"
        },
        "mechanisms": [
          {
            "type": "catalog_price"
          }
        ]
      },
      "checkoutMode": "direct_to_checkout"
    }
  ],
  "valueClaims": [],
  "terms": {},
  "costProfile": "dtc-us"
}

Three of the structures above are in it: quantity tiers of 1, 2 and 4 units, each subscription option paired with a one-time twin on the same card (cardKey), and free gift lines on the subscription options of the two larger kits. Every option ships free on the cheapest rate and sends the buyer straight to checkout.

Validation that names the path

In production

The API checks every create and replace against the schema. A wrong document is refused with 400 invalid_document and one issue per problem, each with the path to the field and what is wrong, so an agent can fix it without guessing. Nothing is stored and nothing reaches Shopify.

PathWhat the schema saysDev API
options[4].lines[0].purchase.sellingPlanIda subscription purchase requires sellingPlanIdSame issue
defaultOptionKeydefaultOptionKey "ritual" names no option in this documentSame issue
What the dev API returnedPOST /offers, 400, dev API, 2 October 2026
{
  "error": {
    "code": "invalid_document",
    "message": "The offer document is invalid",
    "issues": [
      {
        "path": "options[4].lines[0].purchase.sellingPlanId",
        "message": "a subscription purchase requires sellingPlanId"
      },
      {
        "path": "defaultOptionKey",
        "message": "defaultOptionKey \"ritual\" names no option in this document"
      }
    ]
  }
}
The schema's issues on a copy of OF_DEV_KITS with two mistakes: a default option that names no option, and a subscription line without its selling plan.

Prices Shopify does not show as a discount

In production

Line price. Sell a one-time, non-gift line at or below the catalog price captured in the document, as the product's own price: there is no strikethrough and no discount line at checkout. A higher price is refused when the document is validated. Subscription lines, gift lines and lines already priced by a bundle or tier group cannot take one. If the catalog later falls to or below the line price, the catalog price stays and the verifier reports it.

Presentations. Offer Suite's own cart transform can change how an option's one-time lines look and price in the cart and at checkout:

  • Line override: one line shown under another title or image, in the cart and at checkout only; the order keeps the real title. Shopify Plus only.
  • Merge: several lines shown as one parent line, a bundle product that cannot be bought on its own.
  • Expand: one line shown as its components, each with its own price and role, on every plan.

The real variants still ship. On the order they arrive as a Shopify bundle group carrying each component's _os_role, so the ingest costs every component to its option.

Store capability. Each store's capability, its plan and whether the cart transform is registered, is read at install, on shop and scope changes, and daily, and served at GET /stores/{id}. A publish refuses a document the store cannot run with 409 store_capability_missing, naming each option and operation.

OperationShopify PlusOther plansDevelopment store
Line priceRunsRuns, as an expandRuns
Line override (title or image)RunsRefused at publishRuns on Plus dev stores; refused on others
Merge into a parent lineRunsRunsRuns
Expand into priced componentsRunsRunsRuns
Every cell needs Offer Suite's cart transform registered on the store; without it nothing runs and the publish is refused. A development store runs what its plan runs. A line override on a line with a line price rides in that line's expand, so it runs on every plan.

Subscriptions on your selling plans

In production

Bind the selling plans you already sell through, from apps that use Shopify selling plans such as Recharge or Skio. Renewal orders have been verified with Shopify's own subscription contracts; renewals from those apps are not confirmed yet (tracking). Offer Suite puts the price on two app discounts:

  • First-charge pricing goes on the Offer Suite app discount, which applies once.
  • Recurring discounts and free shipping on every shipment go on Offer Suite renewals, which Shopify saves on the subscription contract for every billing cycle.
  • The schema refuses a mechanism that would price one line on both, because Shopify applies one product discount per line.

At publish, a real cart and a read of the live discounts check the selling plan, the first charge, the renewal price and that the renewals discount is active on every cycle. No renewal order is placed for the check. Publish and verify has every check.

Drafts and versions

In production

Every replace stores draft n+1 with an audit row, and the live version keeps serving. A create or a replace never touches Shopify: only an explicit publish does, and it flips the live version only after real carts charge what the document promises.

A version's status, as the document's status field holds it.
DraftWritten by a create or a replace, or a version whose publish did not match. Nothing serves it.
VerifiedPublished as an experiment arm: proved on real carts and served only to the visitors an experiment assigns to it.
LiveThe version every other visitor is served.
RetiredReplaced by a newer live version. Kept as it was, never edited.

Storefront code cannot change an offer. The public key a buy box holds can resolve the live offer, price a selection, build a cart and post events; it cannot write an offer.

Written for agents

In production

Every property of the offer document, the resolve response and the event body carries a description, and a test fails any new field without one. A second test checks the same schemas on the served /openapi.json.

The hosted MCP server builds create_offer and replace_offer from /openapi.json at runtime, so an agent reads those descriptions field by field and can write an offer from the schema alone.

Three descriptions as the schema writes them: the text an agent reads in create_offer.
unitCountThe quantity the buyer perceives ("4 units"); not the sum of line quantities
cardKeyOptions sharing a card key are drawn as one card (a subscribe and one-time pair of one product)
checkoutModeWhere the brand's page sends the buyer after the choice: the cart page, a cart drawer, straight to checkout, a cart on the landing page, or an external flow

1 call

In an internal test in September 2026, a fresh agent with only the MCP resources wrote a valid offer on its first create_offer call.

Dev API, 16 September 2026

Checking stored documents

In production

GET /offers/document-check (check_offer_documents) validates every offer's live version, current version and verified experiment arms against today's schema, and lists each failing version with the path and message of every issue.

A repair is a new draft written with PUT /offers/{id} and a reason. That write records an audit row naming the versions it supersedes, and the new version then goes through a normal publish. A stored version is never edited in place, and the failing one keeps serving until its replacement is live.

Category builders

Partly available

Priced and verified through the API, with no ready-made builder UI yet.

An option can be a category builder: optional groups, each one category bought on subscription or once, with an overall unit cap (maxUnits) and one cadence set by the units in the largest subscription group (cadence). Each group's first-order unit price falls as the cart holds more of it (unit_price_tiers), and renewals are priced by the brand's own selling plans, bound per tier and cadence.

POST /offers/{id}/price (price_offer_selection) prices any selection, cart creation builds a matching Shopify cart, and the publish verifier checks both.

48 of 48

One category-builder offer had 48 of 48 options matched and 377 of 377 audit carts, and test order #1023 charged $154.06.

Dev store, 29 September 2026

Your developers build the builder's UI. Nothing Offer Suite ships draws one yet: the Verified offer theme block draws an offer's options, not a builder's groups.

What it does not do yet

  • Descriptions are enforced for the offer document, the resolve response and the event body, not for every query parameter.
  • Free-shipping checks run only for US-market stores. Elsewhere the check is skipped and the version is not published.
  • Line overrides need Shopify Plus.
  • Category builders have no ready-made UI.

Questions

Is there a list of offer types?

No. An offer is options and lines with a main, add-on or gift role, plus pricing and shipping. Tiers, bundles, subscriptions, gift ladders and build-your-own boxes are arrangements of those few primitives, so a new structure needs no new type.

What happens when a document is wrong?

The API refuses it with 400 invalid_document and one issue per problem, each naming the field's path and what is wrong. Nothing is stored and nothing reaches Shopify, so your agent fixes the fields and sends it again.

Can I change a live offer?

Not in place. A replace stores a new draft while the live version keeps serving, and the change goes live only when that draft is published and its real carts charge what it promises.