# STOQ Docs Preorders & back-in-stock for Shopify — REST APIs, the v2 action API, a JavaScript SDK, theme metafields, and an MCP server. ## Home ### STOQ Docs Path: `/docs/index/` Preorders & back-in-stock for Shopify — REST APIs, the v2 action API, a JavaScript SDK, theme metafields, and an MCP server. Preorders & back-in-stock for Shopify. Pick your path below, or browse a section from the top nav. How do I integrate? Headless or Hydrogen storefront (SDK · Beta) Build on the JavaScript SDK — a typed client plus React hooks for Hydrogen. Install once and ship notify-me and preorder widgets. Themed store with the STOQ app Already running the app's theme embed? Customize with the on-page JavaScript API, custom events, and theme metafields — no SDK required. Server / backend integration Manage signups, selling plans, and variants over REST with API v1 — or the action-driven, AI-native API v2 (Beta). AI agent (MCP · Beta) Drive STOQ from an agent — every v2 action is exposed as an MCP tool. Quickstart 1. Get your API key In the STOQ app, go to Settings → Integrations → API Key and copy the key. Send it in the X-Auth-Token header on every request — keep it secret. Warning Never expose your API key in browser/client code. For storefronts, the JavaScript SDK authenticates with your shop's public Storefront API token instead. 2. Make your first call Create a back-in-stock signup — the one public endpoint (no key; identified by the shop domain): Copy curl -X POST https://app.stoqapp.com/api/v1/intents.json \ -H "Content-Type: application/json" \ -H "X-Shopify-Shop-Domain: my-store.myshopify.com" \ -d '{ "intent": { "email": "shopper@example.com", "variant_id": 43900910010503 } }' List your preorder offers with the action-driven v2 API (uses your key): Copy curl https://app.stoqapp.com/api/v2/external/preorders/offers \ -H "X-Auth-Token: $STOQ_API_KEY " That's it. From here, follow your path above — or see API Key and Rate Limits for the details. ### API Key Path: `/docs/api-key/` To integrate with STOQ's Back in Stock API, you'll need to obtain your API key. This key is essential for authenticating your API requests. Here's how to get it: To integrate with STOQ's Back in Stock API, you'll need to obtain your API key. This key is essential for authenticating your API requests. Here's how to get it: Steps to Get Your API Key Open the STOQ app inside your Shopify Admin panel Navigate to "Back in stock alerts > Integrations" Scroll down to find the "API Key" section Click the copy button to get your API key ### Rate Limits Path: `/docs/rate-limits/` The STOQ API uses a points-based rate limiting system to ensure fair usage and platform stability. Each API request consumes a certain number of points from your allowance, and the limit resets on... Rate Limits The STOQ API uses a points-based rate limiting system to ensure fair usage and platform stability. Each API request consumes a certain number of points from your allowance, and the limit resets on a rolling window. How It Works Every store gets a budget of 360 points per 60-second window . Each API request costs a number of points depending on whether it reads or writes data: Request Type Methods Cost Read GET 1 point Write POST , PUT , PATCH , DELETE 2 points Once you've used all 360 points in a window, further requests will be rejected until the window resets. Example If your integration only makes GET requests, you can make up to 360 requests per minute . If it only makes write requests ( POST , PUT , etc.), you can make up to 180 requests per minute . A mix of both will fall somewhere in between. Rate Limit Headers Every API response includes headers to help you track your current usage: Header Description Example X-RateLimit-Limit Your total points budget per window 360 X-RateLimit-Remaining Points remaining in the current window 284 X-RateLimit-Reset Unix timestamp (in seconds) when the current window resets 1716300120 Example Response Headers Copy X-RateLimit-Limit: 360 X-RateLimit-Remaining: 284 X-RateLimit-Reset: 1716300120 You can use these headers to pace your requests and avoid hitting the limit. When You Exceed the Limit If you exceed 360 points within a 60-second window, the API returns a 429 Too Many Requests response. The response includes an additional Retry-After header telling you how many seconds to wait before retrying. 429 Response Example Headers: Copy HTTP/1.1 429 Too Many Requests X-RateLimit-Limit: 360 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1716300120 Retry-After: 24 Body: Copy { "error" : "Rate limit exceeded" , "retry_after" : 24 , "message" : "You have exceeded the rate limit of 360 points per 60 seconds. Read requests cost 1 point(s) and write requests cost 2 point(s). Please retry after 24 second(s)." } Best Practices Monitor the headers Check X-RateLimit-Remaining after each request. If it's getting low, slow down or pause before making more calls. Respect Retry-After When you receive a 429, wait at least the number of seconds specified in the Retry-After header before retrying. Retrying immediately will continue to fail. Use bulk endpoints where available Some endpoints support bulk operations (e.g., bulk_notify_intent , bulk_destroy ). A single bulk request costs the same as one write request (2 points) but can process multiple records — making it far more efficient than individual calls. Spread requests over time Rather than sending a burst of requests all at once, distribute them evenly across the 60-second window to stay within limits. Implement exponential backoff If you receive multiple 429 responses in a row, increase the wait time between retries. For example, wait 1 second after the first 429, then 2 seconds, then 4 seconds, and so on. Quick Reference Parameter Value Points per window 360 Window duration 60 seconds Read request cost 1 point Write request cost 2 points Rate limit exceeded status 429 Too Many Requests Retry header Retry-After (seconds) ## API v1 ### STOQ API v1 Path: `/v1/` Version: 1.0.0 ## Back in Stock Before diving into the API reference, it's important to understand the key term we use - Intent Intents are essentially "waitlist requests" or "subscriptions" that customers create when they want to be notified with a back in stock alert. 1. **Core Purpose**: * When a product is out of stock, customers can sign up to be notified when it becomes available again * Each intent represents a single customer's request to be notified about a specific product variant. * Intent is unique to a customer, product variant & market. 2. **What data is present in an Intent?** * **Customer Information**: Each intent contains customer details like email, phone, and name * **Product Information**: Shopify product and product variant IDs * **Notification Preference**: Specifies how the customer wants to be notified (email or SMS) * **Quantity**: How many units the customer is interested in when the product is back in stock 3. **Lifecycle of an Intent**: * Created when a customer signs up for notifications * Can be notified when the product becomes available * Can be blocked from sending notifications * Can be unsubscribed by the customer * Tracks notification history and opt-in status 4. **Compliance & Privacy**: * Includes GDPR opt-in confirmation tracking * Tracks marketing preferences * Maintains unsubscribe status ## Preorders Before diving into the API reference, it's important to understand the key term we use - Selling Plans #### What are Selling Plans? Selling Plans in the context of preorders are configurations that define how a preorder product is sold, billed, and delivered to customers. #### Core Purpose: * Enable merchants to sell products before they're in stock or available for immediate shipping * Define payment schedules, delivery timelines, and inventory management for preordered items * Provide customization options for how preorders appear and function in the storefront #### What data is present in a Selling Plan? * **Basic Information**: Name, internal reference name, enabled status * **Billing Configuration**: When and how much to charge (at checkout or later), charge types (percentage or fixed amount) * **Delivery Settings**: When products will be delivered (exact date or interval after purchase) * **Inventory Management**: How inventory is reserved and which system manages it * **Pricing Options**: Discount amounts or percentages for preordered items * **UI Customization**: Button text, badge styling, shipping information text #### Variant Association: * Selling plans can be associated with product variants in three ways: * All products in the store * Products in a specific collection * Custom selection of individual variants * Each variant can have custom settings like shipping text and maximum preorder quantity #### Multi-Market Support: * Selling plans can be configured for specific markets in multi-market stores * Different markets may have different preorder terms or availability #### Operations ##### POST /intents.json Summary: Create Customer Intent to Buy Product Create a new intent for a customer to be notified when a product is back in stock. Unlike other API endpoints for Back in Stock, this API endpoint does not require an API Key. Parameters: - `X-Shopify-Shop-Domain` (header, string, required) - The Shopify shop domain (e.g. 'restock-rocket-demo.myshopify.com') - `Content-Type` (header, string, required) - Must be application/json Request body: application/json Responses: - 200: Request processed but with errors - 201: Intent created successfully - 400: Bad request - 401: Unauthorized ##### DELETE /intents/bulk_destroy Summary: Delete intents (single or multiple) Delete one or more intents by their IDs. Only intents that have not been sent (notified_at is null) can be deleted. If any of the intents have been sent, the entire operation will fail and return a 422 error with the IDs of sent intents. You can pass a single intent ID in the array to delete just one intent. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication Request body: application/json Responses: - 200: All intents deleted successfully - 400: Bad request - invalid parameters - 401: Unauthorized - 404: No valid intents found - 422: Cannot delete intents that have already been sent ##### GET /intents/{id} Summary: Get a specific intent Retrieve a single back-in-stock intent (signup) by its STOQ ID. Returns the full intent record — the customer contact details, channel, requested quantity, and notification status. Returns 404 if no intent with that ID exists for the authenticated shop. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Intent ID Responses: - 200: Intent details - 401: Unauthorized - 404: Intent not found ##### GET /intents/products_in_demand Summary: Get products in demand report Retrieve a paginated list of product variants with demand metrics based on customer intents. Returns aggregated data showing total intents, pending intents, and last requested date for each variant. Results can be filtered by date range and sorted by different metrics. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `start_date` (query, string, optional) - Filter by start date (YYYY-MM-DD). Filters based on last_requested_at field. - `end_date` (query, string, optional) - Filter by end date (YYYY-MM-DD). Filters based on last_requested_at field. - `sort_by` (query, string, optional) - Sort results by pending intents, total intents, or last requested date - `direction` (query, string, optional) - Sort direction (ascending or descending) - `page` (query, integer, optional) - Page number for pagination - `per_page` (query, integer, optional) - Number of items per page (maximum 500) - `intent_type` (query, string, optional) - Filter by intent type (used when loading product data) Responses: - 200: A paginated list of product variants with demand metrics - 400: Bad request - invalid parameters - 401: Unauthorized ##### GET /intents Summary: List and filter intents List back-in-stock intents (signups) for the shop, newest first. Supports filtering by Shopify product or variant, customer email or phone, creation date range, and notification status, with page-based pagination (up to 500 per page). Returns the matching intents alongside pagination metadata. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `shopify_variant_id` (query, integer, optional) - Filter by Shopify Variant ID - `shopify_product_id` (query, integer, optional) - Filter by Shopify Product ID - `email` (query, string, optional) - Filter by customer email - `phone` (query, string, optional) - Filter by customer phone - `start_date` (query, string, optional) - Filter by start date (YYYY-MM-DD) - `end_date` (query, string, optional) - Filter by end date (YYYY-MM-DD) - `notified` (query, boolean, optional) - Filter by notification status (true = notified, false = not notified) - `page` (query, integer, optional) - Page number for pagination - `per_page` (query, integer, optional) - Number of items per page (maximum 500) Responses: - 200: A paginated list of intents - 400: Bad request - 401: Unauthorized ##### POST /intents/notify_intent Summary: Notify a single intent Send the back-in-stock notification for a single intent immediately, regardless of current inventory. By default an intent that has already been notified will not be sent again; set `allow_resend` to true to force a resend. Returns 404 if the intent does not exist and 422 if the notification could not be sent. Request body: application/json Responses: - 200: Notification sent successfully - 401: Unauthorized - 404: Intent not found - 422: Failed to notify intent ##### POST /intents/bulk_notify_intent Summary: Notify multiple intents in bulk Send back-in-stock notifications for multiple intents in one request. Each intent is processed independently; the response reports which succeeded and which failed, returning 207 when only some succeed. Set `allow_resend` to true to re-notify intents that were already sent. Request body: application/json Responses: - 200: All notifications sent successfully - 207: Partial success (some notifications failed) - 400: Bad request - 401: Unauthorized ##### POST /intents/transfer_signups Summary: Transfer signups from one variant to another Transfer intents (signups) from a source variant to a target variant. This is useful when consolidating variants or when products are restructured. By default transfers pending intents, but can transfer sent or all intents based on intent_type parameter. Request body: application/json Responses: - 200: Transfer completed successfully - 400: Bad request - invalid parameters - 401: Unauthorized - 422: Transfer failed ##### POST /external/preorders/{id}/add_variant Summary: Add multiple product variants to a selling plan Attach one or more Shopify product variants to a selling plan so they become available for preorder. Pass the variant IDs in `shopify_variant_ids`; any that are already attached are reported back under `existing_variants` with a 422. Returns the list of variant IDs that were newly added. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID Request body: application/json Responses: - 200: Variants added successfully - 400: Bad request - missing or invalid shopify_variant_ids - 401: Unauthorized - 404: Selling plan not found - 422: Some variants already added or other error ##### GET /external/preorders Summary: List and filter selling plans List the shop's preorder selling plans, newest first, with page-based pagination. Optionally filter by `name` (matches the display or internal name). Returns a lightweight representation of each plan plus pagination metadata; use Get a Specific Selling Plan for the full object. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `name` (query, string, optional) - Filter by selling plan name or internal name - `page` (query, integer, optional) - Page number for pagination - `per_page` (query, integer, optional) - Number of items per page Responses: - 200: A paginated list of selling plans - 400: Bad request - 401: Unauthorized ##### POST /external/preorders Summary: Create a new selling plan Create a new preorder selling plan. The `selling_plan` object defines billing, delivery, discount, button, badge, and fulfillment behavior; only the fields you set are applied and the rest fall back to defaults. Returns the created selling plan, including its STOQ ID, on success. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication Request body: application/json Responses: - 201: Selling plan created successfully - 401: Unauthorized - 422: Validation errors ##### GET /external/preorders/{id} Summary: Get a specific selling plan Retrieve a single preorder selling plan by its STOQ ID, including the full billing, delivery, pricing, button, badge, fulfillment, and market configuration, the count of attached variants, and the associated Shopify selling-plan IDs. Returns 404 if no plan with that ID exists for the shop. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID Responses: - 200: Selling plan details - 401: Unauthorized - 404: Selling plan not found ##### PUT /external/preorders/{id} Summary: Update a selling plan Update an existing preorder selling plan. Supply a `selling_plan` object with the fields you want to change; omitted fields are left unchanged. Returns the updated selling plan, or 422 with validation errors if the changes are invalid. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID Request body: application/json Responses: - 200: Selling plan updated successfully - 401: Unauthorized - 404: Selling plan not found - 422: Validation errors ##### DELETE /external/preorders/{id} Summary: Delete a selling plan (soft delete) Soft-delete a preorder selling plan by ID. The plan is discarded (its `discarded_at` is set) and detached from the storefront, but historical preorder data is preserved. Returns 404 if the plan does not exist and 422 if it could not be deleted. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID Responses: - 200: Selling plan deleted successfully - 401: Unauthorized - 404: Selling plan not found - 422: Failed to delete selling plan ##### GET /external/preorders/{id}/product_variants Summary: Get product variants associated with a selling plan Retrieve product variants associated with a selling plan, including their preorder details and sales metrics. Filtering behavior: - No parameters: Returns all product variants in the selling plan - shopify_product_id: Returns all variants for that specific product - shopify_variant_id: Returns only that specific variant (single object response) Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID - `shopify_product_id` (query, integer, optional) - Filter by specific Shopify product ID to get all variants for that product - `shopify_variant_id` (query, integer, optional) - Filter by specific Shopify variant ID to get only that variant (returns single object) Responses: - 200: Product variants with their preorder details and sales metrics - 400: Bad request - cannot filter by both product and variant ID - 401: Unauthorized - 404: Selling plan not found or variant not found in this selling plan ##### DELETE /external/preorders/{id}/remove_variant Summary: Remove multiple product variants from a selling plan Remove multiple product variants from a selling plan. Optionally, you can also update the inventory policy of the removed variants in the same request to avoid making separate API calls. When `inventory_policy` is provided, the inventory policy update is performed synchronously (inline) for better performance and to avoid race conditions. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID - `shopify_variant_ids` (query, array, required) - Array of Shopify variant IDs to remove - `inventory_policy` (query, string, optional) - Optional. Inventory policy to set for the removed variants. CONTINUE allows overselling, DENY prevents it. Responses: - 200: Variants removed successfully - 400: Bad request - missing or invalid parameters - 401: Unauthorized - 404: Selling plan or variants not found - 422: Failed to remove variants or invalid inventory policy ##### POST /external/preorders/send_shipping_update Summary: Send shipping update emails to multiple orders Send shipping update emails to multiple preorders. You can specify orders using either: - STOQ internal order IDs (UUIDs) via `order_ids` - Shopify order IDs via `shopify_order_ids` You must provide one or the other, but not both. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication Request body: application/json Responses: - 200: Shipping update emails queued successfully - 400: Bad request - missing required parameters or invalid input - 401: Unauthorized - 404: No valid orders found ##### POST /external/preorders/{id}/bulk_toggle_inventory_policy Summary: Queue a bulk update of inventory policy for multiple variants Queues a background job to update the inventory policy for multiple variants. The operation is performed asynchronously to handle large numbers of variants efficiently. Updates are processed in batches of up to 1000 variants to stay within message size limits. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID Request body: application/json Responses: - 200: Inventory policy update has been queued successfully - 400: Bad request - missing or invalid parameters - 401: Unauthorized - 404: Selling plan not found - 422: Invalid inventory policy ##### PATCH /external/preorders/{id}/update_variant_settings Summary: Update variant-specific settings for a selling plan Update per-variant preorder settings within a selling plan, such as the variant's custom `shipping_text` and `preorder_max_count` (the cap on preorder units). Identify the variant with `shopify_variant_id`; only the settings you include are changed. Returns the updated variant settings. Parameters: - `X-Auth-Token` (header, string, required) - API key for authentication - `id` (path, string, required) - Selling plan ID Request body: application/json Responses: - 200: Variant settings updated successfully - 400: Bad request - missing required parameters or no settings to update - 401: Unauthorized - 404: Selling plan or variant not found - 422: Failed to update variant settings #### Models - Intent - ProductVariantDemand: Product variant with demand metrics and product data - SellingPlanInput: Input schema for creating or updating selling plans - SellingPlan - SellingPlanProductVariantsResponse - ProductVariantDetails - SellingPlanSingleVariantResponse - SellingPlanList: Simplified selling plan object for list views ## API v2 (Beta) ### API v2 — Overview Path: `/v2/docs/api-v2/` STOQ's action-driven external API. One catch-all endpoint, classes that describe themselves, and a discovery surface (/help, skill.md, llms.txt, MCP) designed for both humans and AI agents. [Public beta] v2 is in public beta . It's stable enough to build on, but actions and schemas may still change before GA — pin only what you can adjust, and send feedback to support@stoqapp.com . The v1 Back in Stock and Preorders APIs remain fully supported. STOQ's AI-native API. v2 is a ground-up rewrite designed so a developer — or an AI agent — can turn a natural-language intent into a single, correct call. Instead of REST resources with overloaded update calls, it ships intent-bearing actions — release , charge_balance , set_delivery_date — alongside one predictable, deep-partial PATCH per capability for plain settings. What makes it AI-native Self-describing actions. Every action registers its description , aliases , notes , verb , and a real JSON request_schema — so an agent can map "charge the remaining balance" to exactly one call, with the required fields and gotchas attached. A built-in discovery surface. GET /help returns the full manifest, skill.md is an unauthenticated agent-readable guide, and llms.txt is the root index — an agent can learn the whole API before it even has a token. MCP-first. Every action is also exposed as an MCP tool , so agents can drive STOQ without touching HTTP. Predictable writes. Named POST actions carry side effects; one deep-partial PATCH per capability carries plain settings and toggles. No guessing which field combination means what. Quick links Interactive API reference Browse all v2 actions with schemas and a try-it console (Scalar). Getting Started Auth, base URL, your first request. Dispatch & Discovery How /help , skill.md , llms.txt , and the MCP server work. Errors & Responses Status codes, envelope shape, validation. Bulk & Async Jobs The job_id → status-URL polling pattern. Resources Offer Selling-plan lifecycle: create, schedule, enable/disable. Offer capabilities The 12 capability groups exposed on an offer. Product variants Per-variant reads, metafield admin, bulk recalculation. Reports Preorder analytics + async CSV exports. Orders Preorder order lifecycle: release, charge, refund, tag. MCP Every action, exposed as an MCP tool. Design at a glance One verb-and-path per action. Every action class registers itself with a description , aliases , notes , path , and verb . Named actions carry side effects; PATCH carries settings. A named POST action exists only when the operation does more than write settings — state transitions, Shopify mutations, variant fan-out. Plain settings and all boolean toggles (e.g. badge.enabled , terms.enabled , remaining_balance.auto_collect ) go through the capability's deep-partial PATCH . Each PATCH action's manifest notes list its toggleable field paths. Catch-all dispatcher. Every path under /api/v2/external/* is matched against ApiV2::Registry — no per-route wiring. Discovery primitives. GET /api/v2/external/help returns the full manifest (scoped via ?prefix= or GET /help at any URL level). GET /api/v2/external/preorders/skill.md is an unauthenticated, AI-readable usage guide. GET /llms.txt is the root index. And every action is also an MCP tool . Same auth model as v1. X-Auth-Token header carrying the shop API key. Rate-limiting and points budget are identical (1 pt read, 2 pt write). Standardized async jobs. Bulk and long-running actions return 202 Accepted with {job_id, status_url} . Poll the status URL until a terminal status. What "action-driven" buys you For a human: Copy curl -X POST https://app.stoqapp.com/api/v2/external/preorders/orders/{order_id}/payments/charge_balance \ -H "X-Auth-Token: $STOQ_API_KEY " …instead of working out which combination of fields on a giant order PATCH means "charge the remaining balance now". For an AI agent: aliases on every action map natural-language phrasings to a single canonical call — and the capability PATCH actions carry aliases for their toggles too ("turn on auto-collect" resolves to PATCH .../payments with remaining_balance.auto_collect: true ). notes carry the gotchas (e.g., release: "Returns 409 if balance hasn't been collected; pass force: true to override."). request_schema in the manifest is a real JSON Schema — required fields, enums, and bounds included. /help returns the full manifest so the agent can plan multi-step workflows without having to re-read docs each turn — or skip HTTP entirely and connect to the MCP server , where every action is a tool. See Dispatch & Discovery for the full discovery surface. ### Getting Started Path: `/v2/docs/api-v2/getting-started/` Authenticate and make your first v2 API call. Base URL Copy https://app.stoqapp.com/api/v2/external All v2 endpoints live under this prefix. Authentication v2 uses the same auth scheme as v1 — an X-Auth-Token header carrying the shop's API key. Copy curl https://app.stoqapp.com/api/v2/external/help \ -H "X-Auth-Token: stoq_api_key_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" You can find your API key in the STOQ app: Settings → Integrations → API Key . See API Key for the full walkthrough. A missing or invalid token returns 401 Unauthorized . Rate limits Identical to v1 — 360 points per 60s rolling window. Reads cost 1 point, writes cost 2. The same X-RateLimit-* headers are present on every response. See Rate Limits for details. Your first call The simplest health check is /help — it returns a manifest of every action the API exposes. Copy curl https://app.stoqapp.com/api/v2/external/help \ -H "X-Auth-Token: $STOQ_API_KEY " Filter to a single resource — either with the prefix param or by appending /help at any URL level: Copy curl 'https://app.stoqapp.com/api/v2/external/help?prefix=/preorders/orders' \ -H "X-Auth-Token: $STOQ_API_KEY " curl 'https://app.stoqapp.com/api/v2/external/preorders/orders/help' \ -H "X-Auth-Token: $STOQ_API_KEY " Response envelope Successful responses are returned bare — the action's data payload at the top level, with no wrapper. Reads return 200 OK ; writes return 202 Accepted (see Errors & Responses ). Copy { "id" : "uuid" , "name" : "Spring Drop" , "..." : "..." } Errors are returned wrapped under an errors array: Copy { "errors" : [ "mode must be one of: auto, manual" ] } See Errors & Responses for the full table. Where to go next Browse capabilities by resource: start with Offer for the most common selling-plan flows. Plan AI integrations: read Dispatch & Discovery — the /help manifest and skill.md are built for this. Every action is also exposed as an MCP tool — see MCP . Run bulk jobs: the Bulk & Async Jobs page explains the job_id polling pattern used everywhere from CSV exports to bulk order cancellation. ### Dispatch & Discovery Path: `/v2/docs/api-v2/dispatch-and-discovery/` How requests are routed and how to introspect the API — for humans and AI agents. v2 is built around two ideas: a single catch-all dispatcher, and a discovery surface that lets any consumer (human or AI) find every action without external documentation. Catch-all dispatch Every path under /api/v2/external/* is handled by one dispatcher. It walks ApiV2::Registry — a lookup table populated by classes that include the api_v2 do ... end DSL — and routes the verb+path to the matching action. All action paths and /help require X-Auth-Token and consume rate-limit points. skill.md and /llms.txt are public. A request that doesn't match any registered action returns 404 : Copy { "errors" : [ "No v2 action registered for POST /preorders/offers/nope" ] } /help — the canonical manifest Copy curl https://app.stoqapp.com/api/v2/external/help \ -H "X-Auth-Token: $STOQ_API_KEY " Returns one entry per registered action, wrapped in a small envelope: Copy { "version" : "v2" , "base_path" : "/api/v2/external" , "actions" : [ { "name" : "set_button_text" , "method" : "POST" , "path" : "/preorders/offers/:id/widget/set_button_text" , "description" : "Set the preorder button's call-to-action label." , "aliases" : [ "change button text" , "rename button" , "set button label" , "change preorder button label" , "update button text" ], "path_params" : [ "id" ], "request_schema" : { "type" : "object" , "properties" : { "text" : { "not" : { "type" : "null" } } }, "required" : [ "text" ] } } ] } request_schema is a real JSON Schema , generated from the same contract that validates the request at runtime — including required , enum values, and numeric bounds. Dispatcher-injected keys and URL placeholders are stripped; they're not part of the request body. path_params lists the :placeholders in the path (present only when the path has them). notes carries free-text caveats — including, for every capability PATCH , the list of toggleable field paths. Scoped help Two equivalent ways to load a focused subset into an agent's context: Copy # 1. The prefix filter on /help curl 'https://app.stoqapp.com/api/v2/external/help?prefix=/preorders/orders' \ -H "X-Auth-Token: $STOQ_API_KEY " # 2. GET /help — works at every URL level curl https://app.stoqapp.com/api/v2/external/preorders/offers/help \ -H "X-Auth-Token: $STOQ_API_KEY " curl https://app.stoqapp.com/api/v2/external/preorders/offers/5b2e.../widget/help \ -H "X-Auth-Token: $STOQ_API_KEY " The scope may contain concrete IDs where the registered patterns have :placeholders — /preorders/offers/5b2e.../widget/help returns the widget actions. A scope with no registered actions returns 404 ("No v2 actions registered under ..."). The scoped response carries a scope key alongside actions so you can tell what you asked for. skill.md — per-domain AI guide Copy GET https://app.stoqapp.com/api/v2/external/preorders/skill.md Unauthenticated, plain markdown, generated from the live registry. Designed to be passed verbatim into an AI agent's prompt — explains the preorder domain, the action conventions (named POSTs for side effects, capability PATCH for settings and toggles), shows representative request shapes, and embeds the full action table. If you're building an integration that lets your users say "increase the deposit by 10%" or "release all orders that paid the balance", point your agent at this URL. MCP — every action as a tool Copy POST https://app.stoqapp.com/api/v2/external/mcp The same registry also backs an MCP server: every registered action is exposed as an MCP tool (name, description, input schema — generated from the manifest entries above). Auth is the same API key, via X-Auth-Token or Authorization: Bearer . New actions become MCP tools automatically. See MCP for connection details and tool naming. llms.txt — root index Copy GET https://app.stoqapp.com/llms.txt A top-level index that points agents at the per-domain skill documents. As STOQ exposes more APIs, they show up here. Why this matters for AI agents The combination of /help + skill.md lets an agent operate without baked-in knowledge of STOQ: The agent fetches skill.md once to understand the domain. It calls /help?prefix=... (or GET /help ) to discover the exact actions and request schemas. It maps the user's natural-language intent to a single aliases entry → canonical action. It executes the call, reading notes for any gotchas (e.g., which field paths a capability PATCH toggles, or when force: true is needed). No SDK, no schema registry, no rebuild when STOQ adds new actions. Prefer tool-calling over raw HTTP? Use the MCP server — same registry, same actions. DSL recap (for reference) Every action class declares itself like this: Copy class Preorders::Offer::Widget::SetButtonText ApiV2Interactor api_v2 do description "Set the preorder button's call-to-action label." aliases "change button text" , "rename button" , "set button label" , "change preorder button label" , "update button text" path "/preorders/offers/:id/widget/set_button_text" verb :post end expects do required ( :shop ). filled required ( :id ). filled required ( :text ). filled end def call # ... end end The class is the manifest: description / aliases / notes / path / verb come from the api_v2 block, the request_schema is derived from the same expects contract that validates requests at runtime, and path_params come from the :placeholders in the path. /help , scoped help, skill.md , and the MCP tool list are all generated by iterating ApiV2::Registry — there's no separate registry file to keep in sync, so none of the discovery surfaces can drift from runtime behavior. ### Errors & Responses Path: `/v2/docs/api-v2/errors/` v2 status codes, response envelope, and validation behavior. Success envelopes Successful reads and writes return the action's data payload directly, with no wrapper. The HTTP status code is the contract: Code When 200 OK Read completed 202 Accepted Write accepted — this is the success code for all writes. Synchronous writes return the updated resource snapshot; bulk/async actions return {job_id, status_url} — see Bulk & Async Jobs 204 No Content Action succeeded with no payload (rare) 207 Multi-Status Reserved for bulk actions reporting per-item failures Note v2 writes return 202 Accepted even when the local database write committed synchronously — Shopify-side sync (metafields, selling-plan groups, order mutations) generally completes in the background. The response body reflects the committed local state. Error envelope All non-2xx responses share one shape: Copy { "errors" : [ "one or more human-readable strings" ] } errors is always an array of strings. Even single-error responses come back as a one-element array. Status codes Code Meaning Typical cause 400 Bad Request Malformed JSON Missing/invalid JSON body 401 Unauthorized Missing or invalid X-Auth-Token Wrong key or no header 404 Not Found Unknown action path, or resource not found Typo in path, deleted offer, expired job ID 409 Conflict State-machine refused the transition e.g., release on a cancelled order, set_collection on a non-collection offer 422 Unprocessable Entity Schema validation failure Missing required field, bad enum value, over a bulk cap 429 Too Many Requests Rate limit exhausted See Rate Limits 500 Internal Server Error Server bug — please report Unexpected exception 404 — unknown path vs missing resource An unregistered verb+path combination: Copy { "errors" : [ "No v2 action registered for POST /preorders/offers/nope" ] } A registered path addressing a resource that doesn't exist in this shop: Copy { "errors" : [ "Preorder offer not found: 5b2e..." ] } If you hit the first kind, ask the API itself: GET /help?prefix=... or GET /help returns the registered actions under any scope — see Dispatch & Discovery . 409 — state-machine conflict State-mutating actions check whether the transition is legal before running. If not, they return 409 with a one-line reason: Copy { "errors" : [ "Cannot release: order is already cancelled." ] } Some 409s can be overridden with force: true on the request body (e.g., releasing an order before the balance is paid). The action's notes in the /help manifest will say so. 422 — validation Schema breaches surface as one error per field: Copy { "errors" : [ "mode must be one of: auto, manual" ] } Caps are validation errors too: Copy { "errors" : [ "Too many order_ids: 1200 (max 1000 per request)" ] } Idempotency v2 does not support request-level idempotency keys (yet). Practical rules: Reads are safe to retry. Lifecycle actions are idempotent in their effect (enabling an already-enabled offer is a no-op success). Bulk submissions create a new job each time — don't auto-retry without checking the previous job_id first. Creates are not idempotent — duplicate submissions create duplicate resources. Stable error messages We treat the human-readable strings in errors as part of the API contract for cross-cutting messages — "X is required", "Y must be one of: ...". Resource-specific 409 reasons may evolve over time; don't pattern-match on those. ### Bulk & Async Jobs Path: `/v2/docs/api-v2/bulk-and-async-jobs/` How to submit and poll long-running operations: bulk actions and CSV exports. Three classes of v2 action return a job handle instead of a synchronous result: Bulk order actions — bulk_release , bulk_cancel , bulk_set_tags , bulk_send_payment_collection_url . Bulk variant administration — bulk_recalculate_preorder_counts , bulk_reset_metafields , bulk_sync_metafields . CSV exports — every report has a paired */export action. All three share one envelope, one polling URL pattern, and one terminal-state semantics. Submission response — 202 Accepted Copy { "job_id" : "bulk_orders_release_a8c3f01b9d2e4471" , "status_url" : "/api/v2/external/preorders/orders/jobs/bulk_orders_release_a8c3f01b9d2e4471" , "order_count" : 47 } job_id — opaque, prefixed by action ( bulk_orders_* for order jobs, bulk_* for variant jobs, exp_* for exports). status_url — exactly where to GET to poll; never construct this yourself. order_count / variant_count — number of items the job will process (informational; not present on export submissions). Polling Copy curl https://app.stoqapp.com/api/v2/external/preorders/orders/jobs/bulk_orders_release_a8c3f01b9d2e4471 \ -H "X-Auth-Token: $STOQ_API_KEY " Copy { "job_id" : "bulk_orders_release_a8c3f01b9d2e4471" , "status" : "running" , "progress" : 25 , "total" : 47 , "processed" : 12 } progress is a percentage (0–100); total / processed are item counts. error is present when the job failed. status is one of: Status Meaning running Queued or being processed by a worker completed Done (bulk jobs) ready Done, download available (CSV exports) failed Job failed — see error cancelled Operator cancelled (rare) Polling an unknown or expired job_id returns 404 . Terminal payloads Bulk actions complete with no extra data: Copy { "job_id" : "..." , "status" : "completed" , "progress" : 100 , "total" : 47 , "processed" : 47 } Per-item errors are swallowed inside the job — one bad order doesn't poison the batch. Inspect via the audit log / Stoq Admin if needed. CSV exports terminate as ready and add the download fields: Copy { "job_id" : "exp_a8c3f01b9d2e4471" , "status" : "ready" , "download_url" : "https://...s3.amazonaws.com/..." , "file_name" : "preorders_revenue_2026-01-01_2026-05-28.csv" , "row_count" : 22 , "progress" : 100 } The download URL is a 7-day pre-signed S3 link. Polling cadence Bulk actions: poll every 5s for the first minute, then every 15s. Most bulk jobs of CSV exports: poll every 10s. Large reports can take a few minutes. Always check rate-limit headers; polling is a GET and costs 1 point. Caps and behavior Bulk order actions: max 1000 order IDs per submission. Exceeding the cap returns 422 . Bulk variant actions: max 5000 variant IDs per submission. Job retention: status records expire ~24h after completion. (Export download URLs stay valid for 7 days, but you can only fetch them from the status endpoint while the record exists.) Inventory of async endpoints The pattern is the same across all of them — the table below is just so you can find the right one: Resource Job endpoint preorders/orders/bulk_* GET /preorders/orders/jobs/:job_id preorders/product_variants/bulk_* GET /preorders/product_variants/jobs/:job_id preorders/reports/*/export GET /preorders/reports/exports/:job_id Some non-bulk writes are also asynchronous under the hood — e.g. the offer's products/add_variants and products/bulk_toggle_inventory_policy fan out to background jobs. See each resource page for the full list of bulk and export actions. ### Migrating from v1 Path: `/v2/docs/api-v2/migrating-from-v1/` How the v1 REST endpoints map to v2 actions, and why the action model. The v1 API is REST-style: resources with GET / POST / PUT / DELETE and overloaded update calls. v2 is action-driven — every operation is a self-describing action with a verb, a path, aliases, and a JSON request schema. Both are fully supported; migrate when it suits you. What changes v1 (REST) v2 (actions) PUT /preorders/{id} with a grab-bag of fields A named action per side effect (e.g. enable , disable , set_delivery_date ) plus one deep-partial PATCH per capability for plain settings Figure out which field combo triggers a behavior One action = one intent; required fields and gotchas are in the action's notes / request_schema Read the docs to discover endpoints GET /help returns the full manifest; skill.md and llms.txt describe the API to agents HTTP only Every action is also an MCP tool Same as v1 Auth — the same X-Auth-Token API key. Rate limits — the same points budget (1 pt read, 2 pt write). Base concepts — offers (selling plans), variants, intents (signups) are unchanged. How to map a call Find the capability ( offers , orders , reports , signups , …). For a state change or side effect , use the named action — e.g. POST /preorders/offers/{id}/enable instead of PUT -ing enabled: true . For plain settings/toggles , use the capability's deep-partial PATCH — its manifest notes list the field paths. Unsure of the exact shape? GET /api/v2/external/help?prefix=/preorders/offers returns the manifest for that scope. See Getting Started for the base URL and auth, and the API v2 Reference for every action. ### Integrating AI agents Path: `/v2/docs/api-v2/ai-integration/` Teach any AI agent the v2 API without an SDK — skill.md for the system prompt, llms.txt for discovery, and the /help manifest for exact request shapes. The v2 API is designed so an AI agent can operate it without baked-in knowledge of STOQ . Every action self-describes with a description, natural-language aliases, notes, and a request schema — and that metadata is served live from the same registry that handles dispatch, so it can never drift from what the API actually accepts. There are two ways to consume this: MCP — if your agent runs in a tool-calling client (Claude Code, Claude Desktop, Cursor, or your own MCP-capable harness), connect it to the MCP server . Each action arrives as a ready-made tool; no prompt engineering needed. skill.md + /help — if you're building a prompt-based agent (a support bot, a workflow tool, anything that composes its own HTTP calls), use the discovery endpoints on this page. skill.md — the system-prompt guide Copy GET https://app.stoqapp.com/api/v2/external/preorders/skill.md Public, no auth, plain markdown. Paste it verbatim into your agent's system prompt, or save it as a SKILL.md your agent loads on demand. It teaches the agent: base URL, auth, and content type the core idea (action-driven, alias-matched — not REST-resource-shaped) the key conventions (PATCH for settings, named actions for side effects, the lifecycle state machine, 202 + job_id for async writes) worked example flows ("change the preorder button text on the summer drop", "pause and stop overselling", translations, scheduling) a live table of every registered preorder action with method, path, description, and aliases Because the action table is generated from the live registry at request time, re-fetching skill.md is how your agent picks up new capabilities — there's no version to pin. Note skill.md is public precisely so agents can learn the API before they have a token. The document explains how the merchant obtains one (STOQ dashboard → Settings → Integrations → API Key). llms.txt — the root index Copy GET https://app.stoqapp.com/llms.txt Also public. A discovery breadcrumb (think robots.txt for agents) that lists every per-domain skill.md plus the auth and manifest pointers. Each API domain gets its own skill document so an agent can ingest just the surface it needs; as more domains ship (back-in-stock v2, etc.) they appear here. The /help manifest workflow skill.md is prose for the prompt; /help is structured data for building requests. It requires the API token: Copy curl https://app.stoqapp.com/api/v2/external/help \ -H "X-Auth-Token: $STOQ_API_KEY " Each entry carries everything needed to construct a call: Copy { "name" : "set_button_text" , "method" : "POST" , "path" : "/preorders/offers/:id/widget/set_button_text" , "description" : "Set the preorder button's call-to-action label." , "aliases" : [ "change button text" , "rename button" , "set button label" , "change preorder button label" , "update button text" ], "path_params" : [ "id" ], "request_schema" : { "type" : "object" , "properties" : { "text" : { "not" : { "type" : "null" } } }, "required" : [ "text" ] } } The loop your agent runs: Fetch the manifest (once per session, or scoped — see below). Match the merchant's phrasing against the aliases arrays. "Rename the preorder button" → set_button_text . The aliases are written in merchant vocabulary on purpose; semantic matching against them is far more reliable than matching against paths. Build the request from request_schema (the JSON body, with its required list) plus path_params (substitute each :placeholder in path — fetch GET /preorders/offers first when the merchant gave a name instead of an ID). Call it against https://app.stoqapp.com/api/v2/external + the action's path , with X-Auth-Token . Read notes first — that's where the gotchas live (e.g. which field paths a PATCH toggles, or when a force: true is required). Errors come back as { "errors": [...] } with the status codes documented in Errors & Responses — in particular, 409 Conflict means an invalid lifecycle transition whose message names the unblocking action. Surface it to the model; don't retry. Scoped help for narrow contexts The full manifest is large (~187 actions). When your agent only operates a slice of the API, load just that slice: Copy # Query-param form curl 'https://app.stoqapp.com/api/v2/external/help?prefix=/preorders/orders' \ -H "X-Auth-Token: $STOQ_API_KEY " Scoped help is also reachable at any URL level via the dispatch catch-all — /preorders/offers/help , /preorders/offers/:id/widget/help , and so on. The offer-scoped form ( /preorders/offers/:id/help ) additionally includes current-state hints for that specific offer, which is useful right before a lifecycle action. This keeps token usage down and intent-matching sharp: an agent that only manages preorder orders never needs the widget-styling actions in context. Which approach should I use? You're building... Use An assistant in an MCP-capable client (Claude Code/Desktop, Cursor, custom harness) MCP server — tools arrive pre-built A prompt-based agent that composes its own HTTP calls skill.md in the system prompt + /help for request shapes A traditional integration with no AI in the loop The plain v2 REST API — same actions, same manifest All three are views over the same action registry, so capabilities, aliases, and request schemas are always identical across them. ### MCP Server Path: `/v2/docs/api-v2/mcp/` Connect Claude, Cursor, or any MCP client to the v2 API — every action exposed as a tool, generated from the same registry that drives HTTP dispatch. STOQ ships a built-in MCP server over the v2 external API. Every registered v2 action — preorders and back-in-stock, roughly 187 in total — is exposed as one MCP tool, generated from the same class-level declarations that drive HTTP dispatch and the /help manifest . When STOQ ships a new action, it becomes an MCP tool automatically; there is nothing to update on your side. This means an AI assistant connected to the server can do anything the API can do: list offers, change widget text, schedule campaigns, release preorder fulfillments, pull back-in-stock signup reports, and so on — scoped to your shop, under your API key. Endpoint & auth Copy POST https://app.stoqapp.com/api/v2/external/mcp The server speaks MCP over HTTP (JSON-RPC in the request body). Auth uses the same per-shop API key as the rest of the v2 surface, sent either way: X-Auth-Token: — same header as the REST API Authorization: Bearer — for MCP clients that only support Authorization headers You can find your API key in the STOQ app: Settings → Integrations → API Key . See API Key for the walkthrough. Warning The key grants full read/write access to your shop's preorder and back-in-stock data. Treat it like a password — anyone (or any agent) holding it can modify live offers. Connecting a client Claude Code Copy claude mcp add --transport http stoq https://app.stoqapp.com/api/v2/external/mcp \ --header "X-Auth-Token: " Claude Desktop Claude Desktop launches local processes, so use the mcp-remote bridge in claude_desktop_config.json : Copy { "mcpServers" : { "stoq" : { "command" : "npx" , "args" : [ "-y" , "mcp-remote" , "https://app.stoqapp.com/api/v2/external/mcp" , "--header" , "X-Auth-Token:${STOQ_API_KEY}" ], "env" : { "STOQ_API_KEY" : "your-api-key" } } } } Cursor In .cursor/mcp.json (project) or ~/.cursor/mcp.json (global): Copy { "mcpServers" : { "stoq" : { "url" : "https://app.stoqapp.com/api/v2/external/mcp" , "headers" : { "X-Auth-Token" : "your-api-key" } } } } Any other MCP client that supports HTTP transport with custom headers works the same way — point it at the endpoint and send the key. Tool naming Tool names are the action's URL path with placeholders dropped, joined by underscores, suffixed with the action name when the path doesn't already end in it: HTTP action MCP tool GET /preorders/offers preorders_offers_list PATCH /preorders/offers/:id preorders_offers_update POST /preorders/offers/:id/widget/set_button_text preorders_offers_widget_set_button_text GET /back_in_stock/notifications back_in_stock_notifications_list Path placeholders ( :id etc.) become required string properties on the tool's input schema — there is no URL in an MCP call. So preorders_offers_widget_set_button_text takes { "id": " ", "text": "..." } . Aliases drive intent matching Each tool's description embeds the action's natural-language aliases — the phrasings merchants actually use. preorders_offers_widget_set_button_text carries "change button text", "rename button", "set button label", and so on. This is what lets a model resolve "rename the preorder button" to the right tool out of ~187 without any custom prompting. PATCH tools carry the toggles The same convention as the HTTP API applies: named tools carry real side effects; plain settings — including every boolean toggle — live on the capability's PATCH tool. There is no enable_badge tool; flipping the badge is preorders_offers_widget_update with { "id": "...", "badge": { "enabled": true } } . PATCH tools are deep-partial (fields you don't send are left alone), and each PATCH tool's description notes its toggleable field paths ( badge.enabled , disclaimer.enabled , button.colors.enabled , ...). Result shape Every tool returns a JSON text payload with an explicit success flag: Copy // success { "success" : true , "data" : { "id" : "uuid" , "name" : "Summer Drop" , "..." : "..." } } // failure { "success" : false , "status" : "conflict" , "errors" : [ "Offer is discarded; call restore first." ] } Reads return the resource in data ; writes return the updated resource, or { "job_id": ... } for bulk/async work — poll the matching jobs tool (see Bulk & Async Jobs ). status mirrors the HTTP error classes: unauthorized , not_found , unprocessable_entity , conflict . Warning status: "conflict" means an invalid lifecycle transition (e.g. enabling an offer that's discarded). The error message names the action that unblocks the transition — read it and adjust the plan. Don't retry the same call blindly. Rate limits MCP tool calls hit the same cost-weighted rate limiter as REST calls — reads cost 1 point, writes cost 2, against the same per-token budget. See Rate Limits . Agents running multi-step workflows should expect occasional 429 -equivalent failures and back off. A worked example Merchant asks their assistant: "Change the preorder button text on the summer drop to 'Reserve yours'." A connected client resolves this in two tool calls: Copy // 1. The name "summer drop" isn't an ID — list offers to find it. // tool: preorders_offers_list { } // → { "success": true, "data": { "offers": [ // { "id": "9c2f6a1e-…", "name": "Summer Drop", "status": "enabled", … }, // … // ] } } // 2. Aliases match "change button text" → set_button_text. // tool: preorders_offers_widget_set_button_text { "id" : "9c2f6a1e-…" , "text" : "Reserve yours" } // → { "success": true, "data": { …updated offer… } } The same pattern — resolve a name to an ID with a list tool, then call the intent-bearing tool — covers most merchant requests. Note Building an agent that can't speak MCP? The same discovery surface is available as plain HTTP: see Integrating AI agents for the skill.md + /help manifest workflow. ### Common tasks Path: `/v2/docs/api-v2/common-tasks/` Recipes for things you'll actually do with the Preorders v2 API — find offers, attach variants, release and charge orders. Recipes for the things merchants and integrations do most. All calls use the X-Auth-Token header; see Getting Started . Find existing preorder offers List the shop's selling-plan offers (filter by status, paginate): Copy curl "https://app.stoqapp.com/api/v2/external/preorders/offers?status=enabled" \ -H "X-Auth-Token: $STOQ_API_KEY " Grab an offer's id from the list, then read one: Copy curl https://app.stoqapp.com/api/v2/external/preorders/offers/{id} \ -H "X-Auth-Token: $STOQ_API_KEY " Enable or disable an offer State changes are named actions — not a settings PATCH : Copy curl -X POST https://app.stoqapp.com/api/v2/external/preorders/offers/{id}/enable \ -H "X-Auth-Token: $STOQ_API_KEY " Attach variants to an offer Copy curl -X POST https://app.stoqapp.com/api/v2/external/preorders/offers/{id}/products/add_variants \ -H "X-Auth-Token: $STOQ_API_KEY " -H "Content-Type: application/json" \ -d '{ "variant_ids": [43900910010503, 43900910043271] }' Release held preorder orders Preorder orders sit on hold until you release them (e.g. when stock lands). Release is a named action; it returns 409 if the balance hasn't been collected — pass force: true to override. Copy curl -X POST https://app.stoqapp.com/api/v2/external/preorders/orders/{order_id}/release \ -H "X-Auth-Token: $STOQ_API_KEY " Charge a remaining balance For partial/deposit preorders, collect the rest: Copy curl -X POST https://app.stoqapp.com/api/v2/external/preorders/orders/{order_id}/payments/charge_balance \ -H "X-Auth-Token: $STOQ_API_KEY " Bulk + async Bulk operations return 202 Accepted with { job_id, status_url } . Poll the status URL until a terminal status — see Bulk & async jobs . Note Not sure of an action's exact path or body? GET /api/v2/external/help?prefix=/preorders returns the manifest for everything under preorders, with each action's schema and aliases. The full action list is in the API v2 Reference . ### Offer Path: `/v2/docs/api-v2/offer/` The preorder offer (selling plan) resource: CRUD, lifecycle, schedule, discard/restore. A preorder offer is the top-level entity merchants configure. It carries everything from deposit configuration to widget copy, and attaches to a set of product variants. v2 splits the surface into: CRUD — list / read / create / update / discard / restore. Lifecycle — enable / disable / schedule / unschedule. Capabilities — twelve sub-groups for each configuration concern. See Offer capabilities . Path prefix: /preorders/offers CRUD List offers Copy GET /preorders/offers Returns paginated offers in the shop, newest first. The response key is offers : Copy { "offers" : [ { "id" : "uuid" , "name" : "Spring Drop" , "..." : "..." } ], "meta" : { "total_count" : 12 , "page" : 1 , "per_page" : 25 , "total_pages" : 1 } } Param Type Default page integer 1 per_page integer 25 Aliases: "list offers", "show all offers", "preorder offers", "all preorders" Read an offer Copy GET /preorders/offers/:id Returns the full offer including a capabilities block summarizing every capability's current state. Create an offer Copy POST /preorders/offers Body field Required Notes name no Merchant-facing name (defaults to "Preorder") internal_name no Slug/reference (defaults to name ) Capability settings are configured after creation via their respective capability actions — there is intentionally no kitchen-sink create body. Returns: 202 Accepted with the new offer (like every v2 write — see Errors & Responses ). Update top-level fields Copy PATCH /preorders/offers/:id For only the fields not owned by a capability — name and internal_name . Notes: everything else lives on a capability. Settings and toggles go through the capability's PATCH ; side-effectful operations have named POST actions. See Offer capabilities . Discard (soft-delete) Copy POST /preorders/offers/:id/discard Aliases: "delete", "remove", "archive", "trash", "soft delete" Notes: Recoverable indefinitely via restore. Discarded offers are never hard-deleted. Restore Copy POST /preorders/offers/:id/restore Aliases: "undelete", "undiscard", "undo delete", "bring back" Notes: Variant attachments and metafields are not auto-restored. Re-attach via the Products capability . Lifecycle The lifecycle actions are intent-bearing wrappers around state changes that have side effects on inventory policy and Shopify selling-plan-group sync. Enable Copy POST /preorders/offers/:id/enable Body field Type Default Notes update_inventory_policy boolean false If true, flips attached variants' inventory_policy to CONTINUE variant_ids array all attached Restrict the inventory-policy flip to a subset of variants Aliases: "turn on", "activate", "launch", "go live", "start offer" Notes: Variants are left at their existing inventory policy unless update_inventory_policy: true is passed. The flip delegates to products/bulk_toggle_inventory_policy . Disable (pause) Copy POST /preorders/offers/:id/disable Body field Type Default Notes update_inventory_policy boolean false If true, flips attached variants' inventory_policy back to DENY variant_ids array all attached Restrict the inventory-policy flip to a subset of variants Aliases: "pause", "turn off", "deactivate", "stop offer", "end preorder", "disable and stop overselling", "close out preorder" Schedule Copy POST /preorders/offers/:id/schedule Body field Type Notes start ISO 8601 string Optional end ISO 8601 string Optional Aliases: "set schedule", "schedule launch", "set start date", "set end date", "schedule offer" Notes: Does not auto-enable the offer. The storefront reads the schedule live. Unschedule Copy POST /preorders/offers/:id/unschedule Aliases: "clear schedule", "remove schedule", "no schedule", "unschedule offer" Clears both the start and end of the schedule window. Capability surface Every other action on an offer is grouped under one of twelve capabilities — see Offer capabilities for the full tables. Each capability has: a Read action (GET the capability's current state) an Update action ( PATCH , deep partial) — the home of all settings and boolean toggles named POST actions only where an operation has side effects beyond writing settings (Translations is the one exception: it has no PATCH — translation writes are keyed set / unset / set_many actions.) ### Offer capabilities Path: `/v2/docs/api-v2/offer-capabilities/` The twelve capability groups exposed on a preorder offer. Each has a Read action, an Update (deep partial PATCH) for settings and toggles, and intent-bearing POST actions for operations with side effects. A capability is a coherent sub-group of an offer's configuration. Every capability follows the same shape: GET /preorders/offers/:id/{capability} — read current state PATCH /preorders/offers/:id/{capability} — deep partial update of settings, including every boolean toggle One POST per intent-bearing action — only for operations with side effects beyond writing settings Note The granularity rule. Named POST actions exist only for operations with side effects (Shopify mutations, variant fan-out, state transitions) or high-traffic narrow edits like set_button_text . Plain settings — and all boolean toggles such as badge.enabled or terms.enabled — are set via the capability's PATCH . The PATCH is deep-partial: fields you don't send are left alone. Each capability section below lists its toggleable field paths. The tables below are sourced from the live /help manifest. Every capability is also exposed as MCP tools . Widget Storefront UI — badge, button text & colors, billing widget, disclaimer. Path prefix: /preorders/offers/:id/widget Method Action Path Description GET read (root) Read the widget configuration for a preorder offer. PATCH update (root) Deep partial update of any subset of widget configuration. POST set_badge_colors set_badge_colors Set the preorder badge's text and background colors. POST set_badge_text set_badge_text Set the preorder badge text. POST set_billing_widget_text set_billing_widget_text Set the billing widget's title and description (the PDP payment-breakdown block). POST set_button_colors set_button_colors Set the preorder button's text and background colors (implicitly enables custom colors). POST set_button_text set_button_text Set the preorder button's call-to-action label. POST set_disclaimer_style set_disclaimer_style Set the disclaimer banner's colors and border radius. POST set_disclaimer_text set_disclaimer_text Set the disclaimer banner text shown under the preorder button. Toggles via PATCH .../widget : badge.enabled , disclaimer.enabled , button.colors.enabled , billing_widget.enabled . Clear a value by sending null . Copy PATCH /preorders/offers/:id/widget { "badge": { "enabled": false } } Note The billing-widget title and description are shared with the first payment option's copy (see payments/set_option_copy ). Inventory Inventory provider, reservation timing, and the continue-selling (oversell) policy for newly attached variants. Path prefix: /preorders/offers/:id/inventory Method Action Path Description GET read (root) Read the inventory configuration for a preorder offer. PATCH update (root) Deep partial update of inventory settings. POST set_provider set_provider Set the inventory provider ( stoq or shopify ). POST set_reservation_timing set_reservation_timing Set when inventory is reserved ( on_sale or on_fulfillment ). Toggles via PATCH .../inventory : continue_selling.enabled and continue_selling.auto_apply_to_new_variants — both map to the same underlying auto-flip-on-attach flag (newly attached variants get their Shopify inventory_policy flipped to CONTINUE ). When both fields are present, auto_apply_to_new_variants wins. Copy PATCH /preorders/offers/:id/inventory { "continue_selling": { "enabled": true } } Note Continue-selling on the offer only governs future variant attachments — neither field flips already-attached variants. To flip existing variants, use products/bulk_toggle_inventory_policy , or pass update_inventory_policy: true to the offer's enable / disable lifecycle actions. Setting provider to shopify forces the products source to custom . Shipping Delivery date / window, shipping copy, fulfillment hold, split-order behavior. Path prefix: /preorders/offers/:id/shipping Method Action Path Description GET read (root) Read shipping settings for a preorder offer. PATCH update (root) Deep partial update of shipping settings. POST set_delivery_asap set_delivery_asap Set delivery to "as soon as possible" (no specific date or window). POST set_delivery_date set_delivery_date Set an exact delivery date for the preorder. POST set_delivery_window set_delivery_window Set a delivery window in days from checkout (e.g. "ships within 14 days"). POST set_shipping_text set_shipping_text Set the customer-facing shipping text shown on the PDP. POST set_split_order_tag set_split_order_tag Set the tag applied to split orders on Shopify. POST set_split_transaction_gateway set_split_transaction_gateway Set the payment gateway used for the split-order transaction (e.g. "Prepaid"). Toggles via PATCH .../shipping : Field path Effect fulfillment.hold Hold ( true ) / release ( false ) fulfillment orders containing preorder items display.show_shipping_timeline true shows the detailed PDP timeline; false the simplified text fulfillment.split.enabled Turn order splitting on/off fulfillment.split.hold_fulfillments Hold the split order's fulfillments fulfillment.split.sequential_number Give split orders a sequential order name Copy PATCH /preorders/offers/:id/shipping { "fulfillment": { "hold": true, "split": { "enabled": true } } } Payments Payment modes (full / partial / both), deposit percentage, auto-collect, discounts, payment-option copy. Path prefix: /preorders/offers/:id/payments Method Action Path Description GET read (root) Read the payment configuration for a preorder offer. PATCH update (root) Deep partial update of payment settings. POST disable_option disable_option Remove a payment option ( full or partial ) from the offer. POST enable_option enable_option Add a payment option ( full or partial ) to the offer. POST set_deposit_percent set_deposit_percent Set the deposit percentage charged at checkout on the partial payment option. POST set_discount set_discount Set the preorder discount. POST set_discount_text set_discount_text Set the discount blurb shown on the offer's payment options. POST set_mode set_mode Switch the offer between full , partial , and full_and_partial payment modes. POST set_option_copy set_option_copy Set the merchant-facing copy on a payment option (title, description, discount text). Toggles via PATCH .../payments : remaining_balance.auto_collect , remaining_balance.auto_collect_on_fulfillment . Clear the discount via { "discount": { "type": "no_discount" } } . Copy PATCH /preorders/offers/:id/payments { "remaining_balance": { "auto_collect": false } } Notes from the manifest: set_deposit_percent — requires a partial payment option (mode partial or full_and_partial ); use set_mode first. Percent must be 1–99 (100% upfront is full payment mode). enable_option — idempotent; newly added options start from the dashboard defaults. disable_option — only valid on full_and_partial offers; an offer must keep at least one payment option. set_discount — type is one of percentage , price , fixed_amount , no_discount . set_discount_text — pass option_type ( full | partial ) to target one option; omit to apply to every option. Supports the {{ discount }} placeholder. Limits Min/max per order, per-customer and total caps, remaining-units display. Path prefix: /preorders/offers/:id/limits Method Action Path Description GET read (root) Read order-quantity limits for a preorder offer. PATCH update (root) Deep partial update of quantity limits. POST set_max_per_customer set_max_per_customer Set the maximum quantity a single customer can preorder across all their orders. POST set_max_per_order set_max_per_order Set the maximum quantity a customer can preorder per order. POST set_min_per_order set_min_per_order Set the minimum quantity a customer must preorder per order. POST set_total_max set_total_max Set the total quantity that can be preordered across all customers. Toggles and clears via PATCH .../limits : display.show_remaining (true/false) toggles the remaining-units display; clear a limit by sending null — per_customer.max: null removes the per-customer limit, total.max: null removes the total cap. Copy PATCH /preorders/offers/:id/limits { "display": { "show_remaining": true }, "total": { "max": null } } Warning per_customer.max and total.max are stored config only today — storefront enforcement of these limits is follow-up work and not live yet. Checkout Terms acceptance, order tags applied at checkout, mixed-cart policy. Path prefix: /preorders/offers/:id/checkout Method Action Path Description GET read (root) Read checkout settings for a preorder offer. PATCH update (root) Deep partial update of checkout settings. POST add_order_tag add_order_tag Add a tag to the set applied to orders containing this offer. POST remove_order_tag remove_order_tag Remove a tag from the set applied to orders containing this offer. POST set_mixed_cart_error_message set_mixed_cart_error_message Set the error message shown when a blocked mixed cart reaches checkout. POST set_order_tags set_order_tags Replace the entire set of tags applied to orders containing this offer. POST set_terms_text set_terms_text Set the terms-acceptance text shown next to the checkbox. Toggles via PATCH .../checkout : terms.enabled , terms.disable_button_until_acknowledged , terms.include_in_line_item_properties , mixed_cart.allowed . Copy PATCH /preorders/offers/:id/checkout { "terms": { "enabled": true }, "mixed_cart": { "allowed": false } } Countdown Countdown timer on the PDP — mode, style, copy, custom end date. Path prefix: /preorders/offers/:id/countdown Method Action Path Description GET read (root) Read the countdown timer configuration for a preorder offer. PATCH update (root) Deep partial update of countdown settings. POST set_custom_end_date set_custom_end_date Set the custom end date for the countdown timer. POST set_ends_text set_ends_text Set the text shown when the offer is closing. POST set_mode set_mode Set whether the countdown follows the offer's schedule ( to_schedule_end ) or a custom date ( to_custom_date ). POST set_starts_text set_starts_text Set the text shown before the offer has started. POST set_style set_style Set the countdown visual style (type, colors, border radius). POST set_unit_labels set_unit_labels Set the unit labels (Days/Hours/Mins/Secs). Toggle via PATCH .../countdown : enabled (true/false) shows or hides the countdown. Copy PATCH /preorders/offers/:id/countdown { "enabled": true, "mode": "to_custom_date", "custom_end_date": "2026-07-01T00:00:00Z" } Note set_custom_end_date has no visible effect unless mode is to_custom_date . In the PATCH body, text is an alias for ends_text — both write the same field, and an explicit ends_text wins when both are sent. Integrations POS and B2B behavior. This capability is PATCH-only — there are no named actions. Path prefix: /preorders/offers/:id/integrations Method Action Path Description GET read (root) Read integration settings (POS, B2B). PATCH update (root) Deep partial update of integration settings. Toggles via PATCH .../integrations : pos.enabled , pos.skip_inventory_check , b2b.enabled . Copy PATCH /preorders/offers/:id/integrations { "pos": { "enabled": false }, "b2b": { "enabled": true } } Markets Multi-market scoping — which Shopify markets the offer applies to. Path prefix: /preorders/offers/:id/markets Method Action Path Description GET read (root) Read market scoping for a preorder offer. PATCH update (root) Deep partial update of market scoping. POST add_market add_market Add a market to the preorder offer's scope. POST remove_market remove_market Remove a market from the preorder offer's scope. POST set_markets set_markets Replace the entire set of markets a preorder offer applies to. Toggle via PATCH .../markets : enabled (true/false) turns market-based scoping on or off. The offer applies to all markets when scoping is off, or when scoping is on with an empty market list: Copy PATCH /preorders/offers/:id/markets { "enabled": true, "market_ids": ["gid://shopify/Market/123"] } All markets: { "enabled": false } , or { "enabled": true, "market_ids": [] } , or { "enabled": true, "applies_to_all_markets": true } (which also clears the list). market_ids accepts Shopify GIDs or numeric IDs. For incremental edits, keep using add_market / remove_market / set_markets . Products Variant attachment — source mode, add/remove variants, exclusions, inventory-policy bulk toggle, and per-variant settings. Path prefix: /preorders/offers/:id/products Method Action Path Description GET read (root) Read the products configuration (source rule + variant summary). PATCH update (root) Set the source rule plus its matching sub-block ( source + one of all / collection / custom ). POST add_variants add_variants Attach variants to a custom-source preorder offer. POST bulk_toggle_inventory_policy bulk_toggle_inventory_policy Flip the Shopify inventory policy on variants attached to the offer ( CONTINUE or DENY ). POST remove_variants remove_variants Remove variants from a custom-source preorder offer. POST set_collection set_collection Change the collection driving a collection-sourced offer. POST set_excluded_variants set_excluded_variants Replace the explicit excluded-variant list for an all-source offer. POST set_exclusion_tag set_exclusion_tag Set the exclusion tag for an all-source offer (variants with this tag are excluded). POST set_source_to_all set_source_to_all Switch the offer to apply to every variant in the shop, minus exclusions. Requires confirm: true . POST set_source_to_collection set_source_to_collection Switch the offer to a collection-driven variant source. POST set_source_to_custom set_source_to_custom Switch the offer to a custom variant list. Optionally seed with variant IDs. Notes from the manifest: PATCH .../products — delegates to the set_source_to_* actions; switching source resets the variant set. source: "all" requires confirm: true . Only the sub-block matching source may be provided. add_variants / set_source_to_custom — variant attachment runs through the bulk-variant addition service asynchronously ( 202 + job_id ). bulk_toggle_inventory_policy — async; omit variant_ids to apply to all attached variants. set_collection — collection-sourced offers only; returns 409 Conflict otherwise. Use set_source_to_collection to switch a non-collection offer. set_source_to_collection — new variants added to the collection in Shopify are not auto-picked-up; call the action again to refresh. Per-variant settings sub-resource Variants attached to an offer carry per-offer overrides, addressed by Shopify variant ID : Method Path Description GET /preorders/offers/:id/products/variants List the attached variants with their per-variant settings, paginated ( page , per_page ). GET /preorders/offers/:id/products/variants/:variant_id Read the per-offer settings of one attached variant. PATCH /preorders/offers/:id/products/variants/:variant_id Set per-offer overrides on the variant — shipping_text , max_count , market_overrides . Copy PATCH /preorders/offers/:id/products/variants/:variant_id { "shipping_text": "Ships in August", "max_count": 50 } Sending null clears an override; the variant falls back to the offer-level value. market_overrides writes the per-market metafields ( market_shipping_text , market_preorder_max_count ) keyed by Shopify market GID. Advanced Line-item-property customisation, button-text overrides, custom CSS, and Shopify selling-plan-group attachment. Path prefix: /preorders/offers/:id/advanced Method Action Path Description GET read (root) Read power-user settings (line item properties, button-text overrides, Shopify selling plan attachment, custom CSS). PATCH update (root) Deep partial update of advanced settings. POST attach_to_shopify_selling_plan attach_to_shopify_selling_plan Re-attach the offer to a Shopify selling plan group (resume syncing). POST detach_from_shopify_selling_plan detach_from_shopify_selling_plan Detach the offer from Shopify selling plan groups; Stoq continues to manage it via the storefront integration. POST set_button_text_override set_button_text_override Override the widget button text for one state ( before_launch , after_launch , or out_of_stock ). POST set_custom_css set_custom_css Set custom CSS overrides for this offer's storefront widget. Send an empty string to clear. POST set_custom_line_item_property set_custom_line_item_property Set a free-text custom line item property added to every preorder cart line. Toggles and clears via PATCH .../advanced : Line-item-property toggles: line_item_properties.include_shipping_text , .include_payment_breakdown , .include_acknowledgement (true/false each). button_text_overrides is deep-partial per state — { "button_text_overrides": { "out_of_stock": null } } clears that one override (the read shape still emits all three states, with null for unset). custom_css is cleared with an empty string or null . Copy PATCH /preorders/offers/:id/advanced { "line_item_properties": { "include_payment_breakdown": false } } Warning detach_from_shopify_selling_plan is strongly discouraged for most merchants — it breaks Shopify-native integrations like checkout selling-plan display and third-party subscription apps. It does not delete the existing Shopify selling plan group; it just stops syncing. Translations Per-locale overrides for offer copy fields. No PATCH — translations are keyed writes, not a settings document. Path prefix: /preorders/offers/:id/translations Method Action Path Description GET read (root) Read every translation override, plus the full enumeration of translatable field paths. POST clear_locale clear_locale Remove all translation overrides for one locale. POST set set Set a single translation override (one locale, one field). POST set_many set_many Set many translation overrides for one locale, atomically — invalid paths abort the whole write. POST unset unset Remove a translation override (one locale, one field). Note The read endpoint is also the source of truth for the catalog of translatable field paths — clients shouldn't hardcode this list, they should fetch and use it. ### Product variants Path: `/v2/docs/api-v2/product-variants/` Per-Shopify-variant reads, metafield administration, and bulk recalculation. The product-variants resource is the variant-centric inverse of the offer's Products capability — instead of asking "which variants does this offer contain?" you ask "which offers is this variant in?", plus run administrative actions that compose with the variant's Shopify metafields. URLs use the Shopify variant ID , not Stoq's internal UUID. This is intentional — most callers already have the Shopify variant ID and don't carry Stoq IDs around. Path prefix: /preorders/product_variants Reads Method Path Description GET /preorders/product_variants List Shopify variants attached to any preorder offer in this shop. Paginated. GET /preorders/product_variants/:variant_id Read a Shopify variant's preorder context: every offer it's in, aggregate counts, and current metafield state. GET /preorders/product_variants/:variant_id/offers List the preorder offers a Shopify variant is attached to. Accepts a state filter. The list endpoint supports page / per_page pagination plus offer_id , product_id , and variant_ids filters. The response key is product_variants : Copy { "product_variants" : [ { "shopify_variant_id" : 123456 , "..." : "..." } ], "meta" : { "total_count" : 80 , "page" : 1 , "per_page" : 25 , "total_pages" : 4 } } The detail endpoint returns the same context the admin UI uses to render the variant detail page — current counts, metafield drift detection, etc. Note Per-offer variant settings (shipping-text and max-count overrides) live on the offer side: see the products/variants sub-resource . Single-variant administration These actions compose with Shopify metafield writes. They're the same operations the merchant admin offers behind the "Reset" / "Recalculate" buttons on the variant detail page. Method Path Description POST /preorders/product_variants/:variant_id/recalculate_preorder_count Recompute preorder_count from active order line items, then push to Shopify. POST /preorders/product_variants/:variant_id/reset_preorder_count Hard-reset preorder_count metafield + DB columns to zero. POST /preorders/product_variants/:variant_id/sync_metafields Force-resync every preorder metafield from local DB state to Shopify. Idempotent. POST /preorders/product_variants/:variant_id/reset_metafields Clear all preorder-related Shopify metafields on a variant. Doesn't detach from offers. POST /preorders/product_variants/:variant_id/detach_from_all_offers Remove a variant from every preorder offer it's attached to. Customer orders are unaffected. When to use which recalculate_preorder_count — use when count has drifted from reality. Recomputes from real orders. reset_preorder_count — destructive zero. Use only when you need to manually start counting from scratch. sync_metafields — use after a transient Shopify outage to push DB state back into Shopify metafields. reset_metafields — clears Shopify metafields but keeps the offer attachment. The next offer-settings change rewrites them from current state. detach_from_all_offers — destructive; equivalent to the offer's products/remove_variants against every offer the variant is in. Bulk administration The bulk variants of the above actions submit a background job and return 202 Accepted with a job_id / status_url / variant_count . Cap is 5000 variants per submission. Method Path Description POST /preorders/product_variants/bulk_recalculate_preorder_counts Recompute preorder_count for many variants in one job. POST /preorders/product_variants/bulk_reset_metafields Reset preorder metafields for many variants in one job. POST /preorders/product_variants/bulk_sync_metafields Force-resync preorder metafields for many variants in one job. Request body Copy { "variant_ids" : [ 123456 , 789012 , 345678 ] } variant_ids are Shopify variant IDs. Polling Copy GET /preorders/product_variants/jobs/:job_id Returns the standard async-job envelope. See Bulk & Async Jobs for the full polling pattern. ### Reports Path: `/v2/docs/api-v2/reports/` Preorder analytics — nine reports, each readable as JSON and exportable as a CSV via an async job. The reports resource exposes nine pre-built reports. Every report has: A JSON read for in-app dashboards or programmatic use. A CSV export that runs asynchronously and returns a signed S3 download URL. Reads run against read replicas, and most aggregations come from pre-aggregated rollups — large date windows return quickly. Path prefix: /preorders/reports Report catalog Copy GET /preorders/reports Returns a manifest of every available report — name, description, aliases, and URL — generated from the live registry: Copy { "reports" : [ { "name" : "revenue" , "description" : "Preorder revenue over a date window, bucketed by day/week/month." , "aliases" : [ "revenue report" , "preorder revenue" , "..." ], "url" : "/api/v2/external/preorders/reports/revenue" } ] } ( GET /preorders/reports/help returns the expanded shape with full query schemas.) Filters All reads accept a date window plus offer scoping: Param Type Notes from ISO 8601 date Start of window (inclusive) to ISO 8601 date End of window (inclusive) offer_id UUID Scope to a single offer offer_ids[] array of UUIDs Scope to several offers currency string Reporting currency Per-report extras (check /help for each report's exact schema): product_id , variant_id , market_id — on summary , revenue , by_date , and the product/variant breakdowns. granularity ( day / week / month ) — on time-series reports ( revenue , by_date ). metrics — on by_date , to restrict which metric columns are computed. include_deposits / include_balances — on revenue (both default true ; at least one must be true ). Restrict revenue to the deposit or balance portion of each line. page , per_page — on customers . by_variant requires offer_id (or offer_ids[] ) — variant-level data without an offer scope isn't supported. Reads Method Path Description GET /preorders/reports/summary High-level overview of preorder activity over a date window. GET /preorders/reports/revenue Preorder revenue, bucketed by day/week/month. GET /preorders/reports/by_offer Per-offer breakdown (orders, units, revenue, customers). GET /preorders/reports/by_variant Per-variant performance for a specific offer. GET /preorders/reports/by_product Per-product performance, rolled up across variants. GET /preorders/reports/by_date Time-series of every preorder metric at the chosen granularity. GET /preorders/reports/cancellations Preorder cancellation metrics with reason + per-offer breakdowns. GET /preorders/reports/balance_collection Outstanding preorder balances and balance-collection performance. GET /preorders/reports/customers Paginated list of customers with preorder activity in the window. Notes per report summary — the totals block includes the payment split: deposits_collected , balances_collected , balances_outstanding , alongside order/unit/revenue counts and a by_offer_top_5 list. revenue — echoes include_deposits / include_balances back in the response so you can tell which slice you're looking at. balance_collection — includes balances_collected and balances_failed collection-performance counters. cancellations — includes refunds_issued . The reason breakdown requires the cancel_reason field on the order record; cancellations from before that field was captured appear under "unknown" . by_variant — offer_id is required. Returns one row per attached variant. CSV exports Every read has a sibling */export action: Method Path POST /preorders/reports/summary/export POST /preorders/reports/revenue/export POST /preorders/reports/by_offer/export POST /preorders/reports/by_variant/export POST /preorders/reports/by_product/export POST /preorders/reports/by_date/export POST /preorders/reports/cancellations/export POST /preorders/reports/balance_collection/export POST /preorders/reports/customers/export Exports accept the same filter parameters as the corresponding read. They submit a job and return 202 Accepted : Copy { "job_id" : "exp_a8c3f01b9d2e4471" , "status_url" : "/api/v2/external/preorders/reports/exports/exp_a8c3f01b9d2e4471" } Polling Copy GET /preorders/reports/exports/:job_id status is one of running , ready , failed , or cancelled . When ready , the response includes the download URL: Copy { "job_id" : "exp_a8c3f01b9d2e4471" , "status" : "ready" , "download_url" : "https://...s3.amazonaws.com/..." , "file_name" : "preorders_revenue_2026-01-01_2026-05-28.csv" , "row_count" : 22 , "progress" : 100 } The pre-signed S3 URL is valid for 7 days . The job-status record itself expires ~24 hours after completion, so fetch the URL promptly (re-run the export if it's gone). Example: revenue by week Copy curl 'https://app.stoqapp.com/api/v2/external/preorders/reports/revenue?from=2026-01-01&to=2026-05-28&granularity=week' \ -H "X-Auth-Token: $STOQ_API_KEY " Copy curl -X POST 'https://app.stoqapp.com/api/v2/external/preorders/reports/revenue/export' \ -H "X-Auth-Token: $STOQ_API_KEY " \ -H "Content-Type: application/json" \ -d '{"from":"2026-01-01","to":"2026-05-28","granularity":"week"}' Copy # Poll until status: "ready" curl 'https://app.stoqapp.com/api/v2/external/preorders/reports/exports/exp_a8c3f01b9d2e4471' \ -H "X-Auth-Token: $STOQ_API_KEY " ### Orders Path: `/v2/docs/api-v2/orders/` Preorder order lifecycle, payments, fulfillment, tags, and bulk actions. The orders resource is the runtime counterpart to Offer . Where the offer resource configures what preorder behavior looks like, the orders resource operates on individual customer orders : releasing them, charging balances, refunding, tagging, cancelling. The resource is derived from OrderAttribution — Stoq's per-order preorder record. URLs use the Shopify order ID. Path prefix: /preorders/orders Order state Every order's state is derived , not stored — a StatusCompiler reads cancelled_at , financial_status , charged_at , fulfillment_status , and total_outstanding , and returns one of: State Meaning placed Order is in the preorder flow, no money collected yet awaiting_balance Deposit paid, balance still owed balance_collected Balance paid, awaiting fulfillment released Fulfillment holds lifted, ready to ship fulfilled Shopify reports the order as fulfilled cancelled cancelled_at is set refunded financial_status == 'refunded' State drives the state machine — invalid transitions return 409 Conflict with an unblock-hint message. Reads Method Path Description GET /preorders/orders List preorder orders in this shop, paginated. GET /preorders/orders/:id Read a preorder order's full representation. List filters Param Notes state Filter by derived state (post-fetch filter) offer_id Scope to a single offer variant_id Orders containing a specific Shopify variant customer_id Filter by Shopify customer ID from , to Created-at window (ISO 8601) page , per_page Pagination The response key is orders , plus a meta pagination block: Copy { "orders" : [ { "id" : 123456789 , "state" : "awaiting_balance" , "..." : "..." } ], "meta" : { "total_count" : 47 , "page" : 1 , "per_page" : 25 , "total_pages" : 2 } } Read shape The serializer returns a rich representation including: Top-level: id , shopify_order_id , state , created_at , customer , totals payments block: deposit , balance , total_outstanding , charged_at , refunds fulfillment block: status , holds , released_at tags block: current Shopify tags attributed_offers : every offer this order is attributed to line_items : per-line variant + offer details Lifecycle The four top-level lifecycle actions are intent-bearing wrappers around state changes. Method Path Description POST /preorders/orders/:id/release Release fulfillment holds and apply offer-configured order tags. POST /preorders/orders/:id/cancel Cancel a preorder. Optionally refund deposit / balance and notify the customer. POST /preorders/orders/:id/hold_fulfillments Apply fulfillment holds on the order's fulfillment orders. POST /preorders/orders/:id/split_fulfillment_orders Split fulfillment orders so preorder items ship separately from regular items. Release Body field Type Default Notes force boolean false Required to release before balance is collected — otherwise returns 409 tag_with array of strings none Extra tags applied alongside the offer-configured ones Cancel Body field Type Default Notes refund_deposit boolean false Issue a refund for the deposit portion refund_balance boolean false Issue a refund for the balance portion (only valid if collected) notify_customer boolean false Send the cancellation email reason enum none One of customer_request , merchant_decision , inventory_unavailable , other Note Cancel is Stoq-side — it sets cancelled_at on the OrderAttribution . It does not cancel the Shopify order. The merchant cancels the Shopify order separately if they want. Payments capability Path prefix: /preorders/orders/:id/payments Method Path Description GET (root) Read the deposit, remaining balance, and refund state of the order. POST send_collection_url Send the remaining-balance payment-collection URL to the customer. Idempotent — resends with a fresh URL. POST charge_balance Charge the remaining balance. See modes below. POST refund_deposit Refund the deposit portion of a preorder. POST refund_balance Refund the remaining-balance portion. Only valid after the balance was collected. POST refund Refund the entire preorder (deposit + balance). The refund actions accept optional notify_customer (boolean) and note (string) body fields. charge_balance modes Body field Type Default Notes mode enum: auto / manual auto auto charges the customer's saved payment method — it may fail if there's no saved method or the charge is declined. manual just records balance collection without charging — for off-platform payments. Note The body field is mode , not method — method collides with Ruby's Object#method in the interactor's context object. Fulfillment capability Path prefix: /preorders/orders/:id/fulfillment Method Path Description GET (root) Read the fulfillment hold state and fulfillment-order summary. PATCH (root) Update fulfillment attributes. Only hold_reason_note is writable. POST apply_holds Apply fulfillment holds on the order's fulfillment orders. Idempotent. POST release_holds Release fulfillment holds without applying tags. Use top-level release if you want tags applied too. POST set_hold_reason Set the hold-reason note ( reason_note ) used next time the order is held. Note fulfillment/release_holds is the diagnostics-friendly counterpart to top-level release — same fulfillment effect, no tag side effects. On the PATCH , all_held , any_held , and fulfillment_orders are read-only — hold state changes go through apply_holds / release_holds . Setting hold_reason_note (via PATCH or set_hold_reason ) updates the note on the first attributed preorder offer; existing holds already in Shopify aren't re-written. Tags capability Path prefix: /preorders/orders/:id/tags Method Path Description GET (root) Read the order tags applied by Stoq. PATCH (root) Replace the order's tag set (PATCH form of set ). applied_by_stoq and applied_by_merchant are read-only. POST add Add one or more tags to the Shopify order. POST remove Remove one or more tags from the Shopify order. POST set Replace every tag on the Shopify order with the supplied list. Body Copy { "tags" : [ "preorder" , "vip" ] } add and remove return 422 on an empty list. set (and the PATCH ) is a true replace : pass an empty list to clear all tags. It's implemented as fetch-current + remove stale + add new on the Shopify side. Bulk actions Bulk endpoints fan out one job-call per order. They submit a job and return 202 Accepted with a job_id / status_url / order_count . Cap is 1000 orders per submission. Method Path Notes POST /preorders/orders/bulk_release Forwards force and tag_with . POST /preorders/orders/bulk_cancel Forwards refund_deposit , refund_balance , notify_customer , reason . POST /preorders/orders/bulk_send_payment_collection_url No extra params. POST /preorders/orders/bulk_set_tags mode is one of add / remove / replace (default add ). replace overwrites each order's full tag set. Per-order error handling Per-order errors are swallowed inside the job — one bad order doesn't poison the batch. The job still completes successfully. Errors are logged and visible in the audit log / Stoq Admin. Polling Copy GET /preorders/orders/jobs/:job_id Returns the standard async-job envelope. See Bulk & Async Jobs . ### Overview Path: `/v2/docs/api-v2/back-in-stock/` The back-in-stock surface: signups (the waitlist), the notification send log, shop settings, and reports. The back-in-stock API covers everything around restock alerts: who is waiting, what was sent, how sends behave, and how it all performed. Path prefix: /back_in_stock Vocabulary Term Meaning Signup One customer waiting on one variant over one channel — a waitlist entry. v1 called this an intent . Notification One delivery attempt of a back-in-stock alert (including sends blocked by plan limits). Settings The shop-level configuration that governs sends — a singleton, not a list. The four resources Resource Path What it covers Signups /back_in_stock/signups The waitlist: list/read/create/delete, notify now, bulk operations, transfer between variants Notifications /back_in_stock/notifications Read-only send log — one entry per delivery attempt Settings /back_in_stock/settings Shop-level singleton with five capabilities: channels, delivery, compliance, tagging, alerts Reports /back_in_stock/reports Five reports, each readable as JSON and exportable as CSV Authentication Identical to the rest of v2 — the X-Auth-Token header carrying the shop's API key. See Getting Started . Copy curl 'https://app.stoqapp.com/api/v2/external/help?prefix=/back_in_stock' \ -H "X-Auth-Token: $STOQ_API_KEY " Granularity convention The back-in-stock surface follows the rule learned from the preorders prune: named actions exist only where there are side effects beyond writing settings — sending notifications ( notify , bulk_notify ), destroying rows ( bulk_delete ), moving signups between variants ( transfer ), creating customers ( create ). Plain settings — including every boolean toggle — are PATCH on the owning capability. There are no enable_sms or set_batch_size named actions; you send PATCH /back_in_stock/settings/channels with { "sms": { "enabled": true } } . v1 → v2 parity v1 endpoint v2 GET /api/v1/external/intents GET /back_in_stock/signups GET /api/v1/external/intents/:id GET /back_in_stock/signups/:id POST /api/v1/external/intents POST /back_in_stock/signups POST …/notify_intent POST /back_in_stock/signups/:id/notify POST …/bulk_notify_intent POST /back_in_stock/signups/bulk_notify DELETE …/bulk_destroy POST /back_in_stock/signups/bulk_delete POST …/transfer_signups POST /back_in_stock/signups/transfer GET …/products_in_demand GET /back_in_stock/reports/products_in_demand Note v1 stays generally available through the deprecation window — nothing breaks the day you start using v2. Migrate at your own pace; the parity table above maps every v1 call to its v2 home. MCP Every back-in-stock action is also exposed as an MCP tool, generated from the same registry that powers /help — same names, same schemas, same behavior. See MCP . ### Signups Path: `/v2/docs/api-v2/back-in-stock-signups/` The waitlist resource: CRUD, notify-now, bulk operations, variant transfers, and the read-only notification send log. A signup is one customer waiting on one variant over one channel. Internally these are Intent rows; v1 exposed them as "intents". Path prefix: /back_in_stock/signups The status field Every signup carries a compiled lifecycle status , derived from its timestamps: Status Meaning pending Waiting — no notification sent yet notified The back-in-stock alert was sent unsubscribed The contact opted out blocked A send was attempted but stopped (e.g. plan limits) Precedence when several timestamps are set: unsubscribed wins (the contact opted out), then notified , then blocked ; a row with none of the three is pending . Note The status filter on list accepts pending , notified , and unsubscribed only — blocked appears in response payloads but is not a filterable value. To find blocked sends, filter the notification log with status=blocked . List signups Copy GET /back_in_stock/signups Param Type Notes channel email / sms / push status pending / notified / unsubscribed variant_id integer Shopify variant id product_id integer Shopify product id email string Partial match on the signup's or its customer's email phone string Partial match on the signup's or its customer's phone from , to ISO 8601 Window on created_at page , per_page integer per_page default 50, max 200 Aliases: "waitlist", "show the waitlist", "notify me list", "who's waiting", "customers waiting for restock" Response: { "signups": [...], "meta": { "total_count", "page", "per_page", "total_pages" } } . Read a signup Copy GET /back_in_stock/signups/:id Copy { "id" : "uuid" , "status" : "pending" , "channel" : "email" , "contact" : "jane@example.com" , "quantity" : 1 , "variant" : { "shopify_variant_id" : 123 , "shopify_product_id" : 456 , "title" : "Small / Blue" }, "customer" : { "id" : "uuid" , "email" : "jane@example.com" , "name" : "Jane" }, "optin" : { "required" : false , "confirmed" : null }, "notifications_sent" : 0 , "last_notified_at" : null , "created_at" : "2026-06-01T12:00:00Z" } optin.confirmed is only meaningful when the shop requires double opt-in (see Compliance settings ); it is null otherwise. Create a signup Copy POST /back_in_stock/signups Admin-side waitlist creation — the storefront widget keeps its own path. Finds or creates the customer, then creates the signup. Body field Required Notes channel yes email or sms shopify_variant_id yes email for the email channel phone for the sms channel shopify_product_id no Resolved from the variant when omitted quantity no integer, default 1 name no Customer name Returns: 201 Created with the full signup representation. Notes: returns 409 Conflict when a pending signup already exists for the same contact + variant + channel — pending signups are unique per contact/variant/channel. Delete a signup Copy DELETE /back_in_stock/signups/:id Aliases: "remove from waitlist", "cancel restock alert", "take customer off the notify me list" Notes: pending signups only — deleting an already-notified signup returns 409 Conflict (history is immutable). Returns: { "deleted": true, "signup_id": "..." } . Notify now Copy POST /back_in_stock/signups/:id/notify Sends the back-in-stock notification for one signup immediately, regardless of stock state . Delivery runs through the normal notification pipeline. Body field Type Default Notes allow_resend boolean false Required to re-notify an already-notified signup Aliases: "send the restock email", "notify this customer", "resend restock notification" Notes: returns 409 Conflict when the signup was already notified and allow_resend is not set. Returns: 202 Accepted with { "signup_id": "...", "queued": true } . Bulk notify Copy POST /back_in_stock/signups/bulk_notify Body field Required Notes signup_ids yes Array of signup ids, max 1000 allow_resend no Same gate as single notify Aliases: "notify the waitlist", "notify everyone waiting", "send restock emails to the whole waitlist" Responds 200 OK when every send queues, or 207 Multi-Status on partial failure — the body is the same shape either way, so always check failed : Copy { "success" : [ "id-1" , "id-2" ], "failed" : [ { "signup_id" : "id-3" , "error" : "Signup was already notified. Pass allow_resend to send again." } ] } Already-notified signups land in failed unless allow_resend is set. A 207 is not an error envelope — the successes in success really did queue. Bulk delete Copy POST /back_in_stock/signups/bulk_delete Body field Required Notes signup_ids yes Array of signup ids, max 1000 Aliases: "clear the waitlist", "purge pending restock signups" Notes: pending-only, all-or-nothing — if any id in the batch was already notified, the whole request fails with 422 listing the offending ids, and nothing is deleted. Returns: { "deleted_count": N } . Transfer Copy POST /back_in_stock/signups/transfer Moves signups from one variant to another — for variant merges or replacements. Body field Required Notes from_shopify_variant_id yes Must differ from the target to_shopify_variant_id yes to_shopify_product_id yes from_shopify_product_id no Resolved from the source variant when omitted scope no pending (default) / notified / all — which signups move Aliases: "move the waitlist", "merge waitlists", "shift restock signups to a new variant" Notes: signups that would duplicate a pending signup on the target variant are dropped rather than moved. Returns: 202 Accepted with { "success": true, "scope": "pending", "from_shopify_variant_id": ..., "to_shopify_variant_id": ... } . Notifications (the send log) A separate, read-only resource: one entry per delivery attempt, including sends blocked by plan limits. Use it to answer "who actually got notified, and what didn't go out". List notifications Copy GET /back_in_stock/notifications Param Type Notes channel email / sms / push status sent / blocked variant_id integer Shopify variant id product_id integer Shopify product id from , to ISO 8601 page , per_page integer per_page default 50, max 200 Aliases: "back in stock send log", "who got notified", "notification history" Notes: status=blocked surfaces sends stopped by plan limits (with blocked_reason ). Response: { "notifications": [...], "meta": {...} } . Read a notification Copy GET /back_in_stock/notifications/:id Copy { "id" : "uuid" , "status" : "sent" , "blocked_reason" : null , "channel" : "email" , "contact" : "jane@example.com" , "shopify_variant_id" : 123 , "shopify_product_id" : 456 , "signup_id" : "uuid" , "sent_at" : "2026-06-01T12:00:00Z" } signup_id links back to the signup the notification was sent for. For aggregate send counts over time, use the notifications report instead. ### Settings Path: `/v2/docs/api-v2/back-in-stock-settings/` The shop-level settings singleton: five capabilities, GET + PATCH only, deep-partial updates. Back-in-stock settings are a shop-level singleton — there is no :id in any path. The surface is split into five capabilities, and everything is GET + PATCH; there are no named actions (no setting here has side effects beyond writing configuration, so the granularity convention means no enable_x / set_y endpoints). Path prefix: /back_in_stock/settings Endpoints Method Path Capability GET /back_in_stock/settings All five blocks in one response PATCH /back_in_stock/settings Any combination of capability blobs GET / PATCH /back_in_stock/settings/channels Which channels send (email, SMS, push) GET / PATCH /back_in_stock/settings/delivery Batching, stock threshold, locations, any-variant GET / PATCH /back_in_stock/settings/compliance GDPR double opt-in GET / PATCH /back_in_stock/settings/tagging Order tag on recovered orders GET / PATCH /back_in_stock/settings/alerts Merchant alerts + scheduled report emails PATCH semantics Every PATCH is deep partial — send only the keys you want to change, nested however deep. The top-level PATCH /back_in_stock/settings accepts any combination of capability blobs and delegates each to the capability's own update; validation errors from all blobs are accumulated into one response. Copy curl -X PATCH 'https://app.stoqapp.com/api/v2/external/back_in_stock/settings' \ -H "X-Auth-Token: $STOQ_API_KEY " \ -H "Content-Type: application/json" \ -d '{"channels":{"sms":{"enabled":true}},"compliance":{"optin_required":true}}' Each capability PATCH returns the capability's full block after the write; the top-level PATCH returns all five. Channels Which notification channels are enabled. Copy { "email" : { "enabled" : true }, "sms" : { "enabled" : false }, "push" : { "enabled" : true } } PATCH — "enable SMS": Copy { "sms" : { "enabled" : true } } Warning Enabling SMS for the first time triggers a restricted-content verification check — sends may be held until the shop is verified. Delivery How and when notifications go out. Four sub-concerns: Copy { "batching" : { "enabled" : false , "mode" : "multiplier" , "multiplier" : 3 , "batch_size" : 100 , "wait_hours" : 24 }, "stock_threshold" : 1 , "locations" : { "filter_enabled" : false , "location_ids" : [] }, "any_variant" : { "enabled" : false , "product_ids" : [] } } Field Notes batching.enabled Send in batches instead of all at once batching.mode multiplier — batch size = restocked quantity × multiplier ; fixed — batch size = batch_size batching.wait_hours Wait between batches stock_threshold Minimum restocked quantity before notifications send at all (≥ 1) locations.filter_enabled Only count inventory at the listed locations locations.location_ids Shopify numeric location ids — replaced wholesale when present any_variant.enabled Notify signups when any variant of the product restocks any_variant.product_ids Products the any-variant behavior applies to — replaced wholesale when present PATCH — "batch notifications" (fixed batches of 100, 24h apart): Copy { "batching" : { "enabled" : true , "mode" : "fixed" , "batch_size" : 100 , "wait_hours" : 24 } } Compliance GDPR double opt-in. When enabled, signups must confirm via email before they are eligible for notifications. Copy { "optin_required" : false } PATCH — "require double opt-in": Copy { "optin_required" : true } The per-signup confirmation state surfaces on the signup's optin block . Tagging The tag applied to Shopify orders attributed to a back-in-stock notification. Copy { "order_tags" : { "enabled" : false , "tag" : "STOQ-back-in-stock" } } PATCH — "tag recovered orders": Copy { "order_tags" : { "enabled" : true , "tag" : "restock-recovered" } } tag is a single tag (default STOQ-back-in-stock ). Alerts Merchant-facing alerting and scheduled report emails. Two sub-concerns: Copy { "signups_threshold" : { "count" : 10 , "notify" : true }, "scheduled_reports" : { "daily" : false , "weekly" : true , "monthly" : false } } Field Notes signups_threshold.count Per-variant pending-signup count that triggers the merchant alert email signups_threshold.notify Whether the threshold alert sends at all scheduled_reports.daily / weekly / monthly Summary report emails on each cadence PATCH — weekly report on, threshold alert at 25 signups: Copy { "signups_threshold" : { "count" : 25 , "notify" : true }, "scheduled_reports" : { "weekly" : true } } Full settings shape GET /back_in_stock/settings returns all five blocks under their capability keys: Copy { "channels" : { "email" : { "enabled" : true }, "sms" : { "enabled" : false }, "push" : { "enabled" : true } }, "delivery" : { "batching" : { "enabled" : false , "mode" : "multiplier" , "multiplier" : 3 , "batch_size" : 100 , "wait_hours" : 24 }, "stock_threshold" : 1 , "locations" : { "filter_enabled" : false , "location_ids" : [] }, "any_variant" : { "enabled" : false , "product_ids" : [] } }, "compliance" : { "optin_required" : false }, "tagging" : { "order_tags" : { "enabled" : false , "tag" : "STOQ-back-in-stock" } }, "alerts" : { "signups_threshold" : { "count" : 10 , "notify" : true }, "scheduled_reports" : { "daily" : false , "weekly" : false , "monthly" : false } } } ### Reports Path: `/v2/docs/api-v2/back-in-stock-reports/` Five back-in-stock reports, each readable as JSON and exportable as a CSV via an async job. The back-in-stock reports resource mirrors the preorder reports machinery: every report has a JSON read and a sibling async CSV export . Reads run against read replicas. Path prefix: /back_in_stock/reports Report manifest Copy GET /back_in_stock/reports Returns the catalog of available reports — { "reports": [{ "name", "description", "aliases", "url" }] } — generated from the live registry so it can't drift from the actual report actions. Shared filters All reads accept the same filter set: Param Type Notes from , to ISO 8601 date Window — defaults to the last 30 days variant_id integer Single Shopify variant id product_id integer Single Shopify product id channel email / sms / push granularity day / week / month Time-series reports only; default day Every response echoes the resolved window at the top: "window": { "from": "...", "to": "..." } . Reads Method Path Description GET /back_in_stock/reports/summary High-level overview of back-in-stock activity over a date window. GET /back_in_stock/reports/signups Time-series of signups created (day/week/month). GET /back_in_stock/reports/notifications Time-series of notifications sent, split by channel. GET /back_in_stock/reports/conversions Time-series of orders attributed to alerts and the revenue recovered. GET /back_in_stock/reports/products_in_demand Ranked variants by waitlist demand. Summary Totals for the window — waitlist size, sends, and what they recovered: Copy { "window" : { "from" : "..." , "to" : "..." }, "currency" : "USD" , "totals" : { "signups" : 420 , "pending" : 180 , "notified" : 240 , "conversions" : 96 , "conversion_rate" : 0.4 , "recovered_revenue" : "4812.50" } } conversion_rate is the share of notified signups that converted to an attributed order. Conversions are order attributions at medium/high confidence. Signups Series of signups created per bucket, with the pending/notified split: Copy { "window" : { "..." : "..." }, "granularity" : "day" , "series" : [ { "date" : "2026-06-01" , "signups" : 24 , "pending" : 10 , "notified" : 14 } ] } Notifications Series of sends per bucket, split by channel, with blocked counts: Copy { "window" : { "..." : "..." }, "granularity" : "day" , "series" : [ { "date" : "2026-06-01" , "sent" : 120 , "blocked" : 4 , "email" : 100 , "sms" : 15 , "push" : 5 } ] } Note This is the aggregate view. For the row-level send log — who got each notification, and why a blocked send was stopped — use the read-only notifications resource at GET /back_in_stock/notifications . Conversions Series of attributed orders and recovered revenue per bucket: Copy { "window" : { "..." : "..." }, "granularity" : "day" , "currency" : "USD" , "series" : [ { "date" : "2026-06-01" , "orders" : 8 , "recovered_revenue" : "401.00" } ] } Products in demand Ranked variants by demand — the v2 replacement for v1 products_in_demand , and the answer to "what should I restock first?". Param Notes sort_by pending (default) / total / last_requested_at direction desc (default) / asc page , per_page per_page default 50, max 500 Copy { "window" : { "..." : "..." }, "sort_by" : "pending" , "direction" : "desc" , "rows" : [ { "shopify_variant_id" : 123 , "shopify_product_id" : 456 , "variant_title" : "Small / Blue" , "product_title" : "Classic Tee" , "pending" : 42 , "total" : 65 , "last_requested_at" : "2026-06-09T08:30:00Z" } ], "meta" : { "total_count" : 1 , "page" : 1 , "per_page" : 50 , "total_pages" : 1 } } CSV exports Every read has a sibling */export action: Method Path POST /back_in_stock/reports/summary/export POST /back_in_stock/reports/signups/export POST /back_in_stock/reports/notifications/export POST /back_in_stock/reports/conversions/export POST /back_in_stock/reports/products_in_demand/export Exports accept the same filters as the corresponding read (plus sort_by , direction , page , per_page where the read supports them). They submit a job and return 202 Accepted : Copy { "job_id" : "exp_1a2b3c4d5e6f7a8b" , "status_url" : "/api/v2/external/back_in_stock/reports/exports/exp_1a2b3c4d5e6f7a8b" } Polling Copy GET /back_in_stock/reports/exports/:job_id status is one of running , ready , failed , cancelled . When ready , the response includes the download URL: Copy { "job_id" : "exp_1a2b3c4d5e6f7a8b" , "status" : "ready" , "download_url" : "https://stoq-exports.s3.amazonaws.com/..." , "file_name" : "back_in_stock_signups_2026-06-10.csv" , "row_count" : 420 , "progress" : 100 } An unknown job_id returns 404 . The polling pattern is the same one used across v2 — see Bulk & Async Jobs . Example: signups by week, then export Copy curl 'https://app.stoqapp.com/api/v2/external/back_in_stock/reports/signups?from=2026-03-01&to=2026-06-01&granularity=week' \ -H "X-Auth-Token: $STOQ_API_KEY " Copy curl -X POST 'https://app.stoqapp.com/api/v2/external/back_in_stock/reports/signups/export' \ -H "X-Auth-Token: $STOQ_API_KEY " \ -H "Content-Type: application/json" \ -d '{"from":"2026-03-01","to":"2026-06-01","granularity":"week"}' Copy # Poll until status: "ready" curl 'https://app.stoqapp.com/api/v2/external/back_in_stock/reports/exports/exp_1a2b3c4d5e6f7a8b' \ -H "X-Auth-Token: $STOQ_API_KEY " ## API v2 Reference ### STOQ API v2 (Beta) Path: `/v2-reference/` Version: 2.0.0-beta STOQ's action-driven, AI-native external API. Every action is self-describing (`description`, `aliases`, `notes`, JSON request schema) and also reachable via the `/help` manifest, `skill.md`, `llms.txt`, and the MCP server. **Public beta** — actions and schemas may change before GA. All endpoints require the `X-Auth-Token` API key. #### Operations ##### GET /back_in_stock/notifications Summary: List List back-in-stock notifications (the send log) in this shop, paginated. **Note:** status=blocked surfaces sends stopped by plan limits (with blocked_reason). Parameters: - `channel` (query, unknown, optional) - `status` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/notifications/{id} Summary: Read Read one back-in-stock notification (a single send-log entry). Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/reports Summary: List List every available back-in-stock report (name, description, aliases, url). Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/reports/conversions Summary: Conversions Time-series of orders attributed to back-in-stock alerts and the revenue recovered. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `channel` (query, unknown, optional) - `granularity` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/reports/conversions/export Summary: Conversions Export Async CSV export of the back-in-stock conversions time series. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/reports/exports/{job_id} Summary: Exports Check the status of an async report export and get the download URL when ready. Parameters: - `job_id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/reports/notifications Summary: Notifications Time-series of back-in-stock notifications sent, split by channel (email/sms/push). Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `channel` (query, unknown, optional) - `granularity` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/reports/notifications/export Summary: Notifications Export Async CSV export of the back-in-stock notifications time series. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/reports/products_in_demand Summary: Products In Demand Ranked variants by back-in-stock demand (pending/total signups, last requested). Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `channel` (query, unknown, optional) - `sort_by` (query, unknown, optional) - `direction` (query, unknown, optional) - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) Responses: - 200: Success — example response shown. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/reports/products_in_demand/export Summary: Products In Demand Export Async CSV export of the back-in-stock products-in-demand report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/reports/signups Summary: Signups Time-series of back-in-stock signups created (day/week/month). Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `channel` (query, unknown, optional) - `granularity` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/reports/signups/export Summary: Signups Export Async CSV export of the back-in-stock signups time series. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/reports/summary Summary: Summary High-level overview of back-in-stock activity over a date window. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `channel` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/reports/summary/export Summary: Summary Export Async CSV export of the back-in-stock summary report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/settings Summary: Read Read all back-in-stock settings for the shop. Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /back_in_stock/settings Summary: Update Update back-in-stock settings for the shop. **Note:** Accepts any combination of capability blobs: channels, delivery, compliance, tagging, alerts — deep partial, delegated to the capability PATCHes, errors accumulated. Boolean toggles are set via this PATCH (or the capability PATCH): channels.email.enabled, channels.sms.enabled, channels.push.enabled, delivery.batching.enabled, delivery.locations.filter_enabled, delivery.any_variant.enabled, compliance.optin_required, tagging.order_tags.enabled, alerts.signups_threshold.notify, alerts.scheduled_reports.daily, alerts.scheduled_reports.weekly, alerts.scheduled_reports.monthly — e.g. { channels: { sms: { enabled: true } } }. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/settings/alerts Summary: Alerts Read back-in-stock merchant alert and scheduled report settings. Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /back_in_stock/settings/alerts Summary: Alerts Update back-in-stock merchant alert and scheduled report settings. **Note:** Toggles set via this PATCH: signups_threshold.notify, scheduled_reports.daily, scheduled_reports.weekly, scheduled_reports.monthly — e.g. { scheduled_reports: { weekly: true } }. signups_threshold.count is the per-variant signup count that triggers the merchant alert email. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/settings/channels Summary: Channels Read back-in-stock notification channel settings. Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /back_in_stock/settings/channels Summary: Channels Update back-in-stock notification channel toggles. **Note:** Channel toggles are set via this PATCH: email.enabled, sms.enabled, push.enabled — e.g. { sms: { enabled: true } }. Enabling SMS for the first time triggers a restricted-content verification check; sends may be held until the shop is verified. Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/settings/compliance Summary: Compliance Read back-in-stock compliance settings (double opt-in). Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /back_in_stock/settings/compliance Summary: Compliance Update back-in-stock compliance settings (double opt-in). **Note:** The double-opt-in toggle is set via this PATCH: optin_required — e.g. { optin_required: true }. When enabled, signups must confirm via email before they are eligible for notifications (GDPR double opt-in). Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/settings/delivery Summary: Delivery Read back-in-stock delivery settings (batching, stock threshold, locations, any-variant). Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /back_in_stock/settings/delivery Summary: Delivery Update back-in-stock delivery settings (batching, stock threshold, locations, any-variant). **Note:** Deep partial. Toggles set via this PATCH: batching.enabled, locations.filter_enabled, any_variant.enabled — e.g. { batching: { enabled: true } }. batching.mode is an enum: 'multiplier' (batch size = restocked quantity x batching.multiplier) or 'fixed' (batch size = batching.batch_size); batching.wait_hours is the wait between batches. stock_threshold is the minimum restocked quantity before notifications send (>= 1). locations.location_ids and any_variant.product_ids are arrays of Shopify numeric ids and are replaced wholesale when present. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/settings/tagging Summary: Tagging Read back-in-stock order tagging settings. Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /back_in_stock/settings/tagging Summary: Tagging Update back-in-stock order tagging settings. **Note:** The toggle is set via this PATCH: order_tags.enabled — e.g. { order_tags: { enabled: true } }. order_tags.tag is the single tag applied to Shopify orders attributed to a back-in-stock notification (default 'STOQ-back-in-stock'). Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/signups Summary: List List back-in-stock signups (the waitlist) in this shop, paginated. **Note:** status compiles notified_at / unsubscribed_at / blocked_at into one lifecycle value; email and phone filters match partial values on the signup or its customer. Parameters: - `channel` (query, unknown, optional) - `status` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `email` (query, unknown, optional) - `phone` (query, unknown, optional) - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) Responses: - 200: Success — example response shown. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/signups Summary: Create Create a back-in-stock signup (add a customer to the waitlist for a variant). **Note:** email is required for the email channel, phone for sms. shopify_product_id is resolved from the variant when omitted. Returns 409 when a pending signup already exists for the same contact + variant + channel. Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /back_in_stock/signups/{id} Summary: Read Read one back-in-stock signup's full representation. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### DELETE /back_in_stock/signups/{id} Summary: Delete Delete a pending back-in-stock signup (remove the customer from the waitlist). **Note:** Pending signups only; deleting an already-notified signup returns 409. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/signups/{id}/notify Summary: Notify Send the back-in-stock notification for one signup now, regardless of stock state. **Note:** 409 when the signup was already notified and allow_resend is not set. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/signups/bulk_delete Summary: Bulk Delete Delete up to 1000 pending back-in-stock signups in one request. **Note:** Pending-only: 422 listing the offending ids when any signup was already notified. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/signups/bulk_notify Summary: Bulk Notify Send back-in-stock notifications for up to 1000 signups in one request. **Note:** Responds 200 when all succeed, 207 with { success, failed } on partial failure. Already-notified signups fail unless allow_resend is set. Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /back_in_stock/signups/transfer Summary: Transfer Move back-in-stock signups from one variant to another (variant merges or replacements). **Note:** scope picks which signups move: pending (default), notified, or all. Signups that would duplicate a pending signup on the target variant are dropped. from_shopify_product_id is resolved from the source variant when omitted. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers Summary: List List preorder offers in this shop, paginated. Parameters: - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) Responses: - 200: Success — example response shown. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers Summary: Create Create a new preorder offer. Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id} Summary: Read Read a preorder offer's full representation. Parameters: - `id` (path, string, required) Responses: - 200: Success — example response shown. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id} Summary: Update Update top-level fields of a preorder offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/advanced Summary: Advanced Read power-user settings for a preorder offer (line item properties, button text overrides, Shopify selling plan attachment). Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/advanced Summary: Advanced Update advanced (power-user) settings on a preorder offer. **Note:** Deep partial. Toggles and clears are done via this PATCH: the line-item-property toggles are `line_item_properties.include_shipping_text`, `.include_payment_breakdown`, and `.include_acknowledgement` (true/false each). `button_text_overrides` is deep-partial per state — send null to clear an override for one state, e.g. `{ "button_text_overrides": { "out_of_stock": null } }` removes that override (the read shape still emits all three states, with null for unset). `custom_css` is cleared by sending an empty string or null. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/advanced/attach_to_shopify_selling_plan Summary: Advanced Attach To Shopify Selling Plan Re-attach the offer to a Shopify selling plan group (resume syncing). Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/advanced/detach_from_shopify_selling_plan Summary: Advanced Detach From Shopify Selling Plan Detach the offer from Shopify selling plan groups; Stoq continues to manage it via the storefront integration. **Note:** Strongly discouraged for most merchants. Breaks Shopify-native integrations like checkout selling plan display and third-party subscription apps. Does not delete the existing Shopify selling plan group — it just stops syncing. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/advanced/set_button_text_override Summary: Advanced Set Button Text Override Override the widget button text for one state (before_launch, after_launch, or out_of_stock). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/advanced/set_custom_css Summary: Advanced Set Custom Css Set custom CSS overrides for this offer's storefront widget. Send an empty string to clear. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/advanced/set_custom_line_item_property Summary: Advanced Set Custom Line Item Property Set a free-text custom line item property added to every preorder cart line. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/checkout Summary: Checkout Read checkout settings for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/checkout Summary: Checkout Update checkout settings on a preorder offer. **Note:** Boolean toggles are set via this PATCH: terms.enabled, terms.disable_button_until_acknowledged, terms.include_in_line_item_properties, mixed_cart.allowed — this PATCH is how you turn these on/off, e.g. { terms: { enabled: true } } or { mixed_cart: { allowed: false } }. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/checkout/add_order_tag Summary: Checkout Add Order Tag Add a tag to the set applied to orders containing this offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/checkout/remove_order_tag Summary: Checkout Remove Order Tag Remove a tag from the set applied to orders containing this offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/checkout/set_mixed_cart_error_message Summary: Checkout Set Mixed Cart Error Message Set the error message shown when a blocked mixed cart reaches checkout. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/checkout/set_order_tags Summary: Checkout Set Order Tags Replace the set of tags applied to orders containing this offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/checkout/set_terms_text Summary: Checkout Set Terms Text Set the terms-acceptance text shown next to the checkbox. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/countdown Summary: Countdown Read the countdown timer configuration for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/countdown Summary: Countdown Update countdown timer settings on a preorder offer. **Note:** Deep partial. `countdown.enabled` is toggled via this PATCH (`enabled` true/false); `text` is an alias for `ends_text` — both write the same column, and an explicit `ends_text` wins when both are sent. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/countdown/set_custom_end_date Summary: Countdown Set Custom End Date Set the custom end date for the countdown timer. **Note:** Has no visible effect unless mode is `to_custom_date`. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/countdown/set_ends_text Summary: Countdown Set Ends Text Set the text shown next to the countdown timer when the offer is closing. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/countdown/set_mode Summary: Countdown Set Mode Set whether the countdown timer follows the offer's schedule or a custom date. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/countdown/set_starts_text Summary: Countdown Set Starts Text Set the text shown before the countdown timer when the offer hasn't started yet. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/countdown/set_style Summary: Countdown Set Style Set the countdown timer's visual style (type, colors, border radius). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/countdown/set_unit_labels Summary: Countdown Set Unit Labels Set the unit labels for the countdown timer (Days/Hours/Mins/Secs). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/disable Summary: Disable Disable (pause) a preorder offer. Optionally flip attached variants' inventory policy back to DENY. **Note:** Variants are left at their existing inventory policy unless `update_inventory_policy: true` is passed. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/discard Summary: Discard Discard (soft-delete) a preorder offer. **Note:** Recoverable via restore indefinitely. Discarded offers are never hard-deleted. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/enable Summary: Enable Enable a preorder offer. Optionally flip attached variants' inventory policy to CONTINUE. **Note:** Variants are left at their existing inventory policy unless `update_inventory_policy: true` is passed. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/integrations Summary: Integrations Read integration settings for a preorder offer (POS, B2B). Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/integrations Summary: Integrations Update integration settings on a preorder offer. **Note:** Boolean toggles are set via this PATCH: pos.enabled, pos.skip_inventory_check, b2b.enabled — e.g. { pos: { enabled: false } } or { b2b: { enabled: true } }. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/inventory Summary: Inventory Read the inventory configuration for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/inventory Summary: Inventory Update inventory settings on a preorder offer. **Note:** Deep partial. Setting provider to 'shopify' will force product_variants_source to 'custom' via the model callback. Continue-selling is toggled via this PATCH: `continue_selling.enabled` and `continue_selling.auto_apply_to_new_variants` BOTH map to the single underlying `inventory_policy_auto_enable` flag — the auto-flip-on-attach intent (newly-attached variants get their Shopify inventory_policy flipped to CONTINUE). When both fields are present, auto_apply_to_new_variants wins. Neither field flips EXISTING variants — to flip variants that are already attached, use POST .../products/bulk_toggle_inventory_policy, or pass the update_inventory_policy flag to the offer lifecycle enable/disable actions. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/inventory/set_provider Summary: Inventory Set Provider Set the inventory provider for a preorder offer (stoq or shopify). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/inventory/set_reservation_timing Summary: Inventory Set Reservation Timing Set when inventory is reserved (at sale or at fulfillment). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/limits Summary: Limits Read order quantity limits for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/limits Summary: Limits Update quantity limits on a preorder offer. **Note:** Deep partial. Toggles and clears are done via this PATCH: clear a limit by sending null — `per_customer.max: null` removes the per-customer limit, `total.max: null` removes the total cap; `display.show_remaining` (true/false) toggles the remaining-units display on the storefront. Note: per_customer.max and total.max are stored config — storefront enforcement of these limits is follow-up work and not live yet. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/limits/set_max_per_customer Summary: Limits Set Max Per Customer Set the maximum quantity a single customer can preorder across all their orders. **Note:** Stored config only today — storefront enforcement of the per-customer limit is follow-up work and not live yet. Clear the limit via PATCH .../limits with `per_customer.max: null`. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/limits/set_max_per_order Summary: Limits Set Max Per Order Set the maximum quantity a customer can preorder per order. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/limits/set_min_per_order Summary: Limits Set Min Per Order Set the minimum quantity a customer must preorder per order. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/limits/set_total_max Summary: Limits Set Total Max Set the total quantity that can be preordered across all customers. **Note:** Stored config only today — storefront enforcement of the total cap is follow-up work and not live yet. Clear the cap via PATCH .../limits with `total.max: null`. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/markets Summary: Markets Read market scoping for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/markets Summary: Markets Update market scoping on a preorder offer. **Note:** Deep partial. Scoping is toggled via this PATCH: `enabled` (true/false). The offer applies to ALL markets when scoping is off (`{ "enabled": false }`) or when scoping is on with an empty market list — the serializer compiles applies_to_all_markets from exactly that state. PATCH bodies: all markets → `{ "enabled": true, "market_ids": [] }` (or `{ "enabled": true, "applies_to_all_markets": true }`, which also clears the list); specific markets → `{ "enabled": true, "market_ids": ["gid://shopify/Market/1", ...] }` (GIDs or numeric IDs). For incremental edits keep using add_market / remove_market / set_markets. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/markets/add_market Summary: Markets Add Market Add a market to the preorder offer's scope. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/markets/remove_market Summary: Markets Remove Market Remove a market from the preorder offer's scope. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/markets/set_markets Summary: Markets Set Markets Replace the set of markets a preorder offer applies to. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/payments Summary: Payments Read the payment configuration for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/payments Summary: Payments Update payment settings on a preorder offer. **Note:** Use SetOptionCopy / EnableOption / DisableOption to edit individual payment options. Boolean toggles are set via this PATCH: remaining_balance.auto_collect, remaining_balance.auto_collect_on_fulfillment — e.g. { remaining_balance: { auto_collect: false } }. Clear the discount via { discount: { type: "no_discount" } } (the stored value is nulled automatically). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/payments/disable_option Summary: Payments Disable Option Remove a payment option (full or partial) from the offer. **Note:** Only valid on full_and_partial offers — an offer must keep at least one payment option. Use set_mode to switch a single-mode offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/payments/enable_option Summary: Payments Enable Option Add a payment option (full or partial) to the offer. **Note:** Idempotent. Newly added options start from the dashboard defaults — use set_deposit_percent / set_option_copy to fine-tune. Disable with disable_option. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/payments/set_deposit_percent Summary: Payments Set Deposit Percent Set the deposit percentage charged at checkout on the partial payment option. **Note:** Requires a partial payment option (mode partial or full_and_partial) — use set_mode first. Percent must be between 1 and 99; 100% upfront is the full payment mode. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/payments/set_discount Summary: Payments Set Discount Set the preorder discount. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/payments/set_discount_text Summary: Payments Set Discount Text Set the discount blurb shown on the offer's payment options. **Note:** Pass option_type (full | partial) to target one payment option; omit it to apply the text to every option. Supports the {{ discount }} placeholder. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/payments/set_mode Summary: Payments Set Mode Switch the offer between full, partial, and full-and-partial payment modes. **Note:** Idempotent. Collapsing from full_and_partial keeps the surviving option's copy; newly enabled modes start from the dashboard defaults. Use set_deposit_percent and set_option_copy afterwards to fine-tune. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/payments/set_option_copy Summary: Payments Set Option Copy Set the merchant-facing copy on a payment option (title, description, discount text). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/products Summary: Products Read the products configuration for a preorder offer (source rule + variant summary). Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/products Summary: Products Update the product selection on a preorder offer — set the source rule plus its matching sub-block. **Note:** Delegates to the set_source_to_* actions; switching source resets the variant set. source 'all' requires confirm: true. Only the sub-block matching `source` may be provided. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/add_variants Summary: Products Add Variants Attach variants to a custom-source preorder offer. **Note:** Custom source only. Runs through BulkVariantAdditionService (async). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/bulk_toggle_inventory_policy Summary: Products Bulk Toggle Inventory Policy Flip the Shopify inventory policy on variants attached to a preorder offer (CONTINUE or DENY). **Note:** Async via SellingPlans::BulkInventoryPolicyUpdateJob. Omit variant_ids to apply to all attached variants. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/remove_variants Summary: Products Remove Variants Remove variants from a custom-source preorder offer. **Note:** Custom source only. Resets per-variant metafields on Shopify. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/set_collection Summary: Products Set Collection Change the collection driving a collection-sourced preorder offer. **Note:** Collection-sourced offers only — returns 409 Conflict otherwise. Use set_source_to_collection to switch a non-collection offer to collection source. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/set_excluded_variants Summary: Products Set Excluded Variants Replace the explicit excluded-variant list for an all-source preorder offer. **Note:** All-source offers only. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/set_exclusion_tag Summary: Products Set Exclusion Tag Set the exclusion tag for an all-source preorder offer (variants with this tag are excluded). **Note:** All-source offers only. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/set_source_to_all Summary: Products Set Source To All Switch the offer to apply to every variant in the shop, minus exclusions. Requires confirm: true. **Note:** Requires `confirm: true` in the body. Resolution runs async over the entire catalog. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/set_source_to_collection Summary: Products Set Source To Collection Switch the offer to a collection-driven variant source. **Note:** Resolution runs asynchronously. New variants added to the collection in Shopify are NOT auto-picked-up — call this action again to refresh. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/products/set_source_to_custom Summary: Products Set Source To Custom Switch the offer to a custom variant list. Optionally seed it with variant IDs. **Note:** Variant attachment runs through BulkVariantAdditionService and is async (202 + job_id). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/products/variants Summary: Products Variants List the variants attached to a preorder offer with their per-variant settings, paginated. Parameters: - `id` (path, string, required) - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/products/variants/{variant_id} Summary: Products Variants Read the per-offer settings of a single variant attached to a preorder offer. Parameters: - `id` (path, string, required) - `variant_id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/products/variants/{variant_id} Summary: Products Variants Set per-offer overrides on an attached variant — shipping text, max preorder count, market-scoped overrides. **Note:** null clears an override; the variant falls back to the offer-level value. market_overrides writes the per-market metafields (market_shipping_text, market_preorder_max_count) keyed by Shopify market GID. Parameters: - `id` (path, string, required) - `variant_id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/restore Summary: Restore Restore a discarded preorder offer. **Note:** Variant attachments and metafields are not auto-restored. Re-attach via the products capability. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/schedule Summary: Schedule Set the schedule window for a preorder offer. **Note:** Does not auto-enable. Storefront reads the schedule live. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/shipping Summary: Shipping Read shipping settings for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/shipping Summary: Shipping Update shipping settings on a preorder offer. **Note:** Deep partial. Prefer the intent-bearing actions (set_delivery_*, set_shipping_text, set_split_order_tag, set_split_transaction_gateway) for narrow edits. Toggles are done via this PATCH: `fulfillment.hold` (true/false) holds/releases fulfillment orders containing preorder items; `display.show_shipping_timeline` (true shows the detailed PDP timeline, false the simplified text); `fulfillment.split.enabled` (true/false) turns order splitting on/off (flips both split flags); `fulfillment.split.hold_fulfillments` (true/false) holds the split order's fulfillments; `fulfillment.split.sequential_number` (true/false) gives split orders a sequential order name. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/shipping/set_delivery_asap Summary: Shipping Set Delivery Asap Set delivery to 'as soon as possible' (no specific date or window). Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/shipping/set_delivery_date Summary: Shipping Set Delivery Date Set an exact delivery date for the preorder. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/shipping/set_delivery_window Summary: Shipping Set Delivery Window Set a delivery window in days from checkout (e.g. 'ships within 14 days'). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/shipping/set_shipping_text Summary: Shipping Set Shipping Text Set the customer-facing shipping text shown on the PDP. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/shipping/set_split_order_tag Summary: Shipping Set Split Order Tag Set the tag applied to split orders on Shopify. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/shipping/set_split_transaction_gateway Summary: Shipping Set Split Transaction Gateway Set the payment gateway used for the split order transaction (e.g. 'Prepaid'). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/translations Summary: Translations Read every translation override on a preorder offer, plus the full enumeration of translatable field paths. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/translations/clear_locale Summary: Translations Clear Locale Remove all translation overrides for one locale on a preorder offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/translations/set Summary: Translations Set Set a single translation override (one locale, one field) on a preorder offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/translations/set_many Summary: Translations Set Many Set many translation overrides for one locale on a preorder offer, atomically. **Note:** All-or-nothing. Pre-validates every field path before writing. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/translations/unset Summary: Translations Unset Remove a translation override (one locale, one field) from a preorder offer. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/unschedule Summary: Unschedule Clear the schedule window on a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/offers/{id}/widget Summary: Widget Read the widget configuration for a preorder offer. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/offers/{id}/widget Summary: Widget Update any subset of the widget configuration on a preorder offer. **Note:** Deep partial. Use the intent-bearing widget actions for narrow edits. Boolean toggles are set via this PATCH: badge.enabled, disclaimer.enabled, button.colors.enabled, billing_widget.enabled — e.g. { badge: { enabled: false } }. Clearing a value: send null. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/widget/set_badge_colors Summary: Widget Set Badge Colors Set the preorder badge's text and background colors. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/widget/set_badge_text Summary: Widget Set Badge Text Set the preorder badge text. **Note:** Use enable_badge / disable_badge to control visibility. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/widget/set_billing_widget_text Summary: Widget Set Billing Widget Text Set the billing widget's title and description (the PDP payment-breakdown block). **Note:** These fields are shared with the first payment option's copy (see payments/set_option_copy). Use enable_billing_widget / disable_billing_widget to control visibility. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/widget/set_button_colors Summary: Widget Set Button Colors Set the preorder button's text and background colors (implicitly enables custom colors). **Note:** Setting either color implicitly enables custom colors. Use disable_button_colors to revert to the theme's defaults. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/widget/set_button_text Summary: Widget Set Button Text Set the preorder button's call-to-action label. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — example response shown. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/widget/set_disclaimer_style Summary: Widget Set Disclaimer Style Set the disclaimer banner's colors and border radius. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/offers/{id}/widget/set_disclaimer_text Summary: Widget Set Disclaimer Text Set the disclaimer banner text shown under the preorder button. **Note:** Use enable_disclaimer / disable_disclaimer to control visibility. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/orders Summary: List List preorder orders in this shop, paginated. Parameters: - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) - `state` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `customer_id` (query, unknown, optional) - `from` (query, unknown, optional) - `to` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/orders/{id} Summary: Read Read a preorder order's full representation. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/cancel Summary: Cancel Cancel a preorder. Optionally refund deposit / balance and notify the customer. **Note:** Does not cancel the Shopify order; sets cancelled_at on the preorder attribution. Use the refund flags for refunds. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/orders/{id}/fulfillment Summary: Fulfillment Read the fulfillment hold state and fulfillment-order summary of a preorder order. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/orders/{id}/fulfillment Summary: Fulfillment Update fulfillment attributes on a preorder order. Only `hold_reason_note` is writable. **Note:** Hold state changes go through release_holds / apply_holds. `all_held`, `any_held`, and `fulfillment_orders` are read-only. Delegates to SetHoldReason; existing holds on Shopify aren't re-written. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/fulfillment/apply_holds Summary: Fulfillment Apply Holds Apply fulfillment holds on the order's fulfillment orders. Idempotent. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/fulfillment/release_holds Summary: Fulfillment Release Holds Release fulfillment holds on the order without applying offer-configured tags. **Note:** Use the top-level `release` action when you want tags applied too. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/fulfillment/set_hold_reason Summary: Fulfillment Set Hold Reason Set the hold-reason note that will be used the next time the order is held. **Note:** Updates the note on the first attributed preorder offer. Existing holds on Shopify aren't re-written. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/hold_fulfillments Summary: Hold Fulfillments Apply fulfillment holds on the preorder order's fulfillment orders. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/orders/{id}/payments Summary: Payments Read the deposit, remaining balance, and refund state of a preorder order. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/payments/charge_balance Summary: Payments Charge Balance Charge the remaining balance. `auto` uses the customer's saved payment method; `manual` just marks balance collected. **Note:** `auto` may fail if no saved payment method or declined; check the response or poll status. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/payments/refund Summary: Payments Refund Refund the entire preorder (deposit + balance). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/payments/refund_balance Summary: Payments Refund Balance Refund the remaining-balance portion of a preorder (only valid after the balance was collected). Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/payments/refund_deposit Summary: Payments Refund Deposit Refund the deposit portion of a preorder. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/payments/send_collection_url Summary: Payments Send Collection Url Send the remaining-balance payment-collection URL to the customer. **Note:** Idempotent — calling twice resends the email with a fresh URL. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/release Summary: Release Release the order's fulfillment holds and apply offer-configured order tags. **Note:** Returns 409 if balance hasn't been collected; pass `force: true` to override. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 409: Invalid lifecycle transition — read the message; don't retry blindly. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/split_fulfillment_orders Summary: Split Fulfillment Orders Split the order's fulfillment orders so preorder items ship separately from regular items. Parameters: - `id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/orders/{id}/tags Summary: Tags Read the order tags applied by Stoq. Parameters: - `id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### PATCH /preorders/orders/{id}/tags Summary: Tags Replace the order's tag set. PATCH form of tags/set. **Note:** `tags` is replaced wholesale (delegates to tags/set). `applied_by_stoq` and `applied_by_merchant` are read-only. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/tags/add Summary: Tags Add Add one or more tags to the Shopify order. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/tags/remove Summary: Tags Remove Remove one or more tags from the Shopify order. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/{id}/tags/set Summary: Tags Set Replace every tag on the Shopify order with the supplied list. **Note:** Pass an empty list to clear all tags. Implemented as fetch-current + remove stale + add new on the Shopify side. Parameters: - `id` (path, string, required) Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/bulk_cancel Summary: Bulk Cancel Cancel many preorder orders (max 1000), with optional refund flags. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/bulk_release Summary: Bulk Release Release fulfillments on many preorder orders (max 1000). Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/bulk_send_payment_collection_url Summary: Bulk Send Payment Collection Url Send payment-collection URLs to many preorder customers (max 1000). Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/orders/bulk_set_tags Summary: Bulk Set Tags Apply tags to many preorder orders in one job (max 1000). **Note:** `mode` is one of add | remove | replace (default add). `replace` overwrites each order's full tag set. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/orders/jobs/{job_id} Summary: Jobs Check the status of a bulk-order job. Parameters: - `job_id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/product_variants Summary: List List Shopify variants attached to any preorder offer in this shop. Parameters: - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `product_id` (query, unknown, optional) - `variant_ids` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/product_variants/{variant_id} Summary: Read Read a Shopify variant's preorder context: every offer it's in, aggregate counts, and current metafield state. Parameters: - `variant_id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/{variant_id}/detach_from_all_offers Summary: Detach From All Offers Remove a variant from every preorder offer it's attached to. Customer orders are unaffected. **Note:** Destructive. Same effect as calling Offer::Products::RemoveVariants against every offer the variant is in. Parameters: - `variant_id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/product_variants/{variant_id}/offers Summary: Offers List the preorder offers a Shopify variant is attached to. Parameters: - `variant_id` (path, string, required) - `state` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/{variant_id}/recalculate_preorder_count Summary: Recalculate Preorder Count Recompute preorder_count for a variant from active order line items, then push to Shopify. Parameters: - `variant_id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/{variant_id}/reset_metafields Summary: Reset Metafields Clear all preorder-related Shopify metafields on a variant. Keeps the variant attached to its offers. **Note:** Doesn't detach. Next offer settings change will re-write the metafields from current state. Parameters: - `variant_id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/{variant_id}/reset_preorder_count Summary: Reset Preorder Count Hard-reset the preorder_count metafield + DB columns on a variant to zero. **Note:** Use recalculate_preorder_count for a recompute from real orders; this is a destructive zero. Parameters: - `variant_id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/{variant_id}/sync_metafields Summary: Sync Metafields Force-resync every preorder metafield for a variant from local DB state to Shopify. **Note:** Idempotent. Useful when a previous metafield write failed. Parameters: - `variant_id` (path, string, required) Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/bulk_recalculate_preorder_counts Summary: Bulk Recalculate Preorder Counts Recompute preorder_count for many variants in one job (max 5000). Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/bulk_reset_metafields Summary: Bulk Reset Metafields Reset preorder metafields for many variants in one job (max 5000). Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/product_variants/bulk_sync_metafields Summary: Bulk Sync Metafields Force-resync preorder metafields for many variants in one job (max 5000). Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/product_variants/jobs/{job_id} Summary: Jobs Check the status of a bulk variant-admin job. Parameters: - `job_id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports Summary: List List every available preorder report (name, description, aliases, url). Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/balance_collection Summary: Balance Collection Outstanding preorder balances and balance-collection performance metrics. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `currency` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/balance_collection/export Summary: Balance Collection Export Async CSV export of the balance-collection report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/by_date Summary: By Date Time-series of all preorder metrics at the chosen granularity (day/week/month). Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `product_id` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `currency` (query, unknown, optional) - `granularity` (query, unknown, optional) - `metrics` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/by_date/export Summary: By Date Export Async CSV export of the time-series preorder report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/by_offer Summary: By Offer Per-offer breakdown of preorder activity (orders, units, revenue, customers). Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `market_id` (query, unknown, optional) - `currency` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/by_offer/export Summary: By Offer Export Async CSV export of the per-offer preorder report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/by_product Summary: By Product Per-product preorder performance, rolled up across variants. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `product_id` (query, unknown, optional) - `currency` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/by_product/export Summary: By Product Export Async CSV export of the per-product preorder report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/by_variant Summary: By Variant Per-variant preorder performance for a specific offer. **Note:** Requires offer_id (or offer_ids[]). Variant-level data without an offer scope isn't supported. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `product_id` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `currency` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/by_variant/export Summary: By Variant Export Async CSV export of the per-variant preorder report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/cancellations Summary: Cancellations Preorder cancellation metrics with reason and per-offer breakdowns. **Note:** Reason breakdown requires the cancel_reason field on OrderAttribution. Reasons not yet captured will appear as 'unknown'. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `currency` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/cancellations/export Summary: Cancellations Export Async CSV export of the cancellations report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/customers Summary: Customers Paginated list of customers with preorder activity in the window. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `currency` (query, unknown, optional) - `page` (query, unknown, optional) - `per_page` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/customers/export Summary: Customers Export Async CSV export of the customers report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/exports/{job_id} Summary: Exports Check the status of an async report export and get the download URL when ready. Parameters: - `job_id` (path, string, required) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 404: The resource does not exist for this shop, or the action path is not registered. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/revenue Summary: Revenue Preorder revenue over a date window, bucketed by day/week/month. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `product_id` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `market_id` (query, unknown, optional) - `currency` (query, unknown, optional) - `granularity` (query, unknown, optional) - `include_deposits` (query, unknown, optional) - `include_balances` (query, unknown, optional) Responses: - 200: Success — example response shown. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/revenue/export Summary: Revenue Export Async CSV export of the preorder revenue report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### GET /preorders/reports/summary Summary: Summary High-level overview of preorder activity over a date window. Parameters: - `from` (query, unknown, optional) - `to` (query, unknown, optional) - `offer_id` (query, unknown, optional) - `offer_ids` (query, unknown, optional) - `product_id` (query, unknown, optional) - `variant_id` (query, unknown, optional) - `market_id` (query, unknown, optional) - `currency` (query, unknown, optional) Responses: - 200: Success — the requested resource. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. ##### POST /preorders/reports/summary/export Summary: Summary Export Async CSV export of the preorder summary report. Request body: application/json Responses: - 200: Success — the updated resource. - 202: Accepted — async/bulk work was queued. Poll the returned job. - 401: Missing or invalid `X-Auth-Token`. - 422: The request failed validation. - 429: Rate limited — cost-weighted per token (read 1, write 2; 40 points / 60s). Check `Retry-After`. #### Models - Error: Error envelope returned by every failed request. - JobAccepted: Returned for async/bulk work — poll the job until it reaches a terminal state. ## SDK (Beta) ### Headless & Hydrogen Path: `/sdk/docs/headless/` Run STOQ preorders and back-in-stock on a headless storefront with the Storefront SDK — a script tag for any stack, React hooks for Hydrogen. STOQ's storefront widgets ship as a Shopify theme app embed , which doesn't load on headless storefronts (Hydrogen, Next.js Commerce, custom builds). The STOQ Storefront SDK brings the same capabilities to those stacks: preorder state per variant, cart lines with the right selling plan attached, and back-in-stock signups — including the notify-me modal. 📦 npm: @artossoftware/stoq-sdk — framework-free, fully typed, SSR-safe (nothing touches window / document at import), zero runtime dependencies. Setup is two fields: your shop domain and its public Storefront API access token (the same token your headless store already uses). It loads the merchant's STOQ config once at init and fetches per-variant availability on demand (batched + cached) from your shop's own Storefront API on Shopify's edge. Note Beta. Both install paths are live: npm install @artossoftware/stoq-sdk , or the script tag below (served from jsDelivr's CDN, pinned to the current major). Feedback: support@stoqapp.com . There are three ways to integrate, from least to most code: Path For You write Script tag Any headless stack, no build step One tag + a few lines of JS npm + React hooks Hydrogen / React 18+ storefronts Components using our hooks Raw APIs Anything else / full control Your own client against our metafields + the signup endpoint 1. Script tag (any stack) Add one tag to your . It identifies your shop by domain — no credentials go in your HTML, and your admin API key is never used in the storefront: Copy script src = "https://cdn.jsdelivr.net/npm/@artossoftware/stoq-sdk@0/dist/stoq.min.js" data-shop = "your-shop.myshopify.com" data-storefront-token = "your-public-storefront-api-token" defer > script > Note data-storefront-token is your public Storefront API access token — the same one your headless store already uses (public tokens are designed to be exposed in browser code; never use a private Storefront API token here). With it, the SDK reads everything from Shopify's edge-cached Storefront API — fast, close to your shoppers, and as fresh as Shopify itself. The script defines a global Stoq , initializes automatically, and fires stoq:loaded when ready: Copy script > window . addEventListener ( 'stoq:loaded' , async () => { // Ask by id — the SDK fetches the variant's availability itself // (batched + cached). If you already have the variant object, pass // { id, availableForSale, currentlyNotInStock } to skip that lookup. const state = await Stoq . client . getVariantState ( 42424242424242 ) if ( state . isPreorder ) { // Show your preorder UI; state.shippingText has the estimated-shipping copy. // Add to cart with state.sellingPlanGid (see "Carts" below). } else if ( Stoq . client . signupsEnabled /* && your data says it's sold out */ ) { // One call opens the STOQ notify-me modal — form, validation, // success states and styling come from your STOQ design settings: Stoq . openModal ({ variantId: 42424242424242 , productTitle: 'Classic Tee' }) } }) script > Optional attributes: data-market (Shopify Market id for market-scoped offers) and data-locale . Without data-shop the script only defines the global — call Stoq.init({...}) yourself. The modal can also render as an inline form inside any element you supply: Stoq.openInlineForm({ container: '#notify-me', variantId }) . Texts and colors default to your dashboard design settings and accept per-call overrides. 2. React / Hydrogen Copy npm install @artossoftware/stoq-sdk Wrap your app (or just the product route) in the provider, then use the hooks. Pass the public Storefront API token your Hydrogen app already ships with — the PUBLIC_STOREFRONT_API_TOKEN env var (the same value createStorefrontClient uses as publicStorefrontToken ): Copy export default function App () { return ( StoqProvider config = { { shop: 'your-shop.myshopify.com' , storefrontToken: env . PUBLIC_STOREFRONT_API_TOKEN , } } > Outlet /> StoqProvider > ) } A complete add-to-cart button that handles preorders automatically — the line input from useStoqCartLine already carries sellingPlanId when (and only when) the variant should be sold as a preorder, so it passes straight into Hydrogen's CartForm : Copy function BuyBox ({ variant }) { // Pass the variant object itself (include availableForSale and // currentlyNotInStock in your variant fragment) — its availability fields // decide preorder state. GIDs are accepted directly. const state = useStoqVariant ( variant ) const line = useStoqCartLine ( variant , 1 ) if ( ! line ) return null // SDK still loading (or SSR) if ( ! variant . availableForSale && ! state ?. isPreorder ) { return StoqNotifyMeButton variantId = { variant . id } productId = { variant . product . id } /> } return ( CartForm route = "/cart" action = { CartForm . ACTIONS . LinesAdd } inputs = { { lines: [ line ] } } > button type = "submit" > { state ?. isPreorder ? 'Preorder now' : 'Add to cart' } button > { state ?. isPreorder && state . shippingText ? p > { state . shippingText } p > : null } CartForm > ) } Available from @artossoftware/stoq-sdk/react : Export What it does useStoqVariant(variant) { isPreorder, sellingPlanId, shippingText, maxCount, remainingCount } — null while loading useStoqCartLine(variant, qty?) Storefront Cart API CartLineInput , with sellingPlanId only when preorderable useStoqSignup() { submit, submitting, result, error } for building your own notify-me UI useStoq() Raw { client, status, refresh } access Button → inline email form → signup; minimal overrideable default styling ( stoq-* classnames + --stoq-* CSS variables) Renders children (default "Preorder") only when the variant is preorderable Everything is SSR-safe — importing never touches window , and components render nothing on the server. The vanilla core ( import { init } from '@artossoftware/stoq-sdk' ) is the same engine without React — use it from Vue, Svelte, or plain JS with the identical getVariantState / cartLineFor / createSignup API. How data loads: all reads come from Shopify init({ shop, storefrontToken }) makes ONE GraphQL call to your shop's own edge-cached Storefront API for the merchant's STOQ configuration (offers, texts, limits), and per-variant availability is looked up on demand when you ask about a variant (batched — concurrent lookups share one request — and cached for 60 seconds). There are no catalog-wide inventory lists to download, and reads are served from Shopify's edge — fast and as fresh as Shopify itself. Already have the variant object from your own product query? Pass it ( { id, availableForSale, currentlyNotInStock } ) instead of a bare id and the availability lookup is skipped — your (possibly @inContext -localized) data is used verbatim. Signups post to STOQ (it's a write into our system) — that's the SDK's only STOQ call, it only fires on actual signups, and like all our APIs it is subject to rate limits . Carts: how preorders attach On headless storefronts, carts are Storefront API carts, and preorders work by attaching a selling plan to the line. The SDK hands you the finished line input: Copy mutation cartLinesAdd ( $cartId : ID ! , $lines : [ CartLineInput ! ] ! ) { cartLinesAdd ( cartId : $cartId , lines : $lines ) { cart { id totalQuantity } } } Copy { "cartId" : "gid://shopify/Cart/..." , "lines" : [{ "merchandiseId" : "gid://shopify/ProductVariant/42424242424242" , "sellingPlanId" : "gid://shopify/SellingPlan/3232323232" , "quantity" : 1 }] } That's all a preorder is at checkout time — STOQ's backend takes over from the order webhook onward (payment scheduling, fulfillment holds, tagging). Events The SDK fires the same custom events as the theme embed (on window ), so analytics and integrations behave identically: Event When stoq:loaded SDK initialized stoq:restock-modal:opened / closed Modal lifecycle stoq:restock-modal:submitted Signup created (modal, inline form, or createSignup ) Behavior notes Bare ids work everywhere — the SDK fetches availability itself. Passing your variant object ( availableForSale , currentlyNotInStock ) skips that lookup and uses your @inContext -localized data verbatim, which is the most precise option for multi-market stores (or set country in the config). Gate notify-me UI on your own availability data ( !variant.availableForSale ) plus client.signupsEnabled . Offer configuration can be briefly stale. The metafields STOQ writes are re-synced on merchant saves, Shopify's edge may cache reads, and the SDK session-caches for 5 minutes per shop+market — call refresh() after operations that change offers. Availability is fresher: a 60-second cache, or exactly your own data when you pass the variant object. 3. No SDK: raw APIs & metafields Everything the SDK does is built on public surfaces you can use directly: Back-in-stock signups — POST /api/v1/intents.json is CORS-open and unauthenticated (shop identified by domain): see Create an Intent . Variant/offer state via Storefront API metafields — STOQ's shop- and variant-level metafields (namespace restockrocket_production : cachedSellingPlans , preorder_variant_ids , variant shipping_text , preorder_max_count , …) are exposed with PUBLIC_READ storefront access, so any Storefront API client — including a Hydrogen loader running server-side — can read preorder state with zero STOQ API calls: Copy query ProductPreorderState ( $handle : String ! ) { shop { sellingPlans : metafield ( namespace : "restockrocket_production" , key : "cachedSellingPlans" ) { value } } product ( handle : $handle ) { variants ( first : 50 ) { nodes { id shippingText : metafield ( namespace : "restockrocket_production" , key : "shipping_text" ) { value } preorderMaxCount : metafield ( namespace : "restockrocket_production" , key : "preorder_max_count" ) { value } } } } } Admin operations (creating offers, releasing fulfillments, reports) — the v2 API , server-side only. Warning Never ship X-Auth-Token (your admin API key) to the client. It grants full read/write access to your STOQ data. The SDK never uses it — storefront calls are identified by shop domain only. ### Guide: Build a headless product page Path: `/sdk/docs/build-with-the-sdk/` Step-by-step: preorder button, payment-option modal, and back-in-stock signups on a headless storefront with the STOQ SDK — every step verified end-to-end. This guide walks through building a complete product page — preorder CTA, payment-option modal, add-to-cart with the right selling plan, and a notify-me flow — using the STOQ Storefront SDK . Every step here was executed against a real shop before being written down; the troubleshooting section lists the actual errors you can hit and what they mean. Note Before you start , you need: a STOQ-enabled shop with at least one enabled preorder offer that has variants attached , and the Shopify variant/product ids of one attached variant. Offer state lives in STOQ (dashboard → Preorders); a variant that merely looks sold out isn't preorderable until it's attached to an enabled offer. Step 1 — Load the SDK Script tag (any stack) — add to : Copy script src = "https://cdn.jsdelivr.net/npm/@artossoftware/stoq-sdk@0/dist/stoq.min.js" data-shop = "your-shop.myshopify.com" data-storefront-token = "your-public-storefront-api-token" defer > script > npm (Hydrogen/React): Copy npm install @artossoftware/stoq-sdk Copy // root.tsx StoqProvider config = { { shop: 'your-shop.myshopify.com' , storefrontToken: env . PUBLIC_STOREFRONT_API_TOKEN } } > Outlet /> StoqProvider > storefrontToken is your public Storefront API access token — the one your headless store already uses (in Hydrogen it ships as the PUBLIC_STOREFRONT_API_TOKEN env var; use the public token, never the private one, in browser code). It's required: the SDK reads everything from Shopify's edge in a single call with it (see How data loads ). Verify: the stoq:loaded event fires on window (script tag), or useStoq().status becomes 'ready' (React). Step 2 — Read the variant's state Ask by id — the SDK looks up the variant's availability itself (batched and cached). If you already have the variant object from your own product query, pass it ( { id, availableForSale, currentlyNotInStock } ) and the lookup is skipped: Copy const state = await Stoq . client . getVariantState ( VARIANT_ID ) // number, numeric string, or GID // { isPreorder, sellingPlanId, sellingPlanGid, shippingText, // maxCount, remainingCount } React: const state = useStoqVariant(VARIANT_ID) ( null while resolving). This is the branching point for the whole page: state Render isPreorder: true Preorder CTA (step 3) not preorderable, not availableForSale Notify-me (step 4) otherwise Normal add to cart Step 3 — The preorder button + modal Get the merchant-configured copy, open the modal on click, send the confirmed line to the cart: Copy const button = Stoq . client . preorderButtonFor ( VARIANT_ID ) buyButton . textContent = button . label // e.g. "Preorder" shippingEl . textContent = button . shippingText ?? '' // e.g. "Shipping: as soon as possible" buyButton . onclick = async () => { const confirmation = await Stoq . openPreorderModal ({ variantId: VARIANT_ID , productId: PRODUCT_ID , productTitle: 'Classic Tee' , }) if ( confirmation ) await addLinesToCart ([ confirmation . cartLine ]) } Two behaviors to know: The modal only renders when a choice is needed — multiple payment options (full vs partial payment) or a required acknowledgement checkbox. A single-option offer resolves immediately with the default option, exactly like the theme embed. confirmation.cartLine is a finished Storefront API CartLineInput carrying the chosen option's sellingPlanId : Copy { "merchandiseId" : "gid://shopify/ProductVariant/5095…" , "sellingPlanId" : "gid://shopify/SellingPlan/6904…" , "quantity" : 2 } Pass it to cartLinesAdd (or Hydrogen's CartForm ). That selling plan on the line is the entire checkout-side mechanic — STOQ takes over from the order webhook (payments, holds, tagging). React: …} /> does all of the above; useStoqPreorder(variantId) if you want your own UI. Step 4 — The notify-me (back-in-stock) flow One call opens the full modal — form, validation, duplicate handling, success state, merchant-configured texts/colors: Copy Stoq . openModal ({ variantId: VARIANT_ID , productId: PRODUCT_ID , productTitle: 'Classic Tee' }) Or inline in your own container: Stoq.openInlineForm({ container: '#notify', variantId, productId }) . Or fully custom UI on the data call: Copy const result = await Stoq . client . createSignup ({ variantId: VARIANT_ID , productId: PRODUCT_ID , // required — the backend rejects signups without it email: 'shopper@example.com' , }) // result.ok === true on creation; a repeat signup returns // ok: false with errors.already_registered: true React: or useStoqSignup() . Step 5 — Verify end-to-end Run through this checklist (it's the one we use): Load : stoq:loaded fires; getVariantState on your attached variant shows isPreorder: true with a sellingPlanId . CTA : preorderButtonFor returns your dashboard-configured button label and shipping text. Cart : the confirmed line carries sellingPlanId ; after cartLinesAdd , the Shopify cart shows the deferred-purchase option on the line. Checkout : place a test order — it appears in STOQ → Preorders → Orders with the offer attributed. Signup : createSignup returns ok: true (HTTP 201); the email appears in STOQ → Back in Stock → Waitlist. Submit again and you get already_registered — dedup working. Events : watch stoq:* events in the console — your analytics integrations receive the same payloads as on themed stores. Troubleshooting (real errors, real causes) Symptom Cause isPreorder: false unexpectedly The variant isn't attached to the enabled offer (it belongs to a disabled/old offer), or its availability doesn't match the offer type (STOQ-inventory offers need the variant out of stock but purchasable — currentlyNotInStock: true ; Shopify-inventory offers need it in stock). Attach it in STOQ → Preorders, or check GET /api/v2/external/preorders/offers/:id/products/variants server-side. Console warning: variant availability lookup failed The SDK's bare-id availability query failed (network, token). State degrades to isPreorder: false — retry, or pass the variant object with availability fields. createSignup → "shopify_variant_id and shopify_product_id are required" Missing productId . It's required — the SDK throws early if you omit it. init rejects: "init requires a data source" Neither storefrontToken nor data was provided. Pass your shop's public Storefront API token (or SSR-loaded metafield data). init rejects with StoqStorefrontApiError The Storefront API call failed as a whole — usually a wrong/private token or an unsupported storefrontApiVersion . Use the shop's public Storefront API token. init rejects: the "settings" shop metafield is missing STOQ hasn't synced its metafields for this shop yet (or a namespace override doesn't match). Save your STOQ settings once to trigger a sync. State looks stale after changing an offer Data is session-cached for 5 minutes (plus Shopify's edge caching) — call Stoq.client.refresh() . Working examples Both ship in the SDK repo and run as-is: examples/vanilla.html — complete script-tag product page (preorder CTA + modal + notify-me + event logging) examples/HydrogenBuyBox.tsx — drop-in Hydrogen buy box covering all three states For the full API reference see the Headless & Hydrogen page and the package README. ### SDK recipes Path: `/sdk/docs/sdk-recipes/` Build the common storefront pieces with the JavaScript SDK — notify-me, preorder buttons, and variant state. Short, copy-paste recipes for the things you'll build with the Storefront SDK . All assume an initialized client: Copy import { init } from '@artossoftware/stoq-sdk' const stoq = await init ({ shop: 'my-store.myshopify.com' , storefrontToken: '...' , // public Storefront API token }) Show the right control for a variant Ask the SDK about a variant and branch on its state: Copy const state = await stoq . getVariantState ( variantId ) if ( state . isPreorder ) { // offer a preorder button } else if ( ! state . availableForSale ) { // out of stock → offer notify-me } else { // in stock → normal add-to-cart } Add a notify-me (back-in-stock) form Copy // Drop the modal in on click… stoq . openModal ( variantId ) // …or render an inline form into your own element: stoq . openInlineForm ( variantId , { target: '#notify-me' }) // Submit a signup yourself: await stoq . createSignup ({ variantId , email: 'shopper@example.com' }) Add a preorder button + cart line Copy // A ready-made preorder button for a variant: const button = stoq . preorderButtonFor ( variantId ) // Or build your own and add the cart line with the selling plan attached: const line = await stoq . cartLineFor ( variantId , 1 ) // → pass `line` to the Storefront Cart API (cartLinesAdd) React / Hydrogen Hooks live under the /react entry — see the headless guide for useVariantState and friends. Note Already have the variant from your own product query? Pass it to skip the lookup: stoq.getVariantState({ id, availableForSale, currentlyNotInStock }) . ### Live demo Path: `/sdk/docs/examples/live-demo/` The Storefront SDK running live in your browser against a real store — preorder widget, notify-me, and derived variant state, with source. Everything on this page is rendered by the real @artossoftware/stoq-sdk in your browser — loaded from jsDelivr, talking to a live store ( nina-and-kulfi-dog-toys.myshopify.com ) with its public Storefront API token. No server, no build step. It's the same script tag the notify-me and preorder examples show, wired to a real preorder variant. ↗ Open the live demo in a full page Note The demo points at a test product (“Rabbit in a Carrot — Zero or less inventory”) that carries a STOQ preorder selling plan, so the preorder UI and derived state are real. The token is the shop's public Storefront token — the same kind you embed client-side in your own storefront. How it's built The page is a single self-contained HTML file — no framework. It loads the script-tag build, then reads state imperatively through Stoq.client : Copy window . addEventListener ( 'stoq:loaded' , async () => { const variant = { id: 49360289497368 , availableForSale: false , currentlyNotInStock: true } const state = await Stoq . client . getVariantState ( variant ) // { isPreorder, sellingPlanId, shippingText, … } const cta = Stoq . client . preorderButtonFor ( variant ) // { label, … } or null const line = await Stoq . client . cartLineFor ( variant , 1 ) // { merchandiseId, sellingPlanId, quantity } }) The drop-in and elements need zero JavaScript — see the preorder and notify-me examples for the full breakdown. If you already have your own JavaScript, the same widget is one imperative call — Stoq.renderPreorderWidget(variant, { container }) mounts the badge + CTA into any element, wires click → modal → cart, and returns a handle with .update() / .destroy() : Copy const handle = await Stoq . renderPreorderWidget ( variant , { container: '#preorder-mount' , productId: 9564103180568 , badge: true , notifyMeFallback: true , onAddToCart : ( line ) => addToCart ( line ), // your Storefront Cart API cartLinesAdd }) Go deeper Headless & Hydrogen — provider, hooks, and the SSR data flow. Build with the SDK — the full client + element API. Full SSR reference storefront — a complete Shopify Hydrogen app wired to the SDK lives at examples/hydrogen-nina-kulfi in the repo, exercising every SDK surface on a real product page. ### Notify-me (back in stock) Path: `/sdk/docs/examples/notify-me/` Copy-paste back-in-stock signup examples — drop-in custom element, one-call renderer, modal, inline form, and React. Let shoppers join the waitlist for a sold-out variant. Every example below uses the Storefront SDK — load it with your shop domain and public Storefront API token. A signup needs both variant-id and product-id (the API rejects it otherwise). Copy script src = "https://cdn.jsdelivr.net/npm/@artossoftware/stoq-sdk@0/dist/stoq.min.js" data-shop = "my-store.myshopify.com" data-storefront-token = "your-public-storefront-api-token" defer > script > Drop-in: the element Zero JavaScript. The script tag above auto-registers the element; drop it where you want the button (it hides itself when signups are disabled for the shop): Copy stoq-notify-me variant-id = "43900910010503" product-id = "7813090115847" label = "Notify me when available" > stoq-notify-me > Change variant-id (e.g. when the shopper picks another size) and it re-renders itself — no JS. Add expanded to show the email field immediately. One call: renderNotifyMe When you control the JS, render a button into your own container: Copy div id = "notify" > div > script > window . addEventListener ( 'stoq:loaded' , () => { Stoq . renderNotifyMe ( { id: 43900910010503 , availableForSale: false }, { container: '#notify' , productId: 7813090115847 } ) }) script > Modal or inline form Open STOQ's notify-me modal (texts/colors default to the merchant's STOQ design settings): Copy Stoq . openModal ({ variantId: 43900910010503 , productId: 7813090115847 , // required productTitle: 'Trail Runner' , variantTitle: '43 / Blue' , prefill: { email: customerEmail }, // optional }) Render the same form inside your own element instead of a modal: Copy Stoq . openInlineForm ({ container: '#notify-me' , variantId , productId }) // Stoq.removeInlineForm() to tear it down Roll your own Submit a signup directly and build any UI you like: Copy const result = await Stoq . client . createSignup ({ variantId: 43900910010503 , productId: 7813090115847 , email: 'shopper@example.com' , }) if ( result . ok ) { // subscribed — `stoq:restock-modal:submitted` fired on window } React / Hydrogen Copy import { StoqNotifyMeButton } from '@artossoftware/stoq-sdk/react' StoqNotifyMeButton variantId = { variant . id } productId = { product . id } /> StoqNotifyMeButton expands into an inline email form and creates the signup. It hides itself when signups are disabled, but is not gated on stock — gate it on your own data, e.g. render it only when !variant.availableForSale . For full control, build on the useStoqSignup() / useStoqVariant() hooks. Note Events fire on window for analytics: stoq:restock-modal:opened , :closed , :submitted . ### Preorder widget & button Path: `/sdk/docs/examples/preorder/` Copy-paste preorder examples — drop-in widget element, one-call renderer, a hand-built button, and React. Preorder widget & button Sell a variant before it's in stock, with the right selling plan attached to the cart line. Examples use the Storefront SDK loaded with your shop + public Storefront API token (see the notify-me examples for the script tag). Drop-in: the element Badge + preorder CTA (with an optional notify-me fallback), zero JS. Because an HTML attribute can't carry a callback, the cart line comes back as a stoq:add-to-cart event — hand it to your cart: Copy stoq-preorder-widget variant-id = "43900910010503" product-id = "7813090115847" notify-me-fallback > stoq-preorder-widget > script > document . addEventListener ( 'stoq:add-to-cart' , ( e ) => { // e.detail.line = { merchandiseId, sellingPlanId, quantity } addToCart ( e . detail . line ) // your Storefront Cart API cartLinesAdd }) script > Attributes in, events out. Change variant-id and it re-renders itself. For a no-framework store, watch="select[name=id]" copies a 's value into variant-id on change. Smaller pieces: and the CTA-only widget. One call: renderPreorderWidget Copy div id = "preorder" > div > script > window . addEventListener ( 'stoq:loaded' , async () => { const handle = await Stoq . renderPreorderWidget ( { id: 43900910010503 , availableForSale: false , currentlyNotInStock: true }, { container: '#preorder' , notifyMeFallback: true } ) // handle.update(newVariant) on variant change; handle.destroy() to remove }) script > These render their own element into a container you own — the right model for headless (unlike the theme embed, which hijacks the theme's add-to-cart button). Hand-built button (full control) Use the primitives when you want your own markup: Copy const variant = { id: 43900910010503 , availableForSale: false , currentlyNotInStock: true } const cta = Stoq . client . preorderButtonFor ( variant ) // null if no plan applies if ( cta ) { buttonEl . textContent = cta . label // e.g. "Preorder now" buttonEl . addEventListener ( 'click' , async () => { // Payment-option picker (full vs partial) + quantity limits + acknowledgement: const confirmation = await Stoq . client . openPreorderModal ({ variantId: variant , productTitle: 'Trail Runner' }) if ( confirmation ) addToCart ( confirmation . line ) // cart line with sellingPlanId attached }) } Skip the modal and attach the plan directly: Copy const line = await Stoq . client . cartLineFor ( variant , 1 ) // { merchandiseId, sellingPlanId: 'gid://shopify/SellingPlan/…', quantity: 1 } React / Hydrogen Copy import { StoqPreorderButton , StoqPreorderBadge } from '@artossoftware/stoq-sdk/react' StoqPreorderBadge variant = { variant } /> StoqPreorderButton variant = { variant } /> Or drive your own UI with useStoqVariant(variant) (gives isPreorder / sellingPlanId / shipping text) and useStoqCartLine(variant) . See Headless & Hydrogen . ### A complete product page Path: `/sdk/docs/examples/product-page/` Branch between in-stock, preorder, and notify-me — and keep it in sync as the shopper switches variants. The real job: for the selected variant, show add-to-cart (in stock), preorder (sold out but on a plan), or notify-me (sold out, no plan) — and update when the shopper picks another variant. Uses the Storefront SDK . Decide what to show Copy const variant = { id , availableForSale , currentlyNotInStock } // from your product query const state = await Stoq . client . getVariantState ( variant ) if ( state . isPreorder ) { // sold out, but a preorder plan applies → preorder widget } else if ( ! variant . availableForSale && Stoq . client . signupsEnabled ) { // sold out, no plan → notify-me } else { // in stock → your normal add-to-cart } Vanilla: one widget that follows the variant The drop-in widget handles all three states and re-binds to the size selector with watch — no JS: Copy select name = "id" > option value = "43900910010503" > 43 / Blue option > option value = "43900910043271" > 44 / Blue option > select > stoq-preorder-widget product-id = "7813090115847" watch = "select[name=id]" notify-me-fallback > stoq-preorder-widget > script > document . addEventListener ( 'stoq:add-to-cart' , ( e ) => addToCart ( e . detail . line )) script > Changing the copies its value into variant-id , and the widget re-renders itself for the new variant — preorder, notify-me, or nothing, automatically. React / Hydrogen In a loader, read the variant (with availableForSale / currentlyNotInStock ) from the Storefront API, then: Copy import { StoqProvider } from '@artossoftware/stoq-sdk/react' import { useStoqVariant } from '@artossoftware/stoq-sdk/react' import { StoqPreorderButton , StoqNotifyMeButton } from '@artossoftware/stoq-sdk/react' function BuyBox ({ variant , product }) { const state = useStoqVariant ( variant ) // null while loading if ( ! state ) return null if ( state . isPreorder ) return StoqPreorderButton variant = { variant } /> if ( ! variant . availableForSale ) return StoqNotifyMeButton variantId = { variant . id } productId = { product . id } /> return AddToCartButton variant = { variant } /> } // Wrap your app once with the public token: // … useStoqVariant re-derives on the ready event, on variant change, and after refresh() , so the buy box stays correct as the shopper switches options. Note Adding to cart in Hydrogen: a preorder line carries a sellingPlanId — pass StoqPreorderButton 's line (or useStoqCartLine(variant) ) to the Storefront Cart API cartLinesAdd . The theme embed's button-mutation trick doesn't work in React SSR; that's why the SDK gives you the line. ## Frontend ### JavaScript API Path: `/frontend/docs/javascript-api/` STOQ provides a JavaScript API for custom integrations within your Shopify theme. This API is accessible through the window.RestockRocket object when our app loads into your theme. STOQ provides a JavaScript API for custom integrations within your Shopify theme. This API is accessible through the window._RestockRocket object when our app loads into your theme. Available APIs Page Type Available APIs Product Page openModal, openInlineForm, removeInlineForm, renderButtonForVariant, getSellingPlan Collection, Home, and Custom Pages openModal All pages marketId openModal openModal(productData, variantId, customerData) Opens the 'Notify me' modal for a specific product. This is typically used for custom elements like quick add buttons. Parameters: productData (Object): Contains product information variantId (String): ID of the specific variant customerData (Object): Contains customer information (Optional) Examples: Opening modal on a collection page: Copy const addToCartBtn = document . getElementById ( 'quick-add-btn' ); addToCartBtn . addEventListener ( 'click' , () => { const productData = { id: 1111 , variants: [{ id: 2222 , available: false }] }; const variantId = '41XX' ; const customerData = { shopify_customer_id: 9999 , email: 'john.doe@example.com' , phone: '8123999123' , // phone number country_code: '1' , // country code country: 'us' // 2 letter ISO country code } // Optional window . _RestockRocket . openModal ( productData , variantId , customerData ); // Alternative: Using Liquid objects directly in Javascript // window._RestockRocket.openModal({{ product | json }}, '{{ product.selected_or_first_available_variant.id }}'); }); Opening modal on a product page: Copy const notifyMeBtn = document . getElementById ( 'notify-me-btn' ); notifyMeBtn . addEventListener ( 'click' , () => { // Default: Opens modal for the current product, it automatically gets the // product data and currently selected variant ID window . _RestockRocket . openModal (); }); openInlineForm openInlineForm(productData, variantId, customerData, inlineFormContainer, inlineFormContainerInsertType) Shows the 'Notify me' modal as an Embedded form for a specific product variant. Use this API to show the Embedded Sign up form in a specific location when an out of stock variant is selected. Parameters: productData (Object): Contains product information variantId (String): ID of the specific variant customerData (Object): Contains customer information (Optional) inlineFormContainer: Selector for the container where you want to insert the form (Optional) inlineFormContainerInsertType: How the form should be inserted into the container (Optional) Notes: inlineFormContainer can be passed down in the function call, or you can set it up in-app. Chat with Support to get access to the relevant settings. inlineFormContainerInsertType can be one of: beforebegin, afterbegin, beforeend, afterend. The default is 'beforeend'. Example: Copy const addToCartBtn = document . getElementById ( 'quick-add-btn' ); addToCartBtn . addEventListener ( 'click' , () => { const productData = { id: 1111 , variants: [{ id: 2222 , available: false }] }; const variantId = '41XX' ; const customerData = { shopify_customer_id: 9999 , email: 'john.doe@example.com' , phone: '8123999123' , country_code: '1' , country: 'us' }; // Basic usage window . _RestockRocket . openInlineForm ( productData , variantId , customerData , '#my-custom-container' , // CSS selector where form will be inserted 'afterend' // Where to insert relative to selector: 'beforebegin', 'afterbegin', 'beforeend', 'afterend' ); // Alternative: Using Liquid objects directly // window._RestockRocket.openInlineForm( // {{ product | json }}, // '{{ product.selected_or_first_available_variant.id }}', // null, // '#my-custom-container' // ); }); removeInlineForm Removes the Embedded form rendered using openInlineForm. Use this API to remove the Embedded Form after a successful submission or when a different variant is selected. Example: Copy window . _RestockRocket . removeInlineForm (); renderButtonForVariant renderButtonForVariant(variantId) Sets up or removes the button for a specific variant on the page. Useful when your theme has a custom variant selector. Parameters: variantId (String): ID of the variant to render the button for. Example: Copy const variantSelector = document . getElementById ( 'variant-selector' ); variantSelector . addEventListener ( 'change' , ( event ) => { const selectedVariantId = event . target . value ; window . _RestockRocket . renderButtonForVariant ( selectedVariantId ); }); getSellingPlan getSellingPlan(variantId) Retrieves the selling plan object for a given variant if a preorder is set up. Parameters: variantId (String): ID of the variant to check for a selling plan. Returns: (Object): The selling plan object if found, or null if no preorder is set up for the variant. Example: Copy const variantId = '41XX' ; const sellingPlan = window . _RestockRocket . getSellingPlan ( variantId ); if ( sellingPlan ) { console . log ( 'Preorder selling planDetails:' , sellingPlan ); } else { console . log ( 'No preorder set up for this variant' ); } Example Response: Copy { shopify_selling_plan_group_id : 123456789 , shopify_selling_plan_id : 987654321 , enabled : true , variant_ids : [ 11111 , 22222 , 33333 , 44444 ], name : "Preorder Offer" , billing_checkout_charge_amount : "100.0" , billing_checkout_charge_percentage : "10.0" , billing_checkout_charge_type : "percentage" , billing_description : null , billing_title : "Full payment" , discount_text : "Save {{ discount }}" , enable_billing_widget : true , inventory_provider : "stoq" , payment_type : "full" , preorder_badge_background_color : "#000000" , preorder_badge_enabled : true , preorder_badge_text : null , preorder_badge_text_color : "#FFFFFF" , preorder_button_description : "Note: This is a preorder. Items will ship based on the estimated delivery date." , preorder_button_description_background_color : "#ffffff" , preorder_button_description_border_radius : 20 , preorder_button_description_quantity_limit_suffix : " units available for preorder" , preorder_button_description_shipping_text_prefix : "Shipping: " , preorder_button_description_show_quantity_limit : true , preorder_button_description_show_shipping : true , preorder_button_description_text_color : "#000000" , preorder_button_text : "Preorder Now" , preorder_shipping_text : "Estimated shipping: {{ date }}" , pricing_amount : null , pricing_percentage : null , pricing_type : "no_discount" , quantity_limit_text : "{{ quantity }} stock left" , shipping_applies_to_all_products : true , shipping_text : "Shipping date to be determined" , translations : { /* language translations object */ } } Integration Examples Quick Add Button on Collection Page Copy div data-gb-custom-block data-tag = "for" > div class = "product-item" > h2 > {{ product.title }} h2 > button class = "quick-add-btn" data-product-data = "{{ product | json }}" data-variant-id = "{{ product.selected_or_first_available_variant.id }}" > Quick Add button > div > div > script > document . querySelectorAll ( '.quick-add-btn' ). forEach ( button => { button . addEventListener ( 'click' , function () { const productData = this . getAttribute ( 'data-product-data' ); const variantId = this . getAttribute ( 'data-variant-id' ); window . _RestockRocket . openModal ( productData , variantId ); }); }); script > Notify Me Button on Product Page Copy div data-gb-custom-block data-tag = "if" > button id = "add-to-cart-button" > Add to Cart button > div data-gb-custom-block data-tag = "else" > div > button id = "notify-me-btn" > Notify Me When Available button > div > script > const notifyMeBtn = document . getElementById ( 'notify-me-btn' ); div data-gb-custom-block data-tag = "if" data-0 = 'false' data-1 = 'false' data-2 = 'false' data-3 = 'false' > notifyMeBtn.addEventListener('click', function() { window . _RestockRocket . openModal ({{ product | json }}, '{{ product.selected_or_first_available_variant.id }}' ); } ); div > // For themes with variant selectors document . addEventListener ( 'variantChange' , function ( event ) { const variant = event . detail . variant ; window . _RestockRocket . renderButtonForVariant ( variant . id ); }); script > Custom Integration on Product Page Copy div id = "product-form" data-product-id = "{{ product.id }}" > select id = "variant-selector" > {% for variant in product.variants %} option value = "{{ variant.id }}" {% if variant = = product.selected_or_first_available_variant %}selected = "selected" {% endif %} > {{ variant.title }} option > {% endfor %} select > button id = "add-to-cart-btn" {% unless product.selected_or_first_available_variant.available %}style = "display: none;" {% endunless %} > Add to Cart button > button id = "notify-me-btn" {% if product.selected_or_first_available_variant.available %}style = "display: none;" {% endif %} > Notify Me button > div > script > const productForm = document . getElementById ( 'product-form' ); const variantSelector = document . getElementById ( 'variant-selector' ); const addToCartBtn = document . getElementById ( 'add-to-cart-btn' ); const notifyMeBtn = document . getElementById ( 'notify-me-btn' ); function updateButtons ( variantId ) { const selectedVariant = {{ product . variants | json }} . find ( variant => variant . id . toString () === variantId ); if ( selectedVariant && selectedVariant . available ) { addToCartBtn . style . display = 'block' ; notifyMeBtn . style . display = 'none' ; } else { addToCartBtn . style . display = 'none' ; notifyMeBtn . style . display = 'block' ; } } variantSelector . addEventListener ( 'change' , function () { const selectedVariantId = this . value ; updateButtons ( selectedVariantId ); window . _RestockRocket . renderButtonForVariant ( selectedVariantId ); }); notifyMeBtn . addEventListener ( 'click' , function () { const productData = {{ product | json }}; const variantId = variantSelector . value ; window . _RestockRocket . openModal ( productData , variantId ); }); // Initial setup updateButtons ( variantSelector . value ); script > These examples demonstrate how to integrate RestockRocket's API with buttons using onclick handlers and Shopify Liquid markup. The Liquid examples show how to access product and variant data directly from Shopify, which can then be passed to the RestockRocket API. Remember to adjust these examples to fit your specific theme structure and requirements. marketId This returns the market ID the store is being viewed as: Copy window . _RestockRocketConfig . marketId ### Custom events Path: `/frontend/docs/custom-events/` STOQ emits custom events that you can listen to for custom integrations. All events are dispatched on the window object (except where noted) and include detailed information in the event.detail... STOQ emits custom events that you can listen to for custom integrations. All events are dispatched on the window object (except where noted) and include detailed information in the event.detail property. stoq:loaded Event name: stoq:loaded Description: Event triggered when STOQ's app embed is loaded on any page supported by the app. Pages supported: Product, Collection, Home, Search. Event target: window Event detail: pageType (string): The type of page where the app loaded. Possible values: 'product' , 'collection' , 'index' , 'search' , 'page' enabled (boolean): Whether the app is enabled for this page settings (object): Complete app settings object containing all configuration options Example: Copy window . addEventListener ( 'stoq:loaded' , ( event ) => { const { pageType , enabled , settings } = event . detail ; if ( pageType === 'product' && enabled ) { console . log ( 'Stoq loaded on product page' , settings ); // Handle product page customizations } }); stoq:restock-modal:opened Event name: stoq:restock-modal:opened Description: Event triggered when the restock notification modal is opened. This occurs when a customer clicks the "Notify Me" button for an out-of-stock product. Event target: window Event detail: action (string): Always 'opened' product (object): Product data object containing product information variant (object): Variant data object containing variant information Example: Copy window . addEventListener ( 'stoq:restock-modal:opened' , ( event ) => { const { product , variant } = event . detail ; console . log ( 'Restock modal opened for:' , product . title , variant . title ); // Track analytics or perform custom actions }); stoq:restock-modal:submitted Event name: stoq:restock-modal:submitted Description: Event triggered when a customer successfully submits the restock notification form. This occurs after the form validation passes and the notification request is created. Event target: window Event detail: action (string): Always 'submitted' product (object): Product data object containing product information variant (object): Variant data object containing variant information customer (object): Customer information object email (string|null): Customer email address if provided phone (string|null): Customer phone number if provided Example: Copy window . addEventListener ( 'stoq:restock-modal:submitted' , ( event ) => { const { product , variant , customer } = event . detail ; console . log ( 'Restock notification submitted:' , { product: product . title , variant: variant . title , email: customer . email , phone: customer . phone }); // Track conversion or send to analytics }); stoq:restock-modal:closed Event name: stoq:restock-modal:closed Description: Event triggered when the restock notification modal is closed, either by the user clicking the close button or clicking outside the modal. Event target: window Event detail: action (string): Always 'closed' product (object): Product data object containing product information variant (object): Variant data object containing variant information Example: Copy window . addEventListener ( 'stoq:restock-modal:closed' , ( event ) => { const { product , variant } = event . detail ; console . log ( 'Restock modal closed for:' , product . title ); // Track abandonment or perform cleanup }); stoq:preorder-modal:opened Event name: stoq:preorder-modal:opened Description: Event triggered when the preorder modal is opened. This occurs when a customer clicks a preorder button or attempts to add a preorder product to cart. Event target: window Event detail: productData (object): Complete product data object variantId (string|number): ID of the selected variant sellingPlan (object|null): Selling plan object if a specific plan is selected, null otherwise Example: Copy window . addEventListener ( 'stoq:preorder-modal:opened' , ( event ) => { const { productData , variantId , sellingPlan } = event . detail ; console . log ( 'Preorder modal opened:' , { product: productData . title , variant: variantId , plan: sellingPlan ?. name }); // Track preorder interest }); stoq:preorder-modal:closed Event name: stoq:preorder-modal:closed Description: Event triggered when the preorder modal is closed. This can occur in two scenarios: when the customer accepts the preorder (adds to cart) or when they reject/close the modal. Event target: window Event detail: action (string): Either 'accepted' or 'rejected' indicating how the modal was closed productData (object): Complete product data object variantId (string|number): ID of the selected variant selectedSellingPlanId (number|null): ID of the selected selling plan if accepted, null if rejected sellingPlan (object|null): Selling plan object quantity (number): Quantity selected (only present when action === 'accepted' ) acknowledgedPreorder (boolean): Whether the customer acknowledged the preorder terms (only present when action === 'accepted' ) Example: Copy window . addEventListener ( 'stoq:preorder-modal:closed' , ( event ) => { const { action , productData , variantId , selectedSellingPlanId , quantity } = event . detail ; if ( action === 'accepted' ) { console . log ( 'Preorder accepted:' , { product: productData . title , variant: variantId , plan: selectedSellingPlanId , quantity: quantity }); // Track successful preorder } else { console . log ( 'Preorder rejected:' , productData . title ); // Track abandonment } }); stoq:preorder-payment-option:selected Event name: stoq:preorder-payment-option:selected Description: Event triggered when a customer selects a different payment option (selling plan) in the preorder payment widget. This event bubbles from the payment widget element. Event target: Payment widget element (bubbles to window ) Event detail: paymentWidget (HTMLElement): The DOM element containing the payment widget variantId (string|number): ID of the variant for which the payment option was selected selectedSellingPlanId (number): ID of the newly selected selling plan Example: Copy // Listen on the payment widget element const paymentWidget = document . querySelector ( '.restock-rocket-payment-widget' ); if ( paymentWidget ) { paymentWidget . addEventListener ( 'stoq:preorder-payment-option:selected' , ( event ) => { const { variantId , selectedSellingPlanId } = event . detail ; console . log ( 'Payment option changed:' , { variant: variantId , plan: selectedSellingPlanId }); // Update UI or pricing based on selected plan }); } // Or listen on window (event bubbles) window . addEventListener ( 'stoq:preorder-payment-option:selected' , ( event ) => { const { variantId , selectedSellingPlanId } = event . detail ; // Handle payment option change }); stoq:product-changed Event name: stoq:product-changed Description: Event triggered when the product context changes, such as when navigating to a different product or when the variant selection changes significantly. This is useful for tracking product views and updating custom integrations. Event target: document Event detail: productHandle (string): The Shopify product handle (URL slug) productId (string|number): The Shopify product ID variantId (string|number): The currently selected variant ID Example: Copy document . addEventListener ( 'stoq:product-changed' , ( event ) => { const { productHandle , productId , variantId } = event . detail ; console . log ( 'Product context changed:' , { handle: productHandle , product: productId , variant: variantId }); // Update custom integrations or track product views }); Integration examples Track all modal interactions Copy // Track when modals open window . addEventListener ( 'stoq:restock-modal:opened' , ( event ) => { analytics . track ( 'restock_modal_opened' , { product_id: event . detail . product . id , variant_id: event . detail . variant . id }); }); window . addEventListener ( 'stoq:preorder-modal:opened' , ( event ) => { analytics . track ( 'preorder_modal_opened' , { product_id: event . detail . productData . id , variant_id: event . detail . variantId }); }); // Track when modals close window . addEventListener ( 'stoq:restock-modal:closed' , ( event ) => { analytics . track ( 'restock_modal_closed' , { product_id: event . detail . product . id }); }); window . addEventListener ( 'stoq:preorder-modal:closed' , ( event ) => { analytics . track ( 'preorder_modal_closed' , { action: event . detail . action , product_id: event . detail . productData . id }); }); Update custom UI on product change Copy document . addEventListener ( 'stoq:product-changed' , ( event ) => { const { productHandle , variantId } = event . detail ; // Update custom product display updateCustomProductDisplay ( productHandle , variantId ); // Fetch additional product data fetchCustomProductData ( productHandle ). then ( data => { displayCustomData ( data ); }); }); React to payment option changes Copy window . addEventListener ( 'stoq:preorder-payment-option:selected' , ( event ) => { const { selectedSellingPlanId } = event . detail ; // Update custom pricing display const plan = getSellingPlanDetails ( selectedSellingPlanId ); updatePriceDisplay ( plan . price , plan . discount ); // Update shipping estimates updateShippingEstimate ( plan . estimatedDelivery ); }); ### Preorder metafields Path: `/frontend/docs/preorder-metafields/` Which preorder data lives at the shop (offer) level vs the variant level vs the market level — and how to read each. STOQ writes preorder configuration and live state to Shopify metafields in the restockrocket_production namespace, so a theme or headless storefront can render preorder UI directly. (Preorder offers in the dashboard are selling plans in the data.) Note Building headless or Hydrogen? The Storefront SDK reads all of these for you and answers "is this variant preorderable, and with which plan?" — you rarely need to parse metafields by hand. This page is the underlying contract. Which level holds what The most common point of confusion: an offer's configuration is stored once at the shop level — not on each variant. What lives on the variant is which offer applies and the live counts. Level Owner Holds Key metafields Shop (offer config) Shop The full config of every enabled offer — button text, shipping copy, limits — and each offer's variant_ids selling_plans Variant Product variant Which offers apply to this variant + live preorder state selling_plan_ids , preorder_count , preorder_max_count , shipping_text Market Product variant Per-market preorder counts and limits (for market-scoped offers) market_preorder_count , market_preorder_max_count Is this variant preorderable? Read the variant's selling_plan_ids — non-empty means it's attached to one or more offers. (Equivalently, the variant's id appears in an enabled offer's variant_ids in the shop-level selling_plans .) Look that plan up in the shop-level selling_plans to get its display copy — button text, shipping text, limits. For quantity limits, read the variant's preorder_count / preorder_max_count — or the market-keyed market_preorder_count / market_preorder_max_count for market-scoped offers. In short: render decisions come from variant-level metafields; display copy comes from the shop-level offer config. Note Shipping text lives on the offer, not the variant. A common gotcha: the per-variant shipping_text metafield only exists if a merchant set a per-variant override. Most stores configure one shipping message per offer, stored on the shop-level selling_plans . To show it on the cart, use the cart-page recipe . Reference Shop-level metafields — the selling_plans offer config. Variant-level metafields — selling_plan_ids , counts, and per-variant overrides. Market-level metafields — per-market counts and limits. ### Shop Level Metafields Path: `/frontend/docs/preorder-metafields/shop-level-metafields/` The selling_plans metafield is the source of truth for every preorder offer's config — button text, shipping copy, limits — and the variants it applies to. The shop-level selling_plans metafield ( json , namespace restockrocket_production ) holds the full configuration of every enabled preorder offer — button text, shipping copy, limits — plus the variant_ids each offer applies to. It is the source of truth for offer-level shipping text : the per-variant shipping_text metafield only exists when a merchant sets a per-variant override (see Variant-level metafields ). Show preorder shipping text on the cart page The most common request. Read the offer's shipping text from selling_plans , matching each cart line's variant to an enabled offer: Copy {% comment %} Show preorder shipping text on the cart page. Source of truth = the shop-level `selling_plans` metafield (the offer config), NOT the per-variant `shipping_text` metafield (which most stores never set). {% endcomment %} {% assign offers = shop.metafields.restockrocket_production.selling_plans.value %} {% for line_item in cart.items %} {%- assign variant_id = line_item.variant_id | append: '' -%} {%- for offer in offers -%} {%- if offer.enabled and offer.variant_ids contains variant_id -%} {%- comment -%} Prefer the preorder-specific copy, fall back to the general shipping text {%- endcomment -%} {%- assign shipping_text = offer.preorder_shipping_text | default: offer.shipping_text -%} {%- if shipping_text != blank -%} {{ shipping_text }} {%- endif -%} {%- break -%} {%- endif -%} {%- endfor -%} {% endfor %} A few things that trip people up: offer.variant_ids are stored as strings , so cast the line item's variant_id with | append: '' before contains — otherwise the match silently fails. Prefer preorder_shipping_text (the preorder-specific message); fall back to the general shipping_text . If your shipping text contains a {{ date }} placeholder, substitute it from the offer's delivery_exact_time (see the delivery-date example further down this page). You can instead match a line by its selling plan: line_item.selling_plan_allocation.selling_plan.id against the offer's shopify_selling_plan_id . Selling Plans Metafield Configuration Key: selling_plans Type: json Namespace: restockrocket_production Owner: Shop Purpose Stores the complete configuration of all enabled selling plans (preorder offers) for a shop. Fields Copy { "shopify_selling_plan_group_id" : "string" , // Unique identifier for the selling plan group in Shopify "shopify_selling_plan_id" : "string" , // Unique identifier for the specific selling plan in Shopify "enabled" : boolean , // Whether the selling plan is active "variant_ids" : [ "string" ], // List of product variant IDs associated with this plan "name" : "string" , // Display name of the selling plan "preorder_button_text" : "string" , // Text shown on the preorder button "preorder_button_description" : "string" , // Description shown below the preorder button "preorder_button_description_background_color" : "string" , // Background color of the description box "preorder_button_description_text_color" : "string" , // Text color of the description "preorder_button_description_border_radius" : "number" , // Border radius of the description box "preorder_button_description_show_quantity_limit" : boolean , // Whether to show quantity limits "preorder_button_description_quantity_limit_suffix" : "string" , // Text after quantity limit "preorder_button_description_shipping_text_prefix" : "string" , // Text before shipping info "delivery_exact_time" : "string" , // Exact time of delivery "quantity_limit_text" : "string" , // Text explaining quantity limitations "preorder_button_description_show_shipping" : boolean , // Whether to show shipping info "preorder_shipping_text" : "string" , // Shipping information for preorder items "shipping_applies_to_all_products" : boolean , // Whether shipping settings are global "shipping_text" : "string" , // General shipping information "payment_type" : "string" , // Type of payment (full/partial) "billing_checkout_charge_type" : "string" , // How the charge is calculated "billing_checkout_charge_amount" : "number" , // Fixed amount for checkout charge "billing_checkout_charge_percentage" : "number" , // Percentage amount for checkout "pricing_type" : "string" , // Type of pricing adjustment "pricing_amount" : "number" , // Fixed amount for pricing "pricing_percentage" : "number" , // Percentage for pricing "discount_text" : "string" , // Text explaining discounts "billing_title" : "string" , // Title for billing information "billing_description" : "string" , // Detailed billing information "enable_billing_widget" : boolean , // Whether to show billing widget "inventory_provider" : "string" , // Provider handling inventory "preorder_badge_enabled" : boolean , // Whether to show preorder badge "preorder_badge_text" : "string" , // Text shown on the badge "preorder_badge_text_color" : "string" , // Color of badge text "preorder_badge_background_color" : "string" , // Background color of badge "translations" : { // Translations for multilingual support "locale_code" : { "shipping_text" : "string" , "billing_title" : "string" , "billing_description" : "string" , "discount_text" : "string" , "preorder_badge_text" : "string" , "preorder_button_description" : "string" , "quantity_limit_text" : "string" , "preorder_shipping_text" : "string" , "preorder_button_text" : "string" } } } Accessing in Liquid Copy {% assign current_variant_id = product.selected_or_first_available_variant.id %} {% assign selling_plans = shop.metafields.restockrocket_production.selling_plans.value %} {% for plan in selling_plans %} {% if plan.enabled %} {% assign variant_ids = plan.variant_ids %} {% if variant_ids contains current_variant_id %} {% comment %} This variant is part of the selling plan You can now access plan details: {% endcomment %} {{ plan.preorder_badge_text }} {% if plan.preorder_button_description_show_quantity_limit %} {% assign preorder_count = product.selected_or_first_available_variant.metafields.restockrocket_production.preorder_count | default: 0 %} {% assign preorder_max_count = product.selected_or_first_available_variant.metafields.restockrocket_production.preorder_max_count %} {% if preorder_max_count %} {% assign remaining = preorder_max_count | minus: preorder_count %} {{ remaining }}{{ plan.preorder_button_description_quantity_limit_suffix }} {% endif %} {% endif %} {% endif %} {% endif %} {% endfor %} Example: Working with Delivery Times Copy {% assign current_variant_id = product.selected_or_first_available_variant.id %} {% assign selling_plans = shop.metafields.restockrocket_production.selling_plans.value %} {% for plan in selling_plans %} {% if plan.enabled %} {% assign is_variant_in_plan = false %} {% for variant_id in plan.variant_ids %} {% if variant_id == current_variant_id %} {% assign is_variant_in_plan = true %} {% break %} {% endif %} {% endfor %} {% if is_variant_in_plan and plan.delivery_exact_time %} {% assign delivery_date = plan.delivery_exact_time | date: '%Y-%m-%d' | date: '%s' %} {% assign today_date = 'now' | date: '%Y-%m-%d' | date: '%s' %} {% assign seconds_diff = delivery_date | minus: today_date %} {% assign days_until_delivery = seconds_diff | divided_by: 86400 %} Delivery Information {% if days_until_delivery > 0 %} Estimated delivery: {{ plan.delivery_exact_time | date: '%B %d, %Y' }} ({{ days_until_delivery }} days left) {% if plan.preorder_shipping_text %} {% assign formatted_date = plan.delivery_exact_time | date: '%B %d, %Y' %} {% capture date %}{{ formatted_date | escape }}{% endcapture %} {{ plan.preorder_shipping_text | url_escape | replace: "%7B%7B%20date%20%7D%7D", date | replace: "%20", " " }} {% endif %} {% else %} Delivery date approaching {% endif %} {% endif %} {% endif %} {% endfor %} Accessing via GraphQL Copy # Query to get shop selling plans metafield { shop { metafield ( namespace : "restockrocket_production" , key : "selling_plans" ) { id namespace key value type } } } # Query to get multiple shop metafields at once { shop { metafields ( namespace : "restockrocket_production" , first : 10 ) { edges { node { id namespace key value type } } } } } This example demonstrates: Converting the delivery time to a readable format Calculating days remaining until delivery Displaying a formatted delivery date Replacing placeholders in shipping text Adding conditional styling based on delivery status Basic CSS styling for the delivery information Simple examples of using STOQ's shop-level metafields with Shopify liquid Example 1: Basic Preorder Badge Use Case : Show preorder badge when variant is part of a selling plan. Copy {% assign current_variant_id = product.selected_or_first_available_variant.id %} {% assign selling_plans = shop.metafields.restockrocket_production.selling_plans.value %} {% for plan in selling_plans %} {% if plan.enabled and plan.preorder_badge_enabled %} {% if plan.variant_ids contains current_variant_id %} style="background: {{ plan.preorder_badge_background_color }}; color: {{ plan.preorder_badge_text_color }};"> {{ plan.preorder_badge_text }} {% endif %} {% endif %} {% endfor %} Example 2: Preorder Button Text Use Case : Use custom button text from selling plan configuration. Copy {% assign current_variant_id = product.selected_or_first_available_variant.id %} {% assign selling_plans = shop.metafields.restockrocket_production.selling_plans.value %} {% assign button_text = "Preorder Now" %} {% for plan in selling_plans %} {% if plan.enabled and plan.variant_ids contains current_variant_id %} {% assign button_text = plan.preorder_button_text %} {% break %} {% endif %} {% endfor %} {{ button_text }} Example 3: Show Remaining Quantity Use Case : Display how many preorder spots are left. Copy {% assign current_variant = product.selected_or_first_available_variant %} {% assign selling_plans = shop.metafields.restockrocket_production.selling_plans.value %} {% for plan in selling_plans %} {% if plan.enabled and plan.variant_ids contains current_variant.id and plan.preorder_button_description_show_quantity_limit %} {% assign preorder_count = current_variant.metafields.restockrocket_production.preorder_count | default: 0 %} {% assign preorder_max_count = current_variant.metafields.restockrocket_production.preorder_max_count %} {% if preorder_max_count %} {% assign remaining = preorder_max_count | minus: preorder_count %} {{ remaining }}{{ plan.preorder_button_description_quantity_limit_suffix | default: ' left' }} {% endif %} {% endif %} {% endfor %} Example 4: Delivery Date Display Use Case : Show estimated delivery date from selling plan. Copy Estimated Delivery {{ plan.delivery_exact_time | date: '%B %d, %Y' }} {% if plan.preorder_shipping_text %} {{ plan.preorder_shipping_text | replace: '{{ date }}', plan.delivery_exact_time | date: '%B %d, %Y' }} {% endif %} ### Product Variant Level Metafields Path: `/frontend/docs/preorder-metafields/product-variant-level-metafields/` Key: sellingplanids Type: json Namespace: restockrocketproduction Owner: Product Variant 1. Selling Plan IDs Metafield Configuration Key : selling_plan_ids Type : json Namespace : restockrocket_production Owner : Product Variant Purpose Stores the list of selling plan IDs associated with a variant Used to track which preorder plans are available for a variant Enables quick lookup of available preorder options Stored as a JSON array of selling plan IDs Accessing in Liquid Copy {% assign selling_plan_ids = product_variant.metafields.restockrocket_production.selling_plan_ids.value | json %} 2. Preorder Count Metafield Configuration Key: preorder_count Type: number_integer Namespace: restockrocket_production Owner: Product Variant Purpose Tracks the current number of preorders for a specific variant Used for inventory management and display Automatically updated when orders are processed Accessing in Liquid Copy {% assign preorder_count = product_variant.metafields.restockrocket_production.preorder_count %} 3. Preorder Max Count Metafield Configuration Key: preorder_max_count Type: number_integer Namespace: restockrocket_production Owner: Product Variant Purpose Sets the maximum number of preorders allowed for a variant Controls inventory policy switching Triggers blocking orders when count reaches max Accessing in Liquid Copy {% assign preorder_max_count = product_variant.metafields.restockrocket_production.preorder_max_count %} Example Usage: Preorder Count and Max count Copy {% assign variant = product.selected_or_first_available_variant %} {% assign preorder_count = variant.metafields.restockrocket_production.preorder_count.value | default: 0 %} {% assign preorder_max = variant.metafields.restockrocket_production.preorder_max_count.value %} {% if preorder_max %} {% assign spots_remaining = preorder_max | minus: preorder_count %} {% if spots_remaining > 0 %} {{ spots_remaining }} spots remaining {% if spots_remaining Almost sold out! {% endif %} {% else %} Preorder limit reached {% endif %} {% endif %} Copy # Query to get metafields for a specific product variant { productVariant ( id : "gid://shopify/ProductVariant/YOUR_VARIANT_ID" ) { metafields ( namespace : "restockrocket_production" , first : 10 ) { edges { node { id namespace key value type } } } } } # Query to get specific metafields for multiple variants of a product { product ( id : "gid://shopify/Product/YOUR_PRODUCT_ID" ) { variants ( first : 10 ) { edges { node { id title metafields ( namespace : "restockrocket_production" , keys : [ "preorder_count" , "preorder_max_count" , "shipping_text" ] ) { edges { node { id key value type } } } } } } } } 4. Shipping Text Metafield Configuration Key: shipping_text Type: single_line_text_field Namespace: restockrocket_production Owner: Product Variant Note Most stores will not have this metafield. The variant-level shipping_text metafield is only written when a merchant sets a per-variant shipping-text override for that variant in the offer's Variants tab — and it's only used at runtime when the offer's "Apply shipping text to all products" setting is off ( shipping_applies_to_all_products = false ). For the typical store — one shipping message for the whole offer ( shipping_applies_to_all_products = true , the default) — STOQ stores the shipping text once on the offer , not on each variant, so this variant metafield will not exist and the Liquid below returns nothing. To read an offer's shipping text reliably, use the shop-level selling_plans metafield — see Show preorder shipping text on the cart page . Purpose Stores a per-variant override of the offer's shipping/delivery text, as a plain string. Only populated when a per-variant override has been entered (see the note above); otherwise read the offer-level text from the shop-level selling_plans metafield. Per- market overrides live in a separate market_shipping_text metafield ( json , keyed by Shopify market id) — not in this field. Accessing in Liquid Copy {% comment %} Present only if a per-variant override was set on this variant. {% endcomment %} {% assign variant_shipping = product_variant.metafields.restockrocket_production.shipping_text.value %} Here's an example of how to access the above metafields via GraphQL Accessing via GraphQL Copy # Query to get metafields for a specific product variant { productVariant ( id : "gid://shopify/ProductVariant/YOUR_VARIANT_ID" ) { metafields ( namespace : "restockrocket_production" , first : 10 ) { edges { node { id namespace key value type } } } } } # Query to get specific metafields for multiple variants of a product { product ( id : "gid://shopify/Product/YOUR_PRODUCT_ID" ) { variants ( first : 10 ) { edges { node { id title metafields ( namespace : "restockrocket_production" , keys : [ "preorder_count" , "preorder_max_count" , "shipping_text" ] ) { edges { node { id key value type } } } } } } } } Simple examples of using STOQ's variant-level metafields with basic Shopify liquid Use Case : Show current preorder count and remaining spots. Copy {% assign target_variant = product.selected_or_first_available_variant %} {% assign preorder_count = target_variant.metafields.restockrocket_production.preorder_count | default: 0 %} {% assign preorder_max_count = target_variant.metafields.restockrocket_production.preorder_max_count %} {% if preorder_max_count %} {% assign remaining = preorder_max_count | minus: preorder_count %} Preorder Status {{ preorder_count }} preordered {{ remaining }} remaining {% endif %} Use Case : Show a variant's shipping override , if one is set. shipping_text is a plain string and only exists when a per-variant override was entered (see the note above). For the offer's shipping text — what most stores want — read the shop-level selling_plans metafield instead, e.g. the cart-page recipe . Copy {% assign target_variant = product.selected_or_first_available_variant %} {% assign variant_shipping = target_variant.metafields.restockrocket_production.shipping_text.value %} {% if variant_shipping != blank %} {{ variant_shipping }} {% endif %} ### Market level product variant Metafields Path: `/frontend/docs/preorder-metafields/market-level-product-variant-metafields/` This document outlines the structure and usage of market-specific metafields for product variants. These metafields enable market-specific order limit tracking, and shipping information display. This document outlines the structure and usage of market-specific metafields for product variants. These metafields enable market-specific order limit tracking, and shipping information display. 1. Market Preorder Count Metafield Configuration Key : market_preorder_count Type : json Namespace : restockrocket_production Owner : Product Variant Purpose Tracks the current number of preorders for a variant per market Used for market-specific inventory management Automatically updated when orders are processed in different markets Stored as a JSON object mapping market IDs to counts Accessing in Liquid Copy {% assign market_preorder_count = product_variant.metafields.restockrocket_production.market_preorder_count.value | json %} Copy Example value of market_preroder_count => { "market_id_1": 1, "market_id_2": 5, "market_id_3": 3 } 2. Market Preorder Max Count Metafield Configuration Key : market_preorder_max_count Type : json Namespace : restockrocket_production Owner : Product Variant Purpose Sets the maximum number of preorders allowed per market Controls market-specific inventory policy switching Triggers blocking orders when count reaches max in specific markets Stored as a JSON object mapping market IDs to maximum counts Accessing in Liquid Copy {% assign market_preorder_max_count = product_variant.metafields.restockrocket_production.market_preorder_max_count.value | json %} Copy Example value of market_preorder_max_count => { "market_id_1": 10, "market_id_2": 15, "market_id_3": 10 } Example Usage: Market-Specific Preorder Count and Max Count Copy {% assign variant = product.selected_or_first_available_variant %} {% assign market_counts = variant.metafields.restockrocket_production.market_preorder_count.value | json %} {% assign market_max_counts = variant.metafields.restockrocket_production.market_preorder_max_count.value | json %} {% assign current_market = shop.market.id %} {% if market_max_counts[current_market] %} {% assign current_count = market_counts[current_market] %} {% assign max_count = market_max_counts[current_market] %} {% assign spots_remaining = max_count | minus: current_count %} {% if spots_remaining > 0 %} {{ spots_remaining }} spots remaining in your market {% if spots_remaining Almost sold out in your region! {% endif %} {% else %} Preorder limit reached for your market {% endif %} {% endif %} 3. Market Shipping Text Metafield Configuration Key : market_shipping_text Type : json Namespace : restockrocket_production Owner : Product Variant Purpose Stores market-specific shipping/delivery timeline text Allows different shipping information per market Stored as a JSON object mapping market IDs to shipping text Accessing in Liquid Copy {% assign market_shipping_text = product_variant.metafields.restockrocket_production.market_shipping_text.value | json %} Copy Example value of market_shipping_text => { "market_id_1": "Ships in 1 week", "market_id_2": "Ships in 2 weeks", "market_id_3": "Ships in 4 days" } Accessing via GraphQL Query for Market-Specific Metafields Copy { productVariant ( id : "gid://shopify/ProductVariant/YOUR_VARIANT_ID" ) { metafields ( namespace : "restockrocket_production" , keys : [ "market_preorder_count" , "market_preorder_max_count" , "market_shipping_text" ] ) { edges { node { id key value type } } } } } Query for Multiple Variants with Market Data Copy { product ( id : "gid://shopify/Product/YOUR_PRODUCT_ID" ) { variants ( first : 10 ) { edges { node { id title metafields ( namespace : "restockrocket_production" , keys : [ "market_preorder_count" , "market_preorder_max_count" , "market_shipping_text" ] ) { edges { node { id key value type # Example response for market_preorder_count: # value: {"market_id_1": 45, "market_id_2": 20, "market_id_3": 15} # Example response for market_preorder_max_count: # value: {"market_id_1": 100, "market_id_2": 50, "market_id_3": 30} # Example response for market_shipping_text: # value: {"market_id_1": "Ships in 2 weeks", "market_id_2": "Ships in 3 weeks", "market_id_3": "Ships in 4 weeks"} } } } } } } } } ## MCP (Beta) ### stoq-api-v2 Path: `/mcp/` Version: 2.0.0-beta STOQ's MCP server exposes every v2 API action as a tool, so AI agents can run preorder and back-in-stock operations directly — no glue code. Tool descriptions include the natural-language aliases merchants use, so an agent can map an intent to a single call. ## How the API works - **Action-driven, not endpoint-driven.** Each tool maps 1:1 to a v2 API action; descriptions carry the natural-language aliases merchants use, so you can match a request to a single tool. - **Two domains.** *Preorders* (offers, orders, product variants, reports) and *Back in Stock* (signups, notifications, settings, reports). Tools are grouped by domain and resource below. - **Named tools carry side effects; `*_update` tools are deep-partial PATCH** for settings and toggles — only the fields you send change. - **Results.** Reads return the resource; writes return the updated resource or `{ job_id }` for bulk/async work (poll the matching jobs tool). Failures return `{ success: false, status, errors: [...] }`; `status: "conflict"` means an invalid lifecycle transition — read the error, don't retry blindly. - **More discovery.** `GET /api/v2/external/help` is the canonical machine-readable manifest. The full agent guide lives at `GET /api/v2/external/skill.md` (and `/preorders/skill.md`, `/back_in_stock/skill.md`). ## Connect - **Endpoint:** `https://app.stoqapp.com/api/v2/external/mcp` (JSON-RPC over HTTP) - **Auth:** send your STOQ API key in the `X-Auth-Token` header — find it in the STOQ app under **Settings → Integrations → API Key**. Most MCP clients take a remote server URL plus headers. Generic config: ```json { "mcpServers": { "stoq": { "url": "https://app.stoqapp.com/api/v2/external/mcp", "headers": { "X-Auth-Token": "YOUR_STOQ_API_KEY" } } } } ``` ## Test the connection List the available tools with a JSON-RPC `tools/list` call — you should get back the tools documented below: ```bash curl -X POST https://app.stoqapp.com/api/v2/external/mcp \ -H "X-Auth-Token: $STOQ_API_KEY" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` Then call one — e.g. list your preorder offers: ```bash curl -X POST https://app.stoqapp.com/api/v2/external/mcp \ -H "X-Auth-Token: $STOQ_API_KEY" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call", "params":{"name":"preorders_offers_list","arguments":{}}}' ``` Full API guide for agents: `GET /api/v2/external/preorders/skill.md`. #### Operations ##### TOOL back_in_stock_notifications_list Summary: back_in_stock_notifications_list List back-in-stock notifications (the send log) in this shop, paginated. Aliases: list notifications, back in stock send log, restock notifications sent, who got notified, notification history, show sent restock alerts. Note: status=blocked surfaces sends stopped by plan limits (with blocked_reason). Parameters: - `channel` (argument, unknown, optional) - `status` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_notifications_read Summary: back_in_stock_notifications_read Read one back-in-stock notification (a single send-log entry). Aliases: show notification, get notification, look up sent restock alert, notification details. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL back_in_stock_reports_list Summary: back_in_stock_reports_list List every available back-in-stock report (name, description, aliases, url). Aliases: reports, list reports, available reports, what reports are there, report catalog. ##### TOOL back_in_stock_reports_conversions_read Summary: back_in_stock_reports_conversions_read Time-series of orders attributed to back-in-stock alerts and the revenue recovered. Aliases: did the alerts convert, recovered revenue, conversion trend, orders from alerts, revenue recovered over time. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) ##### TOOL back_in_stock_reports_conversions_export Summary: back_in_stock_reports_conversions_export Async CSV export of the back-in-stock conversions time series. Aliases: export conversions, download conversions, conversions csv, recovered revenue csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `sort_by` (argument, unknown, optional) - `direction` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_reports_exports_read Summary: back_in_stock_reports_exports_read Check the status of an async report export and get the download URL when ready. Aliases: check export status, get export, poll export, download url for export, export status. Parameters: - `job_id` (argument, string, required) - URL path parameter :job_id ##### TOOL back_in_stock_reports_notifications_read Summary: back_in_stock_reports_notifications_read Time-series of back-in-stock notifications sent, split by channel (email/sms/push). Aliases: notification trend, alerts sent, sends by channel, how many alerts went out, blocked notifications. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) ##### TOOL back_in_stock_reports_notifications_export Summary: back_in_stock_reports_notifications_export Async CSV export of the back-in-stock notifications time series. Aliases: export notifications, download notifications, notifications csv, alerts csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `sort_by` (argument, unknown, optional) - `direction` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_reports_products_in_demand_read Summary: back_in_stock_reports_products_in_demand_read Ranked variants by back-in-stock demand (pending/total signups, last requested). Aliases: demand report, what should I restock, most wanted products, top requested variants, products in demand, restock priorities. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `sort_by` (argument, unknown, optional) - `direction` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_reports_products_in_demand_export Summary: back_in_stock_reports_products_in_demand_export Async CSV export of the back-in-stock products-in-demand report. Aliases: export demand report, download products in demand, demand csv, restock list csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `sort_by` (argument, unknown, optional) - `direction` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_reports_signups_read Summary: back_in_stock_reports_signups_read Time-series of back-in-stock signups created (day/week/month). Aliases: signup trend, signups over time, waitlist growth, new signups, daily signups. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) ##### TOOL back_in_stock_reports_signups_export Summary: back_in_stock_reports_signups_export Async CSV export of the back-in-stock signups time series. Aliases: export signups, download signups, signups csv, waitlist csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `sort_by` (argument, unknown, optional) - `direction` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_reports_summary_read Summary: back_in_stock_reports_summary_read High-level overview of back-in-stock activity over a date window. Aliases: summary, overview, how is back in stock doing, how many people are waiting, waitlist size, dashboard stats. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) ##### TOOL back_in_stock_reports_summary_export Summary: back_in_stock_reports_summary_export Async CSV export of the back-in-stock summary report. Aliases: export summary, download summary, summary csv, export overview. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `channel` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `sort_by` (argument, unknown, optional) - `direction` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_settings_update Summary: back_in_stock_settings_update Update back-in-stock settings for the shop. Aliases: edit settings, change settings, modify settings, update back in stock settings, configure notifications. Note: Accepts any combination of capability blobs: channels, delivery, compliance, tagging, alerts — deep partial, delegated to the capability PATCHes, errors accumulated. Boolean toggles are set via this PATCH (or the capability PATCH): channels.email.enabled, channels.sms.enabled, channels.push.enabled, delivery.batching.enabled, delivery.locations.filter_enabled, delivery.any_variant.enabled, compliance.optin_required, tagging.order_tags.enabled, alerts.signups_threshold.notify, alerts.scheduled_reports.daily, alerts.scheduled_reports.weekly, alerts.scheduled_reports.monthly — e.g. { channels: { sms: { enabled: true } } }. Parameters: - `channels` (argument, unknown, optional) - `delivery` (argument, unknown, optional) - `compliance` (argument, unknown, optional) - `tagging` (argument, unknown, optional) - `alerts` (argument, unknown, optional) ##### TOOL back_in_stock_settings_read Summary: back_in_stock_settings_read Read all back-in-stock settings for the shop. Aliases: get settings, show settings, view settings, back in stock settings, notification settings, settings. ##### TOOL back_in_stock_settings_alerts_read Summary: back_in_stock_settings_alerts_read Read back-in-stock merchant alert and scheduled report settings. Aliases: get alert settings, show alerts, view signup threshold, view scheduled reports, alerts. ##### TOOL back_in_stock_settings_alerts_update Summary: back_in_stock_settings_alerts_update Update back-in-stock merchant alert and scheduled report settings. Aliases: edit alert settings, change alert settings, set signup threshold, notify me on high demand, enable signup threshold alerts, disable signup threshold alerts, alert me when a product gets lots of signups, enable daily report, enable weekly report, enable monthly report, disable daily report, disable weekly report, disable monthly report, schedule reports, turn off report emails. Note: Toggles set via this PATCH: signups_threshold.notify, scheduled_reports.daily, scheduled_reports.weekly, scheduled_reports.monthly — e.g. { scheduled_reports: { weekly: true } }. signups_threshold.count is the per-variant signup count that triggers the merchant alert email. Parameters: - `signups_threshold` (argument, unknown, optional) - `scheduled_reports` (argument, unknown, optional) ##### TOOL back_in_stock_settings_channels_update Summary: back_in_stock_settings_channels_update Update back-in-stock notification channel toggles. Aliases: edit channels, change channels, modify channels, enable email notifications, turn on email alerts, disable email notifications, turn off email alerts, enable sms, turn on SMS alerts, enable text notifications, disable sms, turn off SMS alerts, stop text notifications, enable push notifications, turn on web push, disable push notifications, turn off web push. Note: Channel toggles are set via this PATCH: email.enabled, sms.enabled, push.enabled — e.g. { sms: { enabled: true } }. Enabling SMS for the first time triggers a restricted-content verification check; sends may be held until the shop is verified. Parameters: - `email` (argument, unknown, optional) - `sms` (argument, unknown, optional) - `push` (argument, unknown, optional) ##### TOOL back_in_stock_settings_channels_read Summary: back_in_stock_settings_channels_read Read back-in-stock notification channel settings. Aliases: get channels, show channels, view notification channels, which channels are enabled, channels. ##### TOOL back_in_stock_settings_compliance_update Summary: back_in_stock_settings_compliance_update Update back-in-stock compliance settings (double opt-in). Aliases: edit compliance settings, change compliance settings, require double opt-in, enable double opt-in, turn on double opt-in, require opt-in confirmation, gdpr opt-in, disable double opt-in, turn off double opt-in, remove opt-in confirmation. Note: The double-opt-in toggle is set via this PATCH: optin_required — e.g. { optin_required: true }. When enabled, signups must confirm via email before they are eligible for notifications (GDPR double opt-in). Parameters: - `optin_required` (argument, unknown, optional) ##### TOOL back_in_stock_settings_compliance_read Summary: back_in_stock_settings_compliance_read Read back-in-stock compliance settings (double opt-in). Aliases: get compliance settings, show compliance, view opt-in settings, is double opt-in enabled, compliance. ##### TOOL back_in_stock_settings_delivery_read Summary: back_in_stock_settings_delivery_read Read back-in-stock delivery settings (batching, stock threshold, locations, any-variant). Aliases: get delivery settings, show delivery settings, view batching, view stock threshold, view location filter, delivery. ##### TOOL back_in_stock_settings_delivery_update Summary: back_in_stock_settings_delivery_update Update back-in-stock delivery settings (batching, stock threshold, locations, any-variant). Aliases: edit delivery settings, change delivery settings, batch notifications, enable batching, turn on batching, disable batching, turn off batching, send all at once, set batch size, set batching multiplier, set batching mode, set wait between batches, set stock threshold, set minimum stock to notify, only notify when 5 in stock, require minimum quantity before notifying, filter by location, enable location filter, disable location filter, set notification locations, only count inventory at locations, notify for any variant, enable any variant notifications, disable any variant notifications, notify when any variant restocks. Note: Deep partial. Toggles set via this PATCH: batching.enabled, locations.filter_enabled, any_variant.enabled — e.g. { batching: { enabled: true } }. batching.mode is an enum: 'multiplier' (batch size = restocked quantity x batching.multiplier) or 'fixed' (batch size = batching.batch_size); batching.wait_hours is the wait between batches. stock_threshold is the minimum restocked quantity before notifications send (>= 1). locations.location_ids and any_variant.product_ids are arrays of Shopify numeric ids and are replaced wholesale when present. Parameters: - `batching` (argument, unknown, optional) - `stock_threshold` (argument, unknown, optional) - `locations` (argument, unknown, optional) - `any_variant` (argument, unknown, optional) ##### TOOL back_in_stock_settings_tagging_read Summary: back_in_stock_settings_tagging_read Read back-in-stock order tagging settings. Aliases: get tagging settings, show order tagging, view order tag, what tag is applied to recovered orders, tagging. ##### TOOL back_in_stock_settings_tagging_update Summary: back_in_stock_settings_tagging_update Update back-in-stock order tagging settings. Aliases: edit tagging settings, change tagging settings, tag recovered orders, enable order tagging, turn on order tagging, disable order tagging, turn off order tagging, set back in stock order tag, change order tag, rename order tag. Note: The toggle is set via this PATCH: order_tags.enabled — e.g. { order_tags: { enabled: true } }. order_tags.tag is the single tag applied to Shopify orders attributed to a back-in-stock notification (default 'STOQ-back-in-stock'). Parameters: - `order_tags` (argument, unknown, optional) ##### TOOL back_in_stock_signups_create Summary: back_in_stock_signups_create Create a back-in-stock signup (add a customer to the waitlist for a variant). Aliases: create signup, add to waitlist, add customer to the notify me list, register a restock alert, sign someone up for back in stock. Note: email is required for the email channel, phone for sms. shopify_product_id is resolved from the variant when omitted. Returns 409 when a pending signup already exists for the same contact + variant + channel. Parameters: - `channel` (argument, unknown, required) - `shopify_variant_id` (argument, unknown, required) - `quantity` (argument, integer, optional) - `shopify_product_id` (argument, unknown, optional) - `email` (argument, unknown, optional) - `phone` (argument, unknown, optional) - `name` (argument, unknown, optional) ##### TOOL back_in_stock_signups_list Summary: back_in_stock_signups_list List back-in-stock signups (the waitlist) in this shop, paginated. Aliases: list signups, waitlist, show the waitlist, notify me list, who's waiting, back in stock signups, list waitlist entries, restock signups, customers waiting for restock. Note: status compiles notified_at / unsubscribed_at / blocked_at into one lifecycle value; email and phone filters match partial values on the signup or its customer. Parameters: - `channel` (argument, unknown, optional) - `status` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `email` (argument, unknown, optional) - `phone` (argument, unknown, optional) - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL back_in_stock_signups_read Summary: back_in_stock_signups_read Read one back-in-stock signup's full representation. Aliases: show signup, get signup, look up waitlist entry, view restock signup, signup details. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL back_in_stock_signups_delete Summary: back_in_stock_signups_delete Delete a pending back-in-stock signup (remove the customer from the waitlist). Aliases: delete signup, remove from waitlist, remove waitlist entry, cancel restock alert, take customer off the notify me list. Note: Pending signups only; deleting an already-notified signup returns 409. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL back_in_stock_signups_notify Summary: back_in_stock_signups_notify Send the back-in-stock notification for one signup now, regardless of stock state. Aliases: notify signup, send the restock email, notify this customer, send back in stock notification, resend restock notification. Note: 409 when the signup was already notified and allow_resend is not set. Parameters: - `allow_resend` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL back_in_stock_signups_bulk_delete Summary: back_in_stock_signups_bulk_delete Delete up to 1000 pending back-in-stock signups in one request. Aliases: bulk delete signups, clear the waitlist, delete waitlist entries, remove signups in bulk, purge pending restock signups. Note: Pending-only: 422 listing the offending ids when any signup was already notified. Parameters: - `signup_ids` (argument, array, required) ##### TOOL back_in_stock_signups_bulk_notify Summary: back_in_stock_signups_bulk_notify Send back-in-stock notifications for up to 1000 signups in one request. Aliases: bulk notify signups, notify the waitlist, notify everyone waiting, send restock emails to the whole waitlist, bulk send back in stock notifications. Note: Responds 200 when all succeed, 207 with { success, failed } on partial failure. Already-notified signups fail unless allow_resend is set. Parameters: - `signup_ids` (argument, array, required) - `allow_resend` (argument, unknown, optional) ##### TOOL back_in_stock_signups_transfer Summary: back_in_stock_signups_transfer Move back-in-stock signups from one variant to another (variant merges or replacements). Aliases: transfer signups, move the waitlist, move signups to another variant, merge waitlists, shift restock signups to a new variant. Note: scope picks which signups move: pending (default), notified, or all. Signups that would duplicate a pending signup on the target variant are dropped. from_shopify_product_id is resolved from the source variant when omitted. Parameters: - `from_shopify_variant_id` (argument, unknown, required) - `to_shopify_variant_id` (argument, unknown, required) - `to_shopify_product_id` (argument, unknown, required) - `scope` (argument, unknown, optional) - `from_shopify_product_id` (argument, unknown, optional) ##### TOOL preorders_offers_list Summary: preorders_offers_list List preorder offers in this shop, paginated. Aliases: list offers, show all offers, preorder offers, all preorders. Parameters: - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_offers_create Summary: preorders_offers_create Create a new preorder offer. Aliases: new offer, make a preorder, create preorder offer. Parameters: - `name` (argument, unknown, optional) - `internal_name` (argument, unknown, optional) ##### TOOL preorders_offers_update Summary: preorders_offers_update Update top-level fields of a preorder offer. Aliases: edit offer, modify offer, change offer settings. Parameters: - `name` (argument, unknown, optional) - `internal_name` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_read Summary: preorders_offers_read Read a preorder offer's full representation. Aliases: show offer, get preorder offer, look up offer, view offer. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_advanced_update Summary: preorders_offers_advanced_update Update advanced (power-user) settings on a preorder offer. Aliases: edit advanced, change advanced settings, modify advanced, enable line item property, include in line item properties, add cart property, show on cart line, disable line item property, exclude from line item properties, remove cart property, hide from cart line, clear button text override, remove button text override, reset button text for state, use default button text. Note: Deep partial. Toggles and clears are done via this PATCH: the line-item-property toggles are `line_item_properties.include_shipping_text`, `.include_payment_breakdown`, and `.include_acknowledgement` (true/false each). `button_text_overrides` is deep-partial per state — send null to clear an override for one state, e.g. `{ "button_text_overrides": { "out_of_stock": null } }` removes that override (the read shape still emits all three states, with null for unset). `custom_css` is cleared by sending an empty string or null. Parameters: - `line_item_properties` (argument, unknown, optional) - `button_text_overrides` (argument, unknown, optional) - `use_shopify_selling_plan` (argument, unknown, optional) - `custom_css` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_advanced_read Summary: preorders_offers_advanced_read Read power-user settings for a preorder offer (line item properties, button text overrides, Shopify selling plan attachment). Aliases: get advanced, show advanced settings, view advanced settings, view line item properties, view advanced. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_advanced_attach_to_shopify_selling_plan Summary: preorders_offers_advanced_attach_to_shopify_selling_plan Re-attach the offer to a Shopify selling plan group (resume syncing). Aliases: attach to shopify selling plan, use shopify selling plans, resume shopify sync, reattach to shopify. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_advanced_detach_from_shopify_selling_plan Summary: preorders_offers_advanced_detach_from_shopify_selling_plan Detach the offer from Shopify selling plan groups; Stoq continues to manage it via the storefront integration. Aliases: detach from shopify selling plan, stop using shopify selling plans, manage selling plan internally, bypass shopify selling plan. Note: Strongly discouraged for most merchants. Breaks Shopify-native integrations like checkout selling plan display and third-party subscription apps. Does not delete the existing Shopify selling plan group — it just stops syncing. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_advanced_set_button_text_override Summary: preorders_offers_advanced_set_button_text_override Override the widget button text for one state (before_launch, after_launch, or out_of_stock). Aliases: set button text override, override button text, change button text for state, set state button text. Parameters: - `state` (argument, unknown, required) - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_advanced_set_custom_css Summary: preorders_offers_advanced_set_custom_css Set custom CSS overrides for this offer's storefront widget. Send an empty string to clear. Aliases: set custom css, add custom css, override widget css, customize widget styles, clear custom css. Parameters: - `css` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_advanced_set_custom_line_item_property Summary: preorders_offers_advanced_set_custom_line_item_property Set a free-text custom line item property added to every preorder cart line. Aliases: set custom line item property, set custom cart property, set custom cart text, add custom cart text. Parameters: - `text` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_checkout_read Summary: preorders_offers_checkout_read Read checkout settings for a preorder offer. Aliases: get checkout settings, show checkout, view terms, view mixed cart, view order tags, checkout. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_checkout_update Summary: preorders_offers_checkout_update Update checkout settings on a preorder offer. Aliases: edit checkout, change checkout settings, modify checkout, enable terms, require terms, require acknowledgement, show terms checkbox, turn on terms, disable terms, hide terms checkbox, remove terms requirement, turn off terms, enable disable until acknowledged, require acknowledgement before buying, lock button until terms accepted, grey out button until terms checked, disable disable until acknowledged, don't lock button on terms, allow buying without acknowledging first, keep button enabled before terms, include terms in line item properties, record terms acceptance on order, add acknowledgement to line items, show terms accepted on order, allow mixed cart, permit mixed cart, enable mixed cart, let preorders mix with regular items, block mixed cart, disable mixed cart, prevent mixed cart, force separate checkout. Note: Boolean toggles are set via this PATCH: terms.enabled, terms.disable_button_until_acknowledged, terms.include_in_line_item_properties, mixed_cart.allowed — this PATCH is how you turn these on/off, e.g. { terms: { enabled: true } } or { mixed_cart: { allowed: false } }. Parameters: - `terms` (argument, unknown, optional) - `mixed_cart` (argument, unknown, optional) - `order_tags` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_checkout_add_order_tag Summary: preorders_offers_checkout_add_order_tag Add a tag to the set applied to orders containing this offer. Aliases: add order tag, add tag to preorder orders, append order tag. Parameters: - `tag` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_checkout_remove_order_tag Summary: preorders_offers_checkout_remove_order_tag Remove a tag from the set applied to orders containing this offer. Aliases: remove order tag, delete order tag, drop order tag. Parameters: - `tag` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_checkout_set_mixed_cart_error_message Summary: preorders_offers_checkout_set_mixed_cart_error_message Set the error message shown when a blocked mixed cart reaches checkout. Aliases: set mixed cart error message, change mixed cart message, set mixed cart warning, customize mixed cart error. Parameters: - `message` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_checkout_set_order_tags Summary: preorders_offers_checkout_set_order_tags Replace the set of tags applied to orders containing this offer. Aliases: set order tags, replace order tags, change order tags, set tags on preorder orders. Parameters: - `tags` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_checkout_set_terms_text Summary: preorders_offers_checkout_set_terms_text Set the terms-acceptance text shown next to the checkbox. Aliases: set terms text, change terms text, update terms, change acknowledgement text, set fine print. Parameters: - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_update Summary: preorders_offers_countdown_update Update countdown timer settings on a preorder offer. Aliases: edit countdown, change timer, modify countdown settings, enable countdown, show timer, turn on countdown, enable countdown timer, disable countdown, hide timer, turn off countdown, disable countdown timer, set countdown text, set timer text, change countdown label, set timer label. Note: Deep partial. `countdown.enabled` is toggled via this PATCH (`enabled` true/false); `text` is an alias for `ends_text` — both write the same column, and an explicit `ends_text` wins when both are sent. Parameters: - `enabled` (argument, unknown, optional) - `mode` (argument, unknown, optional) - `custom_end_date` (argument, unknown, optional) - `custom_start_date` (argument, unknown, optional) - `format` (argument, unknown, optional) - `text` (argument, unknown, optional) - `starts_text` (argument, unknown, optional) - `ends_text` (argument, unknown, optional) - `unit_labels` (argument, unknown, optional) - `style` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_read Summary: preorders_offers_countdown_read Read the countdown timer configuration for a preorder offer. Aliases: get countdown, show timer, view countdown timer, view timer settings, countdown settings. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_set_custom_end_date Summary: preorders_offers_countdown_set_custom_end_date Set the custom end date for the countdown timer. Aliases: set custom end date, set timer end date, change timer deadline. Note: Has no visible effect unless mode is `to_custom_date`. Parameters: - `date` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_set_ends_text Summary: preorders_offers_countdown_set_ends_text Set the text shown next to the countdown timer when the offer is closing. Aliases: set ends text, set timer end text, set 'ends in' text. Parameters: - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_set_mode Summary: preorders_offers_countdown_set_mode Set whether the countdown timer follows the offer's schedule or a custom date. Aliases: set countdown mode, change timer mode, use schedule for timer, use custom date for timer. Parameters: - `mode` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_set_starts_text Summary: preorders_offers_countdown_set_starts_text Set the text shown before the countdown timer when the offer hasn't started yet. Aliases: set starts text, set timer pre-launch text, set 'starts in' text. Parameters: - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_set_style Summary: preorders_offers_countdown_set_style Set the countdown timer's visual style (type, colors, border radius). Aliases: set timer style, change timer style, set countdown style, change timer colors. Parameters: - `type` (argument, unknown, optional) - `text_color` (argument, unknown, optional) - `background_color` (argument, unknown, optional) - `border_radius` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_countdown_set_unit_labels Summary: preorders_offers_countdown_set_unit_labels Set the unit labels for the countdown timer (Days/Hours/Mins/Secs). Aliases: set unit labels, change timer labels, set countdown unit text, change timer unit text. Parameters: - `days` (argument, unknown, optional) - `hours` (argument, unknown, optional) - `minutes` (argument, unknown, optional) - `seconds` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_disable Summary: preorders_offers_disable Disable (pause) a preorder offer. Optionally flip attached variants' inventory policy back to DENY. Aliases: pause, turn off, deactivate, stop offer, end preorder, disable and stop overselling, close out preorder. Note: Variants are left at their existing inventory policy unless `update_inventory_policy: true` is passed. Parameters: - `update_inventory_policy` (argument, unknown, optional) - `variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_discard Summary: preorders_offers_discard Discard (soft-delete) a preorder offer. Aliases: delete, remove, archive, trash, soft delete. Note: Recoverable via restore indefinitely. Discarded offers are never hard-deleted. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_enable Summary: preorders_offers_enable Enable a preorder offer. Optionally flip attached variants' inventory policy to CONTINUE. Aliases: turn on, activate, launch, go live, start offer. Note: Variants are left at their existing inventory policy unless `update_inventory_policy: true` is passed. Parameters: - `update_inventory_policy` (argument, unknown, optional) - `variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_integrations_read Summary: preorders_offers_integrations_read Read integration settings for a preorder offer (POS, B2B). Aliases: get integrations, show integrations, view pos settings, view b2b settings, integration settings. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_integrations_update Summary: preorders_offers_integrations_update Update integration settings on a preorder offer. Aliases: edit integrations, change integration settings, modify integrations, enable pos, turn on pos, allow in pos, enable point of sale, disable pos, turn off pos, block from pos, disable point of sale, enable pos skip inventory check, skip inventory check on pos, bypass inventory at pos, allow pos sale without inventory, disable pos skip inventory check, enforce inventory check at pos, stop bypassing inventory at pos, enable b2b, turn on b2b, allow b2b, enable wholesale, disable b2b, turn off b2b, block b2b, disable wholesale. Note: Boolean toggles are set via this PATCH: pos.enabled, pos.skip_inventory_check, b2b.enabled — e.g. { pos: { enabled: false } } or { b2b: { enabled: true } }. Parameters: - `pos` (argument, unknown, optional) - `b2b` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_inventory_update Summary: preorders_offers_inventory_update Update inventory settings on a preorder offer. Aliases: edit inventory, change inventory settings, modify inventory, enable continue selling, allow overselling, enable oversell, auto continue selling, auto-apply continue selling on attach, disable continue selling, stop auto continue selling, disable oversell, prevent overselling on attach, enable auto apply continue selling, auto continue selling on attach, auto-flip new variants to continue, apply oversell to new variants, disable auto apply continue selling, stop auto continue selling on attach, don't auto-flip new variants, no oversell on new variants. Note: Deep partial. Setting provider to 'shopify' will force product_variants_source to 'custom' via the model callback. Continue-selling is toggled via this PATCH: `continue_selling.enabled` and `continue_selling.auto_apply_to_new_variants` BOTH map to the single underlying `inventory_policy_auto_enable` flag — the auto-flip-on-attach intent (newly-attached variants get their Shopify inventory_policy flipped to CONTINUE). When both fields are present, auto_apply_to_new_variants wins. Neither field flips EXISTING variants — to flip variants that are already attached, use POST .../products/bulk_toggle_inventory_policy, or pass the update_inventory_policy flag to the offer lifecycle enable/disable actions. Parameters: - `provider` (argument, unknown, optional) - `reservation` (argument, unknown, optional) - `continue_selling` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_inventory_read Summary: preorders_offers_inventory_read Read the inventory configuration for a preorder offer. Aliases: get inventory settings, show inventory, view inventory policy, view reservation timing, view stock handling. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_inventory_set_provider Summary: preorders_offers_inventory_set_provider Set the inventory provider for a preorder offer (stoq or shopify). Aliases: set inventory provider, switch inventory provider, use shopify inventory, use stoq inventory. Parameters: - `provider` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_inventory_set_reservation_timing Summary: preorders_offers_inventory_set_reservation_timing Set when inventory is reserved (at sale or at fulfillment). Aliases: set reservation timing, reserve on sale, reserve on fulfillment, change inventory reservation, set when inventory is held. Parameters: - `timing` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_limits_read Summary: preorders_offers_limits_read Read order quantity limits for a preorder offer. Aliases: get limits, show limits, view quantity limits, view order min max, limits. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_limits_update Summary: preorders_offers_limits_update Update quantity limits on a preorder offer. Aliases: edit limits, change limits, modify quantity limits, clear max per customer, remove customer limit, no customer limit, unlimited per customer, clear total max, remove total cap, no total limit, uncap total preorders, enable show remaining, show remaining units, show stock left, display quantity remaining, disable show remaining, hide remaining units, hide stock left, stop showing quantity. Note: Deep partial. Toggles and clears are done via this PATCH: clear a limit by sending null — `per_customer.max: null` removes the per-customer limit, `total.max: null` removes the total cap; `display.show_remaining` (true/false) toggles the remaining-units display on the storefront. Note: per_customer.max and total.max are stored config — storefront enforcement of these limits is follow-up work and not live yet. Parameters: - `per_order` (argument, unknown, optional) - `per_customer` (argument, unknown, optional) - `total` (argument, unknown, optional) - `display` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_limits_set_max_per_customer Summary: preorders_offers_limits_set_max_per_customer Set the maximum quantity a single customer can preorder across all their orders. Aliases: set max per customer, set customer limit, limit per customer, maximum per customer, one per customer. Note: Stored config only today — storefront enforcement of the per-customer limit is follow-up work and not live yet. Clear the limit via PATCH .../limits with `per_customer.max: null`. Parameters: - `max` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_limits_set_max_per_order Summary: preorders_offers_limits_set_max_per_order Set the maximum quantity a customer can preorder per order. Aliases: set maximum per order, set max quantity, maximum order quantity, set order limit. Parameters: - `max` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_limits_set_min_per_order Summary: preorders_offers_limits_set_min_per_order Set the minimum quantity a customer must preorder per order. Aliases: set minimum per order, set min quantity, minimum order quantity, set order minimum. Parameters: - `min` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_limits_set_total_max Summary: preorders_offers_limits_set_total_max Set the total quantity that can be preordered across all customers. Aliases: set total max, set total cap, cap total preorders, limit total units, set overall limit. Note: Stored config only today — storefront enforcement of the total cap is follow-up work and not live yet. Clear the cap via PATCH .../limits with `total.max: null`. Parameters: - `max` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_markets_update Summary: preorders_offers_markets_update Update market scoping on a preorder offer. Aliases: edit markets, change market scoping, modify markets, enable market scoping, turn on market scoping, scope to markets, enable markets, disable market scoping, turn off market scoping, remove market restrictions, global preorder, enable all markets, apply to all markets, all countries, available everywhere. Note: Deep partial. Scoping is toggled via this PATCH: `enabled` (true/false). The offer applies to ALL markets when scoping is off (`{ "enabled": false }`) or when scoping is on with an empty market list — the serializer compiles applies_to_all_markets from exactly that state. PATCH bodies: all markets → `{ "enabled": true, "market_ids": [] }` (or `{ "enabled": true, "applies_to_all_markets": true }`, which also clears the list); specific markets → `{ "enabled": true, "market_ids": ["gid://shopify/Market/1", ...] }` (GIDs or numeric IDs). For incremental edits keep using add_market / remove_market / set_markets. Parameters: - `enabled` (argument, unknown, optional) - `applies_to_all_markets` (argument, unknown, optional) - `market_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_markets_read Summary: preorders_offers_markets_read Read market scoping for a preorder offer. Aliases: get markets, show market scoping, view markets, what markets, which countries, market settings. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_markets_add_market Summary: preorders_offers_markets_add_market Add a market to the preorder offer's scope. Aliases: add market, include market, add country, extend to market. Parameters: - `market_id` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_markets_remove_market Summary: preorders_offers_markets_remove_market Remove a market from the preorder offer's scope. Aliases: remove market, exclude market, drop market, remove country. Parameters: - `market_id` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_markets_set_markets Summary: preorders_offers_markets_set_markets Replace the set of markets a preorder offer applies to. Aliases: set markets, change markets, restrict to markets, set countries, set regions. Parameters: - `market_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_read Summary: preorders_offers_payments_read Read the payment configuration for a preorder offer. Aliases: get payment settings, show payments, view billing, view deposit, view discount, payment options. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_update Summary: preorders_offers_payments_update Update payment settings on a preorder offer. Aliases: edit payments, change payment settings, modify billing, change discount, update remaining balance settings, clear discount, remove discount, no discount, stop discount, remove preorder discount, enable auto collect, enable auto charge, auto-charge balance, automatically charge balance, turn on auto collect, disable auto collect, disable auto charge, stop auto charge, turn off auto collect, manual balance collection, enable auto collect on fulfillment, charge on fulfillment, auto charge at ship time, collect balance on ship, disable auto collect on fulfillment, don't charge on fulfillment, turn off charge at ship time, stop collecting on ship. Note: Use SetOptionCopy / EnableOption / DisableOption to edit individual payment options. Boolean toggles are set via this PATCH: remaining_balance.auto_collect, remaining_balance.auto_collect_on_fulfillment — e.g. { remaining_balance: { auto_collect: false } }. Clear the discount via { discount: { type: "no_discount" } } (the stored value is nulled automatically). Parameters: - `discount` (argument, unknown, optional) - `remaining_balance` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_disable_option Summary: preorders_offers_payments_disable_option Remove a payment option (full or partial) from the offer. Aliases: disable payment option, remove full payment option, remove partial payment option, stop offering deposit, stop offering full payment. Note: Only valid on full_and_partial offers — an offer must keep at least one payment option. Use set_mode to switch a single-mode offer. Parameters: - `option_type` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_enable_option Summary: preorders_offers_payments_enable_option Add a payment option (full or partial) to the offer. Aliases: enable payment option, add full payment option, add partial payment option, offer both payment options, let customers choose how to pay. Note: Idempotent. Newly added options start from the dashboard defaults — use set_deposit_percent / set_option_copy to fine-tune. Disable with disable_option. Parameters: - `option_type` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_set_deposit_percent Summary: preorders_offers_payments_set_deposit_percent Set the deposit percentage charged at checkout on the partial payment option. Aliases: set deposit percent, set deposit percentage, change deposit amount, set upfront payment percent, change down payment. Note: Requires a partial payment option (mode partial or full_and_partial) — use set_mode first. Percent must be between 1 and 99; 100% upfront is the full payment mode. Parameters: - `percent` (argument, integer, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_set_discount Summary: preorders_offers_payments_set_discount Set the preorder discount. Aliases: set discount, apply discount, change discount, set percentage off, set amount off. Parameters: - `type` (argument, unknown, required) - `value` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_set_discount_text Summary: preorders_offers_payments_set_discount_text Set the discount blurb shown on the offer's payment options. Aliases: set discount text, change discount blurb, set discount message, change discount copy, set savings text. Note: Pass option_type (full | partial) to target one payment option; omit it to apply the text to every option. Supports the {{ discount }} placeholder. Parameters: - `text` (argument, unknown, required) - `option_type` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_set_mode Summary: preorders_offers_payments_set_mode Switch the offer between full, partial, and full-and-partial payment modes. Aliases: set payment mode, full payment only, partial payment only, deposit only, offer both full and partial payment, change payment options. Note: Idempotent. Collapsing from full_and_partial keeps the surviving option's copy; newly enabled modes start from the dashboard defaults. Use set_deposit_percent and set_option_copy afterwards to fine-tune. Parameters: - `mode` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_payments_set_option_copy Summary: preorders_offers_payments_set_option_copy Set the merchant-facing copy on a payment option (title, description, discount text). Aliases: set option copy, set partial option copy, set full option copy, change billing title, set payment option text, set deposit option text. Parameters: - `option_type` (argument, unknown, required) - `billing_title` (argument, unknown, optional) - `billing_description` (argument, unknown, optional) - `discount_text` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_read Summary: preorders_offers_products_read Read the products configuration for a preorder offer (source rule + variant summary). Aliases: get products, show linked products, view variants on offer, view product source, what's on this offer. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_update Summary: preorders_offers_products_update Update the product selection on a preorder offer — set the source rule plus its matching sub-block. Aliases: edit products, change product selection, change variant source, set products, modify products on offer. Note: Delegates to the set_source_to_* actions; switching source resets the variant set. source 'all' requires confirm: true. Only the sub-block matching `source` may be provided. Parameters: - `source` (argument, unknown, required) - `custom` (argument, null | object, optional) - `collection` (argument, null | object, optional) - `all` (argument, null | object, optional) - `confirm` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_add_variants Summary: preorders_offers_products_add_variants Attach variants to a custom-source preorder offer. Aliases: add variants, attach variants, add products to offer, link variants, include variants. Note: Custom source only. Runs through BulkVariantAdditionService (async). Parameters: - `variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_bulk_toggle_inventory_policy Summary: preorders_offers_products_bulk_toggle_inventory_policy Flip the Shopify inventory policy on variants attached to a preorder offer (CONTINUE or DENY). Aliases: flip inventory policy, toggle continue selling on variants, set inventory policy for variants, enable oversell on these variants, disable oversell on these variants. Note: Async via SellingPlans::BulkInventoryPolicyUpdateJob. Omit variant_ids to apply to all attached variants. Parameters: - `policy` (argument, unknown, required) - `variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_remove_variants Summary: preorders_offers_products_remove_variants Remove variants from a custom-source preorder offer. Aliases: remove variants, detach variants, remove products from offer, unlink variants, exclude variants. Note: Custom source only. Resets per-variant metafields on Shopify. Parameters: - `variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_set_collection Summary: preorders_offers_products_set_collection Change the collection driving a collection-sourced preorder offer. Aliases: change collection, swap collection, use a different collection, update the collection on the offer. Note: Collection-sourced offers only — returns 409 Conflict otherwise. Use set_source_to_collection to switch a non-collection offer to collection source. Parameters: - `collection_id` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_set_excluded_variants Summary: preorders_offers_products_set_excluded_variants Replace the explicit excluded-variant list for an all-source preorder offer. Aliases: set excluded variants, exclude variants list, block variants from offer. Note: All-source offers only. Parameters: - `variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_set_exclusion_tag Summary: preorders_offers_products_set_exclusion_tag Set the exclusion tag for an all-source preorder offer (variants with this tag are excluded). Aliases: set exclusion tag, exclude by tag, set exclude tag. Note: All-source offers only. Parameters: - `tag` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_set_source_to_all Summary: preorders_offers_products_set_source_to_all Switch the offer to apply to every variant in the shop, minus exclusions. Requires confirm: true. Aliases: set source to all, apply to all products, auto-source, every variant, shop-wide preorder. Note: Requires `confirm: true` in the body. Resolution runs async over the entire catalog. Parameters: - `confirm` (argument, unknown, optional) - `exclusion_tag` (argument, unknown, optional) - `excluded_variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_set_source_to_collection Summary: preorders_offers_products_set_source_to_collection Switch the offer to a collection-driven variant source. Aliases: set source to collection, use a collection, link to collection, set products from collection, drive variants from collection. Note: Resolution runs asynchronously. New variants added to the collection in Shopify are NOT auto-picked-up — call this action again to refresh. Parameters: - `collection_id` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_set_source_to_custom Summary: preorders_offers_products_set_source_to_custom Switch the offer to a custom variant list. Optionally seed it with variant IDs. Aliases: set source to custom, use custom variants, custom product list, switch to custom. Note: Variant attachment runs through BulkVariantAdditionService and is async (202 + job_id). Parameters: - `variant_ids` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_variants_list Summary: preorders_offers_products_variants_list List the variants attached to a preorder offer with their per-variant settings, paginated. Aliases: list variants on offer, variants attached to offer, show offer variants, attached variants, offer variant list. Parameters: - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_products_variants_read Summary: preorders_offers_products_variants_read Read the per-offer settings of a single variant attached to a preorder offer. Aliases: show variant settings on offer, view per-variant overrides, get variant on offer, variant settings. Parameters: - `id` (argument, string, required) - URL path parameter :id - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_offers_products_variants_update_settings Summary: preorders_offers_products_variants_update_settings Set per-offer overrides on an attached variant — shipping text, max preorder count, market-scoped overrides. Aliases: override variant settings, set shipping text for variant, set max count for variant, per-variant overrides. Note: null clears an override; the variant falls back to the offer-level value. market_overrides writes the per-market metafields (market_shipping_text, market_preorder_max_count) keyed by Shopify market GID. Parameters: - `shipping_text` (argument, null | string, optional) - `max_count` (argument, null | integer, optional) - `market_overrides` (argument, null | object, optional) - `id` (argument, string, required) - URL path parameter :id - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_offers_restore Summary: preorders_offers_restore Restore a discarded preorder offer. Aliases: undelete, undiscard, undo delete, bring back. Note: Variant attachments and metafields are not auto-restored. Re-attach via the products capability. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_schedule Summary: preorders_offers_schedule Set the schedule window for a preorder offer. Aliases: set schedule, schedule launch, set start date, set end date, schedule offer. Note: Does not auto-enable. Storefront reads the schedule live. Parameters: - `start` (argument, unknown, optional) - `end` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_update Summary: preorders_offers_shipping_update Update shipping settings on a preorder offer. Aliases: edit shipping, change shipping settings, change delivery, change fulfillment, modify shipping, enable fulfillment hold, hold fulfillments, hold preorder fulfillments, turn on fulfillment hold, prevent shipping until released, disable fulfillment hold, release fulfillments, stop holding fulfillments, turn off fulfillment hold, let preorder ship immediately, enable shipping timeline, show shipping timeline, show detailed shipping, turn on timeline, disable shipping timeline, hide shipping timeline, show simplified shipping, turn off timeline, enable split, enable split orders, enable split fulfillments, turn on order splitting, separate preorder from regular, disable split, disable split orders, disable split fulfillments, turn off order splitting, stop separating preorders, enable split hold fulfillments, hold split order fulfillments, hold fulfillments on split order, don't ship split order immediately, disable split hold fulfillments, release split order fulfillments, ship split order immediately, stop holding split order, enable split sequential number, sequential split order numbers, number split orders sequentially, sequential order name for split, disable split sequential number, no sequential split order numbers, stop numbering split orders sequentially. Note: Deep partial. Prefer the intent-bearing actions (set_delivery_*, set_shipping_text, set_split_order_tag, set_split_transaction_gateway) for narrow edits. Toggles are done via this PATCH: `fulfillment.hold` (true/false) holds/releases fulfillment orders containing preorder items; `display.show_shipping_timeline` (true shows the detailed PDP timeline, false the simplified text); `fulfillment.split.enabled` (true/false) turns order splitting on/off (flips both split flags); `fulfillment.split.hold_fulfillments` (true/false) holds the split order's fulfillments; `fulfillment.split.sequential_number` (true/false) gives split orders a sequential order name. Parameters: - `delivery` (argument, unknown, optional) - `display` (argument, unknown, optional) - `fulfillment` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_read Summary: preorders_offers_shipping_read Read shipping settings for a preorder offer. Aliases: get shipping, show shipping, view delivery, view fulfillment, view shipping text, shipping settings. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_set_delivery_asap Summary: preorders_offers_shipping_set_delivery_asap Set delivery to 'as soon as possible' (no specific date or window). Aliases: set delivery asap, ship asap, set delivery to asap, ship as soon as possible. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_set_delivery_date Summary: preorders_offers_shipping_set_delivery_date Set an exact delivery date for the preorder. Aliases: set delivery date, ship on date, set ship date, set exact delivery date. Parameters: - `date` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_set_delivery_window Summary: preorders_offers_shipping_set_delivery_window Set a delivery window in days from checkout (e.g. 'ships within 14 days'). Aliases: set delivery window, ship within days, set delivery in days, set ship window. Parameters: - `days` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_set_shipping_text Summary: preorders_offers_shipping_set_shipping_text Set the customer-facing shipping text shown on the PDP. Aliases: set shipping text, change shipping text, update shipping message, set delivery text, change PDP shipping line. Parameters: - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_set_split_order_tag Summary: preorders_offers_shipping_set_split_order_tag Set the tag applied to split orders on Shopify. Aliases: set split order tag, change split tag, set split order label. Parameters: - `tag` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_shipping_set_split_transaction_gateway Summary: preorders_offers_shipping_set_split_transaction_gateway Set the payment gateway used for the split order transaction (e.g. 'Prepaid'). Aliases: set split transaction gateway, set split payment gateway, change split order gateway. Parameters: - `gateway` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_translations_read Summary: preorders_offers_translations_read Read every translation override on a preorder offer, plus the full enumeration of translatable field paths. Aliases: get translations, show translations, view translations, list translations, view localized text, i18n, localization. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_translations_clear_locale Summary: preorders_offers_translations_clear_locale Remove all translation overrides for one locale on a preorder offer. Aliases: clear locale, remove locale, wipe locale, drop translations for locale, remove all translations for a language. Parameters: - `locale` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_translations_set Summary: preorders_offers_translations_set Set a single translation override (one locale, one field) on a preorder offer. Aliases: set translation, add translation, translate field, localize a field, set localized text. Parameters: - `locale` (argument, unknown, required) - `field` (argument, unknown, required) - `value` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_translations_set_many Summary: preorders_offers_translations_set_many Set many translation overrides for one locale on a preorder offer, atomically. Aliases: set many translations, translate offer, bulk translate, localize offer, translate to french, add translations. Note: All-or-nothing. Pre-validates every field path before writing. Parameters: - `locale` (argument, unknown, required) - `translations` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_translations_unset Summary: preorders_offers_translations_unset Remove a translation override (one locale, one field) from a preorder offer. Aliases: unset translation, remove translation, clear translation, delete localized text, remove localized override. Parameters: - `locale` (argument, unknown, required) - `field` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_unschedule Summary: preorders_offers_unschedule Clear the schedule window on a preorder offer. Aliases: clear schedule, remove schedule, no schedule, unschedule offer. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_read Summary: preorders_offers_widget_read Read the widget configuration for a preorder offer. Aliases: get widget, show widget, view button settings, view badge settings, view disclaimer settings, preorder ui. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_update Summary: preorders_offers_widget_update Update any subset of the widget configuration on a preorder offer. Aliases: edit widget, change widget settings, modify widget, update preorder ui, enable badge, show badge, turn on badge, make badge visible, disable badge, hide badge, turn off badge, remove badge, enable disclaimer, show disclaimer, turn on disclaimer, show fine print, show shipping notice, disable disclaimer, hide disclaimer, turn off disclaimer, hide fine print, remove shipping notice, enable custom button colors, use custom button colors, turn on button colors, apply custom colors, disable custom button colors, use theme button colors, turn off button colors, revert to theme colors, enable billing widget, show payment breakdown, show payment widget, turn on billing widget, disable billing widget, hide payment breakdown, hide payment widget, turn off billing widget. Note: Deep partial. Use the intent-bearing widget actions for narrow edits. Boolean toggles are set via this PATCH: badge.enabled, disclaimer.enabled, button.colors.enabled, billing_widget.enabled — e.g. { badge: { enabled: false } }. Clearing a value: send null. Parameters: - `button` (argument, unknown, optional) - `disclaimer` (argument, unknown, optional) - `badge` (argument, unknown, optional) - `billing_widget` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_set_badge_colors Summary: preorders_offers_widget_set_badge_colors Set the preorder badge's text and background colors. Aliases: change badge colors, set badge color, customize badge colors, change badge background. Parameters: - `text_color` (argument, unknown, optional) - `background_color` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_set_badge_text Summary: preorders_offers_widget_set_badge_text Set the preorder badge text. Aliases: change badge text, set badge label, rename badge, change preorder badge text, update badge text. Note: Use enable_badge / disable_badge to control visibility. Parameters: - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_set_billing_widget_text Summary: preorders_offers_widget_set_billing_widget_text Set the billing widget's title and description (the PDP payment-breakdown block). Aliases: set billing widget title, set billing widget description, change payment breakdown text, edit billing widget copy. Note: These fields are shared with the first payment option's copy (see payments/set_option_copy). Use enable_billing_widget / disable_billing_widget to control visibility. Parameters: - `title` (argument, unknown, optional) - `description` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_set_button_colors Summary: preorders_offers_widget_set_button_colors Set the preorder button's text and background colors (implicitly enables custom colors). Aliases: change button colors, set button color, customize button colors. Note: Setting either color implicitly enables custom colors. Use disable_button_colors to revert to the theme's defaults. Parameters: - `text_color` (argument, unknown, optional) - `background_color` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_set_button_text Summary: preorders_offers_widget_set_button_text Set the preorder button's call-to-action label. Aliases: change button text, rename button, set button label, change preorder button label, update button text. Parameters: - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_set_disclaimer_style Summary: preorders_offers_widget_set_disclaimer_style Set the disclaimer banner's colors and border radius. Aliases: change disclaimer colors, set disclaimer style, style the disclaimer, set disclaimer border radius, change disclaimer background. Parameters: - `text_color` (argument, unknown, optional) - `background_color` (argument, unknown, optional) - `border_radius` (argument, integer, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_offers_widget_set_disclaimer_text Summary: preorders_offers_widget_set_disclaimer_text Set the disclaimer banner text shown under the preorder button. Aliases: change disclaimer text, set disclaimer text, edit disclaimer, change disclaimer message, set shipping disclaimer. Note: Use enable_disclaimer / disable_disclaimer to control visibility. Parameters: - `text` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_list Summary: preorders_orders_list List preorder orders in this shop, paginated. Aliases: list preorder orders, show all preorder orders, preorder orders, list orders. Parameters: - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) - `state` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `customer_id` (argument, unknown, optional) - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) ##### TOOL preorders_orders_read Summary: preorders_orders_read Read a preorder order's full representation. Aliases: show preorder order, get order, look up order, view preorder order. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_cancel Summary: preorders_orders_cancel Cancel a preorder. Optionally refund deposit / balance and notify the customer. Aliases: cancel order, cancel preorder, void preorder, stop this preorder. Note: Does not cancel the Shopify order; sets cancelled_at on the preorder attribution. Use the refund flags for refunds. Parameters: - `refund_deposit` (argument, unknown, optional) - `refund_balance` (argument, unknown, optional) - `notify_customer` (argument, unknown, optional) - `reason` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_fulfillment_update Summary: preorders_orders_fulfillment_update Update fulfillment attributes on a preorder order. Only `hold_reason_note` is writable. Aliases: update fulfillment, edit fulfillment, change hold reason, update hold note. Note: Hold state changes go through release_holds / apply_holds. `all_held`, `any_held`, and `fulfillment_orders` are read-only. Delegates to SetHoldReason; existing holds on Shopify aren't re-written. Parameters: - `hold_reason_note` (argument, null | string, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_fulfillment_read Summary: preorders_orders_fulfillment_read Read the fulfillment hold state and fulfillment-order summary of a preorder order. Aliases: order fulfillment, shipping, hold state, fulfillment hold. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_fulfillment_apply_holds Summary: preorders_orders_fulfillment_apply_holds Apply fulfillment holds on the order's fulfillment orders. Idempotent. Aliases: apply holds, add holds to fulfillments, place fulfillments on hold. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_fulfillment_release_holds Summary: preorders_orders_fulfillment_release_holds Release fulfillment holds on the order without applying offer-configured tags. Aliases: release holds, release fulfillments without tags, remove fulfillment hold only. Note: Use the top-level `release` action when you want tags applied too. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_fulfillment_set_hold_reason Summary: preorders_orders_fulfillment_set_hold_reason Set the hold-reason note that will be used the next time the order is held. Aliases: set hold reason, change hold note, update fulfillment hold note. Note: Updates the note on the first attributed preorder offer. Existing holds on Shopify aren't re-written. Parameters: - `reason_note` (argument, unknown, required) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_hold_fulfillments Summary: preorders_orders_hold_fulfillments Apply fulfillment holds on the preorder order's fulfillment orders. Aliases: hold order, hold fulfillments, re-hold fulfillments, pause fulfillment, stop the order from shipping. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_payments_read Summary: preorders_orders_payments_read Read the deposit, remaining balance, and refund state of a preorder order. Aliases: order payments, deposit, balance, remaining balance, refund. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_payments_charge_balance Summary: preorders_orders_payments_charge_balance Charge the remaining balance. `auto` uses the customer's saved payment method; `manual` just marks balance collected. Aliases: charge remaining balance, collect balance, auto-charge balance, take the balance now. Note: `auto` may fail if no saved payment method or declined; check the response or poll status. Parameters: - `mode` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_payments_refund Summary: preorders_orders_payments_refund Refund the entire preorder (deposit + balance). Aliases: refund order, refund preorder, full refund, give the customer their money back. Parameters: - `notify_customer` (argument, unknown, optional) - `note` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_payments_refund_balance Summary: preorders_orders_payments_refund_balance Refund the remaining-balance portion of a preorder (only valid after the balance was collected). Aliases: refund balance, refund remaining balance, refund the rest. Parameters: - `notify_customer` (argument, unknown, optional) - `note` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_payments_refund_deposit Summary: preorders_orders_payments_refund_deposit Refund the deposit portion of a preorder. Aliases: refund deposit, give the deposit back, refund up-front payment. Parameters: - `notify_customer` (argument, unknown, optional) - `note` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_payments_send_collection_url Summary: preorders_orders_payments_send_collection_url Send the remaining-balance payment-collection URL to the customer. Aliases: send payment link, request balance payment, send payment collection url, ask customer for balance, send collection email. Note: Idempotent — calling twice resends the email with a fresh URL. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_release Summary: preorders_orders_release Release the order's fulfillment holds and apply offer-configured order tags. Aliases: release order, release fulfillments, let it ship, unhold order, open fulfillments. Note: Returns 409 if balance hasn't been collected; pass `force: true` to override. Parameters: - `force` (argument, unknown, optional) - `tag_with` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_split_fulfillment_orders Summary: preorders_orders_split_fulfillment_orders Split the order's fulfillment orders so preorder items ship separately from regular items. Aliases: split order, separate preorder lines, split fulfillments, separate preorder from regular. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_tags_read Summary: preorders_orders_tags_read Read the order tags applied by Stoq. Aliases: order tags, order labels, tag the order. Parameters: - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_tags_update Summary: preorders_orders_tags_update Replace the order's tag set. PATCH form of tags/set. Aliases: update order tags, edit order tags, replace order tags. Note: `tags` is replaced wholesale (delegates to tags/set). `applied_by_stoq` and `applied_by_merchant` are read-only. Parameters: - `tags` (argument, null | array, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_tags_add Summary: preorders_orders_tags_add Add one or more tags to the Shopify order. Aliases: add order tag, add tag, tag the order, append order tag. Parameters: - `tags` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_tags_remove Summary: preorders_orders_tags_remove Remove one or more tags from the Shopify order. Aliases: remove order tag, remove tag, untag the order, drop order tag. Parameters: - `tags` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_tags_set Summary: preorders_orders_tags_set Replace every tag on the Shopify order with the supplied list. Aliases: set order tags, replace order tags, overwrite order tags, reset tags on order. Note: Pass an empty list to clear all tags. Implemented as fetch-current + remove stale + add new on the Shopify side. Parameters: - `tags` (argument, unknown, optional) - `id` (argument, string, required) - URL path parameter :id ##### TOOL preorders_orders_bulk_cancel Summary: preorders_orders_bulk_cancel Cancel many preorder orders (max 1000), with optional refund flags. Aliases: bulk cancel, cancel many orders, batch cancel preorders. Parameters: - `order_ids` (argument, unknown, optional) - `refund_deposit` (argument, unknown, optional) - `refund_balance` (argument, unknown, optional) - `notify_customer` (argument, unknown, optional) - `reason` (argument, unknown, optional) ##### TOOL preorders_orders_bulk_release Summary: preorders_orders_bulk_release Release fulfillments on many preorder orders (max 1000). Aliases: bulk release, release many orders, batch release, release these orders. Parameters: - `order_ids` (argument, unknown, optional) - `force` (argument, unknown, optional) - `tag_with` (argument, unknown, optional) ##### TOOL preorders_orders_bulk_send_payment_collection_url Summary: preorders_orders_bulk_send_payment_collection_url Send payment-collection URLs to many preorder customers (max 1000). Aliases: bulk send payment collection url, request balance from many customers, send payment links in bulk, ask many customers for balance. Parameters: - `order_ids` (argument, unknown, optional) ##### TOOL preorders_orders_bulk_set_tags Summary: preorders_orders_bulk_set_tags Apply tags to many preorder orders in one job (max 1000). Aliases: bulk set tags, tag many orders, batch tag orders, add tags to many orders, remove tags from many orders, replace tags on many orders. Note: `mode` is one of add | remove | replace (default add). `replace` overwrites each order's full tag set. Parameters: - `order_ids` (argument, unknown, optional) - `tags` (argument, unknown, optional) - `mode` (argument, null | string, optional) ##### TOOL preorders_orders_jobs_read Summary: preorders_orders_jobs_read Check the status of a bulk-order job. Aliases: check bulk order job, poll bulk order job, get order job status. Parameters: - `job_id` (argument, string, required) - URL path parameter :job_id ##### TOOL preorders_product_variants_list Summary: preorders_product_variants_list List Shopify variants attached to any preorder offer in this shop. Aliases: list preorder variants, variants in preorder, find variants in preorder offers, preorder variants, all preorder variants. Parameters: - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_ids` (argument, unknown, optional) ##### TOOL preorders_product_variants_read Summary: preorders_product_variants_read Read a Shopify variant's preorder context: every offer it's in, aggregate counts, and current metafield state. Aliases: show preorder context for variant, look up variant preorders, variant preorder state, get preorder variant, show variant in preorders. Parameters: - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_product_variants_detach_from_all_offers Summary: preorders_product_variants_detach_from_all_offers Remove a variant from every preorder offer it's attached to. Customer orders are unaffected. Aliases: remove variant from all preorders, detach from preorder, kick variant out of offers, remove from all offers. Note: Destructive. Same effect as calling Offer::Products::RemoveVariants against every offer the variant is in. Parameters: - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_product_variants_offers_list_offers Summary: preorders_product_variants_offers_list_offers List the preorder offers a Shopify variant is attached to. Aliases: which offers is this variant in, find offers for variant, variant's preorder offers, what offers contain this variant. Parameters: - `state` (argument, unknown, optional) - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_product_variants_recalculate_preorder_count Summary: preorders_product_variants_recalculate_preorder_count Recompute preorder_count for a variant from active order line items, then push to Shopify. Aliases: recalculate preorder count from orders, fix preorder count, rebuild count, recompute preorder count, fix variant count drift. Parameters: - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_product_variants_reset_metafields Summary: preorders_product_variants_reset_metafields Clear all preorder-related Shopify metafields on a variant. Keeps the variant attached to its offers. Aliases: clear preorder metafields, wipe variant preorder data, reset variant metafields, clear variant metafields. Note: Doesn't detach. Next offer settings change will re-write the metafields from current state. Parameters: - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_product_variants_reset_preorder_count Summary: preorders_product_variants_reset_preorder_count Hard-reset the preorder_count metafield + DB columns on a variant to zero. Aliases: zero out preorder count, clear preorder count, reset count to zero. Note: Use recalculate_preorder_count for a recompute from real orders; this is a destructive zero. Parameters: - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_product_variants_sync_metafields Summary: preorders_product_variants_sync_metafields Force-resync every preorder metafield for a variant from local DB state to Shopify. Aliases: force resync to shopify, push metafields to shopify, refresh shopify metafields, fix shopify metafield drift. Note: Idempotent. Useful when a previous metafield write failed. Parameters: - `variant_id` (argument, string, required) - URL path parameter :variant_id ##### TOOL preorders_product_variants_bulk_recalculate_preorder_counts Summary: preorders_product_variants_bulk_recalculate_preorder_counts Recompute preorder_count for many variants in one job (max 5000). Aliases: recalculate counts for variants, bulk fix preorder counts, recalculate many variants, batch recompute counts. Parameters: - `variant_ids` (argument, unknown, optional) ##### TOOL preorders_product_variants_bulk_reset_metafields Summary: preorders_product_variants_bulk_reset_metafields Reset preorder metafields for many variants in one job (max 5000). Aliases: clear metafields for variants, bulk reset variant metafields, wipe metafields for many variants. Parameters: - `variant_ids` (argument, unknown, optional) ##### TOOL preorders_product_variants_bulk_sync_metafields Summary: preorders_product_variants_bulk_sync_metafields Force-resync preorder metafields for many variants in one job (max 5000). Aliases: force resync many variants, bulk refresh metafields, push many variants to shopify. Parameters: - `variant_ids` (argument, unknown, optional) ##### TOOL preorders_product_variants_jobs_read Summary: preorders_product_variants_jobs_read Check the status of a bulk variant-admin job. Aliases: check bulk variant job status, poll bulk variant job, get variant job status. Parameters: - `job_id` (argument, string, required) - URL path parameter :job_id ##### TOOL preorders_reports_list Summary: preorders_reports_list List every available preorder report (name, description, aliases, url). Aliases: reports, list reports, available reports, what reports are there, report catalog. ##### TOOL preorders_reports_balance_collection_read Summary: preorders_reports_balance_collection_read Outstanding preorder balances and balance-collection performance metrics. Aliases: outstanding balances, balance collection, who hasn't paid, balance status, payment collection, outstanding payments. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `currency` (argument, unknown, optional) ##### TOOL preorders_reports_balance_collection_export Summary: preorders_reports_balance_collection_export Async CSV export of the balance-collection report. Aliases: export balance_collection, download outstanding balances, balance collection csv, export who hasn't paid. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_by_date_read Summary: preorders_reports_by_date_read Time-series of all preorder metrics at the chosen granularity (day/week/month). Aliases: time series, daily breakdown, trend, over time, preorder trend, show trend. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) ##### TOOL preorders_reports_by_date_export Summary: preorders_reports_by_date_export Async CSV export of the time-series preorder report. Aliases: export by_date, download time series, trend csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_by_offer_read Summary: preorders_reports_by_offer_read Per-offer breakdown of preorder activity (orders, units, revenue, customers). Aliases: per-offer report, offer performance, which offers are working, compare offers, report by offer. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) ##### TOOL preorders_reports_by_offer_export Summary: preorders_reports_by_offer_export Async CSV export of the per-offer preorder report. Aliases: export by_offer, download by_offer, by offer csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_by_product_read Summary: preorders_reports_by_product_read Per-product preorder performance, rolled up across variants. Aliases: per-product report, product performance, which products are preordering, report by product. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) ##### TOOL preorders_reports_by_product_export Summary: preorders_reports_by_product_export Async CSV export of the per-product preorder report. Aliases: export by_product, download by_product, by product csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_by_variant_read Summary: preorders_reports_by_variant_read Per-variant preorder performance for a specific offer. Aliases: per-variant report, variant performance, which variants are selling, variant report. Note: Requires offer_id (or offer_ids[]). Variant-level data without an offer scope isn't supported. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) ##### TOOL preorders_reports_by_variant_export Summary: preorders_reports_by_variant_export Async CSV export of the per-variant preorder report. Aliases: export by_variant, download by_variant, by variant csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_cancellations_read Summary: preorders_reports_cancellations_read Preorder cancellation metrics with reason and per-offer breakdowns. Aliases: cancellations, refunds, how often do customers cancel, cancellation rate, cancellation report. Note: Reason breakdown requires the cancel_reason field on OrderAttribution. Reasons not yet captured will appear as 'unknown'. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `currency` (argument, unknown, optional) ##### TOOL preorders_reports_cancellations_export Summary: preorders_reports_cancellations_export Async CSV export of the cancellations report. Aliases: export cancellations, download cancellations, cancellations csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_customers_read Summary: preorders_reports_customers_read Paginated list of customers with preorder activity in the window. Aliases: customer list, who preordered, preorder customers, customers report, list customers. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_customers_export Summary: preorders_reports_customers_export Async CSV export of the customers report. Aliases: export customers, download customers, customers csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_exports_read Summary: preorders_reports_exports_read Check the status of an async report export and get the download URL when ready. Aliases: check export status, get export, poll export, download url for export, export status. Parameters: - `job_id` (argument, string, required) - URL path parameter :job_id ##### TOOL preorders_reports_revenue_read Summary: preorders_reports_revenue_read Preorder revenue over a date window, bucketed by day/week/month. Aliases: revenue, how much money, sales, preorder revenue, how much did we make, what did we earn. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) ##### TOOL preorders_reports_revenue_export Summary: preorders_reports_revenue_export Async CSV export of the preorder revenue report. Aliases: export revenue, download revenue, revenue csv. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL preorders_reports_summary_read Summary: preorders_reports_summary_read High-level overview of preorder activity over a date window. Aliases: summary, overview, how is preorder doing, preorder summary, dashboard stats, what's happening with preorders. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) ##### TOOL preorders_reports_summary_export Summary: preorders_reports_summary_export Async CSV export of the preorder summary report. Aliases: export summary, download summary, summary csv, export overview. Parameters: - `from` (argument, unknown, optional) - `to` (argument, unknown, optional) - `offer_id` (argument, unknown, optional) - `offer_ids` (argument, unknown, optional) - `product_id` (argument, unknown, optional) - `variant_id` (argument, unknown, optional) - `market_id` (argument, unknown, optional) - `currency` (argument, unknown, optional) - `granularity` (argument, unknown, optional) - `metrics` (argument, unknown, optional) - `include_deposits` (argument, unknown, optional) - `include_balances` (argument, unknown, optional) - `page` (argument, unknown, optional) - `per_page` (argument, unknown, optional) ##### TOOL generate_storefront_widget Summary: generate_storefront_widget Generate a ready-to-paste STOQ storefront widget snippet for a variant — a preorder widget/button, a notify-me (back-in-stock) form, or event-tracking listeners. Returns code, not an API call. surface=sdk for headless/Hydrogen (@artossoftware/stoq-sdk); surface=theme for a Shopify theme running the STOQ app embed (window._RestockRocket + Liquid). Parameters: - `surface` (argument, string, optional) - sdk = headless/Hydrogen SDK. theme = Shopify theme app embed. Default sdk. - `widget` (argument, string, required) - Which snippet to generate. events = analytics listeners for the stoq:* events. - `format` (argument, string, optional) - For surface=sdk only. Default custom-element; ignored for surface=theme. - `variant_id` (argument, string, optional) - Shopify variant id to embed (optional). - `product_id` (argument, string, optional) - Shopify product id — required for notify-me.