API Reference
Back to site
Orders & customers

Orders

10. Orders API

GET/ordersList Orders

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger20Items per page
statusstring-Filter: pending, processing, completed, cancelled
searchstring-Search by order number or customer email

Each order in the list includes an items array (full line rows, not only a count). Every element includes product (with images, primary category, categories, brand, tags) and variant when applicable, matching GET /orders/:id. items_count is also set for convenience. Each order also carries its addresses array (shipping / billing), including the delivery place_id and latitude / longitude when they were captured at checkout — see GET /orders/:id below.

Example Request:

bash
curl -X GET "https://your-store.com/api/v1/orders?page=1&limit=20&status=completed" \
  -u "ck_xxx:cs_yyy"

Example Response (200):

json
{
  "status": "success",
  "data": {
    "orders": [
      {
        "id": 123,
        "order_number": "ORD-1709468833000",
        "customer_id": 42,
        "status": "completed",
        "payment_status": "paid",
        "subtotal": "2400.00",
        "shipping": "300.00",
        "tax": "0.00",
        "discount": "0.00",
        "total": "2700.00",
        "currency": "KES",
        "customer": {
          "id": 42,
          "email": "customer@example.com",
          "first_name": "Jane",
          "last_name": "Doe"
        },
        "items_count": 2,
        "items": [
          {
            "id": 1,
            "order_id": 123,
            "product_id": 101,
            "variant_id": 1001,
            "name": "Organic Flour 1kg",
            "sku": "FLOUR-ORG-1KG",
            "quantity": 2,
            "price": "1200.00",
            "product": {
              "id": 101,
              "name": "Organic Flour 1kg",
              "slug": "organic-flour-1kg",
              "sku": "FLOUR-ORG-1KG",
              "images": [{ "id": 1, "url": "/uploads/...", "alt": null, "order": 0 }],
              "category": { "id": 5, "name": "Baking", "slug": "baking" },
              "categories": [],
              "brand": { "id": 2, "name": "Shopflow", "slug": "shopflow" },
              "tags": []
            },
            "variant": {
              "id": 1001,
              "sku": "FLOUR-ORG-1KG",
              "price": "1200.00",
              "attributes_json": "{\"Size\":\"1kg\"}"
            }
          }
        ],
        "source": "api",
        "api_label": "WooCommerce",
        "order_date": "2024-12-25T10:30:00.000Z",
        "created_at": "2026-03-04T10:00:00.000Z"
      }
    ],
    "total": 50,
    "page": 1,
    "limit": 20
  }
}

source - "api" for API-created orders, "admin" for orders created in the dashboard, null for legacy orders. The admin displays an API badge for source === "api" orders.

api_label - The custom label set by the API caller (e.g. "WooCommerce"). Shown on the badge in the admin orders table instead of the default "API".

order_date - The actual order date provided by your system. null when not set (admin falls back to created_at).


GET/orders/sourcesList all order sources

Returns an array of unique sources from which orders have been created across the store.

Auth: Admin session required.

bash
curl "https://your-store-api.example.com/api/v1/orders/sources" \
  -H "Cookie: shopflow.sid=..."

Response:

json
{
  "status": "success",
  "data": {
    "sources": ["admin", "api", "storefront", "website"]
  }
}

GET/orders/count/unviewedCount unviewed orders

Get the total number of orders that have not yet been viewed by staff. Useful for displaying an unviewed order count in the dashboard.

Auth: Admin session required.

Query params:

ParamTypeDescription
X-Branch-Idstring (header)Filter by branch ID, or all for super admin
bash
curl "https://your-store-api.example.com/api/v1/orders/count/unviewed" \
  -H "Cookie: shopflow.sid=..."

Response:

json
{
  "status": "success",
  "data": {
    "count": 5
  }
}

GET/orders/exportExport orders as CSV

Download all orders matching optional filters as a CSV file with comprehensive order, customer, item, and shipping data.

Auth: Admin session required.

Query params:

ParamTypeDescription
date_fromstringStart date (ISO 8601: YYYY-MM-DD)
date_tostringEnd date (ISO 8601: YYYY-MM-DD)
statusstringFilter by status: pending, processing, in_transit, on_hold, completed, cancelled
payment_statusstringFilter by payment status: pending, paid, failed, refunded
sourcestringFilter by order source: api, admin, storefront, website
searchstringSearch by order number, customer email, or customer name (case-insensitive)
X-Branch-Idstring (header)Filter by branch ID, or all for super admin
bash
curl "https://your-store-api.example.com/api/v1/orders/export?status=completed&date_from=2024-01-01&date_to=2024-12-31" \
  -H "Cookie: shopflow.sid=..." \
  -o orders.csv

Response: A text/csv file with columns for order details, customer info, items, addresses, shipments, and transactions.


GET/orders/:idGet Single Order

Example Request:

bash
curl -X GET "https://your-store.com/api/v1/orders/123" \
  -u "ck_xxx:cs_yyy"

Example Response (200):

json
{
  "status": "success",
  "data": {
    "order": {
      "id": 123,
      "order_number": "ORD-1709468833000",
      "customer_id": 42,
      "status": "completed",
      "payment_status": "paid",
      "subtotal": "2400.00",
      "shipping": "300.00",
      "tax": "0.00",
      "discount": "0.00",
      "total": "2700.00",
      "currency": "KES",
      "customer": {
        "id": 42,
        "email": "customer@example.com",
        "first_name": "Jane",
        "last_name": "Doe",
        "phone": "+254700000002"
      },
      "items": [
        {
          "id": 1,
          "order_id": 123,
          "product_id": 101,
          "variant_id": 1001,
          "name": "Organic Flour 1kg",
          "sku": "FLOUR-ORG-1KG",
          "quantity": 2,
          "price": "1200.00",
          "product": {
            "id": 101,
            "name": "Organic Flour 1kg",
            "slug": "organic-flour-1kg",
            "description": "...",
            "images": [],
            "category": { "id": 5, "name": "Baking", "slug": "baking" },
            "categories": [],
            "brand": { "id": 2, "name": "Shopflow", "slug": "shopflow" },
            "tags": [{ "id": 1, "name": "Gluten free", "slug": "gluten-free" }]
          },
          "variant": {
            "id": 1001,
            "sku": "FLOUR-ORG-1KG",
            "price": "1200.00",
            "sale_price": null,
            "stock": 50,
            "attributes_json": "{\"Size\":\"1kg\"}"
          }
        }
      ],
      "addresses": [
        {
          "type": "shipping",
          "name": "Jane Doe",
          "street": "Westlands Road, Apt 4B",
          "city": "Nairobi",
          "state": "Nairobi",
          "zip": "00100",
          "country": "Kenya",
          "latitude": -1.2676,
          "longitude": 36.8108,
          "place_id": "ChIJw8dw9L4RLxgRN6VaR-iGxQY"
        }
      ],
      "notes": [],
      "transactions": [],
      "created_at": "2026-03-04T10:00:00.000Z",
      "updated_at": "2026-03-04T10:00:00.000Z"
    }
  }
}

Delivery geolocation on returned addresses:

FieldTypeDescription
latitudenumber | nullDelivery latitude captured at checkout (map pin / device geolocation). null when not collected.
longitudenumber | nullDelivery longitude captured at checkout. null when not collected.
place_idstring | nullGoogle Place ID of the delivery point, when the storefront resolved one. null when not collected.

These fields are populated from the coordinates the storefront collects at checkout (used for distance-based shipping). They are only present on orders placed after this feature shipped — orders created before it, or through channels that don't collect a location (POS, phone orders, most API imports), return null/omit them. You may also send them yourself on POST /orders (see the Address Fields below).


GET/orders/batchFetch multiple orders

Retrieve up to 100 orders by ID in a single request. Useful for loading an order set without paginating through the list endpoint.

Auth: Admin session required.

Query params:

ParamTypeRequiredDescription
idsstringYesComma-separated order IDs, e.g., "1,2,3" (max 100 IDs)
X-Branch-Idstring (header)NoFilter by branch ID, or all for super admin
bash
curl "https://your-store-api.example.com/api/v1/orders/batch?ids=101,102,103" \
  -H "Cookie: shopflow.sid=..."

Response:

json
{
  "status": "success",
  "data": {
    "orders": [
      {
        "id": 101,
        "order_number": "ORD-1234567890",
        "status": "completed",
        "payment_status": "paid",
        "total": "2700.00",
        "currency": "KES",
        "customer": { "id": 5, "email": "jane@example.com" },
        "items": [
          { "id": 1, "name": "Organic Flour", "quantity": 2, "price": "1200.00" }
        ]
      }
    ],
    "not_found": [],
    "count": 1
  }
}

Gotchas:

  • Order IDs beyond 100 are silently ignored.
  • The not_found array contains any IDs that did not exist or were outside your branch scope.
  • Branch scoping applies: you only see orders you have access to.

POST/ordersCreate Order

Request Body:

json
{
  "customer_email": "jane@example.com",
  "subtotal": 2400,
  "shipping": 300,
  "tax": 0,
  "discount": 0,
  "total": 2700,
  "currency": "KES",
  "status": "pending",
  "payment_status": "paid",
  "payment_method": "mpesa",
  "payment_reference": "MPESA-QAB123XYZ",
  "items": [
    {
      "product_id": 101,
      "variant_id": 1001,
      "name": "Organic Flour 1kg",
      "sku": "FLOUR-ORG-1KG",
      "quantity": 2,
      "price": 1200
    },
    {
      "product_id": 102,
      "name": "Simple Product",
      "sku": "SIMPLE-001",
      "quantity": 1,
      "price": 500
    }
  ],
  "addresses": [
    {
      "type": "shipping",
      "name": "Jane Doe",
      "street": "Westlands Road, Apt 4B",
      "city": "Nairobi",
      "state": "Nairobi",
      "zip": "00100",
      "country": "Kenya"
    }
  ]
}

Field Reference:

FieldTypeRequiredDescription
customer_emailstringYes*Customer email (find existing or create guest customer). Preferred for integrations.
emailstringYes*Alternative to customer_email when customer_id is omitted.
customer_idintegerYes*Legacy: existing customer ID. If set with an email, both must match the same customer.
subtotalnumberYesSum of item prices
shippingnumberYesShipping cost
taxnumberYesTax amount
discountnumberYesDiscount amount
totalnumberYesGrand total
currencystringYesISO 4217 code (e.g. USD, KES)
statusstringNopending (default), processing, completed, cancelled
payment_statusstringNopending (default), paid
payment_methodstringNoPayment method used - free-form string, e.g. mpesa, cash, bank_transfer, cod, card. Stored on the order and on the auto-created transaction record.
payment_referencestringNoPayment / transaction reference (e.g. M-Pesa code). When payment_status is paid and this is set, a transaction record is created automatically. Also accepted as transaction_reference.
coupon_idintegerNoCoupon ID if applied
itemsarrayYesOrder line items
addressesarrayNoShipping and/or billing addresses. Omit if address is not applicable (e.g. POS, phone order). You can provide a free-form address - only type is required by the system; all other address fields are optional.
order_datestring (ISO 8601)NoActual date the order was placed in the originating system (e.g. "2024-12-31T14:30:00Z"). Use this when syncing historical orders so the admin dashboard reflects the real order date. Defaults to the submission time when omitted.
api_labelstringNoOptional display label shown as a badge on the order in the admin (max 100 chars). Useful to identify the source system, e.g. "WooCommerce", "ERP", "POS". When omitted the badge simply shows API.

*Provide customer_email (or email) or customer_id - not all three as conflicting identifiers.

Optional when creating a new customer from email: customer_first_name, customer_last_name, customer_phone (or first_name, last_name, phone). Name defaults may be taken from the shipping address name line.

Item Fields:

FieldTypeRequiredDescription
product_idintegerYesProduct ID. For a variation, the parent product's id - not the variation's
variant_idintegerFor variationsThe variation's id (variants[].id on GET /products/:id). Leave out, or send 0, for simple products
namestringYesLine name as it should read on the order, e.g. Organic Flour - 1kg
skustringYesFor a variation, its own sku (variants[].sku); otherwise the product's
quantityintegerYesQuantity
pricenumberYesUnit price charged. For a variation, the variation's price (see below)

Ordering variable products

A variable product (sizes, colours, weights) is ordered by variation: each line names the parent product and the variation. To order the 1kg bag of the flour from the variant response example:

  1. Read the product with GET /products/:id (or find it in GET /products). Its variants array lists every variation with its id, sku, price, sale_price and options (attributes_json).
  2. Pick the variation whose attributes_json matches the customer's choice - here { "Size": "1kg" }, variant 102.
  3. Build the line from that variation:
Line fieldTake it from
product_idthe product's id (10)
variant_idthe variation's id (102)
skuthe variation's sku (FLOUR-ORG-1KG)
pricethe variation's sale_price while its sale is running, otherwise its price
namethe product name and the option (Organic Flour - 1kg)

A sale is running when sale_price is set and lower than price, and today falls between the variation's sale_price_start_date and sale_price_end_date (either may be empty).

Two options of the same product are two lines, one per variant_id:

json
{
  "customer_email": "jane@example.com",
  "subtotal": 24.97,
  "shipping": 0,
  "tax": 0,
  "discount": 0,
  "total": 24.97,
  "currency": "USD",
  "items": [
    { "product_id": 10, "variant_id": 101, "name": "Organic Flour - 500g", "sku": "FLOUR-ORG-500G", "quantity": 2, "price": 5.99 },
    { "product_id": 10, "variant_id": 102, "name": "Organic Flour - 1kg", "sku": "FLOUR-ORG-1KG", "quantity": 1, "price": 12.99 }
  ]
}

What the server checks, and what it doesn't:

  • Prices, SKUs and names are stored as you send them - the server does not look up the variation's values, so send the ones you read. The order's subtotal and total are stored as sent too.
  • Every line is checked before anything is saved: product_id must exist; variant_id, when set, must be a variation of that product (0 means none); name and sku are required (at most 255 and 100 characters); quantity must be a whole number.
  • A bad line is refused with 400, and nothing is saved - not the order, not the customer - so you can fix it and send the order again. errors lists every bad line:
json
{
  "status": "error",
  "message": "Line 2: variation 101 belongs to product 10, not product 20. Send the parent product's id as product_id",
  "errors": [
    {
      "line": 2,
      "field": "variant_id",
      "message": "Line 2: variation 101 belongs to product 10, not product 20. Send the parent product's id as product_id"
    }
  ]
}
  • Without variant_id, the line is recorded against the parent product only: the admin order page can't show which option was bought, because it reads the options from the variation.
  • GET /orders/:id returns each line with its variant (the variation's current record, including attributes_json) next to product.

POST /orders/batch and PUT /orders/:id (which replaces all lines) take lines in this same shape. A storefront placing an order for a shopper uses create-order instead, which prices each line itself.

Address Fields (all optional - include what you have):

FieldTypeRequiredDescription
typestringYesshipping or billing
namestringNoFull name
streetstringNoStreet address
citystringNoCity
statestringNoState/region
zipstringNoPostal code
countrystringNoCountry name or code

Address is optional. For phone orders, POS, or integrations that don't collect address details, you can omit addresses entirely or pass only a partial address. The order will be saved without address data.

Example Request - Minimal (no address):

bash
curl -X POST "https://your-store.com/api/v1/orders" \
  -u "ck_xxx:cs_yyy" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_email": "jane@example.com",
    "subtotal": 2400,
    "shipping": 0,
    "tax": 0,
    "discount": 0,
    "total": 2400,
    "currency": "KES",
    "items": [
      {
        "product_id": 101,
        "name": "Organic Flour 1kg",
        "sku": "FLOUR-ORG-1KG",
        "quantity": 2,
        "price": 1200
      }
    ]
  }'

Example Request - With paid status and payment reference:

bash
curl -X POST "https://your-store.com/api/v1/orders" \
  -u "ck_xxx:cs_yyy" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_email": "jane@example.com",
    "subtotal": 2400,
    "shipping": 300,
    "tax": 0,
    "discount": 0,
    "total": 2700,
    "currency": "KES",
    "payment_status": "paid",
    "payment_method": "mpesa",
    "payment_reference": "MPESA-QAB123XYZ",
    "items": [
      {
        "product_id": 101,
        "variant_id": 1001,
        "name": "Organic Flour 1kg",
        "sku": "FLOUR-ORG-1KG",
        "quantity": 2,
        "price": 1200
      }
    ],
    "addresses": [
      {
        "type": "shipping",
        "name": "Jane Doe",
        "street": "Westlands Road, Apt 4B",
        "city": "Nairobi",
        "country": "Kenya"
      }
    ]
  }'

Example Request - With order date and API label:

bash
curl -X POST "https://your-store.com/api/v1/orders" \
  -u "ck_xxx:cs_yyy" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_email": "jane@example.com",
    "subtotal": 2400,
    "shipping": 300,
    "tax": 0,
    "discount": 0,
    "total": 2700,
    "currency": "KES",
    "payment_status": "paid",
    "payment_method": "mpesa",
    "payment_reference": "MPESA-QAB123XYZ",
    "order_date": "2024-12-25T10:30:00Z",
    "api_label": "WooCommerce",
    "items": [
      { "product_id": 101, "name": "Organic Flour 1kg", "sku": "FLOUR-ORG-1KG", "quantity": 2, "price": 1200 }
    ]
  }'

Example Response (201):

json
{
  "status": "success",
  "data": {
    "order": {
      "id": 124,
      "order_number": "ORD-1709468834000",
      "customer_id": 42,
      "status": "pending",
      "payment_status": "paid",
      "payment_method": "mpesa",
      "subtotal": "2400.00",
      "shipping": "300.00",
      "tax": "0.00",
      "discount": "0.00",
      "total": "2700.00",
      "currency": "KES",
      "source": "api",
      "api_label": "WooCommerce",
      "order_date": "2024-12-25T10:30:00.000Z",
      "created_at": "2026-03-04T10:00:00.000Z"
    }
  }
}

source is automatically set to "api" for all orders created via API key. The admin orders table shows an API badge (or your custom api_label) on these orders.

order_date is the real date your system recorded the order. The admin dashboard displays this date so historical imports match their original timestamps.

When payment_status is paid and payment_reference (or transaction_reference) is provided, a transaction record is created automatically - no need to call /orders/:id/payment-status separately.


POST/orders/batchCreate Multiple Orders

Create up to 100 orders in a single API call. Each order follows the same field spec as POST /orders. The response includes a created array (successful orders) and a failed array (with per-item error messages). Each order's lines are checked as on POST /orders; an order with a bad line lands in failed and is not saved. The entire batch is logged as a single entry in the admin's API Operation Logs and can be undone with one click.

Request Body:

json
{
  "orders": [
    {
      "customer_email": "alice@example.com",
      "subtotal": 500,
      "shipping": 50,
      "tax": 0,
      "discount": 0,
      "total": 550,
      "currency": "KES",
      "order_date": "2024-11-01T08:00:00Z",
      "api_label": "ERP Import",
      "items": [{ "product_id": 10, "name": "Item A", "sku": "A-001", "quantity": 1, "price": 500 }]
    },
    {
      "customer_email": "bob@example.com",
      "subtotal": 1200,
      "shipping": 100,
      "tax": 0,
      "discount": 0,
      "total": 1300,
      "currency": "KES",
      "order_date": "2024-11-02T09:15:00Z",
      "items": [{ "product_id": 11, "name": "Item B", "sku": "B-002", "quantity": 2, "price": 600 }]
    }
  ]
}

Example Request:

bash
curl -X POST "https://your-store.com/api/v1/orders/batch" \
  -u "ck_xxx:cs_yyy" \
  -H "Content-Type: application/json" \
  -d '{ "orders": [...] }'

Example Response (207 Multi-Status):

json
{
  "status": "success",
  "data": {
    "created_count": 2,
    "failed_count": 0,
    "created": [ { "id": 125, "order_number": "ORD-...", ... }, { "id": 126, ... } ],
    "failed": []
  }
}

Batch size limit: 100 orders per request.


PUT/orders/:idUpdate order

Edit an order's fields, customer profile, items, or addresses. Items and addresses are replaced wholesale (delete + re-create). This is a full order replacement endpoint. New items are checked first, as on POST /orders: if a line is refused (400), the order is left unchanged.

Auth: Admin session required.

Request body:

FieldTypeDescription
statusstringOrder status: pending, processing, in_transit, on_hold, completed, cancelled
payment_statusstringPayment status: pending, paid, failed, refunded
customer_notesstringInternal or customer-facing notes
order_datestringISO 8601 date when order was placed
created_atstringISO 8601 date for order creation (raw SQL update)
itemsarrayArray of item objects to replace all items: { name, sku, quantity, price }
addressesarrayArray of address objects: { type, name, street, city, state, zip, country, phone }
customer_first_name, customer_last_name, customer_phonestringUpdate customer profile fields
bash
curl -X PUT "https://your-store-api.example.com/api/v1/orders/123" \
  -H "Cookie: shopflow.sid=..." \
  -H "Content-Type: application/json" \
  -d '{
    "status": "completed",
    "customer_notes": "Shipped via courier",
    "items": [
      { "name": "Organic Flour", "sku": "FLOUR-ORG", "quantity": 2, "price": "1200.00" }
    ]
  }'

Response (200):

json
{
  "status": "success",
  "data": {
    "order": {
      "id": 123,
      "order_number": "ORD-1234567890",
      "status": "completed",
      "payment_status": "paid",
      "total": "2700.00",
      "customer": { "id": 5, "email": "jane@example.com" },
      "items": [ { "id": 1, "name": "Organic Flour", "sku": "FLOUR-ORG", "quantity": 2, "price": "1200.00" } ],
      "addresses": [ { "type": "shipping", "name": "Jane Doe", "street": "123 Main St", "city": "Nairobi" } ]
    }
  }
}

Gotchas:

  • Items are replaced wholesale — all old items are deleted and new ones created.
  • Addresses are also replaced wholesale.
  • created_at updates bypass Sequelize and use raw SQL directly (useful for backdating orders).
  • Customer name/phone are merged intelligently: if customer has no name, the shipping address name is parsed as a fallback.

PUT/orders/:id/statusUpdate order status

Change an order's status. Optionally auto-mark the order as paid and record a transaction when marking as completed.

PATCH /orders/:id/status is accepted too and runs the same handler — it exists for older integrations. Use PUT in new code.

Auth: Admin session required.

Request body:

FieldTypeRequiredDescription
statusstringYesNew status: pending, processing, in_transit, on_hold, completed, cancelled
transaction_referencestringNoTransaction ID to record if marking as completed. Empty string skips recording.
bash
curl -X PATCH "https://your-store-api.example.com/api/v1/orders/123/status" \
  -H "Cookie: shopflow.sid=..." \
  -H "Content-Type: application/json" \
  -d '{"status": "completed", "transaction_reference": "cash"}'

Response (200):

json
{
  "status": "success",
  "data": {
    "order": {
      "id": 123,
      "order_number": "ORD-1234567890",
      "status": "completed",
      "payment_status": "paid",
      "total": "2700.00",
      "customer": { "id": 5, "email": "jane@example.com" }
    }
  }
}

Auto-mark-paid behavior:

When the status changes to completed AND:

  • The order's payment_status is NOT already paid
  • A transaction_reference is provided and not empty

Then:

  • payment_status is automatically set to paid
  • A new Transaction record is created with method: 'manual' and the provided reference

Gotchas:

  • Status changes trigger email notifications to the customer (fire-and-forget).
  • The order's activity timeline records the status change and any payment status changes.
  • If neither status nor transaction_reference is provided, the request returns 200 but has no side effects.

PATCH/orders/:id/payment-statusUpdate Payment Status

Request Body:

json
{
  "payment_status": "paid",
  "transaction_reference": "MPESA-QAB123XYZ"
}

If payment_status is paid and transaction_reference is provided, a transaction record is created.

Example Request:

bash
curl -X PATCH "https://your-store.com/api/v1/orders/123/payment-status" \
  -u "ck_xxx:cs_yyy" \
  -H "Content-Type: application/json" \
  -d '{"payment_status":"paid","transaction_reference":"MPESA-QAB123XYZ"}'

POST/orders/:id/notesAdd a note to an order

Create a new internal note on an order. The note is timestamped and attributed to the staff member who created it.

Auth: Admin session required.

Request body:

FieldTypeRequiredDescription
contentstringYesNote text (can include markdown)
bash
curl -X POST "https://your-store-api.example.com/api/v1/orders/123/notes" \
  -H "Cookie: shopflow.sid=..." \
  -H "Content-Type: application/json" \
  -d '{"content": "Customer requested expedited shipping"}'

Response (201):

json
{
  "status": "success",
  "data": {
    "note": {
      "id": 1,
      "order_id": 123,
      "author_id": 5,
      "content": "Customer requested expedited shipping",
      "created_at": "2024-09-23T10:30:00.000Z",
      "updated_at": "2024-09-23T10:30:00.000Z"
    }
  }
}

POST/orders/:id/resend-confirmation-emailResend order confirmation email

Re-send the order confirmation email to the customer. This is useful when the original email failed or the customer requests a copy.

Auth: Admin session required.

Request body: Empty object or omitted.

bash
curl -X POST "https://your-store-api.example.com/api/v1/orders/123/resend-confirmation-email" \
  -H "Cookie: shopflow.sid=..." \
  -H "Content-Type: application/json" \
  -d '{}'

Response (200):

json
{
  "status": "success",
  "message": "Confirmation email sent",
  "data": {
    "email_sent": true
  }
}

Errors:

  • 400 if the order's customer has no email address.
  • Email send failures are caught and recorded in the order's email_error field.

PATCH/orders/:id/viewMark order as viewed

Mark an order as viewed by staff. This clears it from the "unviewed" queue and broadcasts a change notification to other logged-in staff.

Auth: Admin session required.

Request body: Empty object or omitted.

bash
curl -X PATCH "https://your-store-api.example.com/api/v1/orders/123/view" \
  -H "Cookie: shopflow.sid=..." \
  -H "Content-Type: application/json" \
  -d '{}'

Response (200):

json
{
  "status": "success",
  "message": "Order marked as viewed"
}

Behavior: Idempotent — if the order was already viewed, no-op but returns 200.


POST/orders/:id/assign-riderAssign or unassign a rider

Manually dispatch an order to a rider, or reclaim it back to the desk. Requires the Riders addon to be enabled.

Auth: Admin session required; requires orders.manage permission.

Addon: Riders addon — returns 503 when switched off.

Request body:

FieldTypeDescription
rider_idintegerRider ID to assign, or null to unassign
bash
curl -X POST "https://your-store-api.example.com/api/v1/orders/123/assign-rider" \
  -H "Cookie: shopflow.sid=..." \
  -H "Content-Type: application/json" \
  -d '{"rider_id": 42}'

Response (200):

json
{
  "status": "success",
  "data": {
    "order": {
      "id": 123,
      "order_number": "ORD-1234567890",
      "rider_id": 42,
      "rider_assigned_at": "2024-09-23T10:30:00.000Z",
      "rider_assigned_by": 5,
      "status": "processing"
    }
  }
}

Unassign (pass null):

bash
curl -X POST "https://your-store-api.example.com/api/v1/orders/123/assign-rider" \
  -H "Cookie: shopflow.sid=..." \
  -H "Content-Type: application/json" \
  -d '{"rider_id": null}'

Errors:

  • 404 if the order or rider does not exist.
  • 422 if rider_id is neither an integer nor null, or if the rider is inactive or belongs to a different branch (when branch enforcement is on).
  • 503 if the Riders addon is disabled.

Gotchas:

  • Branch enforcement: If the store setting riders_enforce_branch is enabled, a rider must belong to the same branch as the order. Pass the correct rider for that branch.
  • Timeline: The assignment is recorded on the order's activity timeline with the assigning staff member's name.

DELETE/orders/:idDelete Order

Example Request:

bash
curl -X DELETE "https://your-store.com/api/v1/orders/123" \
  -u "ck_xxx:cs_yyy"