.dev
REST API

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:

FieldValues
statusdraftpendingreviewacceptedfollowupprocessingcompleted, or cancelled
paymentStatusunpaidpaid (gateway confirmed) → received (cash in hand)
deliveryStatuspendingdeliveringdelivered, or returned
shippingTypehome (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

MethodPathDescriptionAuth
GET/v1/ordersList — store_id or store_ids[], status[], deliveryStatus, paymentStatus, customStatus, q, confirmer, products[], shippingState, shippingCity, deliveryService, variant, offer, references, created_after/created_before, filteratorBearer + scope orders.read
POST/v1/ordersCreate (merchant-side, e.g. phone orders)Bearer + scope orders
GET/v1/orders/{id}Show — contains customer PII, members onlyBearer + scope orders.read
PUT/v1/orders/{id}Update (status, items, shipping, notes …)Bearer + scope orders
DELETE/v1/orders/{id}Soft deleteBearer + scope orders
POST/v1/orders:batchUpdateSame patch for many orders (AIP field mask)Bearer + scope orders

Public storefront endpoints

MethodPathDescriptionAuth
POST/v1/orders/sendGuest order submit (rate-limited per IP/phone)Public
POST/v1/orders/calculatePrice preview — totals + shipping without creating anythingPublic
GET/v1/orders/{id}/trackPublic tracking — reduced, PII-safe shapePublic

Confirmation & assignment

MethodPathDescriptionAuth
POST/v1/orders/confirmation/nextClaim the next order due for confirmation across selected storesBearer
POST/v1/orders/confirmation/releaseReturn a claimed order to the pool (skip / leave screen)Bearer
POST/v1/orders/assignAssign one order to a member as confirmerBearer
POST/v1/orders/assignManyAssign many orders to a memberBearer
POST/v1/stores/{storeId}/integrations/dispatcher/dispatchDistribute selected orders using a strategyBearer
POST/v1/orders/returnReverse logistics — return items to inventory with quantity deltasBearer

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 / shippingStateaddress, 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 HTTP 200 when nothing is due — poll or show an empty state.
  • Store ids the caller may not claim from are silently dropped, not rejected; compare searchedStoreIds against what you sent to detect stale selections.
  • backlog is 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 released flag 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}/orders SSE 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}/send and 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_label resolved from the geo tables using the request locale (Accept-Language or ?locale=).

On this page