This guide covers the order lifecycle from creation through fulfillment. You will learn how to interpret the three status fields on an order, retrieve and filter orders, poll for updates, and handle cancellations.
By the end, you will be able to build a complete order tracking experience for your customers.
Every order has three independent status fields. Each tracks a different dimension of the order's lifecycle.
| Field | Tracks | Values | When to check |
|---|---|---|---|
status | Overall order lifecycle | PENDING, CONFIRMED, COMPLETED, FAILED, VOIDED, CANCELLED | To determine if the order is active, done, or cancelled. |
payment_status | Aggregate payment state | UNPAID, PROCESSING, PARTIALLY_PAID, PAID | To determine if payment is complete before showing confirmation. |
fulfillment_status | Physical preparation and handoff | PENDING, IN_PROGRESS, PREPARING, READY_FOR_PICKUP, FULFILLED, DELIVERED, RETURNED, CANCELLED | To show the customer real-time progress ("Your order is being prepared"). |
These fields move independently. For example, an order can be status: CONFIRMED + payment_status: PAID + fulfillment_status: PREPARING -- meaning the order is paid, confirmed, and the store is actively making it.
Checkout -> status: PENDING, payment_status: UNPAID, fulfillment_status: PENDING
Payment -> status: CONFIRMED, payment_status: PAID, fulfillment_status: PENDING
Store accepts -> status: CONFIRMED, payment_status: PAID, fulfillment_status: IN_PROGRESS
Preparation -> status: CONFIRMED, payment_status: PAID, fulfillment_status: PREPARING
Ready -> status: CONFIRMED, payment_status: PAID, fulfillment_status: READY_FOR_PICKUP
Picked up -> status: COMPLETED, payment_status: PAID, fulfillment_status: FULFILLEDThe status field tracks the high-level order lifecycle.
| State | Description | Terminal? |
|---|---|---|
PENDING | Order created from checkout. Awaiting payment. | No |
CONFIRMED | At least one payment completed. Order accepted by the store. | No |
COMPLETED | Order fulfilled and closed. | Yes |
FAILED | Order could not be processed (e.g., all payments failed). | Yes |
VOIDED | Order voided before any fulfillment began. | Yes |
CANCELLED | Order cancelled by customer or system. | Yes |
The fulfillment_status field tracks the physical preparation and handoff of the order. This is the field your customer-facing UI should display most prominently.
| State | Description | Applicable modes |
|---|---|---|
PENDING | Order created, not yet accepted by the store. | All |
IN_PROGRESS | Store accepted the order and is queuing it. | All |
PREPARING | Store staff actively assembling the order. | All |
READY_FOR_PICKUP | Order assembled and waiting for the customer. | Pickup, Curbside, Kiosk |
FULFILLED | Customer received the order at the store. | Pickup, Curbside, Kiosk |
DELIVERED | Order delivered to the customer's address. | Delivery |
RETURNED | Order returned after fulfillment. | All |
CANCELLED | Fulfillment cancelled before completion. | All |
Orders can only be cancelled via the API when fulfillment_status is PENDING or IN_PROGRESS. Once preparation begins (PREPARING or later), the API returns 409 Conflict and cancellation requires store intervention (e.g., calling the store directly).
The payment_status field is a server-computed aggregate across all payments on the order. You do not set it directly.
| State | Meaning |
|---|---|
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 < total. |
PAID | Total paid >= order total. The order is fully covered. |
For the full payment state machine and split payment details, see the Checkout & Payments guide.
Fetch the full order detail including items, payments, and all status fields.
import requests
BASE_URL = "https://sandbox.api.tote.ai/v1/online-ordering"
headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"}
order_id = "f9a8b7c6-d5e4-3210-fedc-ba9876543210"
response = requests.get(
f"{BASE_URL}/orders/{order_id}",
headers=headers,
)
order = response.json()
print(f"Status: {order['status']}")
print(f"Payment: {order['payment_status']}")
print(f"Fulfillment: {order['fulfillment_status']}")
print(f"Total: ${order['total']['amount'] / 100:.2f}")
print(f"Paid: ${order['total_paid']['amount'] / 100:.2f}")
print(f"Balance due: ${order['balance_due']['amount'] / 100:.2f}")Response (200 OK):
{
"id": "f9a8b7c6-d5e4-3210-fedc-ba9876543210",
"cart_id": "c1d2e3f4-a5b6-7890-cdef-1234567890ab",
"location_id": "b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d",
"status": "CONFIRMED",
"payment_status": "PAID",
"fulfillment_status": "PREPARING",
"items": [
{
"id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
"name": "Build Your Own Sub Sandwich",
"quantity": 1,
"item_total": { "amount": 1399, "currency": "USD" }
},
{
"id": "e7f8a9b0-c1d2-3456-7890-abcdef123456",
"name": "Bottled Water",
"quantity": 2,
"item_total": { "amount": 398, "currency": "USD" }
}
],
"payments": [
{
"id": "a1b2c3d4-0001-4000-8000-000000000001",
"status": "COMPLETED",
"payment_method": "CREDIT_CARD",
"amount": { "amount": 1945, "currency": "USD" }
}
],
"handoff": {
"mode": "CURBSIDE",
"vehicle_make": "Toyota",
"vehicle_model": "Camry",
"vehicle_color": "Silver"
},
"subtotal": { "amount": 1797, "currency": "USD" },
"total_tax": { "amount": 148, "currency": "USD" },
"total_discount": { "amount": 0, "currency": "USD" },
"total": { "amount": 1945, "currency": "USD" },
"total_paid": { "amount": 1945, "currency": "USD" },
"balance_due": { "amount": 0, "currency": "USD" },
"created_at": "2026-01-31T10:07:00Z",
"updated_at": "2026-01-31T10:09:00Z"
}The GET /orders endpoint returns lightweight OrderSummary objects (no items or payments arrays). Use filters to narrow results.
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by order status (e.g., CONFIRMED, COMPLETED). |
fulfillment_status | string | Filter by fulfillment stage (e.g., PREPARING, READY_FOR_PICKUP). |
location_id | uuid | Filter by store location. |
date_from | date-time | Orders created on or after this time (ISO 8601). |
date_to | date-time | Orders created on or before this time (ISO 8601). |
All filters are optional and combined with AND logic.
response = requests.get(
f"{BASE_URL}/orders",
headers=headers,
params={
"location_id": "b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d",
"limit": 20
},
)
data = response.json()
orders = data["data"]
pagination = data["pagination"]
for order in orders:
print(f"{order['id']} | {order['status']} | {order['fulfillment_status']} | ${order['total']['amount'] / 100:.2f}")Find confirmed orders that are ready for customer pickup:
response = requests.get(
f"{BASE_URL}/orders",
headers=headers,
params={
"status": "CONFIRMED",
"fulfillment_status": "READY_FOR_PICKUP",
"location_id": "b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d"
},
)
ready_orders = response.json()["data"]
print(f"{len(ready_orders)} orders ready for pickup")from datetime import datetime, timezone
today_start = datetime.now(timezone.utc).replace(
hour=0, minute=0, second=0, microsecond=0
).isoformat()
response = requests.get(
f"{BASE_URL}/orders",
headers=headers,
params={
"date_from": today_start,
"location_id": "b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d"
},
)
todays_orders = response.json()["data"]Results are paginated using cursor-based pagination. Each response includes a pagination object:
{
"data": [ ... ],
"pagination": {
"has_more": true,
"next_cursor": "eyJpZCI6ImIyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYxMjM0NTY3ODkwMiJ9"
}
}To fetch the next page, pass the next_cursor value as the cursor parameter:
def fetch_all_orders(headers, params):
"""Fetch all orders with automatic pagination."""
all_orders = []
cursor = None
while True:
if cursor:
params["cursor"] = cursor
response = requests.get(
f"{BASE_URL}/orders",
headers=headers,
params=params,
)
data = response.json()
all_orders.extend(data["data"])
if not data["pagination"]["has_more"]:
break
cursor = data["pagination"]["next_cursor"]
return all_orders
# Fetch all confirmed orders at a location
all_confirmed = fetch_all_orders(headers, {
"status": "CONFIRMED",
"location_id": "b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d",
"limit": 50
})
print(f"Total confirmed orders: {len(all_confirmed)}")Until webhooks are available (coming in Phase 4), poll the order detail endpoint to detect status changes.
import time
def poll_order_status(order_id, headers, target_status, max_attempts=30):
"""
Poll an order until it reaches the target fulfillment status.
Uses exponential backoff starting at 2 seconds, capping at 30 seconds.
"""
base_delay = 2
max_delay = 30
for attempt in range(max_attempts):
response = requests.get(
f"{BASE_URL}/orders/{order_id}",
headers=headers,
)
order = response.json()
current = order["fulfillment_status"]
print(f"[Attempt {attempt + 1}] Fulfillment: {current}")
if current == target_status:
return order
if current == "CANCELLED":
raise Exception("Order was cancelled")
# Exponential backoff: 2s, 4s, 8s, 16s, 30s, 30s, ...
delay = min(base_delay * (2 ** attempt), max_delay)
time.sleep(delay)
raise TimeoutError(f"Order did not reach {target_status} after {max_attempts} attempts")
# Wait for order to be ready for pickup
order = poll_order_status(
"f9a8b7c6-d5e4-3210-fedc-ba9876543210",
headers,
target_status="READY_FOR_PICKUP",
)
print(f"Order is ready! Status: {order['fulfillment_status']}")| Scenario | Initial interval | Max interval | Max duration |
|---|---|---|---|
| Payment processing | 1 second | 5 seconds | 2 minutes |
| Order preparation | 5 seconds | 30 seconds | 60 minutes |
| Delivery tracking | 10 seconds | 60 seconds | 120 minutes |
Phase 4 webhooks will replace polling for most use cases. With webhooks, the server pushes status change events to your endpoint in real time, eliminating the need for periodic polling.
Cancel an order that has not yet started preparation. The server automatically voids or refunds all attached payments.
order_id = "f9a8b7c6-d5e4-3210-fedc-ba9876543210"
cancel_response = requests.post(
f"{BASE_URL}/orders/{order_id}/cancel",
headers={**headers, "Idempotency-Key": str(uuid4())},
json={"reason": "Customer changed their mind"},
)Response (200 OK):
{
"id": "f9a8b7c6-d5e4-3210-fedc-ba9876543210",
"status": "CANCELLED",
"payment_status": "UNPAID",
"fulfillment_status": "CANCELLED",
"total_paid": { "amount": 0, "currency": "USD" },
"balance_due": { "amount": 1945, "currency": "USD" }
}- Allowed when:
fulfillment_statusis PENDING or IN_PROGRESS. - Blocked when:
fulfillment_statusis PREPARING or later. The server returns 409 Conflict. - Automatic payment handling: Payments in PENDING or AUTHORIZED state are voided (no charge). Payments in CAPTURED or COMPLETED state are refunded in full using the minimize-cash-refund strategy.
- No separate refund request is needed after cancellation.
from uuid import uuid4
cancel_response = requests.post(
f"{BASE_URL}/orders/{order_id}/cancel",
headers={**headers, "Idempotency-Key": str(uuid4())},
json={"reason": "Customer changed their mind"},
)
if cancel_response.status_code == 409:
error = cancel_response.json()["error"]
print(f"Cannot cancel: {error['message']}")
print("The order is already being prepared.")
print("Contact the store directly to request cancellation.")
elif cancel_response.status_code == 200:
print("Order cancelled successfully.")
cancelled_order = cancel_response.json()
print(f"Status: {cancelled_order['status']}")Here is a concise end-to-end timeline showing every status transition from cart creation to fulfillment.
1. Customer builds cart
Cart: ACTIVE
2. Customer checks out
Cart: CHECKED_OUT
Order: status=PENDING, payment_status=UNPAID, fulfillment_status=PENDING
3. Customer submits payment
Order: status=CONFIRMED, payment_status=PAID, fulfillment_status=PENDING
4. Store accepts order
Order: status=CONFIRMED, payment_status=PAID, fulfillment_status=IN_PROGRESS
5. Store starts preparation
Order: status=CONFIRMED, payment_status=PAID, fulfillment_status=PREPARING
6. Order is ready
Order: status=CONFIRMED, payment_status=PAID, fulfillment_status=READY_FOR_PICKUP
7. Customer picks up order
Order: status=COMPLETED, payment_status=PAID, fulfillment_status=FULFILLEDFor delivery orders, step 7 uses fulfillment_status: DELIVERED instead of FULFILLED.
| Customer-facing screen | Primary field | What to show |
|---|---|---|
| Order confirmation | payment_status | "Payment received" when PAID |
| Order tracking | fulfillment_status | Progress indicator (Preparing -> Ready -> Picked Up) |
| Order history | status | Overall outcome (Completed, Cancelled) |
| Order detail | All three | Full status breakdown |
- Checkout & Payments guide -- Payment submission, split payments, and refund flows.
- Webhooks guide (coming in Phase 4) -- Real-time push notifications for order and payment status changes.
- API Reference: List Orders -- Full endpoint specification for
GET /orderswith all query parameters.