Home
Updates

API Changelog

Dated list of changes to the STOQ API and MCP server that can affect your integration. Newest first.

API Changelog

Changes to the STOQ API and MCP server that can affect an integration. Newest first. Each entry says what changed, who it affects, and what to send instead.

The API v2 Reference is generated from the code on every deploy, so it always shows the current request fields.

2026-10-01 — Offer schedule and countdown times use the shop's time zone

  • What changed:
    • Behaviour change: start / end on POST /preorders/offers/{id}/schedule, custom_start_date / custom_end_date on PATCH /preorders/offers/{id}/countdown, and date on POST /preorders/offers/{id}/countdown/set_custom_end_date read a time with no Z or offset in the shop's time zone. For a shop in Europe/London, 2026-09-30T18:00 is now 17:00 UTC. Before, it was 18:00 UTC. A value with Z or an offset (2026-09-30T18:00:00Z, 2026-09-30T18:00:00-04:00) means that exact moment, as before. A bare date is midnight in the shop's time zone.
    • Offer reads (GET /preorders/offers, /preorders/offers/{id}, /preorders/offer_lookup, /preorders/offers/{id}/countdown, /preorders/product_variants/{variant_id}) return schedule and countdown times with the shop's offset (2026-09-30T18:00:00+01:00), not in UTC. It is the same moment. status.schedule adds time_zone, the shop's IANA time zone.
  • Who it affects: callers that send schedule or countdown times with no offset, and callers that compare these times as strings instead of parsing them.
  • What to send instead: send the shop's local time with no offset, or keep sending UTC with Z.
  • Source: quinn-shop-timezone-schedule

2026-09-29 — Order list search finds a Shopify order id, gid or admin URL

  • What changed:
    • search on GET /preorders/orders and GET /back_in_stock/orders also matches the Shopify order id. You can send the bare id (7489236566319), the gid (gid://shopify/Order/7489236566319) or an admin order URL (https://admin.shopify.com/store/<store>/orders/7489236566319 or https://<shop>.myshopify.com/admin/orders/7489236566319).
    • A bare number of 10 or more digits is read as an order id, and still matches order number, name, email and customer name as before. A shorter number, or one that starts with # (1001, #1001), is an order number as before.
  • Who it affects: nobody has to change anything. Searches that returned nothing for an order id now return the order.
  • Source: order-search-by-id

2026-09-29 — List reads return a summary of every matching row; shop time zone dates

  • What changed:
    • GET /back_in_stock/notifications always returns summary (total, sent, blocked, blocked_by_reason), not only with group_by=product. It counts every send that matches the filters, not only the page.
    • GET /back_in_stock/signups returns summary (total, pending, notified, blocked, unsubscribed) for every signup that matches the filters. The status filter accepts blocked.
    • GET /preorders/offers returns summary (total, active, scheduled, paused, ended) for every offer that matches the filters.
    • Behaviour change: on GET /back_in_stock/notifications and GET /back_in_stock/signups, from / to given as bare dates now mean those whole days in the shop's time zone, as in the reports. Before, both were read as midnight UTC, so to=2026-09-14 left out all of 14 September. A from or to that does not parse now returns an error. Before, it matched no rows.
    • Behaviour change: on GET /back_in_stock/signups, status=pending no longer returns signups whose status is blocked. Use status=blocked for those. Each signup now matches exactly the filter of its own status.
  • Who it affects: callers of those reads that pass bare dates or status=pending. Everyone else only sees the new summary key.
  • Source: list-summaries

2026-09-27 — Offer status and preorder mode labels; inventory status rows use API values

  • What changed:
    • GET /preorders/offers, /preorders/offers/{id}, /preorders/offer_lookup, /preorders/product_variants/{variant_id} and /preorders/product_variants/{variant_id}/offers list value_labels in /help: offer states in the dashboard's words (active Live, scheduled Scheduled, paused Disabled, ended Expired, discarded Deleted) and the inventory provider in the offer editor's words (stoq "Show 'Preorder' when product is out of stock", shopify "Show 'Preorder' for in-stock products"). Responses still return the raw values.
    • Behaviour change: in GET /preorders/products/inventory_status, each offers[] row now has offer_status (the offer state, as elsewhere in the API) and inventory_provider. It no longer has status ({ state: "Live"|"Scheduled"|"Off", … }), preorder_type or offer_path, which were an assistant's words and an admin link.
  • Who it affects: callers of inventory_status that read offers[].status, offers[].preorder_type or offers[].offer_path.
  • What to send instead: read offers[].offer_status (active means taking preorders) and offers[].inventory_provider.
  • Source: quinn-reads-v2-step10

2026-09-27 — Order reads: look up by any order reference; back-in-stock and unsynced orders; payment and fulfillment detail

  • What changed:
    • GET /preorders/orders/{id} and its /payments, /fulfillment, /tags and /timeline reads accept any order reference as {id}: the order name or number (#1234 or 1234, URL-encode the #), the Shopify order id or gid://shopify/Order/…, the customer's email or name, or STOQ's order id (id in responses). A bare number is read as a Shopify order id when one matches, else as an order number. When a reference matches several orders the read returns 200 with { "ambiguous": true, "query": …, "candidates": [ … ] }; pick one and read it by its id. No match is still 404.
    • GET /preorders/orders/{id} and /timeline also read back-in-stock orders and orders not yet synced from Shopify (order_id and shopify_gid are then null). type says which (preorder or back_in_stock).
    • The order read adds id, type, currency, financial_status and fulfillment_status; payments.balance adds due_amount, due_at and overdue; payments adds last_payment_attempt (status, shopify_payment_status, triggered_by, error_message, attempted_at); each line item adds title, variant_title and fulfillment_status; each attributed offer adds offer_internal_name and fulfillment (status, delivery, strategy, hold_reason_note). The timeline adds id. GET /preorders/orders and GET /back_in_stock/orders rows add id and type; preorder rows add currency, financial_status and fulfillment_status.
    • A path parameter may now contain a dot (for example an email address). Only a trailing .json, .xml, .csv, .html, .txt or .js is still refused.
  • Who it affects: callers of those reads. Existing requests by Shopify order id work as before and see extra fields.
  • Source: quinn-reads-v2-step9

2026-09-27 — New GET /account/setup

  • What changed: new read GET /account/setup. It returns whether STOQ's app embed is on in the published theme (app_embed.enabled, and app_embed.theme_editor_url, a link that opens the theme editor and turns the embed on, when it is off), and the getting-started steps for back in stock and preorders: each step's state, steps_done, steps_total, next_step and completed.
  • Who it affects: nobody; it is new.
  • Source: quinn-reads-v2-step8

2026-09-27 — Back-in-stock reports: each number counts what its name says, and match the Overview dashboard

  • Behaviour changes (existing requests can get different numbers):
    • GET /back_in_stock/reports/summary: notified and notified_customers now count signups notified in the window (by notified_at). Before, they counted signups created in the window that had been notified at any time. pending is now the waitlist now (not limited to the window; with double opt-in on, unconfirmed signups are left out). Before, it counted signups created in the window that were not notified. conversion_rate follows the new notified_customers. On one real shop over 30 days, notified signups went from 114 to 461, notified customers from 77 to 323, and pending from 137 to 661.
    • GET /back_in_stock/reports/signups: notified per bucket now counts signups notified in that bucket. Before, it counted signups created in the bucket that had been notified.
    • GET /back_in_stock/reports/products_in_demand: pending is now signups waiting now, whatever the window; a variant with only older waiting signups now appears. total is still signups created in the window.
    • Unscoped requests (no variant_id, product_id, channel or market_id) now read the Overview dashboard's sources: summary signups, conversions and revenue from the daily rollups plus today (or the lifetime counters for range=all_time); the signups, notifications (emails_sent, sms_delivered) and conversions series from the daily rollups. They now match the dashboard. Scoped requests still count live.
    • from / to given as bare dates now mean those whole days in the shop's time zone. Before, both were read as midnight UTC, so to=2026-09-30 left out all of 30 September.
    • Series buckets are now bucketed in the shop's time zone and date is the bucket's first day ("2026-06-01"). Before, every shop was bucketed in UTC and date was a timestamp.
  • Fix: channel on the summary and conversions reports never matched an attributed order (orders store the message class, EmailMessage); it now filters them.
  • New: range=all_time on all five reads. source on the summary and the three series. Summary adds unique_customers, pending_by_channel and last_notified_at. The notifications series adds emails_sent and sms_delivered and accepts market_id. The conversions series adds unique_customers. products_in_demand accepts group_by=product.
  • Who it affects: every caller of the back-in-stock reports and their CSV exports.
  • Source: quinn-reads-v2-step7

2026-09-27 — Account products: search by product

  • What changed: GET /account/products accepts match (variant, the default, or product). With match=product, search returns every variant of each product it matched, not only the variants that matched (for example a SKU search returns the product's other sizes too).
  • Who it affects: callers of that read. Existing requests work as before.
  • Source: quinn-reads-v2-step6

2026-09-27 — Offer variants: title sort, live inventory and filters

  • What changed: GET /preorders/offers/{id}/products/variants:
    • New params: sort (attached, the default, or title), exclude_discarded=true, include_inventory=true, and the filters inventory (zero_or_below or positive), inventory_policy (deny or continue), product_tag, exclude_product_tag.
    • Each row adds variant_title, sku, discarded, shipping_text_shown (value, source, per_variant_allowed: the text the customer sees) and effective_max_count (the limit that applies; with batches on it is the sum of the kept batches, null when unlimited).
    • With include_inventory or any filter, each row adds inventory (inventory_quantity, continue_selling_enabled, preorder_button_eligible) and product_tags, and the response adds inventory_included and inventory_note. Filters apply to each page after the Shopify read: the response adds scanned and unreadable, and a page can hold fewer rows than per_page. If Shopify cannot be read, a filtered page returns no rows and filter_error.
    • per_page maximum is now 250 (was 200).
  • Who it affects: callers of that read. Only extra fields and params.
  • Source: quinn-reads-v2-step5

2026-09-27 — Back-in-stock products: signups, sales and a waitlist filter

  • What changed: GET /back_in_stock/products adds, per row, pending_signups, total_signups, units_sold_via_back_in_stock and back_in_stock_revenue (lifetime, string money like the reports). The response adds shop_alerts_enabled (the shop's master alert toggle) and currency. New filter has_waitlist=true returns only rows with a pending signup. /help lists value_labels for button_visibility.
  • Who it affects: callers of that read. Only extra fields.
  • Source: quinn-reads-v2-step4

2026-09-27 — Offer list expand, and more detail on a variant's offers

  • What changed:
    • GET /preorders/offers accepts expand (inventory, shipping, payments, markets, products, comma-separated or an array). Each row then carries those capability blocks in the same shape as the capability reads. An unknown block returns 422.
    • GET /preorders/offers/{id}/shipping (and the expanded shipping block) adds display.default_variant_shipping_text, read-only: the offer-level shipping text a variant without its own override shows in preorder notification emails.
    • Each in_preorder_offers entry from GET /preorders/product_variants/{variant_id} and GET /preorders/product_variants/{variant_id}/offers adds offer_internal_name, inventory_provider and shipping_text (value, source, per_variant_allowed: the text the customer sees).
  • Fix: offer_status in those entries now follows the same rule as the offer's own status.state. A scheduled offer saved as disabled (the editor saves "Starts on" and "Date range" that way) read paused; it now reads scheduled before its start and active inside its dates. The state filter on /offers follows the same rule.
  • Who it affects: callers of those reads. Only extra fields, except the offer_status fix.
  • Source: quinn-reads-v2-step3

2026-09-27 — Send log: product names, blocked reasons and counts per product

  • What changed: GET /back_in_stock/notifications and GET /back_in_stock/notifications/{id} add fields to each entry:
    • product_title and variant_title: the product the send was for.
    • blocked_reason_code and blocked_reason_label: why a blocked send did not go out, as a stable code (for example plan_limit, email_bounced, sms_country_not_allowed) and plain words. blocked_reason still returns the stored text. The full list of codes is in the send log docs.
    • attempted_at: when STOQ tried to send. A blocked send has sent_at: null, so this is its only time.
  • New option: group_by=product on GET /back_in_stock/notifications returns summary (total, sent, blocked, and blocked counts by reason code) and products (the same counts per product, highest total first) instead of notifications. The other filters still apply. page and per_page page the products.
  • /help: actions may now list value_labels, the plain words for raw enum values in their response. The send log lists them for status and channel.
  • Who it affects: callers of the send log. Existing requests work as before and only see extra fields.
  • Source: quinn-reads-v2-bridge

2026-09-27 — The rest of the dashboard settings: storefront text, alert switch, email look, limits, default payment option

  • What changed: settings a merchant can change in the dashboard that the API could not write yet.
    • New GET /preorders/settings/storefront_text and PATCH /preorders/settings/storefront_text: the shop-wide preorder words from the offer editor's Advanced section and the preorder translations page. Fields: currency_format, button.out_of_stock_text, button.add_to_cart_text, modal.continue_button_text, modal.cancel_button_text, modal.error_heading. They apply to every preorder offer. The text fields are translated: pass an optional locale to read or write one language; currency_format is one value for all languages. CSS, JavaScript and selectors are not part of it.
    • PATCH /back_in_stock/settings/channels takes alerts_enabled, the "Send automatic notification" master switch. The read already returned it. PATCH /back_in_stock/settings takes it as channels.alerts_enabled.
    • PATCH /account/sending takes email_style: font_family (one of the editor's fonts), header_font_size, description_font_size and logo_size (whole pixels above 0). They apply to every STOQ email. GET /account/sending returns them.
    • PATCH /preorders/offers/{id}/limits takes limit_per_product, the editor's "Same limit for every product". Each change is copied onto every variant in the offer and replaces their own limits, as the editor does. null clears it on every variant. The read returns it.
    • PATCH /preorders/offers/{id}/payments takes show_partial_first (true or false), the editor's "Show partial payment option first". Only for an offer in full_and_partial mode whose options have synced to Shopify; otherwise 422. The read shows the choice as options[].is_default.
    • GET /account/settings/{section_id} has a new section, preorder_storefront_text.
  • Who it affects: callers that change these settings. Existing requests work as before.
  • Source: decl/dashboard-settings-gaps

2026-09-27 — Preorder pages settings: read and write

  • What changed: new GET /preorders/settings/pages and PATCH /preorders/settings/pages for the dashboard's Preorders > Settings > Pages: whether the Preorder button shows on the home, collection and search pages, whether a product card shows the preorder badge when any variant is on preorder, and the locations and markets those pages count stock at.
    • Fields: home.enabled, collection.enabled, collection.show_badge_if_any_variant_is_preorder, search.enabled, locations.filter_enabled, locations.location_ids, market_setup, market_locations_enabled. locations.location_ids replaces the stored list. market_setup and market_locations_enabled are the same settings as in PATCH /back_in_stock/settings/delivery.
    • market_setup: "multiple_markets" turns locations.filter_enabled off, and "single_market" turns market_locations_enabled off, unless the request sets them too, as the dashboard does.
    • The page's CSS selectors (Dev mode) are not part of the API. An unknown field returns 422.
    • GET /account/settings/{section_id} has a new section, preorder_pages: pages, plus locations when the location filter is on and markets when market-based locations are on.
  • Wording: the description of the offer widget's badge.enabled (PATCH /preorders/offers/{id}/widget and set_badge_enabled) no longer says the badge sits on the product image. On product pages it sits after the price by default; on home, collection and search pages it shows on the product card where the Preorder button is on.
  • Who it affects: callers that want to change where the Preorder button shows. Existing requests work as before.
  • Source: decl/preorder-pages

2026-09-24 — Writes for integrations, languages, sending and the rest of the dashboard settings

  • What changed: every shop-level setting the dashboard lets a merchant change can now be written through the API.
    • New PATCH /account/integrations: klaviyo.sync_customers, omnisend.api_key, omnisend.sync_customers, mailchimp.api_key, mailchimp.list_id, mailchimp.sync_customers, shopify.sync_customers, sync_customers_tag, meta_pixel.enabled, meta_pixel.pixel_id, flow_triggers.enabled, flow_triggers.triggers, split_order_app.enabled. A Mailchimp key is checked by fetching the account's audiences and is not saved when Mailchimp refuses it; mailchimp.list_id must be one of those audiences. "" removes a saved key. Klaviyo still connects with OAuth from the dashboard.
    • New PATCH /account/languages: app_locale, multi_language_enabled, product_translations_enabled. Turning multi-language off also turns translated product titles off. Translated titles need the store translations access the dashboard asks for; the call fails until it is granted.
    • New PATCH /account/sending: email_provider, sms_provider, email_from_name, use_custom_email_address, email_reply_to_address, sms_custom_domain_enabled, klaviyo_skip_auto_subscribe, postscript_api_key. The custom address cannot be on a free mail domain. use_custom_email_address: false puts STOQ's address back as the reply-to. A Postscript key is checked with Postscript and not saved when Postscript refuses it.
    • PATCH /back_in_stock/settings/widget adds placement.selectors (product, collection, collection_insertion, collection_container, home, home_container, search, search_container), placement.hide_by_tag (enabled, tags, hides_button), form.inline (enabled, selector, insertion), optin.app_proxy_path_prefix and custom (css, javascript). button.position now also takes "specific". Reads return custom_css_present and custom_js_present, not the code.
    • PATCH /back_in_stock/settings/delivery now writes market_setup, market_locations_enabled and alert_on_publish. market_setup: "multiple_markets" turns locations.filter_enabled off and "single_market" turns market_locations_enabled off, unless the request sets them too.
    • PATCH /back_in_stock/settings/alerts adds merchant_emails.product_updates and merchant_emails.theme_published.
    • New POST /preorders/offers/{id}/payments/set_deposit_amount with amount: a fixed deposit charged at checkout on the partial payment option. It clears the deposit percentage.
  • Credentials: omnisend.api_key, mailchimp.api_key and postscript_api_key are write only. No read returns them; reads return connected flags.
  • Read changes: GET /account/integrations adds meta_pixel, flow_triggers and split_order_app. GET /account/sending adds klaviyo_skip_auto_subscribe.
  • Who it affects: callers that want to change these settings. Existing requests work as before.
  • Source: decl/writes-settings

2026-09-24 — Template writes cover every field the template editor changes

  • What changed: the template update actions now write every field the dashboard's template editor lets a merchant change. Each is read and written under the same name.
    • PATCH /back_in_stock/templates/{stage}/{locale} adds email_section_color, email_border_radius, email_button_border_radius, email_unsubscribe_text, email_reviews_enabled, email_reviews_title, email_reviews_count, email_reviews_min_rating, email_css_config and email_template (the custom HTML email). email_template and email_css_config may contain HTML; other fields still may not. GET returns email_css_config and email_template too.
    • PATCH /preorders/templates/{locale} adds email_background_color, email_section_color, email_text_color, email_border_radius, email_button_background_color, email_button_text_color, email_button_border_radius, email_css_config, the seven social links (email_facebook_link, email_instagram_link, email_pinterest_link, email_twitter_link, email_tiktok_link, email_whatsapp_link, email_youtube_link), email_social_icon_color, email_reviews_enabled, email_reviews_count and email_reviews_min_rating. The preorder template still does not accept email_template_enabled, email_template, email_button_destination, sms_link_destination or email_unsubscribe_text: the preorder editor does not show them.
    • Values are checked the way the editor limits them: colours are hex (#1A73E8), corner radii are whole pixels, email_reviews_count is 1 to 3, email_reviews_min_rating is 5, 4 or 3, email_social_icon_color is #FFFFFF or #000000, and email_button_destination / sms_link_destination are checkout or product. A wrong value returns 422.
    • Social links, email_css_config, email_template, email_preheader and the Klaviyo flow names accept an empty string, which clears them.
    • Shared fields behave as in the editor: on a back-in-stock alert, colours, corner radii and the reviews block are copied to every stage of the alert; social links, the social icon colour and email_unsubscribe_text are copied to every template of the shop. Plan limits apply as in the editor: email_template_enabled, email_template and the reviews block need a paid plan.
  • Who it affects: callers that manage template styling, custom HTML/CSS or the Judge.me reviews block. Callers that sent an invalid colour, destination or icon colour before now get 422 instead of a saved value.
  • Source: decl/writes-templates

2026-09-24 — enable on a scheduled preorder offer starts it now

  • What changed: POST /preorders/offers/{id}/enable on an offer whose status.state is scheduled now starts the offer now, as the dashboard's Enable does. schedule.start is cleared, schedule.end is kept, and with no end date the schedule is removed. The offer reads active. Before, enable set enabled and kept the start date, so the offer still waited for its start date.
  • Who it affects: callers that enable an offer saved with a future start date ("Starts on" or "Date range").
  • What to send instead: to keep the start date, do not call enable; the offer starts on its own. To move the start date, use POST /preorders/offers/{id}/schedule.
  • Source: decl/enable-scheduled

2026-09-23 — GET /account/settings/{section_id} returns the API read shapes; new account reads

  • What changed: settings in GET /account/settings/{section_id} is now the output of the v2 read actions behind the section, keyed by block, with the same field names as the write actions. Before, it was a hand-built summary with renamed keys and words such as "Yes", "Enabled" or "Active". Booleans are now true/false, providers and locales are their stored codes (for example "twilio", "fi"), and prose summaries are gone. Each section also returns columns (field path to model column) and raw (stored value), and unavailable when one of its reads failed. The blocks per section:
    • delivery_settings: delivery, plus locations and markets when in use.
    • signup_widget: widget, compliance, channels.
    • notification_channels: channels, sending, alerts, tagging.
    • notification_templates: locale, initial, reminder, signup_thank_you.
    • preorder_notifications: locale, notifications, template.
    • integrations: integrations.
    • languages: languages. delivery_settings no longer has a default_language line; it showed the dashboard language, not the alert language. Use languages.default_locale.
  • New read-only actions: GET /account/sending (email and SMS providers, sender, custom sending domain), GET /account/integrations (Klaviyo, Postscript, Omnisend, Mailchimp connection flags and customer sync; never keys or tokens), GET /account/languages (default_locale, app_locale, multi-language), GET /account/locations (Shopify location ids and names).
  • New read-only fields: delivery.market_setup, delivery.market_locations_enabled, delivery.alert_on_publish; channels.alerts_enabled; widget.advanced (inline form, button selector, product-tag rule, custom_css_present, custom_js_present); back-in-stock templates add after_n_days, email_section_color, email_border_radius, email_button_border_radius, email_unsubscribe_text, email_custom_css_present, klaviyo_flow_verified, klaviyo_sms_flow_verified, postscript_sms_flow_verified; the preorder template adds its style, social links, destinations, email_template_enabled, unsubscribe text and the same flow flags; preorder notifications add klaviyo_shipping_update_flow_verified and klaviyo_payment_failed_flow_verified. Writes do not accept these fields.
  • Whose account sends: GET /account/sending adds sms_account and email_account ("stoq" or "merchant") and email_sender_domain ("stoq" or "merchant"). sms_provider: "twilio" is STOQ's own SMS service, so its sms_account is "stoq", as are Telnyx and Infobip. Klaviyo or Postscript is "merchant" only when the merchant saved working credentials; otherwise STOQ sends. Shopify Flow email is "merchant". email_sender_domain is "merchant" when mail goes out from the merchant's DKIM-verified address. GET /preorders/notifications/{locale} adds email_account and sms_account too. No credentials are returned.
  • Editor labels: each section may also return labels, keyed like columns: the editor's label and label_id, menu_path, tab, section, group, and value_label / value_label_id for option controls. Only fields of settings areas in the settings catalog have entries; today that is the signup widget.
  • Preorder offer limits: PATCH /preorders/offers/{id}/limits with display.progress_bar.enabled: true now also turns display.show_remaining off, as the editor's "Customer display" radio and set_progress_bar already did. Send show_remaining in the same request to keep it.
  • Preorder offer reads: new fields, read-only: inventory adds continue_selling.in_effect (STOQ turns on "Continue selling when out of stock" for the offer's variants now; false for a manually picked variant list) and continue_selling.pending_until_offer_starts; shipping adds delivery.fulfillment_status (unfulfilled, scheduled or on_hold); limits adds batches_enabled; products adds collection.name from Shopify for a collection-sourced offer. New action GET /preorders/developer_settings: dev mode and the placement selectors (shop-wide). The offer status.state of a scheduled offer now follows its dates: an offer saved as "Starts on" or "Date range" reads scheduled before its start, not paused.
  • Back-in-stock products: PATCH /back_in_stock/products accepts per_product_customization_enabled (true or false). Sent on its own, with no targets, it turns the shop's per-product customization on or off. Off is the Products tab's "Disable per-product overrides": products follow the global settings again, and their saved values are kept. Per-product values still need targets, and false cannot be sent with them.
  • Who it affects: callers of GET /account/settings/{section_id}. Callers of the other reads only see extra fields, except the scheduled-offer status.state change.
  • What to send instead: read the new paths. For example the signup_widget field notify_me_button_text is now settings.widget.button.text.
  • Source: feat/quinn-setup-from-apiv2

2026-09-24 — market_overrides removed from variant settings

  • What changed: PATCH /preorders/offers/{id}/products/variants/{variant_id} no longer accepts market_overrides. A request that sends it now returns 422 and changes nothing, even when it also sends shipping_text or max_count. Before, it wrote the variant's per-market metafields directly. Nothing was saved in STOQ, so the admin could not show the values and later metafield updates overwrote them.
  • Who it affects: callers that set per-market shipping text or limits on one variant.
  • What to send instead: drop market_overrides. Send shipping_text and max_count on their own. To use different values per market, set them on an offer scoped to that market (/preorders/offers/{id}/markets).
  • Source: this PR

2026-09-22 — Discount value no longer clears the discount

  • What changed: PATCH /preorders/offers/{id}/payments with { "discount": { "value": 25 } } and no type now keeps the offer's current discount type and changes the amount. Before, it removed the discount and still returned 202.
  • Who it affects: callers that change the discount amount without sending type.
  • What to send: nothing new. To remove a discount, send { "discount": { "type": "no_discount" } }.
  • Source: #3955

2026-09-21 — display.show_shipping_timeline removed from preorder shipping

  • What changed: display.show_shipping_timeline is gone from PATCH /preorders/offers/{id}/shipping and from the shipping.display block in offer and shipping reads. The setting has done nothing since the simplified shipping text was retired (#3308). Every offer shows the detailed shipping timeline.
  • Who it affects: callers that send or read display.show_shipping_timeline.
  • What to send instead: drop the field. To change the text shoppers see, use POST /preorders/offers/{id}/shipping/set_shipping_text or display.shipping_text.
  • Source: commit 7038fe23a

2026-09-21 — Writes with no known setting now fail

  • What changed: a v2 write where none of the sent keys is a setting the action can change now returns 422 with code unmapped_settings. The error lists the keys the action accepts. Before, it returned 202 and changed nothing.
  • Who it affects: callers that send misspelled or removed field names, for example display.show_shipping_timeline.
  • What to send instead: the keys named in the error, or the fields in the action's request schema.
  • Source: commit 7038fe23a

2026-09-19 — email_template_enabled on back-in-stock templates

  • What changed: PATCH /back_in_stock/templates/{stage}/{locale} accepts email_template_enabled. false goes back to the standard STOQ email. true uses the custom HTML email template.
  • Who it affects: callers that manage custom HTML alert emails.
  • Source: commit 298f0f5fa

2026-09-18 — Back-in-stock templates: stage path segment and new fields

  • What changed: the template path is now /back_in_stock/templates/{stage}/{locale}. stage is initial, reminder or signup_thank_you. The old values 0 and 1 still work for initial and reminder. The update action adds enabled (turns the reminder or the signup thank-you on or off), flat colour fields (email_background_color, email_text_color, email_button_background_color, email_button_text_color, email_social_icon_color) and social link fields (email_facebook_link, email_instagram_link, email_pinterest_link, email_twitter_link, email_tiktok_link, email_whatsapp_link, email_youtube_link).
  • Who it affects: callers of the back-in-stock template actions. Existing 0/1 calls keep working.
  • What to send instead: use the stage names in new code.
  • Source: #3894

2026-09-16 — Hold back-in-stock alerts while the store is password protected

  • What changed: PATCH /back_in_stock/settings/delivery accepts send_while_password_protected. The default is false: alerts are held while the storefront password is on, and signups stay pending for the next restock.
  • Who it affects: stores with a storefront password. Alerts that used to send during a password period are now held unless you set send_while_password_protected: true.
  • Source: #3858

2026-09-16 — Progress bar box background colour

  • What changed: POST /preorders/offers/{id}/limits/set_progress_bar accepts box_background_color. When it is not set, the box keeps using the disclaimer background colour.
  • Who it affects: callers that style the preorder progress bar.
  • Source: #3874

2026-09-16 — Default-locale writes update the main text

  • What changed: a template or translation write for the store's default locale now updates the main text. Before, it was saved as a translation for that locale and the shopper-facing text did not change.
  • Who it affects: callers that pass the store's own default locale to template, notification, offer translation or signup widget writes.
  • Source: #3861

2026-09-08 — "In-stock" preorder mode only on Specific-products offers

  • What changed: POST /preorders/offers/{id}/inventory/set_provider with shopify (show Preorder for in-stock products) now returns 422 on All-products and Collection offers. Before, it silently turned the offer into a Specific-products offer.
  • Who it affects: callers that set the provider on All-products or Collection offers.
  • What to send instead: switch the offer to Specific products first, or keep the stoq provider.
  • Source: #3709

2026-09-02 — Split orders need an explicit split.type

  • What changed: PATCH /preorders/offers/{id}/shipping with fulfillment.split.enabled: true and no fulfillment.split.type now returns 422. enabled: false still turns splitting off. On PATCH /preorders/offers/{id}/payments, sending remaining_balance.auto_collect and remaining_balance.auto_collect_on_fulfillment both as true now returns 422.
  • Who it affects: callers that turn on splitting with enabled alone, or that set both auto-collect options.
  • What to send instead: { "fulfillment": { "split": { "enabled": true, "type": "orders" } } } (or "fulfillments"). Pick one auto-collect option.
  • Source: #3628

2026-09-01 — fulfillment.split.type on preorder shipping

  • What changed: shipping reads and writes carry fulfillment.split.type (fulfillments, orders or null). Split fulfillments and split orders are two separate settings. Before, reads reported one combined enabled flag.
  • Who it affects: callers that read or write order splitting.
  • Source: #3588

2026-09-01 — New filters and read actions

  • What changed:
    • GET /preorders/offers takes search, status, market_ids and sort (#3571).
    • GET /preorders/orders takes search (#3576). GET /back_in_stock/signups takes query (#3494).
    • Back-in-stock reports (summary, signups, conversions, products_in_demand) and GET /preorders/reports/by_date take market_id (#3557).
    • New reads: GET /back_in_stock/orders (#3576), GET /back_in_stock/products/{variant_id}/notification_status (#3577), GET /preorders/offer_lookup (#3566), GET /account/settings/{section_id} (#3573), GET /account/templates/{campaign_type}/translations (#3597), GET /account/translations (#3598).
    • GET /preorders/offers/{id}/translations also returns each field's default-locale source text and the store's published locales (#3596).
  • Who it affects: nobody has to change anything. These are additions.

2026-08-31 — OAuth for the MCP server

  • What changed: the MCP server at /api/v2/external/mcp supports OAuth 2.1, so MCP clients such as Claude and ChatGPT connectors can connect without pasting an API key (#3551). Clients can register themselves automatically (#3641). OAuth grants now expire, can be revoked, and are bound to the client that got them (#3731).
  • Who it affects: MCP clients. The X-Auth-Token header still works.
  • Source: #3551, #3641, #3731

2026-08-28 — New read actions

  • What changed: new reads: GET /back_in_stock/reports/wait_duration (#3493), GET /account/markets and GET /account/products (#3494), GET /preorders/orders/{id}/timeline (#3497), GET /account/plan_usage (#3498), GET /back_in_stock/reports/engagement and GET /preorders/reports/engagement (#3501), GET /preorders/products/inventory_status (#3502), GET /account/help_docs (#3503).
  • Who it affects: nobody has to change anything. These are additions.

2026-08-25 — Paid-plan features enforced on the server

  • What changed: v2 and MCP writes now check the store's plan. On a free plan, a write that changes a paid-only setting fails with 422 and an error that names the paid feature. Nothing in that write is saved.
  • Who it affects: integrations on free-plan stores that change paid-only settings.
  • What to send instead: leave the paid-only fields out, or upgrade the plan.
  • Source: #3372