Add products variants to an offer
API v2 is in public beta: endpoints and fields can change before general availability. See the changelog
/preorders/offers/{id}/products/add_variantsCosts 2 rate-limit points · Rate limits
Attach products and variants to a preorder offer, or re-run a collection, tag or all-products selection.
Custom selection: pass shopify_variant_ids, shopify_product_ids (every variant of the product) or shopify_collection_id (every product in the collection), all Shopify gids. Already-attached variants come back under already_present and variants Shopify has deleted under deleted_in_shopify. Collection, tag and all-products selections fill themselves: call with no ids to re-run the selection, as the dashboard's 'Add all products' does. Up to 250 variants attach during the call; above that a job does it; either way poll job.status_url until it is completed. skip_variants_in_market_offers: true leaves out variants that a market-specific offer owns. A custom add first runs the offer Products page's pre-flight check (the store-wide Products tab's 'Add to offer', /preorders/products/add_to_offer, runs none): when other offers already have the variants, 'Continue selling' is off where STOQ won't turn it on, or a sales channel can't sell preorders, nothing is added and it answers 409 with code preflight_issues, the issues under preflight and the resolve keys still needed under unresolved. Pass resolve with the Products tab's fix for each issue type (remove_from_conflicting_offers, enable_continue_selling, skip_sales_channel_products), or skip_preflight: true to add without the check. The answer lists what each fix did per variant under resolved.
Also searched asadd variantsattach variantsadd products to offerlink variantsinclude variantsadd collection to offeradd all products
Path parameters
idstringRequired
Headers
Idempotency-KeystringA fresh unique key for every write (a new UUID, say); reuse it only to retry that write. Keys are remembered for 1 hour: the same key and the same request within that hour returns the first result, with
Idempotent-Replayed: true, without running again; the same key with a different method, path or body is refused (422idempotency_key_reused); a retry while the first is still running gets 409idempotency_key_in_progress. Keys are per store. Server errors and rate limits are not kept, so their retry runs.
Request body
shopify_variant_idsarray of stringsshopify_product_idsanyshopify_collection_idanyskip_variants_in_market_offersanyskip_preflightanyresolveany
Returns
selectionstringExample:customshopify_collection_idanytagsarray of anyseligible_inventory_policystringExample:allexcluded_shopify_variant_idsarray of anysexclusion_taganyvariants_addedarray of stringsalready_presentarray of stringsdeleted_in_shopifyarray of anysjobobjectShow 2 child attributesHide child attributes
job_idstringExample:73a74948-2b57-4cc3-971f-2b441828c977status_urlstringExample:/api/v2/external/preorders/product_variants/jobs/73a74948…
Errors
Missing or invalid X-Auth-Token.
The resource does not exist for this shop, or the action path is not registered.
An earlier request with the same Idempotency-Key is still running. Wait, then resend the same request with the same key.
The request failed validation.
Rate limited: cost-weighted per token (read 1, write 2; 360 points / 60s). Check Retry-After.
Every error has the same shape: see Errors.
curl -X POST "https://app.stoqapp.com/api/v2/external/preorders/offers/73a74948-2b57-4cc3-971f-2b441828c977/products/add_variants" \
-H "X-Auth-Token: $STOQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shopify_variant_ids": [
"gid://shopify/ProductVariant/47284734423096",
"gid://shopify/ProductVariant/47284734455864",
"gid://shopify/ProductVariant/47284734488632"
]
}'const res = await fetch("https://app.stoqapp.com/api/v2/external/preorders/offers/73a74948-2b57-4cc3-971f-2b441828c977/products/add_variants", {
method: "POST",
headers: {
"X-Auth-Token": process.env.STOQ_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"shopify_variant_ids": [
"gid://shopify/ProductVariant/47284734423096",
"gid://shopify/ProductVariant/47284734455864",
"gid://shopify/ProductVariant/47284734488632"
]
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://app.stoqapp.com/api/v2/external/preorders/offers/73a74948-2b57-4cc3-971f-2b441828c977/products/add_variants",
headers={"X-Auth-Token": os.environ["STOQ_API_KEY"]},
json={
"shopify_variant_ids": [
"gid://shopify/ProductVariant/47284734423096",
"gid://shopify/ProductVariant/47284734455864",
"gid://shopify/ProductVariant/47284734488632"
]
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://app.stoqapp.com/api/v2/external/preorders/offers/73a74948-2b57-4cc3-971f-2b441828c977/products/add_variants")
req = Net::HTTP::Post.new(uri, "X-Auth-Token" => ENV["STOQ_API_KEY"], "Content-Type" => "application/json")
req.body = {
shopify_variant_ids: [
"gid://shopify/ProductVariant/47284734423096",
"gid://shopify/ProductVariant/47284734455864",
"gid://shopify/ProductVariant/47284734488632"
]
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body){
"selection": "custom",
"shopify_collection_id": null,
"tags": [],
"eligible_inventory_policy": "all",
"excluded_shopify_variant_ids": [],
"exclusion_tag": null,
"variants_added": [
"gid://shopify/ProductVariant/47284734455864",
"gid://shopify/ProductVariant/47284734488632"
],
"already_present": [
"gid://shopify/ProductVariant/47284734423096"
],
"deleted_in_shopify": [],
"job": {
"job_id": "73a74948-2b57-4cc3-971f-2b441828c977",
"status_url": "/api/v2/external/preorders/product_variants/jobs/73a74948-2b57-4cc3-971f-2b441828c977"
}
}{
"job_id": "job_a1b2c3",
"status_url": "/api/v2/external/jobs/job_a1b2c3"
}{
"errors": [
"Invalid API key"
],
"code": "auth_invalid",
"details": [
{
"code": "auth_invalid",
"message": "Invalid API key",
"path": null,
"value": null,
"expected": null
}
]
}{
"errors": [
"Offer not found"
],
"code": "not_found",
"details": [
{
"code": "not_found",
"message": "Offer not found",
"path": null,
"value": null,
"expected": null
}
]
}{
"errors": [
"A request with this Idempotency-Key is still running"
],
"code": "idempotency_key_in_progress",
"details": [
{
"code": "idempotency_key_in_progress",
"message": "A request with this Idempotency-Key is still running",
"path": null,
"value": null,
"expected": null
}
]
}{
"errors": [
"name is required"
],
"code": "validation_failed",
"details": [
{
"code": "validation_failed",
"message": "name is required",
"path": null,
"value": null,
"expected": null
}
]
}{
"errors": [
"Rate limit exceeded"
],
"code": "rate_limited",
"details": [
{
"code": "rate_limited",
"message": "Rate limit exceeded",
"path": null,
"value": null,
"expected": null
}
]
}