Skip to content
Last updated

Order Tracking

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.

Understanding the Three Status Fields

Every order has three independent status fields. Each tracks a different dimension of the order's lifecycle.

FieldTracksValuesWhen to check
statusOverall order lifecyclePENDING, CONFIRMED, COMPLETED, FAILED, VOIDED, CANCELLEDTo determine if the order is active, done, or cancelled.
payment_statusAggregate payment stateUNPAID, PROCESSING, PARTIALLY_PAID, PAIDTo determine if payment is complete before showing confirmation.
fulfillment_statusPhysical preparation and handoffPENDING, IN_PROGRESS, PREPARING, READY_FOR_PICKUP, FULFILLED, DELIVERED, RETURNED, CANCELLEDTo 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.

Typical field progression

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: FULFILLED

Order Status Lifecycle

The status field tracks the high-level order lifecycle.

Cart checkout

Payment completed

All payments failed

Voided before payment

Cancelled by customer

Order fulfilled

Cancelled before preparation

PENDING

CONFIRMED

FAILED

VOIDED

CANCELLED

COMPLETED

StateDescriptionTerminal?
PENDINGOrder created from checkout. Awaiting payment.No
CONFIRMEDAt least one payment completed. Order accepted by the store.No
COMPLETEDOrder fulfilled and closed.Yes
FAILEDOrder could not be processed (e.g., all payments failed).Yes
VOIDEDOrder voided before any fulfillment began.Yes
CANCELLEDOrder cancelled by customer or system.Yes

Fulfillment Status Lifecycle

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.

Order created

Store accepted

Cancelled

Staff assembling

Cancelled

Order ready

Cancelled (store intervention)

Customer picked up

Driver delivered

Cancelled (store intervention)

Return processed

Return processed

PENDING

IN_PROGRESS

CANCELLED

PREPARING

READY_FOR_PICKUP

FULFILLED

DELIVERED

RETURNED

All 8 fulfillment states

StateDescriptionApplicable modes
PENDINGOrder created, not yet accepted by the store.All
IN_PROGRESSStore accepted the order and is queuing it.All
PREPARINGStore staff actively assembling the order.All
READY_FOR_PICKUPOrder assembled and waiting for the customer.Pickup, Curbside, Kiosk
FULFILLEDCustomer received the order at the store.Pickup, Curbside, Kiosk
DELIVEREDOrder delivered to the customer's address.Delivery
RETURNEDOrder returned after fulfillment.All
CANCELLEDFulfillment cancelled before completion.All

Cancellation restriction

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).

Payment Status Overview

The payment_status field is a server-computed aggregate across all payments on the order. You do not set it directly.

StateMeaning
UNPAIDNo payments submitted yet.
PROCESSINGAt least one payment is in a non-terminal state (PENDING or AUTHORIZED).
PARTIALLY_PAIDSome payments completed but total_paid < total.
PAIDTotal paid >= order total. The order is fully covered.

For the full payment state machine and split payment details, see the Checkout & Payments guide.

Retrieving a Single Order

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"
}

Listing Orders with Filters

The GET /orders endpoint returns lightweight OrderSummary objects (no items or payments arrays). Use filters to narrow results.

Available filters

ParameterTypeDescription
statusstringFilter by order status (e.g., CONFIRMED, COMPLETED).
fulfillment_statusstringFilter by fulfillment stage (e.g., PREPARING, READY_FOR_PICKUP).
location_iduuidFilter by store location.
date_fromdate-timeOrders created on or after this time (ISO 8601).
date_todate-timeOrders created on or before this time (ISO 8601).

All filters are optional and combined with AND logic.

Example: Orders at a location

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}")

Example: Orders needing attention

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")

Example: Orders from today

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"]

Cursor pagination

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)}")

Polling for Status Updates

Until webhooks are available (coming in Phase 4), poll the order detail endpoint to detect status changes.

Polling with exponential backoff

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']}")

Polling recommendations

ScenarioInitial intervalMax intervalMax duration
Payment processing1 second5 seconds2 minutes
Order preparation5 seconds30 seconds60 minutes
Delivery tracking10 seconds60 seconds120 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.

Cancelling an Order

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" }
}

Cancellation rules

  • Allowed when: fulfillment_status is PENDING or IN_PROGRESS.
  • Blocked when: fulfillment_status is 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.

Handling the 409 Conflict

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']}")

Complete Order Journey

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=FULFILLED

For delivery orders, step 7 uses fulfillment_status: DELIVERED instead of FULFILLED.

Quick reference: which status to display

Customer-facing screenPrimary fieldWhat to show
Order confirmationpayment_status"Payment received" when PAID
Order trackingfulfillment_statusProgress indicator (Preparing -> Ready -> Picked Up)
Order historystatusOverall outcome (Completed, Cancelled)
Order detailAll threeFull status breakdown

Next Steps

  • 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 /orders with all query parameters.