Cart and Orders API
How the CloudPOS POS app submits sales, saves held orders, lists and opens orders, changes order and payment status, cancels orders, drives the kitchen (KOT) item statuses, and handles packing, delivery documents and external delivery.
This page covers every order endpoint the POS app calls, from the Confirm Order button in Billing to Order Details, Kitchen Master and Create Delivery.
All paths are relative to {{server}}. Every call on this page needs:
Header | Value |
|---|---|
|
|
|
|
|
|
|
|
All order routes require a signed-in user. The app adds store_id (active store) to list and detail calls.
Quick reference
Method | Path | Screen / action |
|---|---|---|
POST |
| Confirm Order, Confirm and Print, Confirm & Whatsapp, Save Order, Send Kitchen, sync from Confirmed Orders |
POST |
| Edit a saved order, confirm a restaurant/attender order that already exists |
POST |
| Confirm an existing saved order |
GET |
| Saved Orders / Ongoing Orders, Kitchen Master |
GET |
| Sales > Orders List, Online Orders |
GET |
| Order Details |
POST |
| Cancel Order |
POST |
| Order Status |
POST |
| Payment Status |
GET |
| Kitchen item statuses |
POST |
| Mark Ready / Mark Served on one item |
POST |
| Start or serve all items of an order |
POST |
| After a KOT is printed |
POST |
| Server cart (legacy cart screens) |
POST |
| Server cart quantity / remove / clear |
GET |
| Server cart contents |
POST |
| Change a server cart line price |
GET |
| Order Documents > Delivery Note PDF |
GET |
| Order Documents > Delivery Invoice PDF |
GET / POST |
| Packing (photos, packed at / by) |
GET |
| "Packed by" picker |
GET |
| Create Delivery carrier list |
GET |
| Carrier pickup warehouses |
POST |
| Create Delivery submit |
How a sale reaches the server
- Items are added to a local cart on the device. Billing does not call the server cart API while you scan or tap products.
- Confirm Order opens Finalize Order (customer F3, delivery F4, discount F10, payment F5), then builds one
add-to-orderrequest. - With POS_OFFLINE_SALES on (the default, "offline-first"), the app first stores the sale on the device with its receipt identity, prints it, and then sends
add-to-orderonce in the background. With it off ("online-first"), the app waits for the server: accepted = clear cart and print; rejected = keep the cart; unclear = clear without printing and flag the sale for review. - Sales that did not reach the server are listed in Sales > Confirmed Orders (Sync attention), where staff resend them with Sync with Database. There is no automatic retry.
POST /api/v1/order/add-to-order
Purpose: creates an order from a list of items, with customer, delivery, discount and payment. It is used for confirmed sales, saved (held) orders and restaurant kitchen orders.
Screens: Billing (Confirm Order F2, Confirm and Print F6, Confirm & Whatsapp, Save Order), Restaurant / Attender (Send Kitchen, KOT + BILL, Save Order), quotation conversion, and Sync with Database on Confirmed Orders.
Demo request: ORD-004851
The sale from the demo story: Layla Al-Harbi (customer 2767) buys 2 × classic chicken bucket at 220.00 and 1 × crispy chicken bucket at 218.00, Store Takeaway, paid card 400 + cash 300. Cash is sent net of the 42.00 change, so the stored breakdown is card 400 + cash 258 = 658.00. Counter 11 of store 2 issued receipt 2-11-261027-0003.
{
"items": [
{
"product_id": 12417,
"quantity": 1,
"price": 218.0,
"mrp": 230.0,
"stock_id": "<stock-id>",
"warranty_enabled": false,
"standard_unit_price": 218.0,
"item_discount_amount": 0.0,
"tax_rate": 18.0,
"tax_amount": 33.25,
"total_price": 218.0
},
{
"product_id": 12419,
"quantity": 2,
"price": 220.0,
"mrp": 250.0,
"stock_id": "<stock-id>",
"warranty_enabled": false,
"standard_unit_price": 220.0,
"item_discount_amount": 0.0,
"tax_rate": 18.0,
"tax_amount": 67.12,
"total_price": 440.0
}
],
"pricing_mode": "completed_sale",
"delivery_tax_amount": 0,
"grand_total": 658.0,
"round_off": 0.0,
"tax_total": 100.37,
"client_sale_id": "01a17de3-1cc3-7c31-974c-79e5b293255c",
"receipt_number": "2-11-261027-0003",
"issued_at": "2026-10-27T15:03:18.038Z",
"pos_device_id": "<device-uuid>",
"counter_number": 11,
"phone": "0551234567",
"customer_id": 2767,
"transaction_number": "",
"payment_method": ["CARD", "CASH"],
"paid_methods": [
{ "method": "CARD", "amount": 400.0 },
{ "method": "CASH", "amount": 258.0 }
],
"source_type": "executive",
"balance": "42.0",
"coupon_id": null,
"comment": "",
"delivery_method_id": "149",
"status": "confirmed",
"to_customer_credit": false,
"delivery_charge": 0,
"store_id": 2
}Ids are the real demo ids (classic chicken bucket = product 12419, crispy chicken bucket = 12417, Store Takeaway = delivery method 149, CARD = 7973, CASH = 7974). Angle-bracket values are placeholders. Items are sent in reverse cart order (last added first); the server expects that order.
Request fields
Field | Meaning |
|---|---|
| One line per stock batch used. |
Line snapshot (completed sale only) |
|
|
|
| Totals as printed. |
| UUIDv7 made on the device. Idempotency key: resending the same sale returns the original order. |
|
|
| When and where the receipt was issued. The counter number (1–999) is set in Settings > POS Counter Identity. |
| Customer. Without an id the server finds or creates the customer by phone. |
| One method id/code, or an array for Multi payment. Ids from master data |
| Split payment: |
| Single-method payments send this instead of |
| The balance figure from the payment panel. For Sell on Credit it is the amount left on the customer's account. |
|
|
| Coupon applied in Discount & Coupon. |
| Order discount. The discount is spread over lines in proportion, remainder on the last line. |
| Delivery (F4). Date/time only when ASK_DELIVERY_DATE is on. |
| Delivery address for door delivery. Pincode is required when ECOMMERCE_ENABLED is on. |
| For car delivery methods. |
| Restaurant table. |
| Order comment. |
|
|
| When converting a quotation. |
| Always |
|
|
Responses
Completed sale, created (201):
{
"success": true,
"order_id": 7043,
"order_number": "ORD-004851",
"client_sale_id": "01a17de3-1cc3-7c31-974c-79e5b293255c",
"receipt_number": "2-11-261027-0003",
"review": false,
"review_reasons": [],
"idempotent_replay": false
}How the server stored it (from executive/list-orders, trimmed):
{
"id": 7043,
"cart_id": 7344,
"order_number": "ORD-004851",
"receipt_number": "2-11-261027-0003",
"issued_at": "2026-10-27 18:03:18",
"counter_number": 11,
"payment_id": ["7974", "7973"],
"payment_method": ["CASH", "CARD"],
"payment_status": "paid",
"status": "confirmed",
"sub_total": "557.630",
"discount": "0.000",
"grand_total": "658.000",
"round_off": "0.000",
"tax": "100.370",
"shipping_cost": "0.000",
"delivery_method_id": 149,
"customer_name": "Layla Al-Harbi",
"created_by": "salesexecutiv2",
"orderProps": {
"POS_PRICING": {
"pricing_mode": "completed_sale",
"pricing_review": false,
"completed_sale_snapshot": { "tax_total": 100.37, "grand_total": 658, "round_off": 0, "delivery_charge": 0, "discount_amount": 0 }
},
"ORDER_STATUS": "confirmed - 2026-10-27 15:03:19",
"BALANCE": "42.0",
"CUSTOMER_PHONE": "0551234567",
"CUSTOMER_EMAIL": "layla.demo@funzcart.in"
}
}sub_total is the net without tax (557.63) and tax is 100.37. BALANCE 42.0 is the change shown in red at checkout.
The same sale sent again returns 200 with "idempotent_replay": true and the original order. review: true means the server stored the sale but flagged a pricing difference for an admin to check.
Other orders (saved, kitchen) return 201 with message: "Order created successfully", order_id, order_number, customer_credit_amount, order_details and payment_notice (set for COD: invoice created, payment pending until delivery).
What the server does on a confirmed sale
- Creates the order (
ORD-…) with statusconfirmedand payment status from the payments. - Creates the invoice automatically (in the demo
INV-OR1002060, linked to receipt number2-11-261027-0003) and a receipt for the money collected (RCP1001953). - Deducts stock from the batches in
stock_id. - If ZATCA automatic sync is set up, queues the invoice for ZATCA; otherwise its ZATCA status stays
NOT SENT.
Errors
Code | Body | Meaning |
|---|---|---|
409 |
| Same |
409 |
| Duplicate receipt number. Usually the counter number is shared by two devices or was reset after a reinstall; fix it in POS Counter Identity. |
422 |
| Split amounts do not fit the bill (also: COD must cover the full remaining total; COD cannot create customer credit). |
422 | stock error body | Not enough stock (only when stock rules require it). |
500 |
| Field validation (for example an unknown payment method id). |
POST /api/v1/order/update-order
Purpose: updates an existing order: add or change items of a saved order, or confirm and pay a restaurant / attender order that was sent to the kitchen earlier.
Screens: Edit Order (Save Order, Confirm Order, Create Order and Print), Restaurant Finalize Order on an ongoing table order.
{
"order_id": "<order-id>",
"client_sale_id": "<uuid-v7>",
"receipt_number": "2-11-261027-0004",
"issued_at": "2026-10-27T16:10:00.000Z",
"pos_device_id": "<device-uuid>",
"counter_number": 11,
"phone": "0551234567",
"transaction_number": "",
"payment_method": "CASH",
"paid_amount": "438.0",
"source_type": "executive",
"balance": "0.0",
"coupon_id": null,
"status": "confirmed",
"table_id": "<table-id>",
"delivery_charge": 0
}order_id is required and must exist. Items and customer are not sent: the order already has them. A completed sale cannot be changed (409 completed_sale_immutable).
POST /api/v1/order/confirm-order
Purpose: confirms an order that is still init or new (a saved order). Body: { "order_id": "<order-id>", … payment fields … }. Returns 409 completed_sale_immutable for a completed sale, 403 tenant_mismatch if the order belongs to another company.
GET /api/v1/order/list-saved-orders
Purpose: held and in-progress executive orders: Saved Orders and Ongoing Orders in Billing, Restaurant and Attender, and the Kitchen Master board.
Param | Example | Effect |
|---|---|---|
|
| Orders of one restaurant table. |
|
| Orders of one delivery method (walk-in / delivery tabs). |
|
| Filter by status. Default `new`. |
|
| Order number, receipt number or client sale id. |
|
| Customer name contains. |
|
| Order date range. |
|
| Payment method contains. |
|
| Default 15. |
|
| Sent by the app. |
Captured response (1 of 15 orders, trimmed):
{
"status": "success",
"message": "List of init status orders from executive",
"data": {
"current_page": 1,
"data": [
{
"id": 5653,
"order_number": "ORD-004321",
"order_date": "2026-08-18",
"payment_method": [""],
"payment_status": "pending",
"status": "new",
"sub_total": "19.490",
"discount": "0.000",
"grand_total": "23.000",
"tax": "3.510",
"source_type": "executive",
"delivery_method_id": 149,
"orderProps": {
"ORDER_STATUS": "new - 2026-08-18 07:42:18",
"ORDER_TOKEN_NUMBER": "<token>",
"CUSTOMER_PHONE": "0011001100"
},
"cart_items": { "cart_items": [ { "product_name": " painting set", "quantity": "1.000", "total_price": "7.000", "…": "…" } ] },
"customer_name": "…",
"total_paid": 0,
"balance": 23
}
],
"last_page": 7,
"per_page": 15,
"total": 99
}
}When nothing matches, the server answers with the error envelope "No init status orders found"; the app shows an empty list.
GET /api/v1/order/executive/list-orders
Purpose: the paginated order list (10 per page) behind Sales > Orders List, and Online Orders with filter_online_sales=true (web orders instead of POS orders).
Query parameters
Param | Example | Effect |
|---|---|---|
|
| Store. |
|
| Order number, receipt number (spaces ignored) or client sale id. |
|
| Customer name contains. |
|
| Customer phone contains. |
|
| Customer email. |
|
| Grand total contains. |
|
| Order status. |
|
| Payment status (from the Payment Status filter). |
|
| Amount range (swapped by the app if entered the wrong way round). |
|
| Invoice date-time range (tenant time zone, Asia/Riyadh in the demo). |
|
| One order date. |
|
| Orders whose transactions fall inside the closed day sale of that business date. |
|
| One customer (customer profile All Orders tab). |
|
| Any line's product name, barcode, item code or SKU contains. |
|
| Restaurant table. |
|
| Web orders instead of POS orders. |
|
| Page. |
Response
Captured (1 of 10 orders, trimmed). This is the order before Layla's, from the same counter:
{
"status": "success",
"message": "List of Orders",
"data": {
"current_page": 1,
"data": [
{
"id": 7036,
"order_number": "ORD-004850",
"order_date": "2026-10-27 05:47:08",
"client_sale_id": "01a17be5-e430-7aab-a4b9-e776ac3081c1",
"receipt_number": "2-11-261027-0002",
"issued_at": "2026-10-27 08:47:06",
"counter_number": 11,
"token_number": "<token>",
"payment_id": ["7973"],
"payment_method": ["CARD"],
"payment_status": "paid",
"delivery_status": null,
"status": "confirmed",
"sub_total": "169.490",
"discount": "10.000",
"grand_total": "200.000",
"round_off": "0.000",
"tax": "30.510",
"shipping_cost": "0.000",
"source_type": "executive",
"delivery_method_id": 149,
"customer_name": "Test Default",
"store_name": "متجر النجمة Store ",
"created_by": "salesexecutiv2",
"orderProps": {
"POS_PRICING": { "pricing_mode": "completed_sale", "pricing_review": false, "…": "…" },
"ORDER_STATUS": "confirmed - 2026-10-27 05:47:08",
"BALANCE": "0.0",
"CUSTOMER_PHONE": "0011001100"
},
"cart_items": { "cart_items": [ { "product_name": "family chicken bucket", "quantity": "1.000", "total_price": "210.000", "…": "…" } ], "status_summary": {} },
"invoice_hash": "<invoice-hash>"
}
],
"first_page_url": "…?page=1",
"last_page": 362,
"next_page_url": "…?page=2",
"per_page": 10,
"total": 3614
}
}payment_id keeps the method ids; payment_method is translated to codes. cart_items.status_summary counts kitchen item statuses (for example { "READY": 1, "SERVED": 1 }). No match returns the error envelope "No Orders Found", which the app treats as an empty list.
GET /api/v1/order/executive/order-details/{order_number}
Purpose: one order with lines, totals, customer, payments, props, returns, packing and delivery. Feeds Order Details (Print, Share, Return, Order Status, Payment Status, Store & Delivery, Payment Details, Customer Information, Shipping Details, Packing, Order Documents) and printing.
Example: GET {{server}}/api/v1/order/executive/order-details/ORD-004851?store_id=2
Captured response for another demo order (trimmed):
{
"status": "success",
"data": {
"orders_id": 3386,
"store_id": 2,
"order_date": "2026-05-26 18:57:31",
"delivery_date": null,
"delivery_time": null,
"delivery_charge": null,
"cart": {
"id": 3468,
"customer_id": 2439,
"item_count": 2,
"store_name": "متجر النجمة Store ",
"cart_items": [
{
"id": 8179,
"product_id": 12426,
"product_name": "Veg burger",
"category_name": "Burgers",
"quantity": "1.000",
"mrp": "100.000",
"product_unit": "PC",
"unit_price": "103.550",
"standard_unit_price": null,
"discount_origin": null,
"total_price": "103.550",
"sale_unit_name": "PC",
"conversion_rate": 1,
"currency": "SAR",
"tax_amount": 8.55,
"tax_rate": "9.000",
"line_discount": 0,
"discounted_total": 103.55,
"discounted_base_amount": 95,
"discounted_tax_amount": 8.55
}
],
"price_summary": {
"sub_total": 107.84,
"total_tax": 9.71,
"discount": 0,
"total_mrp": 114,
"grand_total": 117.55,
"net_payable": "118.000",
"net_total": 117.55,
"net_exc_tax": 107.84,
"paid_amount": 118,
"order_balance": 0
}
},
"order_number": "ORD-002649",
"client_sale_id": null,
"receipt_number": null,
"return_state": { "has_returns": false, "has_completed_return": false, "has_draft_return": false, "items": [] },
"token_number": "<token>",
"order_status": "confirmed",
"packing": null,
"external_delivery_job": null,
"customer_details": {
"name": "Amruthesh",
"phone": "7887545465",
"customer_id": 2439,
"customer_type": "B2C",
"address": [],
"customer_balance": 0
},
"kyc_info": { "cr_number": null, "vat_number": null },
"payment_status": "paid",
"delivery_method_id": 199,
"delivery_method_name": "Dine In",
"payment_details": {
"payment_id": ["7973"],
"payment_status": "paid",
"transaction_id": 9229,
"payment_method": ["CARD"]
},
"order_props": [
{ "props_id": 15112, "props_code": "ORDER_STATUS", "props_value": "confirmed - 2026-05-26 21:27:31" },
{ "props_id": 15113, "props_code": "ORDER_TOKEN_NUMBER", "props_value": 137 }
],
"order_returns": [],
"payments": { "CARD": 118 },
"invoice_hash": "<invoice-hash>",
"kot_cancel_queue": []
}
}Field | Shown as |
|---|---|
| Lines with MRP vs rate, tax and totals. |
| Totals panel: sub total (without tax), tax, discount, total MRP and savings, net payable, paid, balance. |
| Transaction ID in Payment Details. |
| Payment breakdown by method. For Layla's order: |
| Customer Information (B2B shows VAT and CR numbers). |
|
|
| Returned quantities; drive the Return button. |
| Packing and Shipping Details. |
| Used for the public invoice link. |
| KOT cancellation events waiting to be printed. |
Order status, payment status and cancel
Allowed status changes
From | Can move to |
|---|---|
init | new, confirmed, cancelled |
new | confirmed, cancelled |
confirmed | processing, shipped, cancelled |
processing | shipped, delivered, cancelled |
shipped | delivered |
delivered | (final) |
cancelled | (final) |
Shipped or delivered orders cannot be cancelled: use a sales return. An order with a processed sales return cannot be cancelled. Shipping needs packing to be complete.
POST /api/v1/order/order-change-status
Screen: Order Status action on the list and on Order Details.
{
"order_id": "<order-id>",
"status": "processing",
"refund_amount": 0,
"payment_method": 7974,
"delivery_charge_refundable": false,
"delivery_logistics": "Door Delivery"
}Field | Rule |
|---|---|
| Required. |
| Required: |
| When moving to |
| Optional carrier text. |
| Optional, up to 500 characters. |
| Optional, when a COD order is delivered. |
POST /api/v1/order/order-change-payment-status
Screen: Payment Status action.
{ "order_id": "<order-id>", "status": "paid", "amount": 658, "payment_method": 7974 }Field | Rule |
|---|---|
| Required: |
| Required unless |
| Optional |
| Optional split: |
POST /api/v1/order/cancel-order
Screen: Cancel Order row action.
{ "order_id": "<order-id>", "refund_method": 7974, "refund_amount": 658, "delivery_charge_refundable": false }Optional reason. Success: { "status": "success", "message": "Order cancelled successfully", "data": { …order… } }. A transition that is not allowed returns 422 with the reason.
Kitchen (KOT) item statuses
Kitchen item statuses come from master data CART_ITEM_STATUS and their ids differ per company, so always read them first.
GET /api/v1/cart/cart-item-statuses
Captured response (trimmed):
{
"status": "success",
"message": "Available cart item statuses",
"data": [
{ "id": 2019, "value": "READY", "description": "Ready", "translations": [] },
{ "id": 2020, "value": "SERVED", "description": "Served", "translations": [] }
]
}The third status is START (started / preparing). An item with no status is NEW (pending). Kitchen Master tabs map to them: Pending = no status, Preparing = START, Ready = READY, Served = SERVED.
POST /api/v1/cart/update-cart-item-status
Screen: Mark Ready per item in Kitchen Master > Order Details, and Mark As Served.
{ "cart_item_id": "<cart-item-id>", "status_id": 2019 }Both fields are required. Returns "Cart item status updated successfully", or an error if the status id is not an active CART_ITEM_STATUS value.
POST /api/v1/order/update-order-items-status
Purpose: sets the status of many items at once (Start Cooking, mark the whole order served).
{ "order_id": "<order-id>", "status_id": 2020, "all": true }Field | Rule |
|---|---|
| Required. |
| Optional |
|
|
Response:
{
"status": "success",
"message": "Order items status updated successfully",
"data": { "order_id": "<order-id>", "order_number": "ORD-004852", "items_updated": 2, "status": "SERVED", "updated_at": "…" }
}POST /api/v1/order/acknowledge-kot-print
Purpose: after the KOT (or a KOT cancellation) is printed, removes those events from the order's KOT queue so they are not printed twice.
{ "order_id": "<order-id>", "event_ids": ["<event-id>"] }Returns "Events acknowledged successfully", or "Nothing to acknowledge" when the queue is empty.
Server cart (legacy)
Billing keeps the cart on the device. These cart endpoints are still used by the older cart and edit screens.
POST /api/v1/cart/add-to-cart
{ "customer_id": "2767", "quantity": 1, "product_id": "<product-id>", "source_type": "executive", "cart_id": "<cart-id>", "price": "210", "comment": "" }cart_id, price and comment are optional. Optional product_variant_id for variants.
POST /api/v1/cart/remove-from-cart
{ "cart_item_id": "<cart-item-id>", "cart_id": "<cart-id>", "action": "update_quantity", "quantity": 2, "customer_id": "2767" }action is remove_item, clear_cart or update_quantity. cart_item_id is required for remove_item and update_quantity; quantity for update_quantity.
GET /api/v1/cart/executive/list-cart-items
Params customer_id (required), cart_id, store_id. Returns the cart: id, customer_id, item_count, store_id, cart_items[{id, product_id, product_name, quantity, product_unit, unit_price, mrp, total_price, tax_rate, tax_amount}], price_summary{sub_total, total_tax, net_total, discount, net_payable}. Errors: "Customer ID is required", "Cart not found/No incomplete cart".
POST /api/v1/cart/change-cart-item-price
{ "cart_item_id": "<cart-item-id>", "unit_price": 200 }Optional tax_id[]. Returns "Cart item price updated successfully!" with the line.
Packing, documents and external delivery
These actions are on Order Details for roles sales_executive and restaurant_sales. Saving packing requires the sales-executive shipping permission (otherwise 403 "Sales Executive permission required.").
GET /api/v1/order/packing/{order_number}
Returns the packing record: status, packed at, packed by, photo and video URLs.
POST /api/v1/order/packing/{order_number}
multipart/form-data:
Field | Meaning |
|---|---|
| Staff id from |
| Free-text name. |
| Date-time. |
| Image files, up to 20 MB each. |
| Paths of photos to remove. |
Only confirmed, processing or shipped orders can be packed (422 otherwise). Video is not accepted here.
GET /api/v1/order/packing-staff
Users who can be chosen as Packed by.
GET /api/v1/order/documents/delivery-note/{order_number}
GET /api/v1/order/documents/delivery-invoice/{order_number}
Return the PDF bytes of the Delivery Note ("Goods Delivery Receipt") and Delivery Invoice. They need the auth headers, so the app downloads them and opens the file; they cannot be opened as plain links.
GET /api/v1/order/external-logistics
Active external carriers for Create Delivery.
GET /api/v1/order/external-logistics/{external_logistic_id}/warehouses
Pickup warehouses of one carrier.
POST /api/v1/order/order-details/{order_number}/external-delivery
Example body (illustrative values):
{
"external_logistic_id": 3,
"warehouse_id": 5,
"shipping_service": "express",
"transport_mode": "surface",
"weight": 2.5,
"package_count": 1,
"length": 30,
"breadth": 20,
"height": 15,
"shipping_charge": 0,
"dispatch_date": "2026-10-28",
"expected_delivery_at": "2026-10-30",
"payment_mode": "Prepaid",
"tracking_url": "https://carrier.example/track/123",
"external_shipment_id": "SHP-123",
"remarks": ""
}Field | Values |
|---|---|
| Master data |
| Master data |
| Master data |
Public invoice links
Shared from Share (WhatsApp, email, link):
Link | Meaning |
|---|---|
| Public PDF download, using |
| Invoice page link used in WhatsApp and email share text. |