POST /orders/{orderId}/addition-offers

Not yet available. Order additions are switched off in production and in the sandbox, so no buyer can ask for added cards and no offer can be sent: the lists answer eligibility.eligible: false with the reason feature_disabled, and sendOrderAdditionOffer answers 422 ORDER_ADDITION_NOT_ELIGIBLE. Do not build against this operation yet; the changelog announces when additions open. The example request shows the shape only and cannot succeed in the sandbox: sending an offer answers 422 ORDER_ADDITION_NOT_ELIGIBLE, and declining a request or withdrawing an offer answers 404 because none can exist. Offer the buyer more cards on an order that has not shipped, priced from the seller's live listings. Snapcaster freezes the lines, the money and the tax, reserves the offered units for 24 hours, and sends order.addition_offer.updated with reason: sent; the buyer approves and pays it on Snapcaster. Name a pending request in requestRef to answer it, which sends order.addition_request.resolved with status: offered. To change an outstanding offer, send a new one with replacesOfferRef: the old offer ends and its reservation moves to the new one in one step, and a refused replacement leaves the old offer as it was. An order holds one outstanding offer at a time. Read GET /shipping-options for the shippingOptionId of an upgrade when the cards no longer fit the parcel. Returns the sent offer.

Example

These examples send requests to the sandbox.

Replace the example orderId in the URL with your own.

Generated cURL request - Sandbox: Generated request
curl --request POST \
  --url 'https://api-sandbox.snapcaster.ca/api/v1/marketplace/orders/ord_sandbox_01K4A8Y6D4QJH2N3P5R7S9T0VW/addition-offers' \
  --header "Authorization: Bearer $MARKETPLACE_API_KEY" \
  --header "Idempotency-Key: $(uuidgen)" \
  --header 'Content-Type: application/json' \
  --data '{
  "lines": [
    {
      "skuId": 1,
      "quantity": 1
    }
  ]
}'

$(uuidgen) makes a new Idempotency-Key each time you run the command. To retry a command after a lost response, send the key from the first attempt.

Response 201: Sent offer
{
  "success": true,
  "data": {
    "offer_ref": "oao_9e2b7c4d1a6f43b8a5c0d3e7f1b8a264",
    "status": "sent",
    "lines": [
      {
        "sku_id": 14602,
        "product_id": 86,
        "quantity": 2,
        "reference_unit_price_cents": 450,
        "unit_price_cents": 400,
        "name": "Abyssal Nightstalker",
        "set_name": "Portal Second Age",
        "condition": "Near Mint",
        "category_name": "Magic: The Gathering",
        "language": "English",
        "finish": "Normal",
        "collector_number": null,
        "rarity": "Uncommon"
      }
    ],
    "money": {
      "currency": "CAD",
      "merchandise_cents": 800,
      "reference_merchandise_cents": 900,
      "shipping_cents": 0,
      "purchase_fee_cents": 28,
      "tax_cents": 104,
      "total_cents": 932,
      "tax_lines": [
        {
          "label": "HST",
          "rate": 0.13,
          "amount": 104
        }
      ]
    },
    "shipping_upgrade": null,
    "note": "Two more, at the price you paid",
    "request_ref": "oar_5c1e9a7f3b2d48e6a0f4c8d1b7e3a925",
    "support_case_id": null,
    "replaces_offer_ref": null,
    "sent_at": "2026-09-04T19:14:03.000Z",
    "expires_at": "2026-09-05T19:14:03.000Z",
    "resolved_by_party": null,
    "resolved_at": null,
    "resolution_note": null,
    "payment": null,
    "seller_fees": {
      "commission_cents": 40,
      "processing_fee_cents": 57,
      "platform_collected_tax_cents": 0,
      "fee_terms": {
        "marketplace_fee_rate_basis_points": 500,
        "marketplace_fee_per_item_cap_cents": 2500,
        "purchase_fee_rate_basis_points": 350,
        "processing_fee_rate_basis_points": 290,
        "processing_fee_fixed_cents": 30
      }
    }
  }
}

Endpoint

EnvironmentURL
Sandboxhttps://api-sandbox.snapcaster.ca/api/v1/marketplace/orders/{orderId}/addition-offers
Productionhttps://api.snapcaster.ca/api/v1/marketplace/orders/{orderId}/addition-offers
Authentication
  • marketplaceApiKey - http bearer. Send the seller's marketplace API key as Authorization: Bearer mp_{sellerCode}_{hex}. The mp_{sellerCode}_ prefix identifies the seller; the trailing hex is the secret. The seller generates the key and supplies it to your platform.

Request

Path parameters

orderId string required

The opaque public order id (the webhook/poll order_id).

Constraints: minimum length: 1 Example ord_sandbox_01K4A8Y6D4QJH2N3P5R7S9T0VW

Headers

Idempotency-Key string required

Keep this UUID when retrying the same order operation and body. Keys remain valid for the lifetime of the order. Changed contents return 409 IDEMPOTENCY_CONFLICT; a concurrent attempt returns 409 REQUEST_IN_PROGRESS with Retry-After.

Constraints: format: uuid Example f04e4a96-3c99-44dc-abce-5b6862c589c9

X-Snapcaster-Api-Version string

Override the platform pin for this request only. Webhook versions and the platform pin do not change.

Constraints: allowed values: 2026-10 Example 2026-10

Request body

Request body (application/json): SendOrderAdditionOfferBody

lines array of object required

The cards being offered. A SKU may be named only once, and you must hold a live listing for each: the reference price and the stock both come from it. The printing, condition, finish and language are frozen FROM the SKU, never restated here.

Constraints: minimum items: 1; maximum items: 25

Show child fieldsHide child fields
lines[].skuId integer required

The canonical catalog SKU of the exact printing offered.

Constraints: exclusive minimum: 0

lines[].quantity integer required

The number of units.

Constraints: minimum: 1; maximum: 100

lines[].unitPriceCents integer

A DISCOUNTED price per unit. Omit it to offer at your current listing price, which is where new units start. A price ABOVE that listing price is refused.

Constraints: minimum: 0

shippingOptionId integer

One of YOUR OWN shipping options to upgrade the parcel to. Required exactly when the combined remaining quantities no longer fit the option the buyer already paid for, and REFUSED when they do - an addition that rides the existing parcel is charged nothing for carriage. The option must be a typed one (a legacy free-text option states no card capacity), must carry the whole combined parcel, and may not cost less than the shipping this parcel has already collected. The buyer approves only the difference over the shipping this parcel has already collected, so a second upgrade never re-charges the first.

Constraints: exclusive minimum: 0

note string

Constraints: minimum length: 1; maximum length: 1000

requestRef string

The buyer request (oar_…) this offer answers. Answering it moves it to offered, so it can no longer be withdrawn or declined. Omit it for a DIRECT offer following a support conversation - a structured request is never a precondition.

Constraints: pattern: ^oar_[0-9a-f]{32}$

replacesOfferRef string

The outstanding offer (oao_…) this one REPLACES. Both the intent and the concurrency token: the old offer is ended and the new one reserved in a single transaction, and a ref that is no longer the outstanding one is refused rather than replacing whatever is. If the replacement cannot reserve its stock, nothing changes and the original offer and its reservation stay usable.

Constraints: pattern: ^oao_[0-9a-f]{32}$

Response

201

The sent offer, its units reserved until expires_at.

Response body: StoreOrderAdditionOfferResponse

success boolean required

Whether the operation completed as requested.

Constraints: allowed values: true Example true

data StoreOrderAdditionOffer required

The operation or event payload.

Show child fieldsHide child fields
data.offer_ref string required

The opaque public offer id (oao_…) - the {offerRef} of the withdraw and decline paths.

Example oao_9e2b7c4d1a6f43b8a5c0d3e7f1b8a264

data.status string required

sent and accepted are the two OUTSTANDING states: accepted means the buyer approved the exact amount and a payment is in flight, which is why the offer can no longer be withdrawn, replaced or expired. withdrawn is the seller taking it back; declined is the buyer’s answer; expired is the 24 hours running out; replaced is a different offer sent in its place; paid means the payment succeeded and the cards are now purchases on the order; payment_blocked means a captured payment was screened out and returned. Every ending is one-way, and every one but paid frees the stock the offer held - paid converts it into the purchase.

Constraints: allowed values: sent, accepted, withdrawn, declined, expired, replaced, paid, payment_blocked Example sent

data.lines OrderAdditionOfferLine[] required

The line items in this request, response, or event.

Show child fieldsHide child fields
data.lines[].sku_id integer required

The exact printing offered. Everything beside it is frozen from it.

Example 14602

data.lines[].product_id integer required

See Canonical catalog in Core concepts.

Example 86

data.lines[].quantity integer required

The number of units.

Example 2

data.lines[].reference_unit_price_cents integer required

The seller’s live listing price when the offer was authored: the ceiling this offer was allowed to charge up to, and the “was” price a discount reads against.

Example 450

data.lines[].unit_price_cents integer required

What this offer charges per unit; at or below the reference.

Example 400

data.lines[].name string or null required

Example Abyssal Nightstalker

data.lines[].set_name string or null required

Example Portal Second Age

data.lines[].condition string or null required

The normalized condition assigned to the card.

Example Near Mint

data.lines[].category_name string or null required

Example Magic: The Gathering

data.lines[].language string or null required

The language of the card printing.

Example English

data.lines[].finish string or null required

The catalog’s printing, under the name the order line snapshot uses.

Example Normal

data.lines[].collector_number string or null required

Example null

data.lines[].rarity string or null required

The rarity assigned to the card printing.

Example Uncommon

data.money OrderAdditionOfferMoney required
Show child fieldsHide child fields
data.money.currency string required

The ISO 4217 currency code used for monetary amounts.

Example CAD

data.money.merchandise_cents integer required

The offered cards, AFTER any seller discount.

Example 800

data.money.reference_merchandise_cents integer required

The same cards at the seller’s listing prices; above merchandise_cents by the discount.

Example 900

data.money.shipping_cents integer required

The shipping DIFFERENCE over shipping already paid for this parcel. EXACTLY 0 when the combined remaining quantities still fit the shipping option already paid for - nothing extra is charged for carriage the buyer has. Otherwise it is the shipping_upgrade option’s price less every shipping amount this parcel has already collected, including earlier paid upgrades, so a second upgrade never re-charges the first.

Example 0

data.money.purchase_fee_cents integer required

The buyer purchase fee at the ORIGINAL order’s frozen rate, on the discounted merchandise.

Example 28

data.money.tax_cents integer required

Example 104

data.money.total_cents integer required

The EXACT amount the buyer would pay - the sum of the four components above, frozen for the offer’s whole validity period. Nothing may accept for a different figure.

Example 932

data.money.tax_lines array of object required
Show child fieldsHide child fields
data.money.tax_lines[].label string required

Example HST

data.money.tax_lines[].rate number required

The decimal tax rate applied to the taxable amount.

Example 0.13

data.money.tax_lines[].amount integer required

The monetary amount in minor currency units.

Example 104

data.shipping_upgrade OrderAdditionOfferShippingUpgrade or null required

The shipping option this addition would UPGRADE the parcel to, or null when it rides the one already paid for. All purchased additions share the original parcel: there is never a second shipment. The upgrade becomes the order’s effective option and tracking requirement only when the payment succeeds, and the historical shipping charges are not rewritten - each stays on the payment that collected it. The amount is money.shipping_cents.

Show child fieldsHide child fields
data.shipping_upgrade.method string required

The upgraded option’s label, in the wording an order’s shipping method uses.

data.shipping_upgrade.is_tracked boolean required

Whether the upgraded option requires tracking when the order is marked shipped.

data.note string or null required

The seller’s own words beside the quote; null when none was given.

Example Two more, at the price you paid

data.request_ref string or null required

The buyer request this offer answered, or null when the seller offered directly.

Example oar_5c1e9a7f3b2d48e6a0f4c8d1b7e3a925

data.support_case_id integer or null required

Example null

data.replaces_offer_ref string or null required

The offer this one replaced, or null when it replaced none.

Example null

data.sent_at string required

Example 2026-09-04T19:14:03.000Z

data.expires_at string required

When the offer and its stock hold stop being valid: 24 hours after sent_at. Show it.

Example 2026-09-05T19:14:03.000Z

data.resolved_by_party string or null required

Who ended it - buyer on a decline, seller on a withdrawal or replacement, system on expiry, which belongs to nobody. Null while outstanding.

Constraints: allowed values: buyer, seller, operator, system, null Example null

data.resolved_at string or null required

Example null

data.resolution_note string or null required

Example null

data.payment OrderAdditionPayment or null required

The buyer’s payment for this offer, or null until one accepts it.

Show child fieldsHide child fields
data.payment.payment_ref string required

The opaque public payment id (oap_…).

data.payment.status string required

processing means the answer is not in yet - a browser redirect does not prove a payment succeeded, so keep asking. succeeded means the cards are on the order. failed means the attempt collected nothing and the offer can be accepted again. blocked means a captured payment was screened out and returned; this offer is over.

Constraints: allowed values: processing, succeeded, failed, blocked

data.payment.currency string required

The ISO 4217 currency code used for monetary amounts.

data.payment.amount_cents integer required

The exact amount the buyer approved, frozen from the offer.

data.payment.accepted_at string required
data.payment.completes_at string required

When this attempt’s 15-minute completion window closes - distinct from the offer’s own 24 hours. Past it a processing payment is UNKNOWN rather than failed, and is reconciled against the provider.

data.payment.resolved_at string or null required
data.payment.failure_reason string or null required
data.payment.attempts integer required

Above 1 when a declined card was retried on the same payment.

data.seller_fees OrderAdditionOfferSellerFees required
Show child fieldsHide child fields
data.seller_fees.commission_cents integer required

Commission on the discounted merchandise, capped per UNIT at the order’s frozen cap.

Example 40

data.seller_fees.processing_fee_cents integer required

Processing on the WHOLE additional payment - purchase fee and tax included - plus one fixed component for this one payment. It is never reapplied to the original capture.

Example 57

data.seller_fees.platform_collected_tax_cents integer required

Example 0

data.seller_fees.fee_terms object required

The five terms this quote was priced at: the ORDER’s frozen terms as captured, never today’s Marketplace settings. An order that cannot prove all five cannot carry an addition at all.

Show child fieldsHide child fields
data.seller_fees.fee_terms.marketplace_fee_rate_basis_points integer required

Example 500

data.seller_fees.fee_terms.marketplace_fee_per_item_cap_cents integer required

Example 2500

data.seller_fees.fee_terms.purchase_fee_rate_basis_points integer required

Example 350

data.seller_fees.fee_terms.processing_fee_rate_basis_points integer required

Example 290

data.seller_fees.fee_terms.processing_fee_fixed_cents integer required

Example 30

Errors

Errors return a MarketplaceError body, except HTTP 413, 500 (ApiError). Error codes explains both error bodies and lists every code.

  • 400 INVALID_REQUEST IDEMPOTENCY_KEY_REQUIRED UNSUPPORTED_API_VERSION

    Invalid command body or missing/invalid UUID Idempotency-Key. Unknown or retired API-version override (UNSUPPORTED_API_VERSION).

    Do this: Correct the request fields and supply a UUID Idempotency-Key. Keep the same key when retrying the same command. For UNSUPPORTED_API_VERSION, choose a supported version from the API versioning guide.

  • 401 UNAUTHORIZED

    Missing or invalid marketplace API key (UNAUTHORIZED).

    Do this: Send the marketplace API key for this seller as a Bearer token, then retry.

  • 403 SUBSCRIPTION_EXCLUDES_ORDERS

    The platform's subscription is inventory, which does not include orders (SUBSCRIPTION_EXCLUDES_ORDERS). Only orders and both subscriptions read and act on orders.

    Do this: Do not retry. Ask Snapcaster to change the platform subscription to orders or both if the platform should handle orders.

  • 404 ORDER_NOT_FOUND ORDER_ADDITION_REQUEST_NOT_FOUND

    Order additions are not yet available. No order matches this id for the authenticated seller (ORDER_NOT_FOUND), or the requestRef names no add-on request on the order (ORDER_ADDITION_REQUEST_NOT_FOUND). Another seller's order also returns 404.

    Do this: Check the order id against GET /orders and the request id against GET /orders/{orderId}/addition-requests.

  • 409 IDEMPOTENCY_CONFLICT REQUEST_IN_PROGRESS ORDER_ADDITION_OFFER_ALREADY_OUTSTANDING ORDER_ADDITION_OFFER_NOT_OUTSTANDING ORDER_ADDITION_OFFER_NOT_SENT ORDER_ADDITION_OFFER_PRICE_CHANGED ORDER_ADDITION_OFFER_INSUFFICIENT_STOCK ORDER_ADDITION_OFFER_SHIPPING_CHANGED ORDER_ADDITION_REQUEST_NOT_PENDING

    Order additions are not yet available. IDEMPOTENCY_CONFLICT or REQUEST_IN_PROGRESS. Retry an in-progress command with the same key after Retry-After seconds. Or the offer cannot be sent as it stands: another offer is awaiting the buyer (ORDER_ADDITION_OFFER_ALREADY_OUTSTANDING, details.offerRef); replacesOfferRef is not the outstanding offer (ORDER_ADDITION_OFFER_NOT_OUTSTANDING); the buyer is paying for the outstanding offer (ORDER_ADDITION_OFFER_NOT_SENT); a listing was repriced while the offer was priced (ORDER_ADDITION_OFFER_PRICE_CHANGED); the units cannot all be reserved (ORDER_ADDITION_OFFER_INSUFFICIENT_STOCK, details.shortfalls); the parcel's shipping changed while the offer was priced (ORDER_ADDITION_OFFER_SHIPPING_CHANGED); or requestRef is no longer pending (ORDER_ADDITION_REQUEST_NOT_PENDING). Nothing is written on any of these: a refused replacement leaves the original offer and its reservation as they were.

    Do this: For REQUEST_IN_PROGRESS, wait Retry-After seconds and retry the same key and body. For IDEMPOTENCY_CONFLICT, reuse the original body or create a new UUID for a different operation. For the ORDER_ADDITION codes, read GET /orders/{orderId}/addition-offers and your listings, then send a corrected offer under a new key.

  • 413 PAYLOAD_TOO_LARGE

    The body is larger than this route accepts (PAYLOAD_TOO_LARGE).

    Do this: Send a smaller body and retry.

  • 422 ORDER_ADDITION_NOT_ELIGIBLE ORDER_ADDITION_SKU_UNKNOWN ORDER_ADDITION_OFFER_LISTING_MISSING ORDER_ADDITION_OFFER_PRICE_ABOVE_LISTING ORDER_ADDITION_OFFER_SHIPPING_CAPACITY ORDER_ADDITION_OFFER_SHIPPING_OPTION_INVALID ORDER_ADDITION_OFFER_SHIPPING_NOT_REQUIRED ORDER_ADDITION_OFFER_SHIPPING_DOWNGRADE ORDER_ADDITION_OFFER_SHIPPING_UNSTATED ORDER_ADDITION_OFFER_BELOW_MINIMUM ORDER_ADDITION_OFFER_TAX_UNRESOLVED

    Order additions are not yet available. The order cannot carry an add-on (ORDER_ADDITION_NOT_ELIGIBLE, details.reasons as in the list's eligibility); a skuId is not in the catalog (ORDER_ADDITION_SKU_UNKNOWN); the seller has no live listing for one (ORDER_ADDITION_OFFER_LISTING_MISSING); a unitPriceCents is above the listing price (ORDER_ADDITION_OFFER_PRICE_ABOVE_LISTING); the combined parcel does not fit the shipping already paid for or the option named (ORDER_ADDITION_OFFER_SHIPPING_CAPACITY, details maxCards, remainingUnits, offeredUnits, upgradeOffered); shippingOptionId is not one of the seller's typed options (ORDER_ADDITION_OFFER_SHIPPING_OPTION_INVALID); the parcel already fits, so no upgrade may be charged (ORDER_ADDITION_OFFER_SHIPPING_NOT_REQUIRED); the option costs less than the shipping already paid (ORDER_ADDITION_OFFER_SHIPPING_DOWNGRADE); the order records no shipping evidence to upgrade from (ORDER_ADDITION_OFFER_SHIPPING_UNSTATED); the total is below the payment minimum (ORDER_ADDITION_OFFER_BELOW_MINIMUM, details.minimumCents); or the delivery province cannot be taxed (ORDER_ADDITION_OFFER_TAX_UNRESOLVED). These are the codes the seller dashboard answers for the same offer.

    Do this: Correct the offer: read GET /shipping-options for an option that carries the parcel, and price each line at or below its listing price. An ineligible order needs no offer.

  • 500 INTERNAL_ERROR

    An unexpected failure (INTERNAL_ERROR).

    Do this: Retry with bounded retries and jitter. If the failure persists, contact Snapcaster with the X-Correlation-ID response header.