# Cancel an order Cancels an order that has not yet started preparation. This endpoint is only available when fulfillment_status is PENDING or IN_PROGRESS. Fulfillment restriction: Once the store begins preparing the order (fulfillment_status is PREPARING or later), this endpoint returns 409 Conflict. Cancellation at that stage requires store intervention. Automatic void/refund: When an order is cancelled, the server automatically handles all attached payments: - Payments in PENDING or AUTHORIZED status are voided (no charge). - Payments in CAPTURED or COMPLETED status are refunded in full. - Refunds follow the minimize-cash-refund strategy: loyalty points are refunded first, then gift cards, then credit/debit cards. No separate refund request is needed after cancellation. Idempotency: This endpoint requires the Idempotency-Key header. If the order was already cancelled with the same key, the server returns the current order state without re-processing. Result: The order's status is set to CANCELLED, fulfillment_status is set to CANCELLED, and all payments are voided or refunded. Endpoint: POST /orders/{order_id}/cancel Version: 1.0.0 Security: oauth2 ## Path parameters: - `order_id` (string, required) Unique identifier for the order. ## Header parameters: - `Idempotency-Key` (string, required) A unique key (UUID v4) to ensure idempotent request processing. Required on all POST, PUT, and DELETE requests. If a request is retried with the same key within 24 hours, the server returns the cached success response without re-processing. Error responses are NOT cached -- retrying after an error with the same key will re-execute the request. Generate a new UUID v4 for each unique operation. Reuse the same key only when retrying a failed or timed-out request. Example: "d7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23" ## Request fields (application/json): - `reason` (string,null) Optional human-readable reason for cancellation. Stored on the order for reporting and analytics. Example: "Customer changed their mind" ## Response 200 fields (application/json): - `id` (string, required) Unique identifier for the order. - `cart_id` (string, required) The cart this order was created from. - `location_id` (string, required) The location (store) this order belongs to. - `customer_id` (string,null) Customer identifier from the cart at checkout time. Null for anonymous orders. Locked at checkout -- cannot be changed after the order is created. Example: "CUST-12345" - `status` (string, required) Order lifecycle status. Tracks the high-level state of the order from creation through completion or cancellation. - PENDING: Order created from cart checkout, awaiting payment. - CONFIRMED: At least one payment completed; order accepted by the store. - COMPLETED: Order fulfilled and closed. - FAILED: Order could not be processed (e.g., all payments failed). - VOIDED: Order voided before any fulfillment began. - CANCELLED: Order cancelled by customer or system. State transitions: - PENDING -> CONFIRMED, FAILED, VOIDED, CANCELLED - CONFIRMED -> COMPLETED, CANCELLED - COMPLETED, FAILED, VOIDED, CANCELLED are terminal (no further transitions) Enum: "PENDING", "CONFIRMED", "COMPLETED", "FAILED", "VOIDED", "CANCELLED" - `payment_status` (string, required) Aggregate payment status across all payments on the order. This is a server-computed summary -- do not set directly. - UNPAID: No payments submitted yet. - PROCESSING: At least one payment is in a non-terminal state (PENDING or AUTHORIZED). - PARTIALLY_PAID: Some payments completed but total paid = order total. The order is fully covered. State transitions: - UNPAID -> PROCESSING, PARTIALLY_PAID, PAID - PROCESSING -> PARTIALLY_PAID, PAID, UNPAID (if all payments fail) - PARTIALLY_PAID -> PROCESSING, PAID - PAID is terminal for forward flow (refunds revert to PARTIALLY_PAID or UNPAID) Enum: "UNPAID", "PROCESSING", "PARTIALLY_PAID", "PAID" - `fulfillment_status` (string, required) Fulfillment lifecycle status. Tracks the physical preparation and handoff of the order. Maps directly from the backend fulfillment state machine. - PENDING: Order created, not yet accepted by store. - IN_PROGRESS: Store accepted the order and is queuing it. - PREPARING: Store staff actively assembling the order. - READY_FOR_PICKUP: Order assembled and waiting for customer (pickup/curbside). - FULFILLED: Customer received the order (pickup/curbside/kiosk). - DELIVERED: Order delivered to customer address (delivery orders). - RETURNED: Order returned after fulfillment. - CANCELLED: Fulfillment cancelled before completion. State transitions: - PENDING -> IN_PROGRESS, CANCELLED - IN_PROGRESS -> PREPARING, CANCELLED - PREPARING -> READY_FOR_PICKUP, CANCELLED - READY_FOR_PICKUP -> FULFILLED, DELIVERED, CANCELLED - FULFILLED -> RETURNED - DELIVERED -> RETURNED - RETURNED, CANCELLED are terminal (no further transitions) Cancellation restriction: Orders can only be cancelled via POST /orders/{order_id}/cancel when fulfillment_status is PENDING or IN_PROGRESS. Once preparation begins, cancellation requires store intervention. Enum: "PENDING", "IN_PROGRESS", "PREPARING", "READY_FOR_PICKUP", "FULFILLED", "DELIVERED", "RETURNED", "CANCELLED" - `items` (array, required) Line items in the order (locked at checkout). - `items.id` (string, required) Unique identifier for this order item. - `items.menu_item_id` (string, required) Reference to the original menu item. - `items.name` (string, required) Display name of the item (locked at checkout). - `items.quantity` (integer, required) Number of this item in the order. - `items.base_price` (object, required) Base price per unit (locked at checkout). - `items.base_price.amount` (integer, required) Monetary value in the smallest currency unit (cents for USD). Example: 1299 = $12.99. Example: 1299 - `items.base_price.currency` (string, required) ISO 4217 currency code. Example: "USD" - `items.modifier_total` (object, required) Total price of all selected modifiers per unit (locked at checkout). - `items.item_total` (object, required) Total for this line item: (base_price + modifier_total) * quantity. Locked at checkout. - `items.modifier_selections` (array, required) Modifiers selected for this item (locked at checkout). - `items.modifier_selections.modifier_group_id` (string, required) ID of the modifier group this selection belongs to. - `items.modifier_selections.modifier_id` (string, required) ID of the selected modifier within the group. - `items.modifier_selections.quantity` (integer) Quantity of this modifier. Relevant for modifier groups with allows_duplicates: true (e.g., "Extra Cheese x2"). - `items.modifier_selections.nested_selections` (array) Selections for nested modifier groups. For example, selecting "Steak" as a protein, then choosing "Medium" from the steak preparation sub-group. Supports up to 3 nesting levels, matching the menu's modifier group depth. - `items.special_instructions` (string,null) Customer's special instructions for this item, locked at checkout. Null if none were provided. - `items.age_verification_required` (boolean) True if this item requires age verification. Copied from the menu item at checkout time. - `items.minimum_age` (integer,null) Minimum age required to purchase this item (e.g., 21 for alcohol). Null if age verification is not required. - `payments` (array, required) Payments attached to this order. Empty array until the first payment is submitted via POST /orders/{order_id}/payments. - `payments.id` (string, required) Unique identifier for the payment. - `payments.order_id` (string, required) The order this payment is attached to. - `payments.status` (string, required) Payment processing status. The typical online ordering flow is: PENDING -> COMPLETED (immediate capture). For pre-authorization flows: PENDING -> AUTHORIZED -> CAPTURED -> COMPLETED. Terminal states: COMPLETED, VOIDED, REFUNDED, FAILED. State transitions: - PENDING -> AUTHORIZED, COMPLETED, FAILED - AUTHORIZED -> CAPTURED, VOIDED, FAILED - CAPTURED -> COMPLETED, REFUNDED, PARTIALLY_REFUNDED - COMPLETED -> REFUNDED, PARTIALLY_REFUNDED - VOIDED, REFUNDED, FAILED are terminal (no further transitions) - PARTIALLY_REFUNDED -> REFUNDED (when remaining amount is refunded) Enum: "PENDING", "AUTHORIZED", "CAPTURED", "COMPLETED", "VOIDED", "REFUNDED", "PARTIALLY_REFUNDED", "FAILED" - `payments.payment_method` (string, required) The payment method used. These are the API-facing values; the server maps them to internal payment processing types. - CREDIT_CARD: Visa, Mastercard, Amex, Discover credit cards - DEBIT_CARD: Debit cards with PIN or signature - CASH: Customer pays cash at the pickup counter. Only meaningful for PICKUP and DINE_IN handoff modes. The order is submitted online but payment is collected in-store. - GIFT_CARD: Store gift cards or third-party gift cards - LOYALTY_POINTS: Loyalty program points redeemed as payment - DIGITAL_WALLET: Apple Pay, Google Pay, or other digital wallets - EBT: SNAP/EBT card. Item eligibility applies -- only SNAP-eligible items (generally food items, excluding hot prepared foods and tobacco) can be paid with EBT. Check allowed_tenders on each menu item. Tolerant Reader: New payment method values may be added in future API versions. Your integration should handle unknown enum values gracefully (e.g., log a warning and skip) rather than failing. Enum: "CREDIT_CARD", "DEBIT_CARD", "CASH", "GIFT_CARD", "LOYALTY_POINTS", "DIGITAL_WALLET", "EBT" - `payments.amount` (object, required) The payment amount in cents. - `payments.tip_amount` (any) Tip amount included in this payment, if any. Null if no tip. - `payments.payment_details` (object,null) Method-specific details. Shape varies by payment_method: - CREDIT_CARD / DEBIT_CARD: { "last_four": "4242", "brand": "visa", "exp_month": 12, "exp_year": 2027 } - GIFT_CARD: { "last_four": "7890", "balance_remaining": { "amount": 1500, "currency": "USD" } } - LOYALTY_POINTS: { "points_used": 500, "points_remaining": 1200 } - DIGITAL_WALLET: { "wallet_type": "apple_pay" } - `payments.idempotency_key` (string) The idempotency key used when this payment was submitted. - `payments.created_at` (string, required) When the payment was created. - `payments.updated_at` (string, required) When the payment status was last updated. - `discounts` (array) Discounts applied to the order (locked at checkout). - `discounts.id` (string, required) Unique identifier for this discount. Server-generated opaque string for reconciliation and tracking. - `discounts.name` (string, required) Display name of the discount (e.g., "10% Off First Order", "$2 Off Subs"). - `discounts.type` (string, required) Whether this discount is a percentage off or a fixed amount. PERCENTAGE: the value field contains the percentage (e.g., "10.00" for 10%). FIXED: the amount field contains the fixed discount in cents. Enum: "PERCENTAGE", "FIXED" - `discounts.value` (string,null) For PERCENTAGE discounts, the percentage value as a decimal string (e.g., "10.00" for 10%). Null for FIXED discounts. - `discounts.amount` (object, required) The calculated discount amount deducted (always positive, in cents). - `discounts.source` (string, required) Where this discount originated. - AUTOMATIC: Discount applied automatically by the server based on rules (happy hour, spend threshold, item combo, location-specific). - PROMO_CODE: Discount from a promo or coupon code entered by the customer. - LOYALTY_REWARD: Discount from a loyalty program reward redemption. - MANUAL: Discount applied manually by store staff or POS override. Enum: "AUTOMATIC", "PROMO_CODE", "LOYALTY_REWARD", "MANUAL" - `discounts.application_scope` (string, required) Whether this discount is applied before or after tax calculation. PRE_TAX discounts reduce the taxable amount. POST_TAX discounts reduce the total after tax. Most discounts are PRE_TAX. Enum: "PRE_TAX", "POST_TAX" - `promo_codes` (array) Promo codes that were active on the cart at checkout (locked at checkout). - `promo_codes.code` (string, required) The promo code string, normalized to uppercase. Codes are case-insensitive on input ("summer25" and "SUMMER25" are treated identically) but always returned as uppercase in responses. Example: "SUMMER25" - `promo_codes.status` (string, required) Current status of this promo code on the cart. - ACTIVE: The code is applied and its discount is reflected in totals. - EXPIRED: The code expired after being applied (e.g., time-limited promo). - REDEEMED: The code was already used (single-use codes) and locked at checkout. Enum: "ACTIVE", "EXPIRED", "REDEEMED" - `promo_codes.discount_preview` (object) Estimated discount this promo code provides. Present when the code is ACTIVE. The actual discount amount appears in the PriceCalculation discounts array with source: PROMO_CODE. - `promo_codes.discount_preview.estimated_discount` (object) Estimated discount amount in cents. - `promo_codes.discount_preview.description` (string) Human-readable description of the discount. Example: "25% off your order (up to $10)" - `promo_codes.applied_at` (string, required) When this promo code was applied to the cart. - `handoff` (any) - `notes` (string,null) Customer notes from checkout (e.g., "No onions please"). Null if no notes were provided. - `subtotal` (object, required) Sum of all item totals before tax and discounts. - `total_tax` (object, required) Total tax amount for the order. - `total_discount` (object) Total of cart-level discounts applied to the order (locked at checkout). Item-level discounts are reflected in each item's total. - `fees` (array) Fees applied to this order (locked at checkout). - `fees.id` (string, required) Unique identifier for this fee. Server-generated opaque string used for reconciliation and tracking. - `fees.name` (string, required) Display name of the fee (e.g., "Delivery Fee", "Service Fee", "Bag Fee"). Use this for detailed receipt views. - `fees.fee_type` (string, required) Category of the fee. - DELIVERY: Fee for delivering the order to the customer. - SERVICE: Platform or convenience service fee. - BAG: Per-bag charge required by local regulations. - SMALL_ORDER: Surcharge applied when the cart subtotal is below the location's minimum order amount. The fee covers the shortfall. - OTHER: Fees that do not fit the above categories. Enum: "DELIVERY", "SERVICE", "BAG", "SMALL_ORDER", "OTHER" - `fees.label` (string, required) Short display label for UI rendering (e.g., "Delivery", "Service", "Bag fee"). Use this for compact summary views. - `fees.type` (string) How the fee amount was calculated. - FLAT: A fixed amount regardless of cart contents. - PERCENTAGE: Calculated as a percentage of the subtotal. Enum: "FLAT", "PERCENTAGE" - `fees.value` (string,null) For PERCENTAGE fees, the percentage as a decimal string (e.g., "5.00" for 5%). Null for FLAT fees. - `fees.amount` (object, required) The calculated fee amount in cents (always positive). For PERCENTAGE fees, this is the result of applying the percentage to the subtotal. - `fees.taxable` (boolean, required) Whether tax applies to this fee. The server decides per fee based on local tax rules. When true, this fee's amount is included in the taxable_amount on the PriceCalculation response. - `total_fees` (object) Sum of all fee amounts (locked at checkout). - `total` (object, required) Grand total: subtotal + total_tax + total_fees - total_discount. Locked at checkout. - `total_paid` (object, required) Sum of all completed payment amounts. Updated as payments reach COMPLETED status. - `balance_due` (object, required) Remaining amount to be paid (total - total_paid). Zero when the order is fully paid. - `age_verification_required` (boolean) True if any item requires age verification. When true, the consumer will be verified at pickup or delivery (offline verification). - `age_verification_notice` (string,null) Human-readable notice about age verification requirements. Null when no age-restricted items are in the order. - `estimated_ready_at` (string,null) Estimated time the order will be ready for pickup or delivery, as an ISO 8601 datetime (e.g., "2026-03-15T14:30:00-05:00"). Null when the estimate is not yet available (e.g., before the order is confirmed by the store). Updated as the order progresses -- poll the order or listen for order.status.changed webhooks to get updated estimates. - `created_at` (string, required) When the order was created (checkout time). - `updated_at` (string, required) When the order was last modified. ## Response 401 fields (application/json): - `error` (object, required) - `error.code` (string, required) Machine-readable error category. Use this field for programmatic error handling (e.g., retry on RATE_LIMIT_ERROR, re-authenticate on AUTHENTICATION_ERROR). Enum: "AUTHENTICATION_ERROR", "INVALID_REQUEST_ERROR", "RATE_LIMIT_ERROR", "NOT_FOUND_ERROR", "CONFLICT_ERROR", "INTERNAL_ERROR" - `error.message` (string, required) Human-readable error description. Safe to display to developers in logs or debugging tools. Do not display to end users. - `error.detail` (string) Additional context about the error, including suggestions for resolution. May include specific field values or limits that were exceeded. - `error.request_id` (string, required) Unique identifier for this request. Include this value when contacting Tote Developer Support for troubleshooting. - `error.field` (string,null) JSON pointer to the field that caused the error. Null if the error is not field-specific. Example: "items[0].modifier_groups[1].modifiers" - `error.change_reasons` (array) Machine-readable reasons why the resource state changed, causing the conflict. Present on checkout 409 responses when expected_total does not match the current total. Clients should re-fetch the cart and call POST /carts/{cart_id}/calculate to get the updated total before retrying checkout. Enum: "PROMO_EXPIRED", "DISCOUNT_CHANGED", "ITEM_PRICE_CHANGED", "ITEM_UNAVAILABLE", "FEE_CHANGED" ## Response 404 fields (application/json): - `error` (object, required) - `error.code` (string, required) Machine-readable error category. Use this field for programmatic error handling (e.g., retry on RATE_LIMIT_ERROR, re-authenticate on AUTHENTICATION_ERROR). Enum: "AUTHENTICATION_ERROR", "INVALID_REQUEST_ERROR", "RATE_LIMIT_ERROR", "NOT_FOUND_ERROR", "CONFLICT_ERROR", "INTERNAL_ERROR" - `error.message` (string, required) Human-readable error description. Safe to display to developers in logs or debugging tools. Do not display to end users. - `error.detail` (string) Additional context about the error, including suggestions for resolution. May include specific field values or limits that were exceeded. - `error.request_id` (string, required) Unique identifier for this request. Include this value when contacting Tote Developer Support for troubleshooting. - `error.field` (string,null) JSON pointer to the field that caused the error. Null if the error is not field-specific. Example: "items[0].modifier_groups[1].modifiers" - `error.change_reasons` (array) Machine-readable reasons why the resource state changed, causing the conflict. Present on checkout 409 responses when expected_total does not match the current total. Clients should re-fetch the cart and call POST /carts/{cart_id}/calculate to get the updated total before retrying checkout. Enum: "PROMO_EXPIRED", "DISCOUNT_CHANGED", "ITEM_PRICE_CHANGED", "ITEM_UNAVAILABLE", "FEE_CHANGED" ## Response 409 fields (application/json): - `error` (object, required) - `error.code` (string, required) Machine-readable error category. Use this field for programmatic error handling (e.g., retry on RATE_LIMIT_ERROR, re-authenticate on AUTHENTICATION_ERROR). Enum: "AUTHENTICATION_ERROR", "INVALID_REQUEST_ERROR", "RATE_LIMIT_ERROR", "NOT_FOUND_ERROR", "CONFLICT_ERROR", "INTERNAL_ERROR" - `error.message` (string, required) Human-readable error description. Safe to display to developers in logs or debugging tools. Do not display to end users. - `error.detail` (string) Additional context about the error, including suggestions for resolution. May include specific field values or limits that were exceeded. - `error.request_id` (string, required) Unique identifier for this request. Include this value when contacting Tote Developer Support for troubleshooting. - `error.field` (string,null) JSON pointer to the field that caused the error. Null if the error is not field-specific. Example: "items[0].modifier_groups[1].modifiers" - `error.change_reasons` (array) Machine-readable reasons why the resource state changed, causing the conflict. Present on checkout 409 responses when expected_total does not match the current total. Clients should re-fetch the cart and call POST /carts/{cart_id}/calculate to get the updated total before retrying checkout. Enum: "PROMO_EXPIRED", "DISCOUNT_CHANGED", "ITEM_PRICE_CHANGED", "ITEM_UNAVAILABLE", "FEE_CHANGED" ## Response 422 fields (application/json): - `error` (object, required) - `error.code` (string, required) Machine-readable error category. Use this field for programmatic error handling (e.g., retry on RATE_LIMIT_ERROR, re-authenticate on AUTHENTICATION_ERROR). Enum: "AUTHENTICATION_ERROR", "INVALID_REQUEST_ERROR", "RATE_LIMIT_ERROR", "NOT_FOUND_ERROR", "CONFLICT_ERROR", "INTERNAL_ERROR" - `error.message` (string, required) Human-readable error description. Safe to display to developers in logs or debugging tools. Do not display to end users. - `error.detail` (string) Additional context about the error, including suggestions for resolution. May include specific field values or limits that were exceeded. - `error.request_id` (string, required) Unique identifier for this request. Include this value when contacting Tote Developer Support for troubleshooting. - `error.field` (string,null) JSON pointer to the field that caused the error. Null if the error is not field-specific. Example: "items[0].modifier_groups[1].modifiers" - `error.change_reasons` (array) Machine-readable reasons why the resource state changed, causing the conflict. Present on checkout 409 responses when expected_total does not match the current total. Clients should re-fetch the cart and call POST /carts/{cart_id}/calculate to get the updated total before retrying checkout. Enum: "PROMO_EXPIRED", "DISCOUNT_CHANGED", "ITEM_PRICE_CHANGED", "ITEM_UNAVAILABLE", "FEE_CHANGED" ## Response 429 fields (application/json): - `error` (object, required) - `error.code` (string, required) Machine-readable error category. Use this field for programmatic error handling (e.g., retry on RATE_LIMIT_ERROR, re-authenticate on AUTHENTICATION_ERROR). Enum: "AUTHENTICATION_ERROR", "INVALID_REQUEST_ERROR", "RATE_LIMIT_ERROR", "NOT_FOUND_ERROR", "CONFLICT_ERROR", "INTERNAL_ERROR" - `error.message` (string, required) Human-readable error description. Safe to display to developers in logs or debugging tools. Do not display to end users. - `error.detail` (string) Additional context about the error, including suggestions for resolution. May include specific field values or limits that were exceeded. - `error.request_id` (string, required) Unique identifier for this request. Include this value when contacting Tote Developer Support for troubleshooting. - `error.field` (string,null) JSON pointer to the field that caused the error. Null if the error is not field-specific. Example: "items[0].modifier_groups[1].modifiers" - `error.change_reasons` (array) Machine-readable reasons why the resource state changed, causing the conflict. Present on checkout 409 responses when expected_total does not match the current total. Clients should re-fetch the cart and call POST /carts/{cart_id}/calculate to get the updated total before retrying checkout. Enum: "PROMO_EXPIRED", "DISCOUNT_CHANGED", "ITEM_PRICE_CHANGED", "ITEM_UNAVAILABLE", "FEE_CHANGED" ## Response 500 fields (application/json): - `error` (object, required) - `error.code` (string, required) Machine-readable error category. Use this field for programmatic error handling (e.g., retry on RATE_LIMIT_ERROR, re-authenticate on AUTHENTICATION_ERROR). Enum: "AUTHENTICATION_ERROR", "INVALID_REQUEST_ERROR", "RATE_LIMIT_ERROR", "NOT_FOUND_ERROR", "CONFLICT_ERROR", "INTERNAL_ERROR" - `error.message` (string, required) Human-readable error description. Safe to display to developers in logs or debugging tools. Do not display to end users. - `error.detail` (string) Additional context about the error, including suggestions for resolution. May include specific field values or limits that were exceeded. - `error.request_id` (string, required) Unique identifier for this request. Include this value when contacting Tote Developer Support for troubleshooting. - `error.field` (string,null) JSON pointer to the field that caused the error. Null if the error is not field-specific. Example: "items[0].modifier_groups[1].modifiers" - `error.change_reasons` (array) Machine-readable reasons why the resource state changed, causing the conflict. Present on checkout 409 responses when expected_total does not match the current total. Clients should re-fetch the cart and call POST /carts/{cart_id}/calculate to get the updated total before retrying checkout. Enum: "PROMO_EXPIRED", "DISCOUNT_CHANGED", "ITEM_PRICE_CHANGED", "ITEM_UNAVAILABLE", "FEE_CHANGED"