Skip to content

Preorder API for developers

Manage preorder offers, products, and shipping updates from your own code.

Last updated

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.

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 IDs v2 accepts a Shopify variant ID as a number or a gid (gid://shopify/ProductVariant/123), and always returns Shopify IDs as gids in fields named shopify_<thing>_id. v1 uses the numeric Shopify variant ID.

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.

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.

On a phone, this menu is collapsed. In the Shopify mobile app, tap the STOQ logo at the top of the screen to open it, then choose the section named below. There is no hamburger icon. See Finding the STOQ menu.

  1. Open Integrations

    1. In the left menu bar of the STOQ app, click Preorders.
    2. Click the Settings tab.
    3. Open the Integrations sub-tab.
  2. Locate the API section

    Scroll to the API section. Your API key is displayed in the Access key field (shown as a password field).

  3. 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.

  4. 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.

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.

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/help

Scope the manifest to preorder actions only:

GET /api/v2/external/help?prefix=/preorders/offers

You can also fetch a machine-readable skill document (no auth required) that AI agents can ingest:

GET /api/v2/external/preorders/skill.md
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. On a scheduled offer this starts it now, as the dashboard does: the start date is cleared and an end date is kept. Pass update_inventory_policy: true to also flip attached variants to CONTINUE in Shopify.
POST /preorders/offers/:id/disable Pause the offer. On a scheduled or dated offer the schedule is cleared, as the dashboard does, so the offer stays off instead of starting on its start date. Pass update_inventory_policy: true to flip variants back to DENY.
POST /preorders/offers/bulk_enable, /preorders/offers/bulk_disable Turn many offers on or off at once, like the bulk actions on the Offers list. Pass the offer ids, and optionally update_inventory_policy: true.
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/duplicate Copy an offer with all its settings, like the dashboard’s Duplicate. The copy starts off, with no products.
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.

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, reset_to_defaults
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, advance_batches
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: shopify_variant_ids, shopify_product_ids (every variant of those products) or shopify_collection_id, all Shopify gids. On other offers, a call with no ids re-runs the selection rule. Runs the Products tab’s pre-flight check first: 409 with code: "preflight_issues" and nothing added when there are issues; resolve runs the dashboard’s fix per issue, skip_preflight: true adds anyway. Returns a pollable job with its own job_id.
POST /preorders/offers/:id/products/remove_variants Remove variants from a custom-source offer: shopify_variant_ids (Shopify variant gids).
POST /preorders/offers/:id/products/advance_batches Batch offers only: move variants to their next batch.
POST /preorders/offers/:id/products/bulk_toggle_inventory_policy Set attached variants’ Shopify inventory policy to CONTINUE or DENY. Always returns a pollable job. Omit shopify_variant_ids to apply to all attached variants.
GET /preorders/offers/:id/products/variants The offer’s Products table, paginated, with search, sort, product_status and limit_reached; group_by=product lists products with their variants. include_inventory=true adds live inventory.
GET /preorders/offers/:id/products/variants/:shopify_variant_id Per-variant settings for one attached variant.
PATCH /preorders/offers/:id/products/variants/:shopify_variant_id Update per-variant overrides: shipping_text and max_count. Send null to clear an override and fall back to the offer-level default. On an offer with batches on, max_count is refused (422): set limits per batch with batch_limits.
POST /preorders/offers/:id/products/variants/:shopify_variant_id/toggle_inventory_policy Set one variant’s inventory policy now.
POST /preorders/offers/:id/products/variants/:shopify_variant_id/reset_count Reset one variant’s preorder count to 0 on this offer.
GET /preorders/products/tags The shop’s product tags (first 250, alphabetically), as the tag picker lists them.
GET /preorders/products/tags/count How many products carry any of tags, as the tag picker counts them.

Read preorder orders and run the order actions the STOQ dashboard has. Each action affects only that one order. There is no API to cancel, refund or edit tags on an order.

Method Path What it does
GET /preorders/orders List preorder orders (paginated).
GET /preorders/orders/:id Full order representation: state, payments, fulfillment, notifications, tags, offers, line items.
GET /preorders/orders/:id/payments, /fulfillment, /notifications One block of the full read.
GET /preorders/orders/:id/timeline The order’s activity log, newest first.
POST /preorders/orders/:id/release Release the order’s “On hold” fulfillments. Every hold on those fulfillments is released, as in Shopify admin. No tags are applied.
POST /preorders/orders/:id/open_scheduled_fulfillments Open scheduled fulfillments now.
POST /preorders/orders/:id/hold_fulfillments Put all of the order’s fulfillments on hold. Pass reason_notes to set the note Shopify shows on the hold.
POST /preorders/orders/:id/split_fulfillment_orders Move the preorder items into their own fulfillment orders, for offers with split fulfillments on.
POST /preorders/orders/:id/reschedule_fulfillment Move the fulfillment date. Pass fulfill_at (ISO 8601). Pass send_shipping_update: true to also email the customer a shipping update, like Send customers a shipping update in the dashboard.
POST /preorders/orders/:id/collect_payment Charge the remaining balance now using the customer’s saved payment method. Same as Collect payment on the order page. Pass send_notifications: false to skip the customer email if the charge fails.
POST /preorders/orders/:id/reschedule_payment Move the remaining-balance due date. Pass due_at as YYYY-MM-DD. Changes the schedule only; no money moves.
POST /preorders/orders/:id/send_confirmation, /send_sms_confirmation, /send_shipping_update Resend the confirmation email or SMS, or send a shipping update.

In the reads, :id can be STOQ’s order id, the Shopify order ID or gid, the order name or number, or the customer’s email. The actions take only STOQ’s order id or the Shopify order ID or gid. Shopify ids in responses are gids named shopify_<thing>_id, e.g. shopify_order_id. See the Orders reference for the response shape and the bulk endpoints.

If you prefer tool calling over raw HTTP, the v2 API is also available as an MCP server. Instead of one tool per action, it has a search tool to find actions and execute_read and execute_write tools to run them. Only the /preorders, /back_in_stock, and /account actions can be run this way:

POST /api/v2/external/mcp

Authenticate with X-Auth-Token: <key> or Authorization: Bearer <key>. See the MCP server docs.

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. Changing enabled turns the offer on or off the way the dashboard does, so a scheduled offer’s dates are updated too.
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. Send null (or an empty string) to clear an override.
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.

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 360 points per 60 seconds

Every response includes rate-limit headers:

X-RateLimit-Limit: 360
X-RateLimit-Remaining: 358
X-RateLimit-Reset: 1751500860

If you exceed the limit you receive 429 Too Many Requests with a Retry-After header indicating how many seconds to wait.

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.

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.

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.

Should I use v1 or v2 for a new preorder integration?

Use the v2 API for new integrations. It exposes:

  1. A richer action surface with natural-language aliases.
  2. A self-describing /help manifest.
  3. An MCP server endpoint.
  4. 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).

  1. STOQ removes the Shopify selling plan group, detaches variants, and resets variant metafields.
  2. Existing customer orders already placed against this offer are not affected: they remain in Shopify and STOQ continues to track them.
  3. 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.

  1. Pass an array of STOQ order IDs (order_ids) or Shopify order IDs (shopify_order_ids), along with a subject, header, and description.
  2. The emails are sent asynchronously via a background job.
  3. You cannot pass both order_ids and shopify_order_ids in the same call.

See also the preorder shipping update email article for how these emails are templated.

Can I split one offer's products into separate offers through the API?

There’s no single “convert” endpoint. Create each new offer first (API-created offers use the custom product source), then add variants to it with the offer’s add_variants action. A variant can be on more than one offer, so to also take it off the grouped offer, either pass remove_from_conflicting_offers: true on add_variants or call the grouped offer’s remove_variants. For a large migration, STOQ support can also do it for you.

Was this page helpful?

Still stuck? Ask Quinn or chat with support