# 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. | 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. ### 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. ```mermaid stateDiagram-v2 [*] --> PENDING : Cart checkout PENDING --> CONFIRMED : Payment completed PENDING --> FAILED : All payments failed PENDING --> VOIDED : Voided before payment PENDING --> CANCELLED : Cancelled by customer CONFIRMED --> COMPLETED : Order fulfilled CONFIRMED --> CANCELLED : Cancelled before preparation ``` | 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 | ## 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. ```mermaid stateDiagram-v2 [*] --> PENDING : Order created PENDING --> IN_PROGRESS : Store accepted PENDING --> CANCELLED : Cancelled IN_PROGRESS --> PREPARING : Staff assembling IN_PROGRESS --> CANCELLED : Cancelled PREPARING --> READY_FOR_PICKUP : Order ready PREPARING --> CANCELLED : Cancelled (store intervention) READY_FOR_PICKUP --> FULFILLED : Customer picked up READY_FOR_PICKUP --> DELIVERED : Driver delivered READY_FOR_PICKUP --> CANCELLED : Cancelled (store intervention) FULFILLED --> RETURNED : Return processed DELIVERED --> RETURNED : Return processed ``` ### All 8 fulfillment states | 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 | ### 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. | 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](/online-ordering/guides/06-checkout-payments#payment-state-machine). ## Retrieving a Single Order Fetch the full order detail including items, payments, and all status fields. ```python 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):** ```json { "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 | 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. ### Example: Orders at a location ```python 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: ```python 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 ```python 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: ```json { "data": [ ... ], "pagination": { "has_more": true, "next_cursor": "eyJpZCI6ImIyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYxMjM0NTY3ODkwMiJ9" } } ``` To fetch the next page, pass the `next_cursor` value as the `cursor` parameter: ```python 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 ```python 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 | 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. ## Cancelling an Order Cancel an order that has not yet started preparation. The server automatically voids or refunds all attached payments. ```python 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):** ```json { "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 ```python 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 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 | ## Next Steps - [Checkout & Payments guide](/online-ordering/guides/06-checkout-payments) -- 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](/online-ordering/spec/openapi) -- Full endpoint specification for `GET /orders` with all query parameters.