Skip to content

API Changelog

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

Last updated

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-11-05 — API v2: availability can return the offer’s limit and shipping text

Section titled “2026-11-05 — API v2: availability can return the offer’s limit and shipping text”
  • What changed:
    • Added: GET /preorders/product_variants/availability takes include[]=offer_details (or include=offer_details). preorder then also has shipping_text, max_count, quantity_limit, preorder_count and quantity_limit_reached, the keys of an offer in GET /preorders/products; shipping_text is the text the buyer sees, not the variant’s stored text. Any other include value is a 422.
    • Without include the response is unchanged.
  • Who it affects: integrations that read a variant’s limit or shipping text per buyer country.
  • What to send instead: nothing; the keys are opt-in.

2026-11-02 — API v2: the fulfillments report counts cancelled orders

Section titled “2026-11-02 — API v2: the fulfillments report counts cancelled orders”
  • What changed:
    • Changed: GET /preorders/reports/fulfillments now includes cancelled preorder orders. Every unit on a cancelled order counts in sold and cancelled. It does not count in fulfilled, outstanding, waiting, on_hold or scheduled. order_count counts the cancelled order too. Until now cancelled orders were left out, and cancelled only counted units refunded or split off from an open order.
    • sold = cancelled + fulfilled + outstanding still holds.
  • Who it affects: integrations that read sold, cancelled or order_count from this report. A variant whose only preorders were cancelled now has a row.
  • What to send instead: nothing. The field names and types are unchanged.

2026-11-02 — API v2: lifetime preorder sales leave out cancelled and refunded units

Section titled “2026-11-02 — API v2: lifetime preorder sales leave out cancelled and refunded units”
  • What changed:
    • Fixed: on GET /preorders/product_variants/{shopify_variant_id}, the variant’s lifetime preorder_units_sold and line_item_revenue now leave out units on cancelled orders and units refunded before they shipped. They count every offer the variant has been on, as before. Until now they kept the quantity first ordered, so a refund, cancellation or order edit after the order was placed did not change them.
    • Changed: existing values were recounted from the orders, so some variants show lower numbers than before.
  • Who it affects: integrations that read the lifetime totals. Offer counts (preorder_units_sold on an offer’s variants, in v1 and v2) are unchanged; they already left these units out.
  • What to send instead: nothing. The field names and types are unchanged.

2026-11-01 — API v2 and MCP: error type URLs use hyphens

Section titled “2026-11-01 — API v2 and MCP: error type URLs use hyphens”
  • What changed:
    • Changed: the type URL of an error now points at a hyphenated page: https://docs.stoqapp.com/v2/docs/api-v2/errors/auth-required/ instead of .../errors/auth_required/. Every code with an underscore changes the same way. The code value itself keeps its underscores (auth_required), and so does the code on each page.
  • Who it affects: integrations that match on the type URL or store it. The API is in beta; the old underscore URLs were published for a short time.
  • What to send instead: branch on code, which is unchanged. If you compare type, replace _ with - in the last path segment.

2026-10-30 — API v2: separate orders can be grouped by ship date

Section titled “2026-10-30 — API v2: separate orders can be grouped by ship date”
  • What changed:
    • Added: ship_date for fulfillment.separate_orders.grouping on PATCH /preorders/offers/{id}/shipping. Preorder items that ship on the same date (the batch’s ship text, else the variant’s, else the offer’s) go into one separate order.
    • Rule: ship_date needs timeline.scope: "per_variant" or batches on. Otherwise every item ships on the offer’s date, and the request returns 422 with invalid_params at shipping.fulfillment.separate_orders.grouping. Sending timeline.scope: "per_variant" in the same request is fine.
    • Changed by another setting: moving an offer that groups by ship_date to timeline.scope: "same" with batches off sets grouping back to offer, listed in also_changed.
  • Who it affects: integrations that set separate-order grouping. Nothing changes for offers grouped by offer or variant.
  • Source: STOQ-2237

2026-10-30 — MCP: execute_read takes response_format, with an opt-in concise format

Section titled “2026-10-30 — MCP: execute_read takes response_format, with an opt-in concise format”
  • What changed:
    • New: execute_read takes response_format: "detailed" (default) or "concise". Concise returns 20 rows a page unless you send per_page, leaves out null and empty-string fields, puts readable fields before ids, shows the first 25 items of a list inside a row, and sends compact JSON. "detailed" returns data exactly as before.
    • New: responses are capped at about 6,000 tokens (concise) or 25,000 tokens (detailed). Anything cut is listed under truncated, and next_page is the exact execute_read call for the next rows.
  • Who it affects: MCP clients that read very large execute_read results: a read without response_format now stops at the detailed cap, and next_page continues it. Nothing else changes unless you send response_format: "concise". The REST API is unchanged.
  • What to send: response_format: "concise" for a smaller answer, or follow next_page for more rows.
  • Source: STOQ-2254

2026-10-30 — API v2 and MCP: one error shape, with a code, a docs page and the next step on every error

Section titled “2026-10-30 — API v2 and MCP: one error shape, with a code, a docs page and the next step on every error”
  • What changed:
    • Breaking: errors is an array of strings on every v2 error again. The section PATCHes and the other writes that returned errors as an object keyed by field (since 2026-10-22) now return ["<path> <message>", ...].
    • New: every error carries details: one object per entry in errors, in the same order, each with the same five keys (null when they do not apply): code (invalid_params for a field error, else the failure’s code), message, path (the dotted field), value (what you sent at path, never for a secret field) and expected (the field’s schema). errors and details never change shape from here on.
    • New: every error carries a stable code and RFC 9457 fields: type (the code’s page under Error codes), title, status and detail (what went wrong, then the next step). Errors that had no code before, such as a missing or invalid API key, an unknown path, a missing resource and a server error, now have one. An unknown path also returns suggestions and help_url.
    • New: Accept: application/problem+json returns that content type; the body is the same.
    • Changed: MCP tool failures are returned with isError: true and carry the same fields; status keeps its name form ("not_found") and the number is in http_status.
    • The top-level code, path, blocked_by, retry_after and key_url an action already returned are unchanged.
  • Who it affects: integrations that read the field-keyed errors object from a 422, and anything that matched on error text.
  • What to send instead: read fields from details[].path, details[].message and details[].value, and branch on code. See Errors & Responses.
  • What changed:
    • Added: OAuth client registration accepts loopback redirect URIs (http://127.0.0.1 and http://localhost, any port), and /authorize matches them on any port, so Claude Code and other native clients can sign in. Approving a loopback sign-in is turned on per store.
  • Who it affects: native MCP clients signing in with OAuth.
  • What to send instead: nothing; register the loopback redirect URI your client listens on.

2026-10-27 — API v2: a variant’s own ship text per batch

Section titled “2026-10-27 — API v2: a variant’s own ship text per batch”
  • What changed:
    • Added: batch_ship_texts on PATCH /preorders/offers/{id}/products/variants/{shopify_variant_id} and the bulk variant settings update, alongside batch_limits. It maps a batch_id to the ship text this variant shows on that batch (for example "Ships July 1" for one size while the batch ships June 10). null or "" puts the variant back on the batch’s own shipping_text; batch_ship_texts: null clears every batch.
    • Added: each variant read returns batch_ship_texts, holding only the batches it overrides.
  • Who it affects: integrations that edit batch offers. Nothing changes for variants without overrides.

2026-10-26 — API v2: offer revenue split into line item and order revenue

Section titled “2026-10-26 — API v2: offer revenue split into line item and order revenue”
  • What changed:
    • Renamed: an offer’s revenue is now line_item_revenue: price × quantity of the offer’s preorder line items. It is a decimal string ("42.50"); it was a number.
    • Added: order_revenue, the total of the orders those preorder lines are in, also a decimal string.
    • Both are on GET /preorders/offers/{id}, each row of GET /preorders/offers, the candidates of GET /preorders/offer_lookup, and the offers returned by the bulk status change. The preorder_revenue desc|asc sort is unchanged.
  • Who it affects: integrations and MCP clients that read an offer’s revenue.
  • What to send instead: read line_item_revenue for the figure revenue used to carry, or order_revenue for whole-order totals.

2026-10-26 — API v2: customer status reads payment_status

Section titled “2026-10-26 — API v2: customer status reads payment_status”
  • What changed:
    • Breaking: GET /customers/status preorders no longer have status, payment.state or payment.deposit_paid. Each preorder’s payment.payment_status is the same value the order endpoints return (paid, paid_in_full, balance_pending, balance_overdue, balance_collected, cancelled or the refund status).
    • Changed: order actions check cancelled_at and fulfillment_status directly. A cancelled order still can’t be released, held, split or charged, and a fulfilled order still can’t be released, held or split.
  • Who it affects: helpdesk sidebar integrations that read the customer status.
  • What to send instead: read payment.payment_status, with fulfillment_status for whether it shipped.

2026-10-26 — API v2: waitlist totals, purchase option rules and lifetime preorder sales

Section titled “2026-10-26 — API v2: waitlist totals, purchase option rules and lifetime preorder sales”

2026-10-26 — API v2: max_count is refused on an offer with batches on

Section titled “2026-10-26 — API v2: max_count is refused on an offer with batches on”
  • What changed:
  • Who it affects: integrations that write max_count to variants on offers that sell in batches, including clients that write a read row back unchanged.
  • What to send instead: leave max_count out for a batch offer, and set each batch’s limit with batch_limits ({ "<batch_id>": 20 }) on PATCH /preorders/offers/{id}/products/variants/{shopify_variant_id}. Other fields in the same request, such as shipping_text, still save when max_count is left out. Read quantity_limit for the limit that applies.

2026-10-26 — Back-in-stock all-time totals match the sum of their days

Section titled “2026-10-26 — Back-in-stock all-time totals match the sum of their days”
  • What changed:
    • Fixed: GET /back_in_stock/reports/summary?range=all_time revenue now follows an order’s edited total and drops an order that falls to low confidence, as orders already did. A date range now picks up those later changes on the order’s day too, so all time equals the sum of its days.
  • Who it affects: anyone comparing all-time back-in-stock revenue with date-range totals. The response shape is unchanged.
  • What to send instead: nothing.

2026-10-26 — API v2: one set of names for report totals, paging and preorder stock rules

Section titled “2026-10-26 — API v2: one set of names for report totals, paging and preorder stock rules”
  • What changed:
  • Who it affects: integrations reading these reports or the availability endpoint, and anything filtering offer variants by inventory_policy.
  • What to send instead: read the new keys. Switch sort_by and inventory_policy to the new values; the old ones will be removed later.

2026-10-26 — API v2: offer status on the Products tab reads, and one name for back-in-stock automatic alerts

Section titled “2026-10-26 — API v2: offer status on the Products tab reads, and one name for back-in-stock automatic alerts”
  • What changed:
    • Breaking: GET /preorders/products and GET /preorders/product_variants/{shopify_variant_id} no longer return offers[].enabled. Each offer has current_status instead (live, scheduled or off), the same value GET /preorders/offers returns. enabled was the raw on/off switch, so an offer scheduled to start later, or one past its end date, read as on.
    • New: PATCH /back_in_stock/products takes automatic_alerts_disabled (true or false), the name and meaning GET /back_in_stock/products already returns. true turns a variant’s automatic back-in-stock alerts off, false turns them back on.
    • Deprecated: automatic_alerts_enabled on PATCH /back_in_stock/products. It still works, with the opposite meaning, and the response carries a warnings entry. Sending both with opposite meanings answers 422.
    • Changed: the PATCH /back_in_stock/products response no longer echoes automatic_alerts_enabled. Its automatic_alerts_disabled is now the value you sent; before, it only said whether always_show had turned alerts off, so it read true even when automatic_alerts_enabled: true kept them on.
  • Who it affects: integrations that read offers[].enabled from the preorder Products tab reads, and integrations that set a variant’s automatic alerts on PATCH /back_in_stock/products.
  • What to send instead: read current_status == "live" where you read enabled. Send automatic_alerts_disabled with the opposite value where you sent automatic_alerts_enabled.

2026-10-26 — API v2: max_count is the variant’s own limit everywhere; new read-only quantity_limit

Section titled “2026-10-26 — API v2: max_count is the variant’s own limit everywhere; new read-only quantity_limit”

2026-10-26 — API v2: preorder reports read from/to in the shop’s time zone

Section titled “2026-10-26 — API v2: preorder reports read from/to in the shop’s time zone”
  • What changed:
    • Fixed: GET /preorders/reports/products and /fulfillments dropped the whole last day: to=2026-10-31 stopped at 2026-10-31T00:00Z. A bare to date now runs to the end of that day.
    • Fixed: summary, by_date, products and fulfillments read from/to dates as UTC days. A bare date is now that day in the shop’s time zone, as the dashboard and the docs have it, so a shop behind UTC no longer gets the day before.
    • Changed: window.from / window.to echo the shop’s offset (2026-10-31T23:59:59-04:00) instead of Z. A date-time with Z or an offset is still read as that exact moment.
  • Who it affects: integrations reading preorder reports with from/to, for any shop not on UTC, and every products or fulfillments call with to.
  • What to send instead: nothing; numbers now match the dashboard for the same dates.

2026-10-23 — API v2: preorder test emails show your own product

Section titled “2026-10-23 — API v2: preorder test emails show your own product”
  • What changed:
    • Fixed: POST /preorders/templates/confirmation/send_test failed with 500 for most shops. It now renders against one of the shop’s own preorder offer products and saves nothing.
    • Added: optional offer_id and shopify_variant_id (a variant gid) pick the offer and product shown. Left out, it uses the first product of the first enabled offer.
    • Behaviour change: answers 422 when the shop has no product in a preorder offer or the variant is not in the offer, and 404 for an unknown offer_id.
  • Who it affects: integrations and MCP clients that send preorder test emails.
  • What to send instead: nothing; the new fields are optional.

2026-10-23 — Web push removed from the storefront settings and old push sends

Section titled “2026-10-23 — Web push removed from the storefront settings and old push sends”
  • What changed:
    • Removed: the storefront settings (GET /api/v1/settings and the shop’s settings metafield) no longer have push_enabled, push_allowed, storefront_form_push_label, storefront_form_push_description, storefront_form_push_error or storefront_form_push_input, and the per-language translations no longer carry the four storefront_form_push_* messages. Push was already off for every shop.
    • Changed: a signup’s history (GET /back_in_stock/signups/{id}/history) no longer has type: push entries for old web push sends. The signup and its notifications still read back with channel: push.
    • Changed: the 10 orders from January 2024 and before that were attributed to a web push send now have notification: null in GET /back_in_stock/orders, the same as an order with no recorded message.
  • Who it affects: custom storefront code that reads the push keys from the settings, and integrations that read old push sends from a signup’s history.
  • What to send instead: nothing. STOQ sends email and SMS.

2026-10-23 — API v2: the store-wide Preorders > Products tab

Section titled “2026-10-23 — API v2: the store-wide Preorders > Products tab”
  • What changed:
    • New: GET /preorders/products lists the tab’s rows (search, sort, product_status, offer_ids, no_offer_linked, include_inventory, page, per_page up to 100), each with the offers it is in and that variant’s shipping_text, max_count, preorder_count and quantity_limit_reached, plus the offers the tab can pick from.
    • New: PATCH /preorders/products saves rows as the tab’s save bar does: variants: [{shopify_variant_id, offer_id?, offers?: [{id, shipping_text?, max_count?}]}] and an optional inventory_policy. A key left out of offers[] keeps its value; null clears it. max_count must be 1 or more, or null. If a value can’t be saved the call answers 502 and saves none of its offer links or values.
    • New: POST /preorders/products/add_to_offer adds variants to one offer as the tab’s “Add to offer” does: offer_id, up to 100 shopify_variant_ids and an optional inventory_policy: "CONTINUE". Like the tab it runs no pre-flight check; POST /preorders/offers/{id}/products/add_variants keeps the offer Products page’s.
    • New: POST /preorders/products/bulk_toggle_inventory_policy and POST /preorders/products/remove_from_offers, the tab’s bulk actions across offers, up to 100 variants, finished before the response.
    • New: GET /preorders/products/inventory_policy_conflicts names the offers that still need “Continue selling” on for the given variants.
    • Behaviour change: the dashboard no longer moves a variant into, or out of, an offer that sells in selected markets only from this tab; it is left where it is and reported skipped.
    • Removed: GET /preorders/product_variants, GET /preorders/product_variants/{variant_id}/offers and GET /preorders/products/inventory_status. GET /preorders/products lists the same variants and offers, and include_inventory=true adds their inventory.
    • Breaking: GET /preorders/product_variants/{shopify_variant_id} returns one variant as GET /preorders/products lists it: variant is a tab row and offers the offers it is in. in_preorder_offers, aggregate, metafields, settings_url and the schedule note are gone.
    • Breaking: the per-variant actions (recalculate_preorder_count, reset_preorder_count, sync_metafields, reset_metafields, detach_from_all_offers) take {shopify_variant_id}, a variant gid (a numeric id also works), and return the same {variant, offers} as the read. The bulk actions and GET /preorders/product_variants/availability take shopify_variant_ids instead of variant_ids, and availability results and errors return shopify_variant_id as a gid instead of variant_id.
    • Breaking: GET /preorders/product_variants/availability no longer returns enabled, limit or shipping. Each result is shopify_variant_id, status, preorder_type and preorder; status: "active" is what enabled: true meant.
  • Who it affects: integrations and MCP clients that manage which offer products are in across offers, or that call /preorders/product_variants, its availability or inventory_status.
  • What to send instead: use GET /preorders/products (filter with search or offer_ids, add include_inventory=true) for the removed reads. Send shopify_variant_ids where you sent variant_ids and read shopify_variant_id where you read variant_id. To add variants to one offer with the pre-flight check keep using POST /preorders/offers/{id}/products/add_variants. Read status == "active" for availability’s enabled, and a variant’s max_count, preorder_count and shipping_text per offer from GET /preorders/products.

2026-10-23 — API v2: an offer’s products and variants match the Products table

Section titled “2026-10-23 — API v2: an offer’s products and variants match the Products table”
  • What changed:
    • Breaking: variant rows under /preorders/offers/{id}/products/variants have our id and shopify_variant_id (a gid) instead of variant_id and shopify_gid, product.shopify_product_id is a gid, and in_offer_since is created_at. The derived shipping_text_shown and effective_max_count are gone (max_count and shipping_text are the variant’s own values). product.status is always there (the stored status; include_statuses=true reads missing ones from Shopify). With include_inventory, a row has inventory_quantity, inventory_policy (CONTINUE / DENY) and product.tags instead of the inventory block and product_tags; preorder_button_eligible and inventory_note are gone. The path segment is {shopify_variant_id}: a variant gid (a numeric id also works), for the read, PATCH (including batch_limits), toggle_inventory_policy and reset_count. Rows under group_by=product are the same rows.
    • Breaking: Shopify ids in the products and variants API are named shopify_*_id and are gids. Requests: shopify_variant_ids replaces variant_ids in add_variants, remove_variants, bulk_toggle_inventory_policy, advance_batches, enable, disable and GET /preorders/products/inventory_status; shopify_product_ids replaces product_ids and shopify_collection_id replaces collection_id in add_variants; PATCH .../products and its read use shopify_collection_id and excluded_shopify_variant_ids. Responses: skipped_shopify_variant_ids and skipped_quantity_limit_shopify_variant_ids; inventory_status rows return shopify_variant_id as a gid. The other ids in the products actions’ responses are gids too (variants_added, already_present, deleted_in_shopify, variants_removed, not_attached).
    • Behaviour change: add_variants runs the Products tab’s pre-flight check first, for ids, products and a collection (not a re-run, as the dashboard’s “Add all products” has none). The check covers other offers that already have the variants, sales channels that can’t sell preorders, and “Continue selling” off where STOQ won’t turn it on. With any issue, nothing is added and the answer is 409 with code: "preflight_issues", the issues under preflight and the resolve keys needed under unresolved.
    • Breaking: resolve: {remove_from_conflicting_offers, enable_continue_selling, skip_sales_channel_products} replaces the top-level remove_from_conflicting_offers. Each key runs the pre-flight modal’s fix for one issue type, and the 202 lists what was fixed per variant under resolved. A type without its key still blocks unless skip_preflight: true is sent.
    • Behaviour change: bulk_toggle_inventory_policy with CONTINUE leaves out variants at their quantity limit unless skip_quantity_limited: false is sent, as the dashboard’s checkbox starts checked. Previously the default was false.
    • Behaviour change: each add_variants and bulk_toggle_inventory_policy run has its own job_id; previously every run on an offer shared the offer’s id.
    • Behaviour change: an offer runs one add_variants job and one inventory-policy job at a time. Starting another of the same kind while the previous one is queued or running answers 409: bulk_toggle_inventory_policy (with code: "job_in_progress"), add_variants (including resolve.enable_continue_selling), and enable / disable with update_inventory_policy: true.
    • Breaking: bulk_toggle_inventory_policy no longer returns the whole offer. The 202 body is policy, variant_count, skipped_shopify_variant_ids, skipped_quantity_limit_shopify_variant_ids (when any) and job. The API v1 offer job status and cancel endpoints answer with that run’s job_id (a uuid) instead of the offer id.
    • Behaviour change: remove_variants and PATCH .../products/variants/{shopify_variant_id} return 200. toggle_inventory_policy on one variant runs now and returns 200 with the row; 502 if Shopify refuses. bulk_toggle_inventory_policy always returns 202 with job: {job_id, status, status_url}. max_count must be 1 or more, or null, here and in bulk_update_settings. reset_count resets the variant’s count on this offer, as the Products table’s Reset does.
    • Added: add_variants takes shopify_product_ids (every variant of those products), shopify_collection_id (custom offers), skip_variants_in_market_offers, skip_preflight and resolve. On all, collection and tags offers, a call with no ids re-runs the selection rule.
    • New: POST /preorders/offers/{id}/products/variants/{shopify_variant_id}/reset_count, POST /preorders/offers/{id}/products/advance_batches, GET /preorders/products/tags and GET /preorders/products/tags/count.
    • Unchanged: the variants list still takes sort=attached / sort=title, exclude_discarded (variants deleted in Shopify are listed with discarded: true unless it is sent) and the inventory, inventory_policy, product_tag and exclude_product_tag filters, which now read the row’s inventory_quantity, inventory_policy and product.tags.
  • Who it affects: integrations and MCP clients that list an offer’s variants, send variant, product or collection ids to the products actions, enable / disable or inventory_status, or read their response ids.
  • What to send instead: send shopify_variant_ids where you sent variant_ids (and shopify_product_ids, shopify_collection_id, excluded_shopify_variant_ids); numeric ids are still accepted as values. Read shopify_variant_id where you read variant_id, and inventory_quantity, inventory_policy and product.tags where you read inventory and product_tags. On a 409 with code: "preflight_issues", show the issues, then resend with the resolve keys in unresolved (send resolve: { remove_from_conflicting_offers: true } where you sent remove_from_conflicting_offers: true) or skip_preflight: true.

2026-10-23 — Web push removed: push is no longer a channel you can filter on

Section titled “2026-10-23 — Web push removed: push is no longer a channel you can filter on”
  • What changed:
    • Breaking: GET /back_in_stock/notifications?channel=push returns 422. channel takes email or sms.
    • Breaking: GET /account/sending no longer has a push channel under notifications.back_in_stock.restock_alert and reminder. STOQ has not sent web push since June 2025, so the sender: stoq it showed was wrong.
    • Breaking: GET /account/translations no longer lists signup_form.push_label, push_input, push_description or push_error. The storefront form has no push option.
    • Old web push sends and signups still read back as they happened: channel: push on notifications, signups and GET /back_in_stock/orders notification.channel, and type: push in a signup’s history.
    • The API v1 reference no longer lists push as an intent channel or customer.push. POST /api/v1/intents already created only email and SMS signups.
  • Who it affects: integrations that filter the send log by channel=push, or read the push key from /account/sending or the push form messages from /account/translations.
  • What to send instead: drop channel=push. Nothing replaces push; STOQ sends email and SMS.

2026-10-23 — API v2: a template edit with locale no longer overwrites the store’s default language

Section titled “2026-10-23 — API v2: a template edit with locale no longer overwrites the store’s default language”
  • What changed:
    • Fixed: PATCH /back_in_stock/templates/{key} with a non-default locale and a colour, radius, social link or reviews field wrote that language’s unsubscribe text and reviews title into the default-language copy of every other template (and the edited template’s own reviews title). The default-language text is now left alone.
    • Changed: a translated email.unsubscribe_text is copied to every template in that language, and a translated email.reviews.title to every template of the alert sequence, the same way the default-language values already were.
  • Who it affects: integrations and MCP clients that edit templates with locale.
  • What to send instead: nothing.

2026-10-23 — API v2: reset_preorder_count stays reset; engagement reads scoped to a product return that product

Section titled “2026-10-23 — API v2: reset_preorder_count stays reset; engagement reads scoped to a product return that product”
  • What changed:
  • Who it affects: integrations that reset preorder counts or read engagement per product.
  • What to send instead: nothing. After a reset, recalculate_preorder_count no longer brings the old orders back.

2026-10-23 — API v2: integration flow setup modes; dashboard and API check and log integration changes the same way

Section titled “2026-10-23 — API v2: integration flow setup modes; dashboard and API check and log integration changes the same way”
  • What changed:
    • New: Klaviyo’s settings.flows on GET /account/integrations and GET /account/integrations/klaviyo is the flow setup mode per campaign type and channel ({ "restock": { "email": "automatic", "sms": null }, ... }): automatic, manual, or null before the merchant chose.
    • Fixed: Postscript’s sms.confirm_flow step now names POST /account/integrations/postscript/flows/verify, the action that completes it. It named /flows/confirm, which left the step todo.
    • Fixed: a Postscript key (PATCH /account/sending postscript_api_key, POST /account/integrations/postscript/connect) marks Postscript setup verified only once it is saved, and removing or disconnecting the key marks it unverified. Before, a key check marked setup verified even when the save then failed, and removing the key left it verified.
    • Changed: GET /account/integrations/{id}/events also lists changes saved in the STOQ dashboard (actor_surface: dashboard): keys set and cleared, settings changed and disconnects. POST /account/integrations/mailchimp/connect with list_id now also records settings_changed for list_id.
    • Changed: the Mailchimp key and audience, custom sender address, Postscript key, product_translations_enabled and email size checks run only on values that change. Sending a value that is already saved is no longer checked again; the dashboard runs the same checks now.
  • Who it affects: callers that read Klaviyo integration settings, follow Postscript setup steps, or read integration events.
  • What to send instead: to finish Postscript setup, POST /account/integrations/postscript/flows/verify.

2026-10-23 — API v2: new offers match the dashboard’s; count of orders a reschedule would move

Section titled “2026-10-23 — API v2: new offers match the dashboard’s; count of orders a reschedule would move”
  • What changed:
    • Changed: POST /preorders/offers creates the offer with no full-payment description (payments.full.option_description is null), as the dashboard’s Create offer does. It used to carry the partial-payment text “Pay {{ payment }} upfront and the remaining will be charged when your product is shipped.”, which the payment widget and cart line showed on a fully paid offer. The dashboard’s Create offer now runs the same code.
    • New: GET /preorders/offers/{id}/shipping/schedulable_orders returns schedulable_orders_count and checked_count: of the offer’s latest 100 uncancelled preorders, how many are unfulfilled, so reschedule_existing_orders: true on PATCH /preorders/offers/{id}/shipping would move them. It is the count the offer editor shows beside “Reschedule existing orders to this date”. An order with several lines on the offer counts once.
  • Who it affects: integrations that create offers and read payments.full.option_description right after. Nothing is removed or renamed.
  • What to send instead: nothing; to set a full-payment description, PATCH /preorders/offers/{id}/payments as before.

2026-10-23 — API v2: sending history filters by variant, product and status; delivery settings name the any-variant products

Section titled “2026-10-23 — API v2: sending history filters by variant, product and status; delivery settings name the any-variant products”
  • What changed:
    • New: GET /back_in_stock/sending_history takes shopify_variant_id and shopify_product_id (gids) and status (active, waiting, completed, or incomplete for active or waiting). status=incomplete with a variant or product answers whether a send is still running for it, as the waitlist page’s banner shows. A numeric id or an unknown status returns 422.
    • New: GET /back_in_stock/settings/delivery returns any_variant.products ([{ shopify_product_id, title }]), the Shopify titles of any_variant.shopify_product_ids, as the any-variant product picker shows them. Products Shopify no longer has are left out.
  • Who it affects: nobody; the fields and params are new.
  • What to send instead: nothing; existing requests work as before.

2026-10-23 — API v2: browse the back-in-stock Products tab

Section titled “2026-10-23 — API v2: browse the back-in-stock Products tab”
  • What changed:
    • New: GET /back_in_stock/products lists the dashboard’s Products tab when you send any of sort, group_by, after, product_status, button_visibility, restock_message or restock_message_query: the whole Shopify catalog, not only the variants STOQ has activity for, through the same code as the tab.
      • sort: title_asc (default), title_desc, created_desc, created_asc. product_status: comma list of active, draft, archived, unlisted. search matches title or SKU. limit is the page size (default 20, max 100).
      • button_visibility (comma list of always_show, when_out_of_stock, never_show) and restock_message (has or none) with restock_message_query (text the message contains) match variants STOQ has a row for, as on the tab.
      • group_by=product returns products (shopify_product_id, product_title, product_status, image_url, variants_count, variants).
      • The response adds has_next_page, end_cursor (send it as after for the next page) and product_status_filter_skipped (true when product statuses could not be checked, so the status filter was left out).
      • These cannot be combined with shopify_product_id, shopify_variant_id, overrides_only or has_waitlist (422). 502 when Shopify cannot list the catalog. Browsing shares the search rate limit.
    • New: every GET /back_in_stock/products row has sku, product_status and image_url.
  • Who it affects: nobody; requests without the new params return what they did before.
  • What to send instead: nothing.

2026-10-23 — API v2: report rows carry the product image; plan usage SMS segments

Section titled “2026-10-23 — API v2: report rows carry the product image; plan usage SMS segments”

2026-10-23 — API v2: report summaries match the Home analytics card

Section titled “2026-10-23 — API v2: report summaries match the Home analytics card”
  • What changed:
    • Breaking: GET /back_in_stock/reports/summary sms_sent (and so notifications_sent) counts every back-in-stock SMS sent, delivered or not, the count plan usage bills and the Home card shows. It was SMS delivered only, so it goes up for shops with undelivered SMS.
    • Breaking: with range=all_time, GET /back_in_stock/reports/summary reads revenue, orders, average_order_value, signups, emails_sent, sms_sent and notifications_sent from the shop’s lifetime totals, and GET /preorders/reports/summary reads preorder_orders_count, order_revenue and average_order_value from them, as Home’s All time does. They were the last ten years of daily totals; the other fields still are.
    • GET /preorders/reports/summary order_revenue is every counted preorder order’s value, the figure average_order_value was already divided from. It used to leave out low-confidence preorder orders, which preorders don’t get, so values don’t change in practice.
  • Who it affects: integrations that read SMS or notification counts from the back-in-stock summary, or read either summary with range=all_time.
  • What to send instead: nothing; the request is the same. For a ten-year window summed from daily totals, send from/to instead of range=all_time.

2026-10-23 — API v2: an offer’s variants list has the offer editor’s search, filters and group by product

Section titled “2026-10-23 — API v2: an offer’s variants list has the offer editor’s search, filters and group by product”
  • What changed:
    • New: GET /preorders/offers/{id}/products/variants takes group_by=product, as the offer editor Products tab groups them. It returns products instead of variants: each has shopify_product_id (gid), title, image_url, variant_count and its variants (the usual rows). Pages count products, so a product’s variants never split across pages; meta.total_count and meta.total_products are the product count and meta.total_variants the variant count.
    • New: the same list takes the Products tab’s search (product title, variant title or SKU), product_status (active, draft, archived, unlisted, comma-separated), limit_reached=true and its sorts (title_asc, title_desc, created_asc, created_desc, added_asc, added_desc, sold_asc, sold_desc). include_statuses=true adds product.status (products[].status when grouped). When a product’s status can’t be read from Shopify the status filter is skipped and the response has product_status_filter_skipped: true.
    • Changed: the list now runs the Products tab’s query. sort=title (now the same as title_asc) compares titles ignoring case and lists a product’s variants in Shopify’s order instead of by variant title.
  • Who it affects: nobody; the params are new and existing requests return the same rows.
  • What to send instead: nothing.

2026-10-23 — API v2: email logo uploads are checked; verify_domain saves a pass

Section titled “2026-10-23 — API v2: email logo uploads are checked; verify_domain saves a pass”
  • What changed:
    • Breaking: POST /account/templates/email_style/logo takes only PNG, JPEG, GIF and WebP files of 2 MB or less, judged from the file’s bytes, not the content type it is sent with. Any other file, or a larger one, returns 422 (errors.file) and the current logo stays. The dashboard’s logo drop zone has the same rule.
    • Fixed: when POST /account/sending/verify_domain returns dkim_verified: true, it now saves that, so GET /account/sending shows custom_address_status: verified and mail goes from the merchant’s address straight away instead of after the next hourly check. A failing check changes nothing stored.
  • Who it affects: callers that upload email logos in other formats or over 2 MB, and callers that poll GET /account/sending after verifying.
  • What to send instead: convert the logo to PNG, JPEG, GIF or WebP under 2 MB.

2026-10-23 — API v2: offer translations use the locale key the storefront reads

Section titled “2026-10-23 — API v2: offer translations use the locale key the storefront reads”
  • What changed:
    • Breaking: POST /preorders/offers/{id}/translations/set, set_many and unset store a regional locale under the key the storefront, emails and dashboard read: pt-BR is stored and returned as pt_br in locales and translations. Before, set with pt-BR stored pt-BR, which the storefront never showed.
    • Fixed: clear_locale with pt-BR now removes the pt_br text (including text written by reset_to_defaults or the dashboard) and any older pt-BR text.
    • Fixed: locales are case-insensitive (pt-br is pt-BR). reset_to_defaults fills a regional locale with no STOQ defaults of its own (ro-RO, zh-CN, bg-BG) with its base language’s defaults instead of returning 422.
    • Changed: set, set_many, unset and clear_locale return 422 for a locale that isn’t a language tag.
    • published_locales on translation responses may be up to 5 minutes old (the list of languages published in Shopify is cached per shop).
  • Who it affects: integrations that write or read regional-locale translations (pt-BR, pt-PT, zh-TW, …). Single-language locales (fr, de) are unchanged.
  • What to send instead: any spelling of the locale works; read results back under the lower-case, underscore form (pt_br).

2026-10-23 — API v2: ships_at takes a date in the shop’s time zone

Section titled “2026-10-23 — API v2: ships_at takes a date in the shop’s time zone”
  • What changed:
    • New: ships_at on PATCH /preorders/offers/{id}/shipping takes a date ("2026-11-05"). It is stored as midnight on that day in the shop’s time zone, so timeline.variant_shipping_text is “05 Nov 2026” and reschedule_existing_orders moves orders to that day. Before, a date returned 422.
    • Datetimes work as before: "2026-11-05T00:00:00Z" is that exact moment, and its day is taken in the shop’s time zone (for a Los Angeles shop, “04 Nov 2026”). Reads still return ships_at as a UTC datetime.
  • Who it affects: integrations and MCP clients that set an offer’s ship date.
  • What to send instead: send the date alone to set a ship day without working out the shop’s offset.

2026-10-23 — Shopify Flow: order and offer actions follow the dashboard’s rules and fail when nothing happened

Section titled “2026-10-23 — Shopify Flow: order and offer actions follow the dashboard’s rules and fail when nothing happened”
  • What changed:
    • Fixed: Collect pending payment refuses cancelled orders and only acts on preorder orders. Release all fulfillments refuses cancelled and fulfilled orders. Both fail with the same message the dashboard and API v2 give.
    • Fixed: Send shipping update fails (400 with a message) when no email was sent, for example on a cancelled or refunded order, instead of reporting success.
    • Fixed: Remove variant from offer fails (400) and leaves the variant on the offer when Shopify refuses the removal, instead of reporting success.
    • Fixed: the offer actions (Add variant to offer, Remove variant from offer) never pick a deleted offer. When a deleted and a live offer share an internal name, the live one is used. selling_plan_identifier also takes the Shopify selling plan id of any of the offer’s payment options.
  • Who it affects: Shopify Flow workflows that use these STOQ actions. A run that used to show success without doing anything now shows as failed.
  • What to send instead: nothing; the action inputs are unchanged.

2026-10-23 — API v2: order reads drop line item images; splitting an order with nothing to split succeeds

Section titled “2026-10-23 — API v2: order reads drop line item images; splitting an order with nothing to split succeeds”
  • What changed:
    • Breaking: GET /preorders/orders/{id} line items no longer have image_url. The read no longer asks Shopify for product images. The order action responses drop it too.
    • Fixed: POST /preorders/orders/{id}/split_fulfillment_orders on an order with nothing to split (every fulfillment order is preorder-only, or none needs splitting) returns 202 with an empty outcome.fulfillment_orders instead of 422. bulk_split_fulfillment_orders no longer reports those orders as failed, and the Shopify Flow action Split fulfillment orders succeeds for them.
    • Fixed: the Shopify Flow actions Hold all fulfillments and Split fulfillment orders always return a message when they fail, instead of null.
  • Who it affects: integrations that read line_items[].image_url from an order read; callers that split preorder-only orders.
  • What to send instead: read product images from Shopify with the line item’s shopify_variant_id.

2026-10-23 — API v2: duplicating an offer keeps its fulfillment status; deleted offers can’t be duplicated

Section titled “2026-10-23 — API v2: duplicating an offer keeps its fulfillment status; deleted offers can’t be duplicated”
  • What changed:
    • Fix: POST /preorders/offers/{id}/duplicate on an offer that sells in batches with a Scheduled fulfillment status (exact_time or anchor) now keeps that status on the copy. It was changed to Unfulfilled (asap). The dashboard’s Duplicate had the same bug.
    • Breaking: POST /preorders/offers/{id}/duplicate on a deleted offer returns 404, the same response as an unknown id. It used to make a live copy. The dashboard refuses it too.
  • Who it affects: integrations that duplicate deleted offers.
  • What to send instead: restore the offer (POST /preorders/offers/{id}/restore) and then duplicate it.

2026-10-23 — API v2: developer insert types are checked

Section titled “2026-10-23 — API v2: developer insert types are checked”
  • What changed:
    • Breaking: PATCH /preorders/settings/developer returns 422 when an insertion_points.*.insert_type or collection.badge_insert_type is not one of beforebegin, afterbegin, beforeend or afterend. The field’s description used to suggest before and after; the storefront cannot place an element with those, and the preorder button did not render. "" still clears the value so the default applies.
  • Who it affects: callers that sent before or after (or any other value) as an insert type.
  • What to send instead: beforebegin (before the selector’s element), afterbegin (inside it, first), beforeend (inside it, last) or afterend (after it).

2026-10-23 — API keys stop working when a store uninstalls STOQ

Section titled “2026-10-23 — API keys stop working when a store uninstalls STOQ”
  • What changed:
    • Breaking: API v2 and API v1 return 401 with Invalid API key for the key of a store that has uninstalled STOQ, the same answer as an unknown key. The MCP server already did this.
    • Breaking: uninstalling STOQ deletes the store’s API key and connector key and revokes every AI connector (OAuth) grant. Reinstalling issues a new API key and connector key; the old ones never work again.
  • Who it affects: integrations still calling the API for a store that uninstalled STOQ.
  • What to send instead: after a reinstall, copy the new API key from Integrations > API, and reconnect each AI connector with the new connector key.

2026-10-23 — API v2: order lists and reads show what the dashboard shows; bulk actions take “select all”

Section titled “2026-10-23 — API v2: order lists and reads show what the dashboard shows; bulk actions take “select all””
  • What changed:
    • New: GET /preorders/orders entries have products ([{ name, shopify_variant_id }], the preorder lines) and linked_offers ([{ offer_id, offer_internal_name, deleted }]), the dashboard row’s Products and Offers linked columns.
    • New: order reads and list entries have cancelled_at and customer.phone. GET /preorders/orders/{id} (and /payments) has payments.subtotal_price and payments.tax_lines ([{ title, rate, price }]); line_items[].image_url is the product image the order page shows, read live from Shopify (null if it can’t be read).
    • New: every POST /preorders/orders/bulk_* takes apply_to_all: true with the GET /preorders/orders filters instead of order_ids, like the dashboard’s select all. No 1000 limit. order_ids together with apply_to_all, a filter without apply_to_all, or filters matching no order return 422.
    • New: GET /preorders/offers?status=deleted lists deleted offers (the orders filter’s Deleted offer group). all still leaves them out.
    • New: GET /back_in_stock/orders entries have notified_product ({ shopify_product_id, shopify_variant_id, title }, gids) and notification ({ channel, sent_at }, channel email, sms or push).
  • Who it affects: integrations that read order lists or run bulk order actions. Nothing is removed or renamed.
  • What to send instead: nothing; existing requests work as before.

2026-10-23 — API v2: back-in-stock reports and signups show what the dashboard shows

Section titled “2026-10-23 — API v2: back-in-stock reports and signups show what the dashboard shows”
  • What changed:
    • New: GET /back_in_stock/reports/products_in_demand rows have image_url and variants_count. Product rows (group_by=product) have deleted.
    • New: include_inventory=true on products_in_demand adds inventory_available to variant rows, the table’s Inventory column, using the market filter. include_prices=true adds price and compare_at_price. Both are opt-in because each asks Shopify once per page.
    • New: GET /back_in_stock/reports/engagement and GET /preorders/reports/engagement return pixel_active. It is false while the STOQ web pixel is not set up.
    • New: POST /account/web_pixel/enable turns the web pixel on, as the Engagement view’s “Set up tracking” button does. Calling it again changes nothing.
    • New: GET /back_in_stock/signups rows have source, product_deleted and variant_deleted, the list’s Quick Resubscribe icon and Deleted badges.
    • New: GET /account/plan_usage returns usage.by_template: email and SMS alerts for the back-in-stock alert (template_position 0) and the first reminder (1), as the usage card’s breakdown shows them.
    • New: GET /back_in_stock/sending_history takes source, a part match on the send’s source, as the Sending history search box does.
    • New: PATCH /back_in_stock/settings/widget takes form.messages.did_you_mean_error, the email-typo suggestion. Send locale to write one language.
  • Who it affects: nobody; the fields and params are new.

2026-10-23 — API v2: plan usage is plan and usage only; one docs search; two reads removed

Section titled “2026-10-23 — API v2: plan usage is plan and usage only; one docs search; two reads removed”
  • What changed:
    • Breaking: GET /account/plan_usage returns only plan (name, friendly_name), period, limits and usage. billing, trial, charges, plan.price_usd and limits.allow_overage are gone. plan.usage_type and plan.unlimited_notifications moved to limits.usage_type and limits.unlimited_notifications.
    • New: GET /account/plan_usage is readable over MCP (execute_read, and search lists it).
    • Removed: GET /account/products (404). Search products with GET /back_in_stock/products?search=…; list the variants on preorder offers with GET /preorders/product_variants.
    • Breaking: GET /account/help_docs searches the help centre and the developer docs, through the same code as the MCP’s find_doc / read_doc. New audience (merchant, developer or all, the default) and, for reads, offset. Each search result adds source (help, developer, api, contract). url accepts docs.stoqapp.com pages as well as help.stoqapp.com. A read returns source and, when the page is cut, next_offset; the images list is gone (screenshots stay inline in content). Titles are the page titles.
  • Who it affects: callers of GET /account/plan_usage, GET /account/products and GET /account/help_docs.
  • What to send instead: read limits.usage_type instead of plan.usage_type; pass audience: "merchant" to GET /account/help_docs for help-centre results only.

2026-10-22 — API v2: preorder reports leave cancelled orders out

Section titled “2026-10-22 — API v2: preorder reports leave cancelled orders out”
  • What changed:
    • Breaking: GET /preorders/reports/summary leaves cancelled orders out of every total: preorder_orders_count, preorder_units_sold, order_revenue, line_item_revenue, average_order_value, unpaid_orders, unfulfilled_orders and unique_customers.
    • Breaking: GET /preorders/reports/by_date leaves cancelled orders out of units_sold and line_item_revenue too (orders and order_revenue already did).
    • Breaking: GET /preorders/reports/fulfillments leaves cancelled orders out. sold, order_count, last_order_date and the totals drop by them, and a variant or product whose only preorders were cancelled has no row. cancelled now counts only units cancelled from an order that is still open (a split order); sold = cancelled + fulfilled + outstanding still holds.
  • Who it affects: callers that compare these numbers with their own order or unit counts.
  • What to send instead: nothing; the numbers drop by the cancelled orders.

2026-10-22 — API v2: offer writes match the offer editor; bulk enable / disable; load default translations

Section titled “2026-10-22 — API v2: offer writes match the offer editor; bulk enable / disable; load default translations”
  • What changed:
    • New: POST /preorders/offers/bulk_enable and POST /preorders/offers/bulk_disable, the Offers list’s bulk Enable / Disable. Body: ids (offer ids) and optional update_inventory_policy (true flips the attached variants to CONTINUE on enable, DENY on disable). An unknown, another shop’s or deleted offer returns 422 under errors.ids before anything changes. Returns 202 with total, updated, inventory_updates_initiated, failed / failed_errors when some offers could not change, and offers.
    • New: POST /preorders/offers/{id}/translations/reset_to_defaults, the offer translator’s “Load default translations”. Optional locale (the shop’s default language when omitted). It overwrites the offer’s 14 default text fields with STOQ’s defaults in that language.
    • Behaviour change: PATCH /preorders/offers/{id}/payments accepts a partial.checkout_charge.charge_amount of 0, the editor’s “No upfront payment”. Negative amounts still return 422.
    • Behaviour change: show_payment_widget on PATCH .../payments also sets the payment cart attribute (advanced.cart_attributes.show_payment) to the same value, as the editor’s “Show payment & discount savings” does. A change is listed in also_changed.
    • Behaviour change: ships_at or days_after_checkout on PATCH .../shipping also rewrites timeline.variant_shipping_text (the {{ date }} value): the date as “05 Nov 2026” in the shop’s time zone, or “N days after checkout”.
    • Behaviour change: turning batches on with PATCH .../batches moves a Scheduled fulfillment status (shipping.delivery_timing exact_time) to Unfulfilled (asap), listed in also_changed. The date and days are kept.
  • Who it affects: integrations and MCP clients that edit offer payments, shipping or batches, or turn many offers on or off.
  • What to send instead: nothing; existing requests work as before.

2026-10-22 — API v2 and MCP: market_ids is now shopify_market_ids

Section titled “2026-10-22 — API v2 and MCP: market_ids is now shopify_market_ids”

2026-10-22 — API v2 and MCP: Shopify ids are always gids

Section titled “2026-10-22 — API v2 and MCP: Shopify ids are always gids”
  • What changed:
    • Fix: these fields returned bare numbers (or numeric strings) and now return gid strings, as every other shopify_*_id field does:
  • Who it affects: callers that read these fields as numbers.
  • What to send instead: read the gid; its last path segment is the number. Filters on these endpoints already accept a gid or a number.

2026-10-22 — API v2: hold and split preorder orders

Section titled “2026-10-22 — API v2: hold and split preorder orders”
  • What changed:
  • Who it affects: Flow workflows that hold or split orders that may already be cancelled or fulfilled. Otherwise nobody unless you call the new endpoints.
  • What to send instead: nothing; a Flow run on such an order now fails instead of holding or splitting it.

2026-10-22 — API v2: custom sender address status

Section titled “2026-10-22 — API v2: custom sender address status”
  • What changed:
    • New: custom_address_status on GET /account/sending and the PATCH /account/sending response: not_set, pending (DKIM not verified, mail goes from STOQ’s address) or verified (mail goes from the merchant’s address).
  • Who it affects: callers that check why mail is not sent from the merchant’s address. The DNS records stay on POST /account/sending/verify_domain.
  • What to send instead: nothing; the field is additive.

2026-10-22 — API v2: report filters match the dashboard’s

Section titled “2026-10-22 — API v2: report filters match the dashboard’s”
  • What changed:
  • Who it affects: integrations that filter products_in_demand or wait_duration by market.
  • What to send instead: shopify_market_ids=<id> for one market, or several ids separated by commas.

2026-10-22 — API v2: per-variant batch limits and batch progress

Section titled “2026-10-22 — API v2: per-variant batch limits and batch progress”
  • What changed:
    • New: batch_limits on PATCH /preorders/offers/{id}/products/variants/{variant_id}: this variant’s own limit per batch, keyed by batch_id. A batch left out is unchanged; null puts the variant back on the batch’s quantity_limit.
    • New: POST /preorders/offers/{id}/products/variants/bulk_update_settings sets shipping_text, max_count and batch_limits on up to 250 variants at once: send shopify_variant_ids (gids); unattached ones come back in skipped_shopify_variant_ids.
    • New: the offer’s variant reads have batch_limits and batch_progress (per batch: quantity_limit enforced for the variant, default_quantity_limit, count sold, and the current_batch_id).
    • Behaviour change: on a batch offer, effective_max_count and quantity_limit_reached follow the variant’s own batch limits.
  • Who it affects: integrations that manage batch offers.
  • What to send instead: nothing; requests without batch_limits work as before.

2026-10-22 — API v2: back-in-stock customers

Section titled “2026-10-22 — API v2: back-in-stock customers”

2026-10-22 — API v2: every Reports page order filter, and a shipping update when rescheduling

Section titled “2026-10-22 — API v2: every Reports page order filter, and a shipping update when rescheduling”
  • What changed:
    • New: send_shipping_update, subject, header and description on POST /preorders/orders/{id}/reschedule_fulfillment and POST /preorders/orders/bulk_reschedule_fulfillment. With send_shipping_update: true the customer gets a shipping update, as when the dashboard’s “Send customers a shipping update” is ticked, only when a scheduled fulfillment moved. A description you send goes out as given; left out, it is what the modal prefills, “Your preorder is now scheduled to ship on {date}.” followed by the template text. The single action’s outcome.notified says whether it went out.
    • New: GET /preorders/orders filters vendors, shopify_product_id, several offer_id values (comma-separated, including inactive and deleted offers) and several shopify_variant_id gids. They run the dashboard’s own orders query.
    • New: GET /back_in_stock/orders filters product_match_type (same_as_notification), shopify_variant_id and shopify_product_id.
    • Breaking: GET /back_in_stock/orders entries drop order_id (the bare Shopify number) and shopify_gid; the Shopify order is shopify_order_id, a gid, as on preorder orders.
    • Breaking: variant_id on GET /preorders/orders is now shopify_variant_id and takes gid://shopify/ProductVariant/<id> gids (a bare number still works).
    • Behaviour change: on both order lists a bare from/to date is that whole day in the shop’s time zone, and an unparseable value returns 422 instead of matching nothing. An unknown offer_id or a malformed gid returns 422.
  • Who it affects: integrations that filter preorder orders by variant_id, read order_id or shopify_gid from back-in-stock orders, or pass dates to from/to.
  • What to send instead: send shopify_variant_id instead of variant_id, and read shopify_order_id instead of order_id / shopify_gid. Send a time with an offset in from/to if you need an exact instant.
Section titled “2026-10-22 — API v2: request and toggle theme integrations (Quick Buy, Featured products)”
  • What changed:
  • Who it affects: nobody; these are new endpoints.
  • What to send instead: nothing.

2026-10-22 — API v2: duplicate an offer; reschedule existing orders when the ship date changes

Section titled “2026-10-22 — API v2: duplicate an offer; reschedule existing orders when the ship date changes”
  • What changed:
    • New: POST /preorders/offers/{id}/duplicate, the dashboard’s Duplicate. Returns 202 with the new offer in the GET /preorders/offers/{id} shape. The copy starts off, with no products and no schedule.
    • New: reschedule_existing_orders on PATCH /preorders/offers/{id}/shipping, the editor’s “Reschedule existing orders to this date”. true moves the scheduled fulfillments of the offer’s unfulfilled preorders to ships_at (or each order date plus days_after_checkout) in the background, and the response has rescheduled_orders_queued. It needs delivery_timing exact_time and sends no customer email.
  • Who it affects: integrations and MCP clients that copy offers or move an offer’s ship date.
  • What to send instead: nothing; existing requests work as before.
Section titled “2026-10-22 — API v2: OAuth connect links”
  • What changed:
    • Behaviour change: POST /account/integrations/{id}/connect for Klaviyo, Judge.me, Smile.io and Gorgias returns a one-use connect_url (15 minutes) with expires_at and single_use. A new link replaces the last. Gorgias takes account.
    • New: 429 with code: rate_limited after 5 links per integration in an hour.
    • New: connect (pending, expires_at, last_result, last_result_at) on each OAuth integration’s status, reason.code: provider_error after a failed connect, and the connected, connect_denied and connect_failed events.
  • Who it affects: callers that show connect_url.
  • What to send instead: show the new connect_url as before, then read connect.last_result.

2026-10-22 — API v2: connect and disconnect integrations

Section titled “2026-10-22 — API v2: connect and disconnect integrations”
  • What changed:
  • Who it affects: callers of disconnect that expected only provider and connected; MCP clients that set API keys. REST callers can still send keys.
  • What to send instead: over MCP, show key_url to the merchant. Over the REST API, prefer connect for keys so they are checked.
  • What changed:
    • Breaking: GET /account/integrations returns integrations, one status per integration (status, reason, connection, selected_for, steps, settings, plan, links), instead of the flat connection flags. Filter with category (marketing, shopify, other: the Integrations page sections) or status. An integration chosen as email_provider or sms_provider that does not send is needs_attention.
    • New: GET /account/integrations/{id}.
    • New: integration ({ id, href }) on each GET /account/sending notification, and on each email and sms block of the back-in-stock and preorder template reads.
  • Who it affects: callers of GET /account/integrations.
  • What to send instead: read integrations[].connection.connected and settings. PATCH /account/integrations is unchanged.

2026-10-22 — API v2: account settings are writable, with Shopify gids

Section titled “2026-10-22 — API v2: account settings are writable, with Shopify gids”

2026-10-22 — API v2: signup history shows which provider sent each message

Section titled “2026-10-22 — API v2: signup history shows which provider sent each message”
  • What changed: each email and sms entry in channels from GET /back_in_stock/signups/{id}/history has provider (stoq for STOQ’s own sending, otherwise the integration: klaviyo, flow, postscript) and provider_fallback_reason (why it went through STOQ instead of the configured integration, or null).
  • Who it affects: nobody; the fields are new.

2026-10-22 — API v2: preorder notifications use the same keys and shape as back-in-stock

Section titled “2026-10-22 — API v2: preorder notifications use the same keys and shape as back-in-stock”
  • What changed:
    • Breaking: /preorders/notifications and its sections are gone (404). Each preorder notification is one template by key, confirmation, shipping_update or payment_failed: GET/PATCH /preorders/templates/{key}, and GET /preorders/templates lists all three.
    • Breaking: template fields are nested under email{...}, and the confirmation also has sms{body, klaviyo_flow_name}, so confirmation_email and confirmation_sms are one template. Responses carry key and locale. Flat fields are 422 “is unknown”; errors are keyed template.<path>.
    • Breaking: the confirmation’s switches (email_enabled, sms_enabled, customer_cancellation_enabled) moved from /preorders/notifications/channels to GET/PATCH /preorders/notification_settings/confirmation, and GET /preorders/notification_settings lists them. Errors are keyed notification_setting.<field>. shipping_update and payment_failed have no switch.
    • Moved: POST /preorders/notifications/confirmation_email/send_test is POST /preorders/templates/confirmation/send_test.
    • Removed: the push.* paths from GET /account/templates/preorder/translations.
  • Who it affects: integrations and MCP clients that read or edit preorder notifications.
  • What to send instead: PATCH /preorders/templates/confirmation with {"email": {"subject": "..."}, "sms": {"body": "..."}} instead of PATCH /preorders/notifications/confirmation_email with {"subject": "..."} and PATCH /preorders/notifications/confirmation_sms with {"body": "..."}. PATCH /preorders/templates/shipping_update with {"locale": "fr", "email": {"subject": "..."}}. PATCH /preorders/notification_settings/confirmation with {"email_enabled": false} instead of PATCH /preorders/notifications/channels.

2026-10-22 — API v2: back-in-stock notifications use named keys, nested fields and one on/off resource

Section titled “2026-10-22 — API v2: back-in-stock notifications use named keys, nested fields and one on/off resource”
  • What changed:
    • Breaking: templates are addressed by key, a name only: signup_thank_you, restock_alert, reminder. /back_in_stock/templates/0, /1 and /initial now return 422. Responses name it in key (was stage).
    • Breaking: template fields are nested, as on preorder notifications: email{subject, preheader, heading, body, footer, button{text, destination, background_color, text_color, radius}, colors{background, section, text}, radius, css, html{enabled, template}, reviews{enabled, count, min_rating, title}, social{...}, unsubscribe_text, klaviyo_flow_name} and sms{body, link_destination, klaviyo_flow_name}. The flat names (email_subject, email_header, sms_body, …) are 422 “is unknown”.
    • Breaking: templates no longer have enabled, after_n_days, push copy or email_custom_css_present. signup_thank_you no longer has email.button.destination or sms.link_destination.
    • Breaking: /back_in_stock/reminders is now /back_in_stock/notification_settings (GET list, GET/PATCH /{key}). It owns on/off for all three notifications, including restock_alert (which can now be turned off). /back_in_stock/reminders returns 404. Errors are keyed notification_setting.<field>. after_n_days is accepted on reminder only. Entries no longer have id or template_id.
    • Breaking: /back_in_stock/settings/channels has only email and sms. alerts_enabled and push are gone.
    • Breaking: GET /account/templates/{campaign_type}/translations takes preorder only.
    • New: GET /back_in_stock/templates lists all three templates. GET /back_in_stock/templates/{key}/translations returns a template’s translations keyed by its paths. POST /back_in_stock/templates/{key}/verify_flow checks the notification’s Klaviyo or Postscript flow.
    • Behaviour change: delivery.locations.filter_enabled, delivery.locations.location_ids and widget.optin.app_proxy_path_prefix are checked by the same save as the dashboard, so a bad value is 422 on both.
  • Who it affects: integrations that read or edit back-in-stock templates or /back_in_stock/reminders, or switch alerts on and off through channels.
  • What to send instead: PATCH /back_in_stock/templates/restock_alert with {"email": {"subject": "..."}} instead of PATCH /back_in_stock/templates/0 with {"email_subject": "..."}. PATCH /back_in_stock/notification_settings/restock_alert with {"enabled": false} instead of PATCH /back_in_stock/settings/channels with {"alerts_enabled": false}. PATCH /back_in_stock/notification_settings/signup_thank_you with {"enabled": true} instead of enabled on the template. GET /back_in_stock/templates/reminder/translations instead of GET /account/templates/restock/translations?position=1.

2026-10-22 — API v2: back-in-stock templates take locale as a parameter, not a path segment

Section titled “2026-10-22 — API v2: back-in-stock templates take locale as a parameter, not a path segment”
  • What changed:
    • Breaking: GET /back_in_stock/templates/{key}/{locale} and PATCH /back_in_stock/templates/{key}/{locale} are now GET /back_in_stock/templates/{key} and PATCH /back_in_stock/templates/{key}. The old paths return 404.
    • locale is optional everywhere back-in-stock copy is read or written. Send it in the query string on GET and in the body on PATCH. Without it, the shop’s default language is used, not English. A write in another language changes only that language. Untranslated fields (colours, switches) ignore it. A value that is not a language tag returns 422.
    • Breaking: POST /back_in_stock/templates/{key}/{locale}/send_test (added earlier today) is now POST /back_in_stock/templates/{key}/send_test with an optional body locale.
    • Template responses have locale, the language they are in.
    • GET /back_in_stock/settings/widget and GET /back_in_stock/settings validate ?locale= the same way.
    • New: placement.selectors.restock_note and placement.selectors.restock_note_insertion (beforebegin, afterbegin, beforeend, afterend) on PATCH /back_in_stock/settings/widget place the restock note, as the dashboard’s dev mode does.
  • Who it affects: integrations that read or edit back-in-stock templates, or that relied on templates defaulting to English.
  • What to send instead: GET /back_in_stock/templates/initial?locale=fr instead of GET /back_in_stock/templates/initial/fr, and PATCH /back_in_stock/templates/initial with {"locale": "fr", ...} instead of PATCH /back_in_stock/templates/initial/fr, and POST /back_in_stock/templates/initial/send_test with {"locale": "fr"} instead of POST /back_in_stock/templates/initial/fr/send_test. Send locale=en to keep reading English on a shop whose default language is not English.

2026-10-22 — API v2: reports follow the dashboard’s report views

Section titled “2026-10-22 — API v2: reports follow the dashboard’s report views”
  • What changed:
    • Removed: every report CSV export: POST /preorders/reports/export, POST /back_in_stock/reports/{report}/export and both …/reports/exports/{job_id} status reads. The dashboard’s CSV buttons still email the file.
    • Removed: preorder by_offer, cancellations, balance_collection, by_product, by_variant, revenue and customers reports. GET /preorders/reports/products (the Revenue table, per variant or product) and GET /preorders/reports/fulfillments (the Fulfillments table) replace the product and variant reads; orders are GET /preorders/orders.
    • Removed: the currency parameter on the preorder reports. Amounts are always in the shop’s currency, which the response’s currency names.
    • New: GET /back_in_stock/reports/product_revenue, the Products > Revenue table.
    • New: both engagement reports return the per-variant table on every call, paged (page, per_page, default 50) and sorted (sort_by, direction), with meta. shopify_variant_ids narrows it. Rows add total_* event counts.
    • Breaking: GET /back_in_stock/reports/products_in_demand is the Products in demand table: it takes the table’s filters, search (title or SKU), vendors, tags, shopify_market_id, group_by, sort_by, direction, count_by, and pages as the table does, per_page default 25 and meta: { page, per_page, has_more_pages } (no total_count). Rows have sku and deleted. The shopify_variant_id, shopify_product_id and channel filters are gone. Deleted variants are left out when the shop has the dashboard set to hide them.
    • Breaking: back-in-stock signups, notifications and conversions are the Overview charts, shop-wide, and preorder by_date is the two Preorders Overview charts. granularity is gone: interval in the response is the chart’s (daily up to 60 days, weekly up to 180, then monthly), and buckets are UTC days as on the charts. signups returns signups; notifications returns notifications_sent, emails_sent, sms_sent and orders; conversions returns orders and revenue; by_date returns orders, order_revenue, units_sold and line_item_revenue. The pending, notified, unique_customers, sms_delivered and paid/unpaid fields and the variant, product, channel, market and offer filters and metrics[] are gone. range=all_time works on all four.
    • Breaking: total_waiting is gone from GET /back_in_stock/reports/wait_duration, as on the dashboard’s wait-time chart. For how many are waiting use products_in_demand.
    • Breaking: the Shopify id filters are renamed on every report and on GET /preorders/orders, GET /back_in_stock/signups and GET /back_in_stock/notifications: variant_id is shopify_variant_id, product_id is shopify_product_id, market_id is shopify_market_id and variant_ids is shopify_variant_ids. Each takes a gid or a numeric id; anything else returns 422.
    • Breaking: Shopify ids in report responses are gid strings: shopify_variant_id, shopify_product_id, and shopify_market_id (was market_id) on preorder by_date and back-in-stock wait_duration.
    • Breaking: preorder summary is the Overview tiles, shop-wide: orders, units, order_revenue, line_item_revenue, average order value, unpaid and unfulfilled orders, unique customers. The deposit and balance fields, paid_orders, fulfilled_orders, the status breakdowns, offer and the offer, product, variant and market filters are gone.
    • Breaking: back-in-stock summary is the Overview tiles, shop-wide: revenue, orders, average_order_value, unique_customers, signups, notifications_sent, emails_sent, sms_sent. signups_by_channel, pending, pending_by_channel, notified, notified_customers, converted_customers, last_notified_at, conversions, conversion_rate, recovered_revenue, source and the variant, product, channel and market filters are gone. Pending counts are on products_in_demand; orders and revenue over time on conversions.
    • Behaviour change: both summaries are summed from the shop’s daily totals, as the dashboard tiles are, and range=all_time is the dashboard’s All time (the last ten years).
    • Behaviour change: every report calls the same service as its dashboard view, so the numbers are the dashboard’s. Back-in-stock signups counts every signup, as the Overview tile and chart do; products_in_demand with count_by=customers counts one person per variant.
  • Who it affects: integrations that export report CSVs, read the removed preorder reports or the removed summary fields, pass currency or the old id filters, or read numeric Shopify ids or market_id from report responses. Numbers change for shops whose shoppers sign up on two channels, cancel preorders, or buy on several days.
  • What to send instead: read the JSON reports and page them. Use products/fulfillments for per-product preorder numbers and products_in_demand for per-product waitlist numbers, read interval instead of sending granularity, send shopify_*_id filters, and parse shopify_*_id as gids.

2026-10-22 — API v2: collect part of an order’s balance for chosen products

Section titled “2026-10-22 — API v2: collect part of an order’s balance for chosen products”
  • What changed:
    • New: line_item_ids on POST /preorders/orders/{id}/collect_payment. Without amount, the charge is the sum of those lines’ totals (price times quantity, plus tax). With amount, that amount is charged and the lines are recorded. Each id is a line item’s id, its shopify_line_item_id gid or the numeric Shopify id; a bundle entry stands for its components. Shopify Plus only, like amount.
    • New: order reads have line_items[].id and line_items[].line_total. payments.payment_attempts[], payments.last_payment_attempt and the action’s outcome have shopify_line_item_ids, the lines a payment was for (empty otherwise).
    • Behaviour change: an amount over the outstanding balance, or a line that is not on the order, returns 422 with a message saying which. The 422 off Shopify Plus now reads “Collecting part of the balance requires Shopify Plus”. Shopify Plus partner sandboxes count as Plus.
  • Who it affects: integrations that collect part of a preorder balance.
  • What to send instead: nothing; requests that send only amount, or nothing, work as before.

2026-10-22 — API v2: preorder notifications and shop-wide preorder settings are sections

Section titled “2026-10-22 — API v2: preorder notifications and shop-wide preorder settings are sections”
  • What changed:
    • Removed: GET/PATCH /preorders/templates/{locale} and the flat PATCH /preorders/notifications/{locale}. Each field now has one section:
      • GET/PATCH /preorders/notifications/channels: email_enabled, sms_enabled, customer_cancellation_enabled.
      • GET/PATCH /preorders/notifications/confirmation_email: subject, preheader, heading, body, footer, button.*, colors.*, radius, css, reviews.*, social.*, klaviyo_flow_name.
      • GET/PATCH /preorders/notifications/confirmation_sms: body, klaviyo_flow_name.
      • GET/PATCH /preorders/notifications/shipping_update: subject, heading, body, klaviyo_flow_name.
      • GET/PATCH /preorders/notifications/payment_failed: subject, heading, body, button_text, klaviyo_flow_name.
    • Breaking: the locale is no longer a path segment. Every translated read and write takes an optional locale (?locale= on GET, "locale" in the PATCH body); omitted means the shop’s default language, and an invalid tag returns 422. channels has no translated field and takes no locale.
    • Breaking: GET /preorders/notifications returns those five sections only. The locale and sending keys are gone; the sending account is GET /account/sending.
    • Added: the offer sections with translated text (widget, shipping, limits, countdown, payments, advanced) and GET /preorders/offers/{id} take the same optional locale. A write in another language changes only that language’s text, like the dashboard’s offer translator. GET /preorders/settings/storefront_text takes ?locale= like its PATCH.
    • Moved: email_provider, sms_provider and email_from_name are PATCH /account/sending. notify_payment_collection_enabled is the new GET/PATCH /preorders/settings/reports.
    • Moved: GET /preorders/developer_settings is now GET /preorders/settings/developer, and PATCH /preorders/settings/developer writes the theme selectors, dev_mode_enabled and css. Dev mode and CSS are the same fields as the back-in-stock widget’s placement.merchant_managed_position and custom.css, and GET /back_in_stock/settings/widget now returns custom.css.
    • Added: POST /preorders/notifications/confirmation_email/send_test (optional body locale) and POST /back_in_stock/templates/{key}/{locale}/send_test send a test email to one address (default: the reply-to address), like the dashboard’s Send test button.
    • Narrowed: PATCH /preorders/settings/pages writes the page switches only (home.enabled, collection.enabled, collection.show_badge_if_any_variant_is_preorder, search.enabled). The locations and markets fields are no longer part of it.
    • The push fields (push_title, push_body, push_button_text) are no longer writable: the preorder editor does not show them.
    • Every section PATCH is a deep partial. Absent keys are unchanged and null clears a text field. Unknown and read-only keys return 422 keyed "<section>.<path>". Success returns 202 with the section. The template sections return 404 when the shop has no single preorder confirmation template.
  • Who it affects: integrations and MCP clients that edit preorder templates, notification settings, the pages settings’ locations, read the developer settings, read sending from the notifications read, or put the locale in a preorder notifications path.
  • What to send instead: send each field to its section above, using the section’s field names (subject, not email_subject). Move the locale from the path to ?locale= or the body: PATCH /preorders/notifications/fr/shipping_update becomes PATCH /preorders/notifications/shipping_update with {"locale": "fr", ...}.

2026-10-22 — API v2: the offers list takes status=enabled and status=disabled again, deprecated

Section titled “2026-10-22 — API v2: the offers list takes status=enabled and status=disabled again, deprecated”
  • What changed:
    • Fix: since 2026-10-21, GET /preorders/offers?status=enabled and ?status=disabled returned 422. They are accepted again and filter as they did before: enabled returns every offer that is turned on, disabled every offer that is turned off, whatever its schedule.
    • Deprecated: both values will be removed. A response to either has a top-level warnings array saying what to send instead. Rows still report current_status, not the old status.
  • Who it affects: integrations that list offers with status=enabled or status=disabled.
  • What to send instead: status=live for offers taking preorders now, status=scheduled for offers waiting for their start date, status=off for the rest. A turned-on offer whose end date has passed is off.

2026-10-22 — API v2: back-in-stock demand can count customers instead of signups

Section titled “2026-10-22 — API v2: back-in-stock demand can count customers instead of signups”
  • What changed:
    • New: count_by (customers or signups) on GET /back_in_stock/reports/products_in_demand, GET /back_in_stock/products, GET /back_in_stock/products/{variant_id}/notification_status and GET /account/products. With customers, a shopper who signed up for a variant by email and SMS counts once, and signing up again after being notified counts again. With signups, each channel’s signup counts, as before.
    • The same fields carry the chosen numbers: pending and total, pending_signups and total_signups, waitlist_count. Sorting and has_waitlist / filter=back_in_stock follow it. Each response has count_by, what it counted by.
    • Behaviour change: without count_by, the API counts as the dashboard does by default: customers for a shop that collects signups on more than one channel, otherwise signups. This is being turned on shop by shop; until it reaches a shop, its responses say count_by: "signups" whatever is sent.
    • A request filtered or grouped by channel always counts signups, since one channel’s signups are one per shopper.
    • An unknown count_by returns 422.
  • Who it affects: integrations that read waitlist or demand numbers for shops that collect signups on more than one channel.
  • What to send instead: send count_by=signups to keep counting every channel’s signup.

2026-10-21 — API v2: orders have only the dashboard’s actions; ids follow one rule; one read per section

Section titled “2026-10-21 — API v2: orders have only the dashboard’s actions; ids follow one rule; one read per section”
  • What changed:
    • Removed: order endpoints the dashboard has no action for. POST /preorders/orders/{id}/cancel, /hold_fulfillments, /split_fulfillment_orders, /payments/send_collection_url, /payments/charge_balance, /payments/refund_deposit, /payments/refund_balance, /payments/refund, /fulfillment/apply_holds, /fulfillment/release_holds, /fulfillment/set_hold_reason, /tags/add, /tags/remove, /tags/set, PATCH /preorders/orders/{id}/fulfillment and PATCH /preorders/orders/{id}/tags, and POST /preorders/orders/bulk_cancel, /bulk_send_payment_collection_url and /bulk_set_tags. They return 404. release no longer takes force or tag_with.
    • Added: POST /preorders/orders/{id}/send_confirmation, send_sms_confirmation and send_shipping_update, and bulk_open_scheduled_fulfillments, bulk_reschedule_fulfillment, bulk_reschedule_payment, bulk_collect_payment, bulk_send_confirmation and bulk_send_shipping_update, matching the dashboard actions.
    • Breaking: GET /preorders/orders/{id} returns every block: payments, fulfillment, notifications, tags, attributed_offers and line_items. GET /preorders/orders/{id}/payments, /fulfillment, /notifications and /timeline return one block each, and all of them also read back-in-stock orders. GET /preorders/orders/{id}/tags is removed; tags ({ tags, configured_by_offers }) is in the full read, with tags.tags read live from Shopify and null when Shopify can’t be read. The timeline is only at /timeline.
    • Breaking: ids follow one rule. Our id is id or <thing>_id; a Shopify id is shopify_<thing>_id and is a gid string. order_id and shopify_gid become shopify_order_id (gid) on orders, list rows and ambiguous-lookup candidates. customer.id is our customer id and customer.shopify_customer_id (gid) is new. On line items, line_item_id, line_item_group_id and variant_id become shopify_line_item_id, shopify_line_item_group_id (gid://shopify/LineItemGroup/…) and shopify_variant_id. attributed_offers[].line_item_ids becomes shopify_line_item_ids. Bulk job succeeded and failed entries are { id, shopify_order_id, name } (plus error), where id is our order id.
    • Breaking: the status block (state, deposit_paid, balance_collected, fulfillment_held, cancelled, refunded_amount) is removed; each part is already in payment_status, financial_status, fulfillment_status and payments. payments.collection_url_sent_at and notifications.payment_collection_url are removed, since STOQ no longer sends payment links.
    • Breaking: payments is flat: payment_status, financial_status, total_price, total_outstanding, amount_paid, deferred_amount, due_at, overdue, balance_collected, balance_collected_at, last_payment_attempt and payment_attempts. The deposit and balance objects and refunds are gone. Values Shopify hasn’t synced are null.
    • Breaking: fulfillment reports fulfillment_status, preorder_fulfillment_status, hold_reason_note, holds_applied_at, shopify_split_order_ids (gids) and offers ([{ offer_id, handling }], handling one of none, hold_all, split_fulfillments, separate_orders). all_held, any_held, held and fulfillment_orders are gone.
    • Breaking: each line item has its own preorder_fulfillment_status (on_hold, scheduled, open, or null once fulfilled or not a preorder line); a bundle shows its most blocked component. Line items no longer have deposit_paid and balance_collected, which repeated the whole order’s payment state; read payments.
    • Breaking: attributed_offers[].deposit_amount and balance_amount are removed. Shopify keeps one payment schedule per order, so they were estimates; read payments.
    • Breaking: attributed_offers[].fulfillment is replaced by attributed_offers[].shipping, the offer’s shipping section exactly as GET /preorders/offers/{id}/shipping returns it, plus current_status.
    • Breaking: actions (POST /preorders/orders/{id}/...) and bulk order_ids take only our order id or a Shopify order id or gid. An order name, number or email returns 404 on an action and 422 in order_ids. Reads still take any order reference.
    • Behaviour change: release (and bulk_release, the dashboard Release and the Flow release action) releases only STOQ’s own fulfillment holds. Holds placed by other apps stay. Before, it removed every hold on the order. It also returns 409 on a cancelled or fulfilled order instead of calling Shopify. The dashboard and bulk collect and release actions refuse the same orders v2 refuses.
  • Who it affects: integrations and MCP clients that read orders, call the removed endpoints, or pass order names or emails to an action.
  • What to send instead: read the renamed shopify_*_id fields and the section URLs above. Pass our order id or the Shopify order id to actions. Cancel, refund, tag, hold and split orders in Shopify admin.

2026-10-21 — API v2: the countdown switches to the end date when a dated offer goes live

Section titled “2026-10-21 — API v2: the countdown switches to the end date when a dated offer goes live”
  • What changed:
    • Fix: a dated offer goes live 5 minutes before its start date. With countdown.date_source: schedule, countdown.current now switches to during_preorder (counting down to the end date) at that moment too. Before, for those 5 minutes the offer read live while the countdown still showed before_launch, counting down to the start.
    • Custom countdown dates are unchanged: they switch at the exact time set.
  • Who it affects: integrations that read countdown.current around a dated offer’s start.
  • What to send instead: nothing.

2026-10-21 — API v2: product variant active_offers_count counts live offers again

Section titled “2026-10-21 — API v2: product variant active_offers_count counts live offers again”
  • What changed:
    • Fix: aggregate.active_offers_count in GET /preorders/product_variants/{variant_id} counts offers that are live or scheduled. Since offer states were renamed to live, scheduled, off and deleted, it had counted only scheduled offers.
    • Fix: the summary aggregate in GET /preorders/product_variants sums total_preorder_count and total_preorder_max_count over the variant’s live and scheduled offers. Since the rename it had summed scheduled offers only, so a variant on a live offer showed zeros.
    • The state filter on GET /preorders/product_variants/{variant_id}/offers takes live, scheduled, off or deleted.
  • Who it affects: integrations and MCP clients that read how many live offers a variant is in.
  • What to send instead: nothing.

2026-10-21 — API v1: DELETE /preorders/{id} clears the offer’s schedule, as the dashboard delete does

Section titled “2026-10-21 — API v1: DELETE /preorders/{id} clears the offer’s schedule, as the dashboard delete does”
  • What changed:
    • Behaviour change: DELETE /api/v1/external/preorders/{id} now turns the offer off and removes its schedule (schedule_offer, schedule_start_date and schedule_end_date are cleared) before deleting it, the same as deleting the offer in the dashboard.
    • Before, only enabled was turned off. A deleted dated offer kept its dates, so restoring it could bring it back live or scheduled.
    • The response is unchanged: { "success": true, "message": "Selling plan deleted" }, and an offer whose Shopify selling plan group is already gone is still deleted.
  • Who it affects: API v1 integrations that delete scheduled offers and read their dates afterwards.
  • What to send instead: nothing.

2026-10-21 — API v2: offer lifecycle matches the dashboard and the editor

Section titled “2026-10-21 — API v2: offer lifecycle matches the dashboard and the editor”
  • What changed:
    • Behaviour change: POST /preorders/offers/{id}/discard turns the offer off and removes its schedule before deleting it, as the dashboard delete does. It returns 502 and keeps the offer when Shopify refuses to remove its selling plan group.
    • Behaviour change: POST /preorders/offers/{id}/restore brings the offer back off with no schedule. Before, a restored dated offer kept its dates and could come back live or scheduled.
    • Behaviour change: POST /preorders/offers/{id}/schedule saves what the editor’s availability options save. A start date, alone or with an end date, turns the offer off until the start date; an end date alone turns it on until then. A countdown is turned off when the kind of schedule changes, and the change is listed in also_changed. Times without an offset are read in the shop’s time zone, and validation errors are keyed by start and end.
    • Behaviour change: POST /preorders/offers/{id}/unschedule removes the schedule and keeps the offer as it is now: a live offer stays on, any other offer stays off. Before, an offer waiting for its start date came back live.
    • Behaviour change: POST /preorders/offers/{id}/enable on an offer whose dates have passed clears its start date, as the dashboard’s Enable does. Before, only scheduled offers had their dates changed.
    • Behaviour change: PATCH /preorders/offers/{id} writes name and internal_name only, as before, but now refuses any other key with 422 keyed by the field, refuses a blank name, and internal_name: null restores the default label.
  • Who it affects: integrations and MCP clients that discard, restore, schedule, unschedule or rename offers.
  • What to send instead: to turn a restored offer on, call enable or send a schedule. To keep an offer on after unscheduling, call enable.

2026-10-21 — API v2: offer products is one PATCH; rename and the seven products setters are removed

Section titled “2026-10-21 — API v2: offer products is one PATCH; rename and the seven products setters are removed”
  • What changed:
    • Breaking: PATCH /preorders/offers/{id}/products takes the fields the read returns: selection, collection_id, tags, eligible_inventory_policy, excluded_variant_ids and exclusion_tag. Send only the fields you change. Unknown and read-only keys are refused with 422 keyed by products.<field>. collection_id is required when switching to collection and tags when switching to tags. Switching to all no longer needs confirm: true; switching to custom starts empty, so add variants with add_variants. When Shopify refuses the reset a selection change needs, the PATCH returns 502 and the offer is left as it was.
    • Removed: POST /preorders/offers/{id}/products/set_source_to_all, set_source_to_collection, set_source_to_custom, set_source_to_tags, set_collection, set_exclusion_tag and set_excluded_variants. They return 404. MCP search finds PATCH /preorders/offers/{id}/products for their old aliases.
    • Removed: POST /preorders/offers/{id}/rename. It returns 404; PATCH /preorders/offers/{id} takes the same name and internal_name.
    • Behaviour change: POST /preorders/offers/{id}/products/add_variants skips variants Shopify has deleted and lists them under deleted_in_shopify. It returns 202 with a job to poll while the variants are attached, 409 while an earlier add is still running, and 502 when Shopify refuses. remove_from_conflicting_offers: true takes the variants off other offers first.
    • Behaviour change: POST /preorders/offers/{id}/products/remove_variants updates Shopify before it removes the variants, and returns 502 with the variants still attached when Shopify refuses. inventory_policy: "DENY" turns “Continue selling” off on the removed variants.
    • Behaviour change: POST /preorders/product_variants/{variant_id}/detach_from_all_offers updates Shopify before the local rows go. It returns 207 when only some offers let go and 502 when none did.
  • Who it affects: callers of the removed endpoints, callers that send the old PATCH /products body (source, collection, all, custom, confirm), and callers that read the status code of add, remove or detach.
  • What to send instead: PATCH /preorders/offers/{id}/products with the field to change, for example { "selection": "collection", "collection_id": "gid://shopify/Collection/1" } or { "exclusion_tag": "no-preorder" }. Send PATCH /preorders/offers/{id} with name / internal_name instead of rename.

2026-10-21 — Every offer section is one GET and one PATCH, in the read’s shape

Section titled “2026-10-21 — Every offer section is one GET and one PATCH, in the read’s shape”
  • What changed:
    • Breaking: PATCH /preorders/offers/{id}/{section} for shipping, limits, countdown, markets, inventory and advanced takes the same fields, at the same paths, as that section’s GET and the matching block of GET /preorders/offers/{id}. This is what widget and payments already did since 2026-10-20. Send only the fields you change; fields you leave out are unchanged. Validation errors name the dotted field path. The top-level fields are now:
      • shipping: delivery_timing, ships_at, days_after_checkout, timeline, fulfillment, on_hold (was delivery, display, fulfillment).
      • limits: order_quantity, default_per_variant, display_style, limit_text, progress_bar, show_icons, plus cart, order_tags and terms from the old checkout section (was per_order, limit_per_product, display).
      • countdown: show_when, date_source, starts_at, ends_at, starts_text, ends_text, labels, units, text_color, background_color, border_radius.
      • markets: scope, market_ids (was enabled, applies_to_all_markets, market_ids).
      • inventory: stock_condition, reserve (was provider, reservation, continue_selling). To change variants’ “Continue selling when out of stock”, use POST /preorders/offers/{id}/products/bulk_toggle_inventory_policy.
      • advanced: checkout_method (selling_plan or line_item_property, was use_shopify_selling_plan), cart_attributes (was line_item_properties). Recording terms acceptance as a cart attribute moved to limits.terms.record_acceptance.
      • integrations (pos, b2b) is unchanged.
    • New: GET and PATCH /preorders/offers/{id}/batches.
    • Removed: the checkout section (GET and PATCH /preorders/offers/{id}/checkout). Its fields are in limits.
    • Removed: button text overrides (advanced.button_text_overrides). The storefront never showed them.
    • Removed: the 38 single-setting actions on these sections:
      • shipping/set_delivery_asap, set_delivery_date, set_delivery_window, set_shipping_text, set_hold_fulfillments, set_auto_release_holds, set_split_order_tag, set_split_order_suffix, set_split_transaction_gateway
      • limits/set_min_per_order, set_max_per_order, set_progress_bar, set_quantity_limit_text, set_icons_enabled
      • countdown/set_mode, set_style, set_custom_end_date, set_starts_text, set_ends_text, set_unit_labels
      • markets/set_markets, add_market, remove_market
      • inventory/set_provider, set_reservation_timing
      • advanced/attach_to_shopify_selling_plan, detach_from_shopify_selling_plan, set_button_text_override, set_custom_line_item_property
      • integrations/set_pos_enabled, set_b2b_enabled
      • checkout/set_mixed_cart_allowed, set_mixed_cart_error_message, set_order_tags, add_order_tag, remove_order_tag, set_terms_settings, set_terms_text
  • Who it affects: integrations and MCP clients that PATCH these sections with the old fields, call any of the removed actions, or read or write checkout.
  • What to send instead: read the section with its GET, then PATCH the fields you change at the same paths. For example, {"delivery_timing": "asap"} on shipping replaces set_delivery_asap, and {"cart": {"allowed_items": "mixed"}} on limits replaces checkout/set_mixed_cart_allowed.

2026-10-21 — telnyx and infobip are no longer SMS providers

Section titled “2026-10-21 — telnyx and infobip are no longer SMS providers”
  • What changed:
    • Breaking: the account sending settings no longer accept sms_provider: "telnyx" or "infobip", and those values are no longer listed as options. Sending them returns a validation error. The accepted values are twilio, klaviyo and postscript.
    • Old SMS messages sent through Telnyx or Infobip keep that provider value.
  • Who it affects: none in practice. No shop uses either provider, and neither has sent a message since 2025.
  • What to send instead: twilio for SMS sent by STOQ.

2026-10-21 — API v1: turning an offer on or off with PUT /preorders/{id} follows its schedule as the dashboard does

Section titled “2026-10-21 — API v1: turning an offer on or off with PUT /preorders/{id} follows its schedule as the dashboard does”
  • What changed:
    • Behaviour change: in API v1, a PUT /api/v1/external/preorders/{id} that changes enabled on an offer with a schedule now changes the schedule the same way the dashboard toggle does:
      • enabled: false removes the schedule (schedule_offer, schedule_start_date and schedule_end_date are cleared), so the offer stays off.
      • enabled: true starts the offer now: the start date is cleared, an end date is kept, and with no end date the schedule is turned off.
    • Before, only enabled changed. A dated offer follows its dates, so it still read live or scheduled, and the schedule turned it back on (or kept it waiting for its start date).
    • A request that sends the enabled value the offer already has, for example after a GET, leaves the schedule as it is.
  • Who it affects: API v1 integrations that toggle enabled on offers that were scheduled in the dashboard.
  • What to send instead: nothing, if you meant to turn the offer on or off now. To keep a schedule, don’t change enabled; API v2’s POST /preorders/offers/{id}/schedule edits the dates.

2026-10-21 — disable on a scheduled or dated offer clears its schedule

Section titled “2026-10-21 — disable on a scheduled or dated offer clears its schedule”
  • What changed:
    • Behaviour change: POST /preorders/offers/{id}/disable on an offer with a schedule now also removes the schedule (schedule.start and schedule.end are cleared), as turning the offer off in the dashboard does. The response’s availability.current_status is off.
    • Before, disable only turned the offer off. A dated offer follows its dates, so it still read scheduled or live, and on its start date it was turned on again.
  • Who it affects: integrations and MCP clients that call disable on a scheduled offer expecting it to keep its dates.
  • What to send instead: to keep a scheduled offer’s start date, don’t call disable; send a new schedule with POST /preorders/offers/{id}/schedule. To turn the offer on again later, call enable or set a new schedule.

2026-10-21 — One offer status everywhere: Live, Scheduled, Off

Section titled “2026-10-21 — One offer status everywhere: Live, Scheduled, Off”
  • What changed:
    • Every response that reports an offer’s status now uses the same four values, the ones the dashboard shows: live, scheduled, off and deleted. A dated scheduled offer follows its dates: scheduled until 5 minutes before its start, live until its end, then off. Any other offer is live when turned on and off when turned off. There is no separate “ended” or “paused” value any more.
    • Breaking: offer_status in GET /preorders/product_variants, GET /preorders/product_variants/{variant_id} and GET /preorders/products/inventory_status is live, scheduled, off or deleted instead of active, scheduled, paused, ended or discarded.
    • Breaking: status in GET /preorders/product_variants/availability is off where it was paused or ended. active, sold_out, scheduled, not_in_country and not_enrolled are unchanged.
    • Breaking: status in GET /preorders/reports/by_offer is live, scheduled, off or deleted instead of active, paused or discarded. It also follows the schedule now: a scheduled offer inside its dates was reported as paused and is now live.
    • Breaking: the 409 messages for enable and disable say “Offer is already live.” and “Offer is already off.” instead of “already enabled” and “already paused”.
    • Breaking: the status filter on GET /preorders/offers takes all, live, scheduled or off, and matches current_status. enabled and disabled are no longer accepted and return 422 (accepted again, deprecated, from 2026-10-22). A scheduled offer inside its dates now matches live; before, it only matched scheduled.
  • Who it affects: integrations that compare offer_status, the availability status or the report status with the old values, filter GET /preorders/offers with status=enabled or status=disabled, or match the 409 message text.
  • What to send instead: compare with the new values: active → live, paused and ended → off, discarded → deleted. Send status=live instead of enabled and status=off instead of disabled. Use the HTTP status, not the message text, to detect a refused transition.

2026-10-20 — Back-in-stock pending is one number everywhere, counted as the dashboard does

Section titled “2026-10-20 — Back-in-stock pending is one number everywhere, counted as the dashboard does”
  • What changed:
    • Behaviour change: every back-in-stock “pending” now counts the same signups: those created in the window that have not been notified yet, as the dashboard’s Products in demand report does. A signup counts whether or not it confirmed double opt-in. That covers:
    • Before, summary, products_in_demand and total_waiting counted everyone waiting now, whatever the window, and summary, the signups series, total_waiting and pending_signups left out unconfirmed signups on shops with double opt-in. The signups list left blocked and unsubscribed signups out of pending, and wishlist reminder signups out of the whole list. On one shop over 30 days, summary pending went from 99,179 to 6,148, and products_in_demand went from 13,316 variants to 1,367.
    • The numbers now agree: for the same request, summary pending equals the sum of products_in_demand pending, the sum of the signups series’ pending, total_waiting, and the signups list’s summary.pending.
    • GET /back_in_stock/reports/wait_duration now takes the same window as the other reports: from / to as shop-local days, range=all_time, and the last 30 days by default (before, it covered all time unless both from and to were sent). The response adds window. median_wait_days, max_wait_days and distribution are measured over the same signups total_waiting counts, so the histogram sums to total_waiting.
    • With range=all_time, products_in_demand and pending_signups read the shop’s lifetime counters when the shop has the all-time report cache on, as the dashboard does. A request that filters on channel or market_id counts live, because the counters don’t record channel or market.
  • Who it affects: callers of these six reads and their CSV exports.
  • What to send instead: for everyone waiting now, send range=all_time.

2026-10-20 — One offer shape for every read; widget and payments are single PATCHes

Section titled “2026-10-20 — One offer shape for every read; widget and payments are single PATCHes”
  • What changed:
    • Breaking: GET /preorders/offers/{id} and every call that returns a whole offer (create, update, rename, enable, disable, schedule, unschedule, discard, restore) return the offer in one shape, the same one the offer editor saves. Most fields are renamed or regrouped. For example, widget.button.text is now widget.button.button_text, widget.badge.enabled is widget.badge.show_badge, payments.mode is payments.options_offered, payments.options[] is payments.full / payments.partial, and status is availability (current_status, starts_at, ends_at, schedule). The new shape also has revenue, products.variant_count and batches. The API v2 Reference lists every field.
    • Breaking: in GET /preorders/offers, each row has current_status (live, scheduled or off) instead of status. summary counts live, scheduled and off instead of active, scheduled, paused and ended.
    • Breaking: PATCH /preorders/offers/{id}/widget and PATCH /preorders/offers/{id}/payments take the same sections and field names as the read. Send only the fields you change. Fields you leave out are unchanged, and text can be cleared with null. Button colors only show when widget.button.custom_colors_enabled is true, so send it with the colors.
    • Removed: the 21 single-setting widget and payments actions: widget/set_button_text, set_button_colors, set_badge_enabled, set_badge_text, set_badge_colors, set_disclaimer_enabled, set_disclaimer_text, set_disclaimer_style, set_billing_widget_text, and payments/set_mode, enable_option, disable_option, set_deposit_percent, set_deposit_amount, set_discount, set_discount_text, set_show_discounted_price, set_payment_date, set_balance_due_days, set_billing_widget, set_option_copy.
    • MCP search results no longer include intents or field_paths. Each action lists aliases instead.
  • Who it affects: integrations that read offer fields from these responses, call PATCH /widget or PATCH /payments, or call the removed actions.
  • What to send instead: read the new field names from GET /preorders/offers/{id}. Replace each removed action with PATCH /widget or PATCH /payments carrying the same setting, for example { "button": { "button_text": "Preorder now" } } or { "options_offered": "partial" }.

2026-10-13 — Back-in-stock product search covers the Shopify catalog

Section titled “2026-10-13 — Back-in-stock product search covers the Shopify catalog”
  • What changed:
    • GET /back_in_stock/products takes an optional search (product name, variant title, SKU, product URL or id). With it, the response lists the matching products STOQ already has rows for, then matching products from the shop’s Shopify catalog that have no STOQ activity yet, with their default settings (button_visibility: "when_out_of_stock", zero signups). Without search the list is unchanged.
    • Every row has a new has_stoq_activity boolean: false for a catalog-only row. A response to a search also has catalog_searched: false when has_waitlist or overrides_only is set (those only match STOQ rows) or when the catalog could not be reached; the STOQ rows still come back.
    • Product search terms containing a quote (' or ") no longer fail.
  • Who it affects: nobody has to change anything. The new fields are additive. Callers that looked up a product by listing every row can send search instead, and the catalog-only rows’ ids work in PATCH /back_in_stock/products.

2026-10-13 — Free plans can switch an offer to in-stock mode

Section titled “2026-10-13 — Free plans can switch an offer to in-stock mode”
  • What changed:
    • Behaviour change: POST /preorders/offers/{id}/inventory/set_provider with provider: "shopify" (in-stock mode) or provider: "stoq" (back to out-of-stock mode), and the same provider on PATCH /preorders/offers/{id}/inventory, no longer return 422 “Product source is only available on a paid plan” for a shop on a free plan. In-stock mode (“Show ‘Preorder’ for in-stock products”) is free, as in the offer editor. It still needs an offer that uses Specific products; the Collection and All products sources still need a paid plan.
  • Who it affects: callers that switch the inventory provider for shops on a free plan.
  • What to send instead: nothing. The call that was refused now works.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.
  • 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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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/{key}/{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.

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

Section titled “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.
  • 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.

2026-09-24 — market_overrides removed from variant settings

Section titled “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).

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

Section titled “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" } }.

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

Section titled “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. 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.

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

Section titled “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.

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

Section titled “2026-09-19 — email_template_enabled on back-in-stock templates”
  • What changed: PATCH /back_in_stock/templates/{key}/{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.

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

Section titled “2026-09-18 — Back-in-stock templates: stage path segment and new fields”
  • What changed: the template path is now /back_in_stock/templates/{key}/{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.

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

Section titled “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.

2026-09-16 — Progress bar box background colour

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

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

Section titled “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.

2026-09-01 — New filters and read actions

Section titled “2026-09-01 — New filters and read actions”
  • 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. Clients can register themselves automatically. OAuth grants now expire, can be revoked, and are bound to the client that got them.
  • Who it affects: MCP clients. The X-Auth-Token header still works.

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

Section titled “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.
Was this page helpful?

Still stuck? Ask Quinn or chat with support