Orders
Order lifecycle, guest checkout, totals calculation, public tracking, batch updates, the confirmation queue and dispatching.
Orders are the core COD (cash-on-delivery) workflow object: a guest submits an order from the
storefront, the store's confirmers call the customer to confirm it, then it ships through a
delivery integration. Items are embedded on the order (items[] — no separate order-items
resource), and the server always recomputes prices from the product catalog. Pagination,
filterator and the batch envelope are documented in API conventions; payment
collection for orders lives in Payments.
Lifecycle
An order carries three independent status fields plus a free-form customStatus label:
| Field | Values |
|---|---|
status | draft → pending → review → accepted → followup → processing → completed, or cancelled |
paymentStatus | unpaid → paid (gateway confirmed) → received (cash in hand) |
deliveryStatus | pending → delivering → delivered, or returned |
shippingType | home (address), pickup (carrier stopdesk), store (merchant branch) |
draft is used for abandoned carts (guest submits with status: 'draft'); pending orders
enter the confirmation queue; accepted orders are handed to delivery. followup is a
confirmed order that still needs merchant action (callback, missing info, hold) before it
moves to processing or completed.
At a glance
CRUD & listing
| Method | Path | Description | Auth |
|---|---|---|---|
GET | /v1/orders | List — store_id or store_ids[], status[], deliveryStatus, paymentStatus, customStatus, q, confirmer, products[], shippingState, shippingCity, deliveryService, variant, offer, references, created_after/created_before, filterator | Bearer + scope orders.read |
POST | /v1/orders | Create (merchant-side, e.g. phone orders) | Bearer + scope orders |
GET | /v1/orders/{id} | Show — contains customer PII, members only | Bearer + scope orders.read |
PUT | /v1/orders/{id} | Update (status, items, shipping, notes …) | Bearer + scope orders |
DELETE | /v1/orders/{id} | Soft delete | Bearer + scope orders |
POST | /v1/orders:batchUpdate | Same patch for many orders (AIP field mask) | Bearer + scope orders |
Public storefront endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
POST | /v1/orders/send | Guest order submit (rate-limited per IP/phone) | Public |
POST | /v1/orders/calculate | Price preview — totals + shipping without creating anything | Public |
GET | /v1/orders/{id}/track | Public tracking — reduced, PII-safe shape | Public |
Confirmation & assignment
| Method | Path | Description | Auth |
|---|---|---|---|
POST | /v1/orders/confirmation/next | Claim the next order due for confirmation across selected stores | Bearer |
POST | /v1/orders/confirmation/release | Return a claimed order to the pool (skip / leave screen) | Bearer |
POST | /v1/orders/assign | Assign one order to a member as confirmer | Bearer |
POST | /v1/orders/assignMany | Assign many orders to a member | Bearer |
POST | /v1/stores/{storeId}/integrations/dispatcher/dispatch | Distribute selected orders using a strategy | Bearer |
POST | /v1/orders/return | Reverse logistics — return items to inventory with quantity deltas | Bearer |
Order items
Each entry in items[] references catalog objects; prices are resolved server-side:
{
"productId": "prd_...",
"quantity": 2,
"variantPath": "red/xl",
"offerCode": "x2",
"addons": { "gift-wrap": 1 }
}variantPath is the slash-joined path through the product's variant tree; offerCode
references a product offer. See Products.
Guest order submit
Field names matter: use shippingAddress / shippingCity / shippingState — address,
cityCode and stateCode are ignored. shippingState is the wilaya code as a string
(e.g. "05").
curl -X POST "https://api.feeef.org/v1/orders/send" \
-H "Content-Type: application/json" \
-d '{
"storeId": "STORE_ID",
"status": "pending",
"customerName": "Amine",
"customerPhone": "0555000000",
"shippingState": "05",
"shippingCity": "Batna",
"shippingAddress": "Rue 12 ...",
"shippingType": "home",
"items": [
{ "productId": "PRODUCT_ID", "quantity": 2, "variantPath": "red/xl" }
]
}'Submitting with status: 'draft' records an abandoned-cart draft. Repeated identical carts
from the same customer are deduplicated, and the endpoint applies per-IP and per-phone rate
limits (merchants can add manual blocks — see
Stores → notes). The response is the created order (with its id for
tracking and payment).
Calculate totals before submitting
curl -X POST "https://api.feeef.org/v1/orders/calculate" \
-H "Content-Type: application/json" \
-d '{
"storeId": "STORE_ID",
"shippingState": "05",
"shippingType": "home",
"items": [{ "productId": "PRODUCT_ID", "quantity": 2, "offerCode": "x2" }]
}'The raw response also includes per-item breakdowns with the resolved products and the shipping
rates table. shippingPrice is null when no rate matches the destination (free shipping
returns 0).
Track an order (public)
curl "https://api.feeef.org/v1/orders/{orderId}/track"Anyone with the order id can read the tracking shape — it exposes progress, not customer data.
Full order reads (GET /v1/orders/{id}) stay member-only.
Batch status update
POST /v1/orders:batchUpdate applies one patch (updateMask + fields) to many orders.
Supported mask fields: status, customStatus, cancelReason, deliveryStatus,
paymentStatus, internalNote. The response is the standard
partial-success envelope.
curl -X POST "https://api.feeef.org/v1/orders:batchUpdate" \
-H "Authorization: Bearer $FEEEF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"storeId": "STORE_ID",
"names": ["ORDER_1", "ORDER_2"],
"updateMask": ["status"],
"status": "accepted",
"returnPartialSuccess": true
}'Confirmation queue
Confirmation staff never pick orders manually — they claim the next due order, which atomically assigns it to the caller so two confirmers can never get the same order:
# Claim
curl -X POST "https://api.feeef.org/v1/orders/confirmation/next" \
-H "Authorization: Bearer $FEEEF_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "storeIds": ["STORE_A", "STORE_B"] }'
# Skip / put back
curl -X POST "https://api.feeef.org/v1/orders/confirmation/release" \
-H "Authorization: Bearer $FEEEF_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "orderId": "ORDER_ID", "storeId": "STORE_A", "skip": true }'Semantics worth knowing:
{ "order": null }comes back with HTTP200when nothing is due — poll or show an empty state.- Store ids the caller may not claim from are silently dropped, not rejected; compare
searchedStoreIdsagainst what you sent to detect stale selections. backlogis an approximate due-count per searched store (memoized for a few seconds).- Releasing an order the store's dispatcher deliberately assigned is a no-op — the response's
releasedflag tells you whether it actually went back to the pool.
Assignment & dispatching
POST /v1/orders/assign / assignMany set confirmerId explicitly (assigning to the store
owner clears it). The dispatcher endpoint distributes a batch using a strategy document:
{
"orderIds": ["ORDER_1", "ORDER_2"],
"strategy": { "type": "roundRobin", "sortBy": "name" }
}Strategy types: firstUpdate, random, weightedRandom (weights map),
roundRobin, manualOnly, priority (confirmerIds list). In Dart this is
ff.orders.dispatch(orderIds: ..., storeId: ..., strategy: OrdersDispatchStrategy.roundRobin()).
Notes
- Realtime: order CRUD broadcasts on the
stores/{storeId}/ordersSSE channel — see API conventions. Webhook delivery of the same events (orderCreated/orderUpdated/orderDeleted) is covered in Webhooks. - Shipping labels & carrier hand-off are per-integration endpoints
(
/v1/stores/{storeId}/orders/{orderId}/carriers/{carrierCode}/sendand friends), out of scope here. - Lists are ordered by
COALESCE(scheduled_at, created_at) DESC— scheduled orders sort by their scheduled time. - Order reads/writes are enriched with
shipping_state_label/shipping_city_labelresolved from the geo tables using the request locale (Accept-Languageor?locale=).