Orders
10. Orders API
GET/ordersList Orders
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 20 | Items per page |
status | string | - | Filter: pending, processing, completed, cancelled |
search | string | - | 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:
curl -X GET "https://your-store.com/api/v1/orders?page=1&limit=20&status=completed" \
-u "ck_xxx:cs_yyy"Example Response (200):
{
"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,nullfor legacy orders. The admin displays an API badge forsource === "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.nullwhen not set (admin falls back tocreated_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.
curl "https://your-store-api.example.com/api/v1/orders/sources" \
-H "Cookie: shopflow.sid=..."Response:
{
"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:
| Param | Type | Description |
|---|---|---|
X-Branch-Id | string (header) | Filter by branch ID, or all for super admin |
curl "https://your-store-api.example.com/api/v1/orders/count/unviewed" \
-H "Cookie: shopflow.sid=..."Response:
{
"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:
| Param | Type | Description |
|---|---|---|
date_from | string | Start date (ISO 8601: YYYY-MM-DD) |
date_to | string | End date (ISO 8601: YYYY-MM-DD) |
status | string | Filter by status: pending, processing, in_transit, on_hold, completed, cancelled |
payment_status | string | Filter by payment status: pending, paid, failed, refunded |
source | string | Filter by order source: api, admin, storefront, website |
search | string | Search by order number, customer email, or customer name (case-insensitive) |
X-Branch-Id | string (header) | Filter by branch ID, or all for super admin |
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.csvResponse: A text/csv file with columns for order details, customer info, items, addresses, shipments, and transactions.
GET/orders/:idGet Single Order
Example Request:
curl -X GET "https://your-store.com/api/v1/orders/123" \
-u "ck_xxx:cs_yyy"Example Response (200):
{
"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:
| Field | Type | Description |
|---|---|---|
latitude | number | null | Delivery latitude captured at checkout (map pin / device geolocation). null when not collected. |
longitude | number | null | Delivery longitude captured at checkout. null when not collected. |
place_id | string | null | Google 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 onPOST /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:
| Param | Type | Required | Description |
|---|---|---|---|
ids | string | Yes | Comma-separated order IDs, e.g., "1,2,3" (max 100 IDs) |
X-Branch-Id | string (header) | No | Filter by branch ID, or all for super admin |
curl "https://your-store-api.example.com/api/v1/orders/batch?ids=101,102,103" \
-H "Cookie: shopflow.sid=..."Response:
{
"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_foundarray 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:
{
"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:
| Field | Type | Required | Description |
|---|---|---|---|
customer_email | string | Yes* | Customer email (find existing or create guest customer). Preferred for integrations. |
email | string | Yes* | Alternative to customer_email when customer_id is omitted. |
customer_id | integer | Yes* | Legacy: existing customer ID. If set with an email, both must match the same customer. |
subtotal | number | Yes | Sum of item prices |
shipping | number | Yes | Shipping cost |
tax | number | Yes | Tax amount |
discount | number | Yes | Discount amount |
total | number | Yes | Grand total |
currency | string | Yes | ISO 4217 code (e.g. USD, KES) |
status | string | No | pending (default), processing, completed, cancelled |
payment_status | string | No | pending (default), paid |
payment_method | string | No | Payment 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_reference | string | No | Payment / 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_id | integer | No | Coupon ID if applied |
items | array | Yes | Order line items |
addresses | array | No | Shipping 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_date | string (ISO 8601) | No | Actual 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_label | string | No | Optional 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:
| Field | Type | Required | Description |
|---|---|---|---|
product_id | integer | Yes | Product ID. For a variation, the parent product's id - not the variation's |
variant_id | integer | For variations | The variation's id (variants[].id on GET /products/:id). Leave out, or send 0, for simple products |
name | string | Yes | Line name as it should read on the order, e.g. Organic Flour - 1kg |
sku | string | Yes | For a variation, its own sku (variants[].sku); otherwise the product's |
quantity | integer | Yes | Quantity |
price | number | Yes | Unit 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:
- Read the product with
GET /products/:id(or find it inGET /products). Itsvariantsarray lists every variation with itsid,sku,price,sale_priceand options (attributes_json). - Pick the variation whose
attributes_jsonmatches the customer's choice - here{ "Size": "1kg" }, variant102. - Build the line from that variation:
| Line field | Take it from |
|---|---|
product_id | the product's id (10) |
variant_id | the variation's id (102) |
sku | the variation's sku (FLOUR-ORG-1KG) |
price | the variation's sale_price while its sale is running, otherwise its price |
name | the 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:
{
"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
subtotalandtotalare stored as sent too. - Every line is checked before anything is saved:
product_idmust exist;variant_id, when set, must be a variation of that product (0means none);nameandskuare required (at most 255 and 100 characters);quantitymust 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.errorslists every bad line:
{
"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/:idreturns each line with itsvariant(the variation's current record, includingattributes_json) next toproduct.
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):
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | shipping or billing |
name | string | No | Full name |
street | string | No | Street address |
city | string | No | City |
state | string | No | State/region |
zip | string | No | Postal code |
country | string | No | Country name or code |
Address is optional. For phone orders, POS, or integrations that don't collect address details, you can omit
addressesentirely or pass only a partial address. The order will be saved without address data.
Example Request - Minimal (no address):
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:
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:
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):
{
"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"
}
}
}
sourceis automatically set to"api"for all orders created via API key. The admin orders table shows an API badge (or your customapi_label) on these orders.
order_dateis the real date your system recorded the order. The admin dashboard displays this date so historical imports match their original timestamps.When
payment_statusispaidandpayment_reference(ortransaction_reference) is provided, a transaction record is created automatically - no need to call/orders/:id/payment-statusseparately.
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:
{
"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:
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):
{
"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:
| Field | Type | Description |
|---|---|---|
status | string | Order status: pending, processing, in_transit, on_hold, completed, cancelled |
payment_status | string | Payment status: pending, paid, failed, refunded |
customer_notes | string | Internal or customer-facing notes |
order_date | string | ISO 8601 date when order was placed |
created_at | string | ISO 8601 date for order creation (raw SQL update) |
items | array | Array of item objects to replace all items: { name, sku, quantity, price } |
addresses | array | Array of address objects: { type, name, street, city, state, zip, country, phone } |
customer_first_name, customer_last_name, customer_phone | string | Update customer profile fields |
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):
{
"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_atupdates 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:
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | New status: pending, processing, in_transit, on_hold, completed, cancelled |
transaction_reference | string | No | Transaction ID to record if marking as completed. Empty string skips recording. |
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):
{
"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_referenceis provided and not empty
Then:
payment_statusis automatically set topaid- 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
statusnortransaction_referenceis provided, the request returns 200 but has no side effects.
PATCH/orders/:id/payment-statusUpdate Payment Status
Request Body:
{
"payment_status": "paid",
"transaction_reference": "MPESA-QAB123XYZ"
}If payment_status is paid and transaction_reference is provided, a transaction record is created.
Example Request:
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:
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Note text (can include markdown) |
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):
{
"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.
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):
{
"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_errorfield.
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.
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):
{
"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:
| Field | Type | Description |
|---|---|---|
rider_id | integer | Rider ID to assign, or null to unassign |
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):
{
"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):
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_idis neither an integer nornull, 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_branchis 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:
curl -X DELETE "https://your-store.com/api/v1/orders/123" \
-u "ck_xxx:cs_yyy"