Changes to the Marketplace API, newest first. Each entry lists the API version and any action your integration needs to take.

  1. API 2026-1022 Additive

    One dated version with purchase identity on every line, retry-safe order commands, ordered listing snapshots and recoverable order state.

    The launch contract. 2026-10 is the only supported version and every platform is pinned to it; X-Snapcaster-Api-Version naming any other version, the pre-launch 2026-07 and 2026-09 included, answers 400 UNSUPPORTED_API_VERSION. Every order line, every line of an order.cancelled or order.refunded event and every line of a pending cancellation request carries line_ref, the purchase identity; At launch, cancellation and refund history lines carried sku_id and quantity only; a later additive change below gives them line_ref. cancelOrder and refundOrder name lines by skuId or lineRef; lineRef is needed only when one SKU is on more than one line the command can still act on, which ORDER_LINE_AMBIGUOUS reports. Order commands require a UUID Idempotency-Key: a retry with the same key and body answers the original status and JSON value, without keeping object key order; changed contents, or a key first used from the seller dashboard, answer 409 IDEMPOTENCY_CONFLICT. Every listing write carries asOf and is applied as an ordered snapshot. GET /orders and GET /orders/{orderId} answer complete current order state, and the final page's syncToken recovers every later change, transactions that commit late included. order.updated carries that same order plus its reason. The 2xx on the event that announced a sale acknowledges its stock, and a fulfillment report acknowledges nothing: an orders subscription deducts stock on order.created and order.updated before returning 2xx and needs a registered webhook endpoint, and a both subscription deducts only on inventory.adjusted. A positive inventory.adjusted that returns sold units is sent only after the platform acknowledged the sale it compensates, and never if that sale is parked dead unacknowledged. An inventory subscription receives 403 SUBSCRIPTION_EXCLUDES_ORDERS from the order routes. Webhook retries repeat the same payload bytes, except that after Snapcaster corrects a guest buyer's mistyped email every later order.created and order.updated delivery carries the corrected customer.email, and nothing else in them changes. A path or method no operation serves answers 404 with the JSON body {"status":"error","message":"Cannot GET <path>","code":"NOT_FOUND"}; a body that does not parse, a path segment that is not valid percent-encoding, and a missing required body answer the operation's 400 INVALID_REQUEST after the API key is checked; a body larger than the operation accepts answers 413 PAYLOAD_TOO_LARGE. GET /me lists every incomplete readiness prerequisite, and its OPERATOR_REVIEW_APPROVED entry carries a review status.

    Required action
    Build against 2026-10. Key order lines on line_ref, send a UUID Idempotency-Key on every order command and asOf on every listing write, and save the final syncToken of each order poll. With an orders subscription, register a webhook endpoint and return 2xx on order.created and order.updated only after deducting their stock.
    Migration or testing
    No migration for a new integration. Walk the Sandbox quickstart, then test a lost-response retry of an order command and a recovery pass with syncToken in the sandbox. Webhook deliveries still queued when 2026-10 launched are not sent if they cannot be stated in its shape: every pre-launch order.updated, an order.created or sale inventory.adjusted for an order whose line a substitution rewrote with no record of that line as bought, and a positive inventory.adjusted returning units no retained sale took or whose sale was not delivered. Recover those orders with GET /orders/{orderId} or your saved syncToken.

    Additive changes

    • upsertListings and bulkWriteListings: productId is optional, and Snapcaster takes each listing's product from the canonical catalog entry for its skuId. An item whose productId names a different product than the catalog holds for its skuId is rejected with the new per-item code PRODUCT_MISMATCH, and Snapcaster stores nothing for it. An item whose skuId is not yet in the catalog still needs productId, and when the catalog gains that SKU the listing takes the catalog's product.
    • getProduct: each product.details[] entry gains runs, its value as text runs and line breaks with emphasis marking the text the catalog emphasized, and value is now plain text with the catalog markup removed and each line break as a newline.
    • Every webhook event (orderCreatedWebhook, orderUpdatedWebhook, orderCancelledWebhook, orderRefundedWebhook, inventoryAdjustedWebhook): Snapcaster now retries a failing delivery for at least 24 hours (37 attempts, the wait doubling from 30 seconds up to one hour, with up to 20% random variation) before holding it for replay, up from 8 attempts over about one hour, so ask for a replay only after an outage longer than 24 hours. A replayed event keeps its original event_id and carries the same payload bytes as its earlier attempts, except that a corrected guest email reaches it in customer.email.
    • Every webhook event (orderCreatedWebhook, orderUpdatedWebhook, orderCancelledWebhook, orderRefundedWebhook, inventoryAdjustedWebhook): an event raised before your platform registers its webhook endpoint now waits unsent, spending none of its retry attempts, and is delivered once the endpoint is registered, where before it spent its attempts and was held for replay.
    • getOrder, listOrders and orderUpdatedWebhook: each cancellations[].lines and refunds[].lines entry gains line_ref, the purchase the cancellation or refund terminated, the same line_ref its order.cancelled or order.refunded line carries. It is null only on a cancellation recorded before Snapcaster kept an order event log, when its sku_id names more than one line on the order or a substitution has rewritten a line on that order; every refund line carries it.
    • getStore and updateStore: the store gains sellerId, the seller's numeric id and the same value as data.seller_id on every webhook event for that seller, so a platform can match events to the store its key belongs to before the first sale.
    • getOrder, listOrders and orderUpdatedWebhook: each order line gains substitution_credit_cents, the credit per unit its accepted substitutions returned (0 when none), substitution, the fulfillment report that last rewrote it and the card it replaced (null on a line that ships as sold), and reported_line_ref, always null today. The order gains substitutions[], one entry per accepted fulfillment report that substituted a line, with each removed or substituted line, its per-unit credit and the report's payment_refund. orderUpdatedWebhook gains reason: payment_refund_updated, sent each time the payment_refund.status of a cancellation, refund or substitution changes after its first event, including a settled refund the bank later returns and a failed one a later retry settles. A refund returned on several payments reads failed if any of them refused it, succeeded once all settled, and pending otherwise.
    • createSandboxTestPurchase and getSandboxTestPurchase, sandbox only: a platform buys its own seller's browsable listings as Snapcaster's sandbox buyer, paid in Stripe test mode, and gets the usual signed order.created or inventory.adjusted once the capture creates the order, so the Sandbox quickstart's test purchase no longer waits on Snapcaster. The production API does not serve these operations.
    • correctOrderAddress and retryOrderAddressVerification: two new order commands, each with a UUID Idempotency-Key. POST /orders/{orderId}/corrections moves the order's ship_to to a corrected name and address while it is created or accepted, keeps customer as the buyer entered it, records your API key as the author and sends order.updated with reason: address_corrected; it answers 409 CORRECTION_NOT_ALLOWED past accepted, 422 CORRECTION_CHANGES_DELIVERY_REGION for a new province or country and 422 CORRECTION_UNCHANGED for the current ship_to. POST /orders/{orderId}/address-verification/retry runs the address check again while address_verification.status is pending or failed and sends order.updated with reason: address_verified when a result lands; it answers 429 ADDRESS_VERIFICATION_RETRY_TOO_SOON within a minute of a failed check and 409 ADDRESS_VERIFICATION_NOT_RETRYABLE once the check has a verdict. A replay of either command answers the stored response, so a retried address check never runs a second lookup.
    • listCancellationRequests and declineCancellationRequest: a platform reads every cancellation request on an order, pending and resolved, and declines a pending one with a reason. Snapcaster emails the buyer the same notice a decline in the seller dashboard sends. A decline needs a UUID Idempotency-Key and no step-up, and an inventory subscription receives 403 SUBSCRIPTION_EXCLUDES_ORDERS from both. Three webhook events, order.cancellation_request.created, order.cancellation_request.withdrawn and order.cancellation_request.resolved (with outcome honored or declined), reach orders and both subscriptions. Each carries the request as the list returns it plus seller_id, and its 2xx acknowledges no stock.
    • getOrder, listOrders and orderUpdatedWebhook: dispatch gains overdue_at, when Snapcaster marked the order overdue for missing its ship-by deadline dispatch.deadline_at (the end of the third Toronto business day after the order was placed), and payout_paused, true while the seller payout for the order is paused. A new webhook event, order.dispatch_overdue (orderDispatchOverdueWebhook), reaches orders and both subscriptions once per order when it goes overdue, with order_id, seller_id, deadline_at, overdue_at and payout_paused: true; its 2xx acknowledges no stock. A missed event is recovered through the syncToken poll.
    • listChangeRequests, acceptChangeRequest and declineChangeRequest: a platform reads every change request on an order (a buyer address change, a buyer removal of lines or of the whole order, and a seller fulfillment report), each line naming its line_ref, and accepts or declines a pending buyer request. An accepted address change moves ship_to, restarts address verification and sends order.updated with reason: address_corrected; an accepted removal cancels the lines before shipping or refunds them after, with the optional restock, keeping the buyer's purchase fee as a dashboard accept does. Both answers need a UUID Idempotency-Key and no step-up, email the buyer as the dashboard does, and answer 409 ORDER_CHANGE_REQUEST_NOT_PENDING and 422 ORDER_CHANGE_REQUEST_NOT_ALLOWED as the dashboard does; an inventory subscription receives 403 SUBSCRIPTION_EXCLUDES_ORDERS. Two webhook events, order.change_request.created and order.change_request.resolved (with outcome accepted, declined, withdrawn, voided, removed_instead or order_cancelled), reach orders and both subscriptions for every request, whoever answers it. Each carries the request as the list returns it plus seller_id, its 2xx acknowledges no stock, and every change to a request brings the order back into the syncToken poll.
    • sendWebhookTest: POST /webhooks/test with a UUID Idempotency-Key queues one signed webhook.test event, data {seller_id, platform_id}, to your registered endpoint, signed and retried like any event, and answers 202 with its event_id. Every subscription can call it in every environment, and it changes no order and no stock. A repeated key returns the same event_id. It answers 409 WEBHOOK_ENDPOINT_NOT_REGISTERED when your platform has no endpoint and 429 RATE_LIMITED with Retry-After past 5 test events a minute for each platform.
    • listShippingOptions, available now: a platform reads its seller's shipping options with the shipping type taxonomy they are drawn from, and in the sandbox uses an option's id as shippingOptionId on createSandboxTestPurchase. Add-on offers, which will name an upgrade by an option's id, are not yet available.
    • Not yet available: order additions are switched off in production and in the sandbox, so no order can take an add-on and none of the three add-on events is sent yet; this entry describes the contract for when they open, which a later entry will announce. listOrderAdditionRequests, declineOrderAdditionRequest, listOrderAdditionOffers, sendOrderAdditionOffer and withdrawOrderAdditionOffer: a platform reads a buyer's requests to add cards to an order and the offers sent on it, declines a pending request, and sends, replaces or withdraws a priced offer, which reserves its units for 24 hours. Each command needs a UUID Idempotency-Key and acts as the API key; it refuses an offer with the codes the seller dashboard answers, and an inventory subscription receives 403 SUBSCRIPTION_EXCLUDES_ORDERS from every one of these routes. The buyer still approves and pays on Snapcaster. Three webhook events reach orders and both subscriptions: order.addition_request.created, order.addition_request.resolved (its status offered, declined or withdrawn), and order.addition_offer.updated with a reason for each offer change, from sent to paid. Each carries the resource as its list returns it plus order_id and seller_id, and its 2xx acknowledges no stock: a paid offer's units are still announced, and acknowledged, by order.updated with reason items_added.
    • listSubstituteCandidates, createChangeRequest and withdrawChangeRequest: a platform files a fulfillment report for lines the seller cannot ship as sold, before the order ships, and withdraws it before the buyer answers. Each report line is named by lineRef or skuId and is either removed (action: remove, optional quantity) or substituted whole (action: substitute) with substituteSkuId, another SKU of the line's catalog product that the seller lists live, and creditCents, the credit per unit. Snapcaster reads the product from its catalog, never from the listing, and resolves the SKU to the seller's own listing. GET /orders/{orderId}/substitute-candidates lists the SKUs that qualify for a line, each with its external_ref, available and unit_price; availability is provisional until the buyer answers. Filing and withdrawing need a UUID Idempotency-Key and no step-up, and are refused with the seller dashboard's codes (422 ORDER_CHANGE_REQUEST_NOT_ALLOWED for another catalog product, a SKU the seller does not list or a substitution smaller than the line's remaining units, 400 ORDER_LINE_AMBIGUOUS with the candidate lineRefs in details); an inventory subscription receives 403 SUBSCRIPTION_EXCLUDES_ORDERS. The report reaches the platform as order.change_request.created, and the buyer's answer or the withdrawal as order.change_request.resolved; an accepted substitution sends order.updated with reason: line_substituted, keeping the line's line_ref, quantity and unit_price and adding the credit, and removed units send order.cancelled. Each change request's substitute gains external_ref, your reference on the substitute listing.
    • createSandboxCancellationRequest, createSandboxChangeRequest and answerSandboxChangeRequest, sandbox only: Snapcaster's sandbox buyer raises a cancellation request, an address change or a removal on an order from the seller's own test purchase, or answers the seller's fulfillment report with accept, remove_instead or cancel_order, through the same buyer action a real buyer takes, so a platform can walk every Path B order command from the docs alone. Each takes a UUID Idempotency-Key, needs an orders or both subscription (an inventory platform receives 403 SUBSCRIPTION_EXCLUDES_ORDERS), sends the same webhooks the buyer's action sends, and answers 404 ORDER_NOT_FOUND for an order no test purchase of the seller created. The production API does not serve these operations. The credentials page also lists three sandbox catalog SKUs of one product, so a substitution can be filed.
    • ChangeRequest, ChangeRequestEventData and ChangeRequestResolvedData: address and previous_address are now documented as an address or null, as they were always sent and as their descriptions said: set only on an address_change, null on every other kind. The schema, not the data, changed; a platform that rejected the null against the old schema can accept it now.
    • getStore: the OPERATOR_REVIEW_APPROVED and AUTHENTICATOR_RECOVERY_VERIFIED readiness actions (CONTACT_MARKETPLACE_OPERATIONS) now link mailto:info@snapcaster.gg, the address the guides name for the store review, in place of the buyer and seller support inbox. CONTACT_SUPPORT actions are unchanged.
    • createSandboxCancellationRequest, sandbox only: the MarketplaceError code enum now allows REQUEST_ALREADY_OPEN and CANCELLATION_REQUEST_LIMIT_REACHED, which its 409 lists, and CANCELLATION_NOT_ALLOWED, which its 422 lists: the codes those answers always carried. The schema, not the data, changed; a platform that rejected these answers against the old schema can accept them now.
    • getStore and updateStore: the store gains subscription, your platform's subscription (orders, inventory or both, or null for a seller linked to no platform), so you can read it instead of inferring it from an order operation's 403 SUBSCRIPTION_EXCLUDES_ORDERS.
    • getStore and updateStore: the VACATION_DISABLED readiness action (DISABLE_VACATION) now links PATCH /api/v1/marketplace/me, which the marketplace API key can send with {"vacation": false}, in place of the account API route /api/v1/marketplace/seller/me that refuses the key with 401. Every other readiness action is unchanged, and the seller dashboard's own readiness list still links the account API route.