Offer capabilities
The capability groups exposed on a preorder offer. Each has a Read action and an Update (deep partial PATCH) for settings and toggles. Only operations with side effects are separate POST actions.
BetaAPI v2 is in public beta: endpoints, fields and behaviour can change before general availability. See the changelog
Last updated
A capability is a coherent sub-group of an offer’s configuration. Every capability follows the same shape:
GET /preorders/offers/:id/{capability}: read current statePATCH /preorders/offers/:id/{capability}: deep partial update of settings, including every boolean toggle- A
POSTonly for operations with side effects (Shopify mutations, variant fan-out, keyed translation writes)
The tables below match the registry. The API v2 Reference lists every field with its full schema, and every capability is also exposed as MCP tools.
Widget
Section titled “Widget”Storefront UI — badge, button text & colors, billing widget, disclaimer.
Path prefix: /preorders/offers/:id/widget
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the widget configuration for a preorder offer. |
PATCH |
update |
(root) | Update any subset of the widget configuration on a preorder offer. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
button.button_text |
string or null | Button text – the preorder call-to-action label. |
button.text_color |
string | The button’s Text color. Hex. Only shows when button.custom_colors_enabled is true; send it too. |
button.background_color |
string | The button’s Background color. Hex. Only shows when button.custom_colors_enabled is true; send it too. |
button.custom_colors_enabled |
boolean | Use custom colors for button. False falls back to the theme’s own button colors. |
disclaimer.show_disclaimer |
boolean | Add disclaimer – shows or hides the disclaimer line under the button. |
disclaimer.disclaimer_text |
string or null | Disclaimer – the line shown under the preorder button. |
disclaimer.text_color |
string | The disclaimer’s Text color. Hex. |
disclaimer.background_color |
string | The disclaimer’s Background color. Hex. |
disclaimer.border_radius |
integer | The disclaimer’s Border radius in pixels. |
badge.show_badge |
boolean | Add badge – shows or hides the preorder badge. On product pages it sits after the price by default. On home, collection and search pages it shows on the product card, only where Preorder pages turns the Preorder button on. |
badge.badge_text |
string or null | Badge – the label printed on the preorder badge. |
badge.text_color |
string | The badge’s Text color. Hex. |
badge.background_color |
string | The badge’s Background color. Hex. |
Inventory
Section titled “Inventory”Inventory provider, reservation timing, and the continue-selling (oversell) policy for newly attached variants.
Path prefix: /preorders/offers/:id/inventory
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the inventory configuration for a preorder offer. |
PATCH |
update |
(root) | Update any subset of the inventory settings on a preorder offer. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
stock_condition |
out_of_stock / in_stock / any_stock_level |
When the Preorder button shows: out_of_stock when the product is out of stock (needs Shopify’s ‘Continue selling when out of stock’ on the variants); in_stock only while the product is in stock (at 0 it stops showing); any_stock_level whenever the variant can be bought, in or out of stock, never changing the inventory policy: “even when in stock” or “in stock too” is any_stock_level. in_stock and any_stock_level need specific products. |
reserve |
on_sale / on_fulfillment |
When to deduct from inventory: on_sale is at checkout, on_fulfillment is at fulfillment. in_stock and any_stock_level always deduct on_sale. |
Shipping
Section titled “Shipping”Delivery date / window, shipping copy, fulfillment hold, split-order behavior.
Path prefix: /preorders/offers/:id/shipping
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the shipping settings of a preorder offer: the shipping timeline text, delivery timing, fulfillment handling (hold, split or separate orders) and hold release. |
GET |
schedulable_orders |
schedulable_orders |
How many of a preorder offer’s latest 100 orders have fulfillments that reschedule_existing_orders on PATCH /preorders/offers/:id/shipping would move: the unfulfilled, uncancelled ones. checked_count is how many orders were looked at. |
PATCH |
update |
(root) | Update any subset of the shipping settings on a preorder offer: the shipping timeline text, delivery timing, fulfillment handling (hold, split or separate orders) and hold release. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
timeline.scope |
same / per_variant |
same shows one shipping text for every product; per_variant uses each variant’s own shipping text (set per variant). |
timeline.show_shipping_text |
boolean | Show shipping timeline on the widget. Also turns the cart line-item property (advanced.cart_attributes.show_shipping) on or off when it changes, as the editor does; sending the current value changes nothing. |
timeline.shipping_text |
string | The line under the preorder button. Required while show_shipping_text is true. |
delivery_timing |
asap / exact_time / unknown |
Fulfillment status of preorder items: asap is Unfulfilled (ship now), exact_time is Scheduled (needs ships_at or days_after_checkout and the Shopify purchase option), unknown is On hold (no date promised). asap and unknown clear ships_at and days_after_checkout. |
ships_at |
string | Ship date: a date (2026-11-05), read as midnight in the shop time zone, or an ISO 8601 datetime (kept as that exact moment). Returned as the UTC datetime. Sending it alone makes delivery_timing exact_time on that date. Also sets timeline.variant_shipping_text (the {{ date }} value) to the date in the shop time zone, as the editor does: “05 Nov 2026”. |
days_after_checkout |
integer | Ship N days after checkout. Sending it alone makes delivery_timing exact_time with that delay; ships_at is then null. Also sets timeline.variant_shipping_text to “N days after checkout”, as the editor does. |
fulfillment.handling |
none / hold_all / split_fulfillments / separate_orders |
none; hold_all holds every fulfillment; split_fulfillments ships in-stock items first (needs delivery_timing asap); separate_orders moves preorder items to their own order, which switches the Shopify purchase option off and resets partial payment, the discount and a Scheduled status (listed in also_changed). |
fulfillment.hold_reason |
string | The hold reason shown in Shopify. |
fulfillment.separate_orders.grouping |
variant / offer / ship_date |
offer creates one separate order per offer; variant one per variant; ship_date one per ship date (batch or shipping text), which needs timeline.scope per_variant or batches on. |
fulfillment.separate_orders.number_format |
sequential / suffix |
sequential gives each separate order its own order number; suffix appends suffix to the original order name. |
fulfillment.separate_orders.suffix |
string | Suffix appended to the separate order name. |
fulfillment.separate_orders.hold_fulfillments |
boolean | Hold fulfillments on the separate order until they’re released. |
fulfillment.separate_orders.tag |
string | Tag applied to the separate preorder order. |
fulfillment.separate_orders.transaction_gateway |
string | Payment gateway recorded on the separate order transaction. |
fulfillment.separate_orders.original_order_hold_tag |
string | Tag on the original order while it is being split. Removed once the split finishes. |
fulfillment.separate_orders.original_order_split_tag |
string | Tag on the original order after the preorder items move to a separate order. |
on_hold.release_schedule |
scheduled / manual |
scheduled releases held fulfillments on release_at; manual leaves them held. |
on_hold.release_at |
string | ISO 8601. Required while release_schedule is scheduled. |
on_hold.require_payment |
boolean | Only release once the balance is paid. |
reschedule_existing_orders |
boolean | true also moves the scheduled fulfillments of existing unfulfilled preorders on this offer to the ship date (ships_at, or each order date plus days_after_checkout), as the editor’s “Reschedule existing orders to this date” checkbox does. Needs delivery_timing exact_time. No customer email. |
ships_at is a calendar day in the shop’s time zone. Send a date ("2026-11-05") and STOQ stores midnight on that day in the shop’s time zone, so the shipping text shows “05 Nov 2026” whatever your own time zone. A full datetime ("2026-11-05T00:00:00Z") is kept as that exact moment and its day is taken in the shop’s time zone: for a Los Angeles shop that one is 4 November, “04 Nov 2026”. Reads return ships_at as a UTC datetime ("2026-11-05T08:00:00Z" for the date above); timeline.variant_shipping_text gives the day as the shop sees it.
Payments
Section titled “Payments”Payment modes (full / partial / both), deposit percentage, auto-collect, discounts, payment-option copy.
Path prefix: /preorders/offers/:id/payments
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the payment configuration for a preorder offer. |
PATCH |
update |
(root) | Update a deep partial of the canonical payment settings. Full and partial are stable kind selectors. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
options_offered |
full / partial / full_and_partial |
Explicitly add or remove full/partial payment kinds. Omitted kinds remain offered. Mode changes initialize payment widget and cart attributes. |
default_option |
full / partial |
Select an offered, synced full or partial kind as the default. |
full.option_title |
string or null | Billing Title for full payment. |
full.option_description |
string or null | Billing Description for full payment. |
full.discount_text |
string or null | Discount copy for full payment. |
full.price_adjustment.adjustment_type |
percentage / price / fixed_amount |
Percentage or fixed amount discount. Null clears the discount. |
full.price_adjustment.adjustment_amount |
number or null | Discount amount; an omitted type uses the saved type. |
partial.option_title |
string or null | Billing Title for partial payment. |
partial.option_description |
string or null | Billing Description for partial payment. |
partial.discount_text |
string or null | Discount copy for partial payment. |
partial.price_adjustment.adjustment_type |
percentage / price / fixed_amount |
Percentage or fixed amount discount. Null clears the discount. |
partial.price_adjustment.adjustment_amount |
number or null | Discount amount; an omitted type uses the saved type. |
partial.checkout_charge.charge_type |
percentage / price |
Deposit as percentage or price. |
partial.checkout_charge.charge_amount |
number | Checkout deposit; percentage must not exceed 100. 0 is the editor’s “No upfront payment”: nothing is charged at checkout. |
partial.remaining_balance.charge_trigger |
exact_time / time_after_checkout |
Collect at an exact time or days after checkout. |
partial.remaining_balance.charge_exact_time |
string or null | ISO 8601 datetime, required for exact_time; otherwise null. |
partial.remaining_balance.days_after_checkout |
integer or null | Positive days, required for time_after_checkout; otherwise null. |
collection_schedule |
manual / scheduled / on_fulfillment |
Collect the remaining balance manually, on its scheduled date, or on fulfillment. A balance due date alone charges nothing: “charge the rest after N days” also needs scheduled, and while it stays manual say the merchant collects it. |
show_payment_widget |
boolean | Show payment & discount savings. Also turns the payment cart attribute (advanced.cart_attributes.show_payment) on or off, as the editor does. |
show_discounted_price |
boolean | Show discounted price. |
Limits
Section titled “Limits”Quantity limits, how remaining units show, order tags, terms acceptance and mixed-cart rules. Terms and mixed-cart settings used to live in a separate Checkout capability.
Path prefix: /preorders/offers/:id/limits
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the limits & terms of a preorder offer: quantity limits and their display, order tags, the terms checkbox and mixed-cart rules. |
PATCH |
update |
(root) | Update any subset of the limits & terms on a preorder offer: quantity limits and their display, order tags, the terms checkbox and mixed-cart rules. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
default_per_variant |
integer | Same limit for every product: the most units of each variant that can be preordered. Copied onto EVERY variant in the offer when it changes, replacing each variant’s own limit. null clears the limit on every variant. Set one variant’s limit with the offer’s variant settings instead. While batches release_strategy is sequential the editor hides it: per-batch quantities govern. |
display_style |
none / disclaimer / progress_bar |
How customers see the limit: none, disclaimer (remaining units in the disclaimer) or progress_bar. |
limit_text |
string | Quantity Limit – the text printed with the remaining-units count. |
show_icons |
boolean | Use icons for quantity & shipping details on the widget. |
progress_bar.progress_text |
string | Progress bar text. |
progress_bar.fill_color |
string | The bar’s Fill color. Hex. |
progress_bar.background_color |
string | The bar’s Background color. Hex. |
progress_bar.box_background_color |
string | Box background – the colour behind the bar. Hex; null falls back to the disclaimer background. |
progress_bar.text_color |
string | The bar’s Text color. Hex. |
progress_bar.border_radius |
integer | The bar’s corner radius in pixels. |
progress_bar.show_percentage |
boolean | Show percentage next to progress bar. |
order_tags |
array | Tags Shopify puts on orders containing this offer. A list replaces them; {“$add”: […], “$remove”: […]} edits them. Blanks and duplicates are dropped; null or [] clears. |
terms.require_acceptance |
boolean | Require customer to accept terms before preordering. |
terms.terms_text |
string | Acknowledgement text – the wording beside the terms checkbox. |
terms.disable_button_until_accepted |
boolean | Disable button until terms are accepted. |
terms.record_acceptance |
boolean | Record when customer accepted terms, as a cart line property. |
terms.acceptance_label |
string | Label for that recorded-acceptance cart line property. |
cart.allowed_items |
mixed / preorder_only |
mixed lets a cart hold preorder and regular items; preorder_only blocks the mix at checkout (the editor’s “Don’t allow mixed cart orders”). |
cart.error_message |
string | Mixed cart error message – shown when a blocked mixed cart reaches checkout. |
order_quantity.minimum |
integer or null | Minimum quantity per order. null removes it. |
order_quantity.maximum |
integer or null | Maximum quantity per order. null removes the cap. |
Countdown
Section titled “Countdown”Countdown timer on the PDP — mode, style, copy, custom end date.
Path prefix: /preorders/offers/:id/countdown
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the countdown timer settings of a preorder offer. |
PATCH |
update |
(root) | Update any subset of the countdown timer settings on a preorder offer. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
show_when |
disabled / before_launch / during_preorder / both |
disabled hides the timer; before_launch counts down to the start, during_preorder to the end, both to the start and then the end. It is read back from the dates: both switches date_source to schedule unless date_source is sent, and with custom dates before_launch clears ends_at and during_preorder clears starts_at. |
date_source |
schedule / custom |
schedule uses the offer schedule start and end dates; custom uses starts_at and ends_at. |
starts_at |
string | Custom launch date and time. Without Z or an offset it is the shop’s local time (2026-09-30T18:00). null clears it. Used when date_source is custom. |
ends_at |
string | Custom deadline date and time. Without Z or an offset it is the shop’s local time (2026-09-30T18:00). null clears it. Used when date_source is custom. |
starts_text |
string | Heading shown before launch. |
ends_text |
string | Heading shown during the preorder. |
labels.days |
string | Days label. |
labels.hours |
string | Hours label. |
labels.minutes |
string | Minutes label. |
labels.seconds |
string | Seconds label. |
text_color |
string | Timer text color. Hex. |
background_color |
string | Timer background color. Hex. |
border_radius |
integer | Corner radius in pixels. |
units |
array | Time units to display, any of days, hours, minutes, seconds. |
Integrations
Section titled “Integrations”POS and B2B behavior. This capability is PATCH-only; there are no named actions.
Path prefix: /preorders/offers/:id/integrations
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read integration settings for a preorder offer (POS, B2B). |
PATCH |
update |
(root) | Update any subset of the POS and B2B settings on a preorder offer. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
pos.enable_pos |
boolean | Sell preorders on POS. |
pos.inventory_check |
skip / required |
skip sells on POS without checking inventory; required keeps the inventory check. |
b2b.enable_b2b |
boolean | Allow B2B customers to place preorders on this offer. |
Markets
Section titled “Markets”Multi-market scoping — which Shopify markets the offer applies to.
Path prefix: /preorders/offers/:id/markets
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read market scoping for a preorder offer. |
PATCH |
update |
(root) | Update any subset of the market settings on a preorder offer. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
scope |
all / selected |
all applies the offer in every market and clears shopify_market_ids; selected applies it only in shopify_market_ids, which must not be empty. |
shopify_market_ids |
array | Shopify Market GIDs (or numeric ids) the offer applies to when scope is selected. An array replaces the list; {“$add”: […], “$remove”: […]} edits it. Emptying the list moves scope to all. |
Products
Section titled “Products”Variant attachment — source mode, add/remove variants, exclusions, inventory-policy bulk toggle, and per-variant settings.
Path prefix: /preorders/offers/:id/products
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the products configuration: selection, shopify_collection_id, tags, eligible_inventory_policy, excluded_shopify_variant_ids, exclusion_tag. |
PATCH |
update |
(root) | Deep partial of the read shape. Switching selection (all / collection / tags / custom) resets the variants on Shopify first, then refills from the new rule. |
POST |
add_variants |
add_variants |
Attach shopify_variant_ids, every variant of shopify_product_ids, or every product of a custom offer’s shopify_collection_id. On all, collection and tags offers, a call with no ids re-runs the selection rule. |
POST |
bulk_toggle_inventory_policy |
bulk_toggle_inventory_policy |
Set the Shopify inventory policy (CONTINUE or DENY) on variants attached to the offer. |
POST |
remove_variants |
remove_variants |
Remove variants from a custom-selection preorder offer. |
POST |
advance_batches |
advance_batches |
Batch offers only: move variants to their next batch. |
Notes:
PATCH .../products— a refused Shopify reset returns502and leaves the offer as it was.shopify_collection_id(gid, numeric id or admin URL) is required when switching tocollection,tagswhen switching totags. Switching tocustomstarts empty; add variants withadd_variants.inventory_policy_for_removed_variants(CONTINUE/DENY) sets the policy of the variants coming off first. The formerset_source_to_*,set_collection,set_exclusion_tagandset_excluded_variantsactions are now aliases of this PATCH.add_variants—202withjob: {job_id, status, status_url}(each add has its ownjob_id), or200when nothing is new.409while an add is running. Every id in the response is a Shopify gid:variants_added,products_added,already_present,deleted_in_shopify.skip_variants_in_market_offers: trueleaves out variants already in a market offer.add_variantspre-flight — an add of ids, products or a collection first runs the offer Products page’s pre-flight check on the new variants: other offers that already have them, sales channels that can’t sell preorders, and “Continue selling” off where STOQ won’t turn it on (a STOQ-inventory offer that has started). A re-run is not checked, as “Add all products” is not. With any issue nothing is added and the answer is409, withunresolvedlisting theresolvekeys needed.resolveruns the modal’s fix per issue type:remove_from_conflicting_offers(move the variants off the other offers),enable_continue_selling(turn Continue selling on after the add, through the Products tab’s toggle job) andskip_sales_channel_products(leave those products out; the dashboard has no channel fix, so neither does the API). A type without its key still blocks unlessskip_preflight: trueis sent; any fixes still run. The202lists what was fixed underresolved. Adds over 250 variants are queued, as the dashboard’s are. The store-wide Preorders > Products tab’s “Add to offer” runs no pre-flight check, so its API path,POST /preorders/products/add_to_offer, runs none either.remove_variants— takesshopify_variant_ids(Shopify variant gids). Shopify is reset before local rows go; a refused reset returns502and changes nothing.200withvariants_removedandnot_attached.bulk_toggle_inventory_policy— always202with its ownjob; omitshopify_variant_idsto apply to all attached variants. WithCONTINUE, variants at their quantity limit are left out by default, as the dashboard’s “Skip variants that have reached their quantity limit” checkbox (checked by default) does, and listed inskipped_quantity_limit_shopify_variant_ids;skip_quantity_limited: falseincludes them.advance_batches— omitshopify_variant_idsfor every variant with a current batch.409on an offer without batches.
POST /preorders/offers/:id/products/add_variants{ "shopify_variant_ids": ["gid://shopify/ProductVariant/123", "gid://shopify/ProductVariant/124"] }409{ "errors": ["Nothing was added: 1 other offer already has some of these variants and 1 variant has 'Continue selling when out of stock' off. Pass resolve: { remove_from_conflicting_offers: true, enable_continue_selling: true } to fix them as the Products tab does, or skip_preflight: true to add anyway."], "code": "preflight_issues", "preflight": { "conflicting_offers": [ { "id": "0c4e7a52-6f1b-4d0e-8a3c-2b9d5e1f7a44", "name": "Spring drop", "enabled": true, "variant_count": 3, "covers_all_products": false, "shopify_variant_ids": ["gid://shopify/ProductVariant/123"], "products": [{ "shopify_product_id": "gid://shopify/Product/45", "title": "Trail Shoe" }] } ], "sales_channel_issues": null, "continue_selling_issues": [ { "shopify_variant_id": "gid://shopify/ProductVariant/124", "title": "10", "shopify_product_id": "gid://shopify/Product/45", "product_title": "Trail Shoe", "sku": "TS-10", "inventory_policy": "DENY", "inventory_quantity": 0 } ] }, "unresolved": ["remove_from_conflicting_offers", "enable_continue_selling"]}Resend with the fixes:
POST /preorders/offers/:id/products/add_variants{ "shopify_variant_ids": ["gid://shopify/ProductVariant/123", "gid://shopify/ProductVariant/124"], "resolve": { "remove_from_conflicting_offers": true, "enable_continue_selling": true } }202{ "variants_added": ["gid://shopify/ProductVariant/123", "gid://shopify/ProductVariant/124"], "job": { "job_id": "9b1f...", "status": "queued", "status_url": "/api/v2/external/preorders/product_variants/jobs/9b1f..." }, "resolved": { "removed_from_conflicting_offers": [ { "shopify_variant_id": "gid://shopify/ProductVariant/123", "offer_ids": ["0c4e7a52-6f1b-4d0e-8a3c-2b9d5e1f7a44"] } ], "continue_selling_enabled": { "shopify_variant_ids": ["gid://shopify/ProductVariant/124"], "job": { "job_id": "4d2a...", "status": "queued", "status_url": "/api/v2/external/preorders/product_variants/jobs/4d2a..." } } }}With skip_sales_channel_products, resolved.skipped_for_sales_channels is [{shopify_variant_id, shopify_product_id}].
sales_channel_issues, when present, is {unsupported_channels, shop_app, variant_count, checked, total, capped, products: [{shopify_product_id, title, unsupported_channels, variant_count}]}. Each continue_selling_issues row is {shopify_variant_id, title, shopify_product_id, product_title, sku, inventory_policy, inventory_quantity}.
Per-variant settings sub-resource
Section titled “Per-variant settings sub-resource”Each attached variant has our id and its shopify_variant_id gid. :shopify_variant_id in the path is the Shopify variant gid (a numeric Shopify id also works).
| Method | Path | Description |
|---|---|---|
GET |
/preorders/offers/:id/products/variants |
The offer’s Products table, paginated (page, per_page up to 250, meta). search (product title, variant title, SKU), sort (title_asc, title_desc, created_asc, created_desc, added_asc, added_desc, sold_asc, sold_desc, plus attached = added_asc and title = title_asc; default attached), product_status (comma-separated), limit_reached=true, include_statuses=true. group_by=product returns products (each with shopify_product_id, title, image_url, variant_count and its variants) paged by product, with meta.total_products. exclude_discarded=true leaves out variants deleted in Shopify (discarded: true). include_inventory=true adds live Shopify inventory_quantity, inventory_policy and product.tags; the filters inventory, inventory_policy, product_tag, exclude_product_tag imply it and apply per page. |
GET |
/preorders/offers/:id/products/variants/:shopify_variant_id |
One row. |
PATCH |
/preorders/offers/:id/products/variants/:shopify_variant_id |
Set the variant’s own shipping_text, max_count (1 or more, or null; 422 on an offer with batches on) and batch_limits. 200 with the row. |
POST |
/preorders/offers/:id/products/variants/:shopify_variant_id/toggle_inventory_policy |
Set policy (CONTINUE / DENY) on one variant now. 200 with the row; 502 if Shopify refuses. |
POST |
/preorders/offers/:id/products/variants/:shopify_variant_id/reset_count |
Reset the variant’s preorder count to 0, like the Products table’s Reset. On this offer only. |
PATCH /preorders/offers/:id/products/variants/:shopify_variant_id{ "shipping_text": "Ships in August", "max_count": 50 }Each row has max_count, the variant’s own limit this PATCH writes, and a read-only quantity_limit, the limit that applies (null for none). Without batches they are equal. With batches on, quantity_limit is the total of the variant’s batch limits; set those with batch_limits, and max_count is kept but not enforced until batches are turned off. Writing max_count there, a value or null, returns 422 with a field error on max_count pointing to batch_limits: leave it out of the request.
Sending null clears an override; the variant falls back to the offer-level value. market_overrides is no longer accepted and returns 422. To use different shipping text or limits per market, set them on an offer scoped to that market (/preorders/offers/:id/markets).
Batches
Section titled “Batches”Batch release strategy, advance schedule, storefront batch widget and the batches themselves.
Path prefix: /preorders/offers/:id/batches
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the batch settings of a preorder offer: release strategy, advance schedule, labels, storefront widget and the kept batches in order. |
PATCH |
update |
(root) | Update any subset of the batch settings on a preorder offer, including the batches themselves. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
release_strategy |
sequential / all_at_once |
sequential sells the batches one after another (needs at least two batches, a paid plan, and no market-specific offer); all_at_once turns batches off. Turning batches on uses one shipping timeline for every product and moves a Scheduled fulfillment status (shipping.delivery_timing exact_time) to Unfulfilled (asap), listed in also_changed, as the editor does. |
advance_schedule |
automatic / manual |
automatic moves to the next batch when one sells its quantity_limit (every batch then needs one); manual waits for the merchant. |
label_format |
string | Batch name; {{ n }} is replaced by the position. null uses “Batch {{ n }}”. |
status_text.past |
string | Word shown for a batch that has passed. |
status_text.live |
string | Word shown for the batch on sale now. |
status_text.upcoming |
string | Word shown for a batch still to come. |
widget.style |
roadmap / stacked / compact / segmented |
Storefront batch widget layout. |
widget.progress |
counts / percent |
counts shows units sold of the limit; percent shows a percentage. |
widget.show_other_ship_dates |
boolean | Show the other batches’ ship dates on the storefront. |
widget.show_names |
boolean | Show batch names on the storefront. |
items |
array | The batches. A list replaces them: entries with an id keep that batch, entries without one are created, and batches left out are archived. An object keyed by batch id edits those batches (quantity_limit, shipping_text) and keeps the rest. Kept batches stay in their current order and new ones go at the end; positions end up 1..N. Each batch needs shipping_text; quantity_limit is all or none across batches. |
Advanced
Section titled “Advanced”Line-item-property customisation, button-text overrides, custom CSS, and Shopify selling-plan-group attachment.
Path prefix: /preorders/offers/:id/advanced
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read the advanced settings of a preorder offer: the purchase option (checkout_method) and the cart attributes. |
PATCH |
update |
(root) | Update any subset of the advanced settings on a preorder offer: the purchase option and the cart attributes. |
PATCH fields (all optional; send only what you change):
| Field | Type | Description |
|---|---|---|
checkout_method |
selling_plan / line_item_property |
selling_plan uses a Shopify purchase option (selling plan); line_item_property runs the offer without one. line_item_property breaks Shopify-native checkout selling-plan display and third-party subscription apps, and resets what needs the purchase option: partial payment to full payment, the discount to none, and a Scheduled fulfillment status to On hold (listed in the response as also_changed). selling_plan is refused while “Move preorder items to a separate order” (shipping fulfillment.handling separate_orders) is on; turn that off first. |
cart_attributes.show_payment |
boolean | Show payment details & savings in cart. |
cart_attributes.show_shipping |
boolean | Show fulfillment timeline in cart. |
cart_attributes.custom_text |
string | Custom attribute: free text added to every preorder cart line. Empty string or null removes it. |
Translations
Section titled “Translations”Per-locale overrides for offer copy fields. No PATCH: translations are keyed writes, not a settings document.
Path prefix: /preorders/offers/:id/translations
| Method | Action | Path | Description |
|---|---|---|---|
GET |
read |
(root) | Read every translation override on a preorder offer, plus the full enumeration of translatable field paths, each field’s default-locale source text, and every locale the shop has actually published (not just ones with an override already). |
POST |
clear_locale |
clear_locale |
Remove all translation overrides for one locale on a preorder offer. |
POST |
reset_to_defaults |
reset_to_defaults |
Reset a preorder offer’s storefront text for one language to STOQ’s default translations, as the offer translator’s “Load default translations” does. |
POST |
set |
set |
Set a single translation override (one locale, one field) on a preorder offer. |
POST |
set_many |
set_many |
Set many translation overrides for one locale on a preorder offer, atomically. |
POST |
unset |
unset |
Remove a translation override (one locale, one field) from a preorder offer. |
Thanks for letting us know.
Still stuck? Ask Quinn or chat with support