Tote supports four handoff modes that determine how a customer receives their order. Each mode has different required fields, a different fulfillment flow, and different notification patterns for your UI.
The handoff mode is set on the cart before checkout using PUT /carts/{cartId}/handoff and persisted to the order when the cart is checked out. It can also be overridden at checkout time. The API uses a discriminated union: the mode field determines which additional fields are required.
This guide covers each mode in detail, walks through the fulfillment flow per mode, and shows how to set and override handoff modes with code examples.
Before following this guide, make sure you have:
- A cart with items -- See the Cart Lifecycle guide for how to create a cart and add items.
- An access token -- See the Authentication guide.
- Familiarity with fulfillment status -- See the Order Tracking guide for the three independent status fields on an order.
All examples in this guide use the sandbox base URL and consistent IDs:
Base URL: https://sandbox.api.tote.ai/v1/online-ordering
Cart ID: c1d2e3f4-a5b6-7890-cdef-1234567890ab
Order ID: f9a8b7c6-d5e4-3210-fedc-ba9876543210
Location ID: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d| Mode | Description | Required Fields | Terminal Fulfillment State |
|---|---|---|---|
PICKUP | Customer picks up at the store counter | mode only (pickup_time optional) | FULFILLED |
CURBSIDE | Staff brings order to the customer's vehicle | vehicle_make, vehicle_model, vehicle_color (pickup_time optional) | FULFILLED |
DELIVERY | Delivered to the customer's address | delivery_address (delivery_instructions optional) | DELIVERED |
KIOSK | Self-service terminal inside the store | mode only (kiosk_id optional) | FULFILLED |
The mode field acts as a discriminator. The API validates that the request body contains the required fields for the specified mode and rejects requests with missing or extraneous fields (422 Unprocessable Entity).
Set the handoff mode on an ACTIVE cart using PUT /carts/{cartId}/handoff. You can call this endpoint multiple times -- the latest call wins. The same endpoint accepts all four mode shapes; only the request body changes.
import requests
from uuid import uuid4
BASE_URL = "https://sandbox.api.tote.ai/v1/online-ordering"
headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"}
cart_id = "c1d2e3f4-a5b6-7890-cdef-1234567890ab"response = requests.put(
f"{BASE_URL}/carts/{cart_id}/handoff",
headers={**headers, "Idempotency-Key": str(uuid4())},
json={
"mode": "PICKUP",
"pickup_time": None, # null = ASAP
},
)
cart = response.json()
# cart["handoff_mode"] == {"mode": "PICKUP", "pickup_time": null}Pass pickup_time as an ISO 8601 datetime to schedule a future pickup, or null for ASAP preparation.
response = requests.put(
f"{BASE_URL}/carts/{cart_id}/handoff",
headers={**headers, "Idempotency-Key": str(uuid4())},
json={
"mode": "CURBSIDE",
"vehicle_make": "Toyota",
"vehicle_model": "Camry",
"vehicle_color": "Silver",
"pickup_time": None,
},
)
cart = response.json()All three vehicle fields are required. Store staff uses this information to identify the customer's vehicle in the parking lot.
response = requests.put(
f"{BASE_URL}/carts/{cart_id}/handoff",
headers={**headers, "Idempotency-Key": str(uuid4())},
json={
"mode": "DELIVERY",
"delivery_address": {
"street": "123 Main St, Apt 4B",
"city": "Austin",
"state": "TX",
"postal_code": "78701",
},
"delivery_instructions": "Leave at the front door",
},
)
cart = response.json()The delivery_address object requires street, city, state (two-letter code), and postal_code. The country field defaults to "US" if omitted. delivery_instructions is optional.
response = requests.put(
f"{BASE_URL}/carts/{cart_id}/handoff",
headers={**headers, "Idempotency-Key": str(uuid4())},
json={
"mode": "KIOSK",
"kiosk_id": "KIOSK-03",
},
)
cart = response.json()The kiosk_id is optional but useful for tracking which terminal placed the order. Omit it if your integration does not track individual kiosks.
When to use: The customer places an order online, drives to the store, and picks it up at the counter.
Required fields:
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | Must be "PICKUP" |
pickup_time | datetime / null | No | Scheduled pickup time (ISO 8601). Null for ASAP. |
| Fulfillment Status | Customer Notification | Action |
|---|---|---|
| PENDING | "Order received" | Display confirmation screen |
| IN_PROGRESS | "Store accepted your order" | Show estimated prep time |
| PREPARING | "Your order is being prepared" | Show progress indicator |
| READY_FOR_PICKUP | "Your order is ready for pickup" | Prominent alert -- customer should head to store |
| FULFILLED | "Order complete" | Show receipt / feedback prompt |
The transition from READY_FOR_PICKUP to FULFILLED happens when the store staff confirms the customer has picked up the order.
Orders can be cancelled via the API when fulfillment_status is PENDING or IN_PROGRESS. Once the status reaches PREPARING, the API returns 409 Conflict and cancellation requires store intervention.
When to use: The customer parks in a designated curbside spot and staff brings the order to their vehicle. Common for convenience stores with drive-through lanes or designated pickup parking.
Required fields:
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | Must be "CURBSIDE" |
vehicle_make | string | Yes | Manufacturer (e.g., "Toyota") |
vehicle_model | string | Yes | Model (e.g., "Camry") |
vehicle_color | string | Yes | Color (e.g., "Silver") |
pickup_time | datetime / null | No | Scheduled time. Null for ASAP. |
The curbside flow uses the same states as PICKUP. The difference is operational: when the order reaches READY_FOR_PICKUP, staff looks for the customer's vehicle using the stored make, model, and color instead of waiting for the customer at the counter.
| Fulfillment Status | Customer Notification | Action |
|---|---|---|
| PENDING | "Order received" | Display confirmation |
| IN_PROGRESS | "Store accepted your order" | Show estimated prep time |
| PREPARING | "Your order is being prepared" | Show progress indicator |
| READY_FOR_PICKUP | "Your order is ready -- pull into a curbside spot" | Prompt customer to park and signal arrival |
| FULFILLED | "Order delivered to your vehicle" | Show receipt / feedback prompt |
How the customer signals their arrival at the store is implementation-specific. Some approaches:
- Check-in button in your app that calls a custom endpoint or updates order metadata
- Push notification asking the customer to confirm they have arrived
- Manual communication (customer calls the store)
The Tote API tracks fulfillment status, not vehicle location. The store staff advances the order from READY_FOR_PICKUP to FULFILLED after delivering the order to the vehicle.
Same rules as PICKUP: cancellable when PENDING or IN_PROGRESS. Once PREPARING, requires store intervention.
When to use: The order is delivered to the customer's address by store staff or a third-party delivery provider.
Required fields:
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | Must be "DELIVERY" |
delivery_address | object | Yes | Destination address (see fields below) |
delivery_instructions | string / null | No | Instructions for the driver |
Delivery address fields:
| Field | Type | Required | Description |
|---|---|---|---|
street | string | Yes | Street address including apt/suite |
city | string | Yes | City name |
state | string | Yes | Two-letter US state code |
postal_code | string | Yes | ZIP or postal code |
country | string | No | Two-letter ISO 3166-1 code (defaults to "US") |
Note on READY_FOR_PICKUP: In delivery context, this state means "order is packed and ready for the driver." The state name reflects the shared backend enum across all modes. In your customer-facing UI, display this as "Out for delivery" or "Awaiting driver pickup" rather than "Ready for pickup."
| Fulfillment Status | Customer Notification | Action |
|---|---|---|
| PENDING | "Order received" | Display confirmation |
| IN_PROGRESS | "Store accepted your order" | Show estimated delivery time |
| PREPARING | "Your order is being prepared" | Show progress indicator |
| READY_FOR_PICKUP | "Your order is packed and awaiting pickup by driver" | Show driver assignment (if available) |
| DELIVERED | "Your order has been delivered" | Confirm delivery / feedback prompt |
The Tote API tracks fulfillment status but does not manage delivery driver assignment or routing. Partners integrate with their preferred delivery provider: store fleet, DoorDash Drive, Uber Direct, or another service.
The recommended integration pattern:
- When the order reaches PREPARING, request a delivery quote from your provider.
- When the order reaches READY_FOR_PICKUP, accept the quote to dispatch a driver.
- When the driver confirms delivery, update the order's fulfillment status to DELIVERED.
Delivery dispatch integration details (provider-specific APIs, driver tracking, estimated arrival times) are outside the scope of the Tote Online Ordering API. Your delivery provider handles driver assignment, routing, and real-time driver location.
The API does not validate delivery addresses. It stores the address as provided. Partners should validate addresses before checkout using their preferred geocoding service to ensure:
- The address is real and deliverable
- The address is within the store's delivery radius (if applicable)
- The address components are properly formatted
Same rules as other modes. Additionally, if a third-party delivery driver has already been dispatched, you may need to cancel the delivery through the provider's API separately.
When to use: The customer places an order at a self-service kiosk terminal inside the store. The customer is already on-site, so no delivery address or vehicle details are needed.
Required fields:
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | Must be "KIOSK" |
kiosk_id | string / null | No | Identifier for the kiosk terminal |
This is the simplest flow. The customer is already in the store, so preparation and handoff happen quickly. Many kiosk orders skip the PENDING and IN_PROGRESS states entirely if the store auto-accepts kiosk orders.
| Fulfillment Status | Customer Notification | Action |
|---|---|---|
| PENDING | "Order placed" | Show order number on kiosk screen |
| PREPARING | "Preparing your order" | Display on kiosk or overhead screen |
| READY_FOR_PICKUP | "Order ready -- pick up at counter" | Prominent display: "Order #XYZ is ready" |
| FULFILLED | "Order complete" | Clear kiosk screen / show receipt |
For kiosk deployments, consider displaying the order number prominently on an overhead display or the kiosk screen itself so the customer knows when to approach the counter.
Same rules as other modes: cancellable when PENDING or IN_PROGRESS.
This table shows which fulfillment states apply to each handoff mode and their meaning in context.
| Fulfillment Status | PICKUP | CURBSIDE | DELIVERY | KIOSK |
|---|---|---|---|---|
| PENDING | Yes | Yes | Yes | Yes |
| IN_PROGRESS | Yes | Yes | Yes | Yes |
| PREPARING | Yes | Yes | Yes | Yes |
| READY_FOR_PICKUP | Yes (at counter) | Yes (awaiting vehicle) | Yes (ready for dispatch) | Yes (at counter) |
| FULFILLED | Yes (terminal) | Yes (terminal) | -- | Yes (terminal) |
| DELIVERED | -- | -- | Yes (terminal) | -- |
| RETURNED | Yes | Yes | Yes | Yes |
| CANCELLED | Yes | Yes | Yes | Yes |
Key observations:
- FULFILLED is the terminal success state for PICKUP, CURBSIDE, and KIOSK. It means the customer physically received the order at the store.
- DELIVERED is the terminal success state for DELIVERY only. It means the order was delivered to the customer's address.
- READY_FOR_PICKUP is shared across all modes but has different operational meaning: "at the counter" for PICKUP/KIOSK, "staff heading to vehicle" for CURBSIDE, and "ready for driver dispatch" for DELIVERY.
- RETURNED and CANCELLED apply to all modes.
The checkout endpoint (POST /carts/{cartId}/checkout) accepts an optional handoff_mode field that overrides whatever was previously set via PUT /carts/{cartId}/handoff.
This is useful when the customer changes their mind at the last moment -- for example, switching from PICKUP to CURBSIDE after they realize they would rather stay in the car.
Assume the cart already has PICKUP set from an earlier call. At checkout, the customer decides they want curbside instead:
response = requests.post(
f"{BASE_URL}/carts/{cart_id}/checkout",
headers={**headers, "Idempotency-Key": str(uuid4())},
json={
"handoff_mode": {
"mode": "CURBSIDE",
"vehicle_make": "Honda",
"vehicle_model": "Civic",
"vehicle_color": "Blue",
},
"expected_total": 1945,
},
)
order = response.json()
# order["handoff"]["mode"] == "CURBSIDE"The order's handoff field will reflect the override (CURBSIDE), not the cart's original stored mode (PICKUP).
- If
handoff_modeis provided in the checkout request, it completely replaces the cart's stored handoff mode. - If
handoff_modeis omitted from the checkout request, the cart's stored handoff mode is used. - If neither the cart nor the checkout request has a handoff mode, the server returns 422 Unprocessable Entity.
Validate mode-specific fields on the client. Check that vehicle details are non-empty for CURBSIDE and that the delivery address has all required fields before calling the API. This saves a round trip and provides instant feedback to the customer.
Default to PICKUP when the mode is unknown. If your UI does not yet support all four modes, default to PICKUP as the simplest option. You can add CURBSIDE, DELIVERY, and KIOSK as your integration matures.
Validate delivery addresses before checkout. The API stores the address as provided without validation. Use a geocoding service to verify the address is real and within the store's delivery radius before the customer completes their order.
Track fulfillment_status for customer-facing updates. The fulfillment_status field is the most relevant for real-time order tracking. Use it to power progress indicators in your app. The status field (PENDING, CONFIRMED, COMPLETED) is better suited for order history views.
Use webhooks for real-time updates. Subscribe to the order.status_changed webhook event to receive push notifications when the fulfillment status changes. This eliminates the need for polling. See the Webhooks guide for setup instructions. If webhooks are not yet configured, fall back to polling with exponential backoff as described in the Order Tracking guide.
Display READY_FOR_PICKUP appropriately per mode. This state has different customer-facing meanings depending on the handoff mode. Map it to the right label in your UI:
| Mode | READY_FOR_PICKUP Display Text |
|---|---|
| PICKUP | "Your order is ready for pickup" |
| CURBSIDE | "Your order is ready -- head to a curbside spot" |
| DELIVERY | "Your order is packed and awaiting driver" |
| KIOSK | "Your order is ready at the counter" |
- Cart Lifecycle guide -- Setting handoff mode as part of the cart flow
- Order Tracking guide -- Monitoring fulfillment status after checkout
- Checkout & Payments guide -- Submitting payment for the order
- Webhooks guide -- Real-time push notifications for fulfillment status changes