Offer capabilities
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- One
POSTper intent-bearing action — only for operations with side effects beyond writing settings
The granularity rule. Named POST actions exist only for operations with side effects (Shopify mutations, variant fan-out, state transitions) or high-traffic narrow edits like set_button_text. Plain settings — and all boolean toggles such as badge.enabled or terms.enabled — are set via the capability's PATCH. The PATCH is deep-partial: fields you don't send are left alone. Each capability section below lists its toggleable field paths.
The tables below are sourced from the live /help manifest. Every capability is also exposed as MCP tools.
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) | Deep partial update of any subset of widget configuration. |
POST | set_badge_colors | set_badge_colors | Set the preorder badge's text and background colors. |
POST | set_badge_text | set_badge_text | Set the preorder badge text. |
POST | set_billing_widget_text | set_billing_widget_text | Set the billing widget's title and description (the PDP payment-breakdown block). |
POST | set_button_colors | set_button_colors | Set the preorder button's text and background colors (implicitly enables custom colors). |
POST | set_button_text | set_button_text | Set the preorder button's call-to-action label. |
POST | set_disclaimer_style | set_disclaimer_style | Set the disclaimer banner's colors and border radius. |
POST | set_disclaimer_text | set_disclaimer_text | Set the disclaimer banner text shown under the preorder button. |
Toggles via PATCH .../widget: badge.enabled, disclaimer.enabled, button.colors.enabled, billing_widget.enabled. Clear a value by sending null.
PATCH /preorders/offers/:id/widget
{ "badge": { "enabled": false } }The billing-widget title and description are shared with the first payment option's copy (see payments/set_option_copy).
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) | Deep partial update of inventory settings. |
POST | set_provider | set_provider | Set the inventory provider (stoq or shopify). |
POST | set_reservation_timing | set_reservation_timing | Set when inventory is reserved (on_sale or on_fulfillment). |
Toggles via PATCH .../inventory: continue_selling.enabled and continue_selling.auto_apply_to_new_variants — both map to the same underlying auto-flip-on-attach flag (newly attached variants get their Shopify inventory_policy flipped to CONTINUE). When both fields are present, auto_apply_to_new_variants wins.
PATCH /preorders/offers/:id/inventory
{ "continue_selling": { "enabled": true } }Continue-selling on the offer only governs future variant attachments — neither field flips already-attached variants. To flip existing variants, use products/bulk_toggle_inventory_policy, or pass update_inventory_policy: true to the offer's enable / disable lifecycle actions. Setting provider to shopify forces the products source to custom.
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 shipping settings for a preorder offer. |
PATCH | update | (root) | Deep partial update of shipping settings. |
POST | set_delivery_asap | set_delivery_asap | Set delivery to "as soon as possible" (no specific date or window). |
POST | set_delivery_date | set_delivery_date | Set an exact delivery date for the preorder. |
POST | set_delivery_window | set_delivery_window | Set a delivery window in days from checkout (e.g. "ships within 14 days"). |
POST | set_shipping_text | set_shipping_text | Set the customer-facing shipping text shown on the PDP. |
POST | set_split_order_tag | set_split_order_tag | Set the tag applied to split orders on Shopify. |
POST | set_split_transaction_gateway | set_split_transaction_gateway | Set the payment gateway used for the split-order transaction (e.g. "Store Credit"). |
Toggles via PATCH .../shipping:
| Field path | Effect |
|---|---|
fulfillment.hold | Hold (true) / release (false) fulfillment orders containing preorder items |
display.show_shipping_timeline | true shows the detailed PDP timeline; false the simplified text |
fulfillment.split.enabled | Turn order splitting on/off |
fulfillment.split.hold_fulfillments | Hold the split order's fulfillments |
fulfillment.split.sequential_number | Give split orders a sequential order name |
PATCH /preorders/offers/:id/shipping
{ "fulfillment": { "hold": true, "split": { "enabled": true } } }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) | Deep partial update of payment settings. |
POST | disable_option | disable_option | Remove a payment option (full or partial) from the offer. |
POST | enable_option | enable_option | Add a payment option (full or partial) to the offer. |
POST | set_deposit_percent | set_deposit_percent | Set the deposit percentage charged at checkout on the partial payment option. |
POST | set_discount | set_discount | Set the preorder discount. |
POST | set_discount_text | set_discount_text | Set the discount blurb shown on the offer's payment options. |
POST | set_mode | set_mode | Switch the offer between full, partial, and full_and_partial payment modes. |
POST | set_option_copy | set_option_copy | Set the merchant-facing copy on a payment option (title, description, discount text). |
Toggles via PATCH .../payments: remaining_balance.auto_collect, remaining_balance.auto_collect_on_fulfillment. Clear the discount via { "discount": { "type": "no_discount" } }.
PATCH /preorders/offers/:id/payments
{ "remaining_balance": { "auto_collect": false } }Notes from the manifest:
set_deposit_percent— requires a partial payment option (modepartialorfull_and_partial); useset_modefirst. Percent must be 1–99 (100% upfront is full payment mode).enable_option— idempotent; newly added options start from the dashboard defaults.disable_option— only valid onfull_and_partialoffers; an offer must keep at least one payment option.set_discount—typeis one ofpercentage,price,fixed_amount,no_discount.set_discount_text— passoption_type(full|partial) to target one option; omit to apply to every option. Supports the{{ discount }}placeholder.
Limits
Min/max per order, per-customer and total caps, remaining-units display.
Path prefix: /preorders/offers/:id/limits
| Method | Action | Path | Description |
|---|---|---|---|
GET | read | (root) | Read order-quantity limits for a preorder offer. |
PATCH | update | (root) | Deep partial update of quantity limits. |
POST | set_max_per_customer | set_max_per_customer | Set the maximum quantity a single customer can preorder across all their orders. |
POST | set_max_per_order | set_max_per_order | Set the maximum quantity a customer can preorder per order. |
POST | set_min_per_order | set_min_per_order | Set the minimum quantity a customer must preorder per order. |
POST | set_total_max | set_total_max | Set the total quantity that can be preordered across all customers. |
Toggles and clears via PATCH .../limits: display.show_remaining (true/false) toggles the remaining-units display; clear a limit by sending null — per_customer.max: null removes the per-customer limit, total.max: null removes the total cap.
PATCH /preorders/offers/:id/limits
{ "display": { "show_remaining": true }, "total": { "max": null } }per_customer.max and total.max are stored config only today — storefront enforcement of these limits is follow-up work and not live yet.
Checkout
Terms acceptance, order tags applied at checkout, mixed-cart policy.
Path prefix: /preorders/offers/:id/checkout
| Method | Action | Path | Description |
|---|---|---|---|
GET | read | (root) | Read checkout settings for a preorder offer. |
PATCH | update | (root) | Deep partial update of checkout settings. |
POST | add_order_tag | add_order_tag | Add a tag to the set applied to orders containing this offer. |
POST | remove_order_tag | remove_order_tag | Remove a tag from the set applied to orders containing this offer. |
POST | set_mixed_cart_error_message | set_mixed_cart_error_message | Set the error message shown when a blocked mixed cart reaches checkout. |
POST | set_order_tags | set_order_tags | Replace the entire set of tags applied to orders containing this offer. |
POST | set_terms_text | set_terms_text | Set the terms-acceptance text shown next to the checkbox. |
Toggles via PATCH .../checkout: terms.enabled, terms.disable_button_until_acknowledged, terms.include_in_line_item_properties, mixed_cart.allowed.
PATCH /preorders/offers/:id/checkout
{ "terms": { "enabled": true }, "mixed_cart": { "allowed": false } }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 configuration for a preorder offer. |
PATCH | update | (root) | Deep partial update of countdown settings. |
POST | set_custom_end_date | set_custom_end_date | Set the custom end date for the countdown timer. |
POST | set_ends_text | set_ends_text | Set the text shown when the offer is closing. |
POST | set_mode | set_mode | Set whether the countdown follows the offer's schedule (to_schedule_end) or a custom date (to_custom_date). |
POST | set_starts_text | set_starts_text | Set the text shown before the offer has started. |
POST | set_style | set_style | Set the countdown visual style (type, colors, border radius). |
POST | set_unit_labels | set_unit_labels | Set the unit labels (Days/Hours/Mins/Secs). |
Toggle via PATCH .../countdown: enabled (true/false) shows or hides the countdown.
PATCH /preorders/offers/:id/countdown
{ "enabled": true, "mode": "to_custom_date", "custom_end_date": "2026-07-01T00:00:00Z" }set_custom_end_date has no visible effect unless mode is to_custom_date. In the PATCH body, text is an alias for ends_text — both write the same field, and an explicit ends_text wins when both are sent.
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 (POS, B2B). |
PATCH | update | (root) | Deep partial update of integration settings. |
Toggles via PATCH .../integrations: pos.enabled, pos.skip_inventory_check, b2b.enabled.
PATCH /preorders/offers/:id/integrations
{ "pos": { "enabled": false }, "b2b": { "enabled": true } }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) | Deep partial update of market scoping. |
POST | add_market | add_market | Add a market to the preorder offer's scope. |
POST | remove_market | remove_market | Remove a market from the preorder offer's scope. |
POST | set_markets | set_markets | Replace the entire set of markets a preorder offer applies to. |
Toggle via PATCH .../markets: enabled (true/false) turns market-based scoping on or off. The offer applies to all markets when scoping is off, or when scoping is on with an empty market list:
PATCH /preorders/offers/:id/markets
{ "enabled": true, "market_ids": ["gid://shopify/Market/123"] }- All markets:
{ "enabled": false }, or{ "enabled": true, "market_ids": [] }, or{ "enabled": true, "applies_to_all_markets": true }(which also clears the list). market_idsaccepts Shopify GIDs or numeric IDs.- For incremental edits, keep using
add_market/remove_market/set_markets.
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 (source rule + variant summary). |
PATCH | update | (root) | Set the source rule plus its matching sub-block (source + one of all / collection / custom). |
POST | add_variants | add_variants | Attach variants to a custom-source preorder offer. |
POST | bulk_toggle_inventory_policy | bulk_toggle_inventory_policy | Flip the Shopify inventory policy on variants attached to the offer (CONTINUE or DENY). |
POST | remove_variants | remove_variants | Remove variants from a custom-source preorder offer. |
POST | set_collection | set_collection | Change the collection driving a collection-sourced offer. |
POST | set_excluded_variants | set_excluded_variants | Replace the explicit excluded-variant list for an all-source offer. |
POST | set_exclusion_tag | set_exclusion_tag | Set the exclusion tag for an all-source offer (variants with this tag are excluded). |
POST | set_source_to_all | set_source_to_all | Switch the offer to apply to every variant in the shop, minus exclusions. Requires confirm: true. |
POST | set_source_to_collection | set_source_to_collection | Switch the offer to a collection-driven variant source. |
POST | set_source_to_custom | set_source_to_custom | Switch the offer to a custom variant list. Optionally seed with variant IDs. |
Notes from the manifest:
PATCH .../products— delegates to theset_source_to_*actions; switching source resets the variant set.source: "all"requiresconfirm: true. Only the sub-block matchingsourcemay be provided.add_variants/set_source_to_custom— variant attachment runs through the bulk-variant addition service asynchronously (202+job_id).bulk_toggle_inventory_policy— async; omitvariant_idsto apply to all attached variants.set_collection— collection-sourced offers only; returns409 Conflictotherwise. Useset_source_to_collectionto switch a non-collection offer.set_source_to_collection— new variants added to the collection in Shopify are not auto-picked-up; call the action again to refresh.
Per-variant settings sub-resource
Variants attached to an offer carry per-offer overrides, addressed by Shopify variant ID:
| Method | Path | Description |
|---|---|---|
GET | /preorders/offers/:id/products/variants | List the attached variants with their per-variant settings, paginated (page, per_page). |
GET | /preorders/offers/:id/products/variants/:variant_id | Read the per-offer settings of one attached variant. |
PATCH | /preorders/offers/:id/products/variants/:variant_id | Set per-offer overrides on the variant — shipping_text, max_count, market_overrides. |
PATCH /preorders/offers/:id/products/variants/:variant_id
{ "shipping_text": "Ships in August", "max_count": 50 }Sending null clears an override; the variant falls back to the offer-level value. market_overrides writes the per-market metafields (market_shipping_text, market_preorder_max_count) keyed by Shopify market GID.
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 power-user settings (line item properties, button-text overrides, Shopify selling plan attachment, custom CSS). |
PATCH | update | (root) | Deep partial update of advanced settings. |
POST | attach_to_shopify_selling_plan | attach_to_shopify_selling_plan | Re-attach the offer to a Shopify selling plan group (resume syncing). |
POST | detach_from_shopify_selling_plan | detach_from_shopify_selling_plan | Detach the offer from Shopify selling plan groups; Stoq continues to manage it via the storefront integration. |
POST | set_button_text_override | set_button_text_override | Override the widget button text for one state (before_launch, after_launch, or out_of_stock). |
POST | set_custom_css | set_custom_css | Set custom CSS overrides for this offer's storefront widget. Send an empty string to clear. |
POST | set_custom_line_item_property | set_custom_line_item_property | Set a free-text custom line item property added to every preorder cart line. |
Toggles and clears via PATCH .../advanced:
- Line-item-property toggles:
line_item_properties.include_shipping_text,.include_payment_breakdown,.include_acknowledgement(true/false each). button_text_overridesis deep-partial per state —{ "button_text_overrides": { "out_of_stock": null } }clears that one override (the read shape still emits all three states, withnullfor unset).custom_cssis cleared with an empty string ornull.
PATCH /preorders/offers/:id/advanced
{ "line_item_properties": { "include_payment_breakdown": false } }detach_from_shopify_selling_plan is strongly discouraged for most merchants — it breaks Shopify-native integrations like checkout selling-plan display and third-party subscription apps. It does not delete the existing Shopify selling plan group; it just stops syncing.
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, plus the full enumeration of translatable field paths. |
POST | clear_locale | clear_locale | Remove all translation overrides for one locale. |
POST | set | set | Set a single translation override (one locale, one field). |
POST | set_many | set_many | Set many translation overrides for one locale, atomically — invalid paths abort the whole write. |
POST | unset | unset | Remove a translation override (one locale, one field). |
The read endpoint is also the source of truth for the catalog of translatable field paths — clients shouldn't hardcode this list, they should fetch and use it.
