Preorder API for developers
The STOQ preorder API lets developers build custom integrations: create and update offers, attach or remove variants, enable and disable offers, manage per-variant settings, and send bulk shipping-update emails, all without touching the STOQ dashboard. Actions you take through the API show up for customers right away, for example enabling an offer makes the preorder button appear on your storefront.
Use this article if you're integrating STOQ preorders with your own tools, scripts, or a Shopify Flow setup that goes beyond the built-in flow actions.
Before you begin
| Requirement | Details |
|---|---|
| API key | Generated automatically per shop. Find it in the Integrations sub-tab (Preorders in the left menu → Settings tab → Integrations); see Set it up below. |
| Content type | All requests and responses are JSON. Include Content-Type: application/json on write requests. |
| Shopify variant IDs | Use the numeric Shopify variant ID (not a GID string) when the API asks for shopify_variant_id or variant_ids. |
Choose your API version
STOQ exposes two external API versions. All paths in this article are relative to the base path for the version you use.
- v2 (
/api/v2/external): the current version. Use it for preorder offer management, creating, configuring, and managing the lifecycle of offers, attaching variants, and configuring widget and shipping settings. Recommended for all new integrations. See the API v2 guide and API v2 reference. - v1 (
/api/v1/external): use it to maintain an existing integration built on the older selling-plan model for preorder offers. See the API v1 reference.
Every endpoint below is listed in those references with full parameters, response schemas, and a Test it button you can call with your own key. Start at the developer docs home to pick an integration path.
What customers will see
Enabling or disabling an offer through the API changes whether the preorder button appears on your storefront, but it doesn't send customers any emails or SMS. Customer notifications, like shipping updates, are sent separately: from the order management endpoints, Shopify Flow, or the send_shipping_update action, which emails customers directly with the subject, header, and description you provide.
Set it up
Open Integrations
- In the left menu bar of the STOQ app, click Preorders.
- Click the Settings tab.
- Open the Integrations sub-tab.
Locate the API section
Scroll to the API section. Your API key is displayed in the Access key field (shown as a password field).
Copy the key
Click Copy to copy the key to your clipboard. Keep this key secure: it grants full access to your shop's STOQ data. See API Key in the developer docs.
Reset the key if needed
If you suspect the key has been exposed, click Reset. Existing integrations using the old key will stop working immediately; update them with the new key.
Authentication
Pass your API key in the X-Auth-Token request header on every call:
X-Auth-Token: <your-api-key>The MCP endpoint (see below) also accepts Authorization: Bearer <your-api-key> for clients that only support standard Authorization headers.
A missing or invalid key returns 401 Unauthorized.
v2 API: preorder offers
The v2 API is action-driven. Each action declares a path, HTTP verb, human-readable description, and a set of natural-language aliases. You can discover the full live manifest at any time:
GET /api/v2/external/helpScope the manifest to preorder actions only:
GET /api/v2/external/help?prefix=/preorders/offersYou can also fetch a machine-readable skill document (no auth required) that AI agents can ingest:
GET /api/v2/external/preorders/skill.mdOffer lifecycle
| Method | Path | What it does |
|---|---|---|
GET | /preorders/offers | List all offers (paginated, up to 200 per page). |
GET | /preorders/offers/:id | Full offer representation including all capability blocks. |
POST | /preorders/offers | Create a new offer. Pass name and optionally internal_name. |
POST | /preorders/offers/:id/enable | Activate the offer. Pass update_inventory_policy: true to also flip attached variants to CONTINUE in Shopify. |
POST | /preorders/offers/:id/disable | Pause the offer. Pass update_inventory_policy: true to flip variants back to DENY. |
POST | /preorders/offers/:id/discard | Soft-delete the offer. Recoverable via restore. |
POST | /preorders/offers/:id/restore | Restore a discarded offer to paused state. |
POST | /preorders/offers/:id/schedule | Set a launch window. Pass start and/or end as ISO 8601 timestamps. Scheduling does not enable the offer automatically. |
Lifecycle state machine. The enable, disable, discard, restore, and schedule actions validate the transition. An invalid transition returns 409 Conflict with a message naming the current state and what would unblock it.
Capabilities
Each capability has a GET to read its current state and a PATCH for catch-all updates (deep partial, only the fields you send are written). Some capabilities also expose named POST actions for specific, high-frequency operations.
| Capability | GET | PATCH | Named POST actions |
|---|---|---|---|
| Widget | /preorders/offers/:id/widget | /preorders/offers/:id/widget | set_button_text, set_badge_text, set_badge_colors, set_button_colors, set_disclaimer_text, set_disclaimer_style, set_billing_widget_text |
| Shipping | /preorders/offers/:id/shipping | /preorders/offers/:id/shipping | set_delivery_asap, set_delivery_date, set_delivery_window, set_shipping_text, set_split_order_tag, set_split_transaction_gateway |
| Payments | /preorders/offers/:id/payments | /preorders/offers/:id/payments | set_mode, set_deposit_percent, set_discount, set_discount_text, set_option_copy, enable_option, disable_option |
| Limits | /preorders/offers/:id/limits | /preorders/offers/:id/limits | set_min_per_order, set_max_per_order, set_max_per_customer, set_total_max |
| Checkout | /preorders/offers/:id/checkout | /preorders/offers/:id/checkout | set_terms_text, set_mixed_cart_error_message, set_order_tags, add_order_tag, remove_order_tag |
| Inventory | /preorders/offers/:id/inventory | /preorders/offers/:id/inventory | set_provider, set_reservation_timing |
| Markets | /preorders/offers/:id/markets | /preorders/offers/:id/markets | add_market, remove_market, set_markets |
| Countdown | /preorders/offers/:id/countdown | /preorders/offers/:id/countdown | set_mode, set_style, set_custom_end_date, set_starts_text, set_ends_text, set_unit_labels |
| Integrations | /preorders/offers/:id/integrations | /preorders/offers/:id/integrations | (boolean toggles only, use PATCH) |
| Translations | /preorders/offers/:id/translations | n/a | set, set_many, unset, clear_locale |
| Advanced | /preorders/offers/:id/advanced | /preorders/offers/:id/advanced | set_button_text_override, set_custom_css, set_custom_line_item_property, attach_to_shopify_selling_plan, detach_from_shopify_selling_plan |
| Products | /preorders/offers/:id/products | n/a | add_variants, remove_variants, bulk_toggle_inventory_policy |
Products and variant management
| Method | Path | What it does |
|---|---|---|
GET | /preorders/offers/:id/products | Read the product type rule and a variant count summary. |
POST | /preorders/offers/:id/products/add_variants | Attach variants to a custom-source offer. Pass variant_ids as an array of Shopify variant IDs. Custom-source offers only. |
POST | /preorders/offers/:id/products/remove_variants | Remove variants from a custom-source offer. |
POST | /preorders/offers/:id/products/bulk_toggle_inventory_policy | Flip attached variants' Shopify inventory policy to CONTINUE or DENY. Async, runs in a background job. Omit variant_ids to apply to all attached variants. |
GET | /preorders/offers/:id/products/variants | Paginated list of attached variants with their per-variant settings. |
GET | /preorders/offers/:id/products/variants/:variant_id | Per-variant settings for one attached variant. |
PATCH | /preorders/offers/:id/products/variants/:variant_id | Update per-variant overrides: shipping_text, max_count, and market_overrides (market-scoped shipping text and max count). Send null to clear an override and fall back to the offer-level default. |
MCP server
If you prefer tool calling over raw HTTP, the v2 API is also available as an MCP server. Every registered action is exposed as an MCP tool:
POST /api/v2/external/mcpAuthenticate with X-Auth-Token: <key> or Authorization: Bearer <key>. See the MCP server docs.
v1 API: preorder offers (selling plans)
The v1 external API also exposes a selling-plan-level CRUD surface for preorder offers. Use the v2 API for new integrations; this surface remains available for existing integrations.
| Method | Path | What it does |
|---|---|---|
GET | /api/v1/external/preorders | List enabled offers. Filter by name. Paginated. |
GET | /api/v1/external/preorders/:id | Single offer. Includes shopify_variant_ids. |
POST | /api/v1/external/preorders | Create an offer. product_variants_source is restricted to "custom" via the API. |
PUT/PATCH | /api/v1/external/preorders/:id | Update offer settings. product_variants_source is restricted to "custom" via the API. |
DELETE | /api/v1/external/preorders/:id | Disable and soft-delete the offer. |
POST | /api/v1/external/preorders/:id/add_variant | Add variants by Shopify variant ID array. |
DELETE | /api/v1/external/preorders/:id/remove_variant | Remove variants. Optionally pass inventory_policy (CONTINUE or DENY) to update Shopify inventory policy on removal. |
PATCH | /api/v1/external/preorders/:id/update_variant_settings | Set shipping_text and/or preorder_max_count for one variant on this offer. |
POST | /api/v1/external/preorders/:id/bulk_toggle_inventory_policy | Bulk-update Shopify inventory policy for a list of variants. Async (batches of 1,000 variant IDs). |
GET | /api/v1/external/preorders/:id/product_variants | Variant list for this offer. Filter by shopify_product_id or shopify_variant_id. |
GET | /api/v1/external/preorders/:id/variant_count | Count of variants attached to this offer. |
GET | /api/v1/external/preorders/:id/job_status | Status of any running background job for this offer (e.g. bulk inventory policy update). |
DELETE | /api/v1/external/preorders/:id/cancel_job | Cancel a running background job. |
GET | /api/v1/external/preorders/jobs | All active background jobs for this shop. |
POST | /api/v1/external/preorders/send_shipping_update | Send a custom shipping update email to a list of preorder orders. Requires order_ids or shopify_order_ids, plus subject, header, and description. Async. |
Rate limits
Both v1 and v2 use a cost-weighted token bucket, scoped per API key. Full details in Rate Limits.
| Request type | Cost |
|---|---|
| GET (read) | 1 point |
| POST / PATCH / PUT / DELETE (write) | 2 points |
| Bucket | 40 points per 60 seconds (default) |
Every response includes rate-limit headers:
X-RateLimit-Limit: 40
X-RateLimit-Remaining: 38
X-RateLimit-Reset: 1751500860If you exceed the limit you receive 429 Too Many Requests with a Retry-After header indicating how many seconds to wait.
Check your setup
Call GET /api/v2/external/help with your X-Auth-Token header. A 200 response with the action manifest confirms your key and header are set up correctly; a 401 means the header name, key, or format is wrong.
Troubleshooting
You get 401 Unauthorized
Check that you're sending X-Auth-Token (or Authorization: Bearer for the MCP endpoint) with a valid key. Reset and re-copy the key from Integrations if you're not sure it's current.
You get 404 Not Found
The offer, variant, or resource doesn't exist for this shop, or the v2 action path isn't registered. Check GET /api/v2/external/help for the current list of valid paths.
You get 409 Conflict on enable, disable, discard, restore, or schedule
The requested state transition isn't valid from the offer's current state. Read the errors array, it names the current state and what would unblock the transition.
You get 422 Unprocessable Entity
A validation failed. Read the errors array for the specific reason (v2 returns an array; v1 returns a single error string).
You get 429 Too Many Requests
You've exceeded the rate limit. Wait the number of seconds in the Retry-After header before retrying.
You called bulk_toggle_inventory_policy but Shopify's inventory policy hasn't changed yet
The update runs asynchronously and can take several minutes on large variant lists. Poll GET .../job_status (v1) or the jobs endpoint to track progress.
Limitations
Storefront enforcement of the per_customer.max and total.max limit fields may not be fully active. Test in a non-production environment before relying on them as hard caps.
FAQ
Should I use v1 or v2 for a new preorder integration?
Use the v2 API for new integrations. It exposes:
- A richer action surface with natural-language aliases.
- A self-describing
/helpmanifest. - An MCP server endpoint.
- Per-capability read/write endpoints that mirror the dashboard's structure.
The v1 selling-plan endpoints remain available for existing integrations.
Why does my v1 create/update call fail with 'product_variants_source is restricted to custom'?
The v1 API enforces product_variants_source: "custom" only. You cannot create collection-based or all-products offers via the API, use the STOQ dashboard for those offer types. Once the offer exists in the dashboard you can manage its custom-variant list (add/remove) via the API.
Does enabling or disabling an offer via the API notify customers?
No. Enabling or disabling an offer changes whether the preorder button is visible on your storefront; it does not send any notification emails or SMS to customers who have already placed preorders. Customer notifications (confirmation, shipping update) are triggered separately through the order management endpoints or Shopify Flow.
What happens to existing preorder orders when I discard an offer via the API?
Discarding an offer soft-deletes it (sets discarded_at).
- STOQ removes the Shopify selling plan group, detaches variants, and resets variant metafields.
- Existing customer orders already placed against this offer are not affected: they remain in Shopify and STOQ continues to track them.
- You can restore the offer via
POST .../restore.
Can I use the API to send a shipping update email to all customers on a preorder?
Yes, via the v1 endpoint POST /api/v1/external/preorders/send_shipping_update.
- Pass an array of STOQ order IDs (
order_ids) or Shopify order IDs (shopify_order_ids), along with asubject,header, anddescription. - The emails are sent asynchronously via a background job.
- You cannot pass both
order_idsandshopify_order_idsin the same call.
See also the preorder shipping update email article for how these emails are templated.
Related guides
Preorders with Shopify Flow
Automate preorder actions using Shopify Flow triggers and actions without writing code.
Find and read preorder orders
Locate individual preorder orders and read their status in STOQ.
Preorder shipping update email
Customize and send shipping update emails to preorder customers.
Preorder advanced settings
Configure line item properties, button text overrides, and custom CSS on an offer.
Connect Klaviyo for preorders
Route preorder events to Klaviyo for advanced email and SMS flows.
