Orders
Use the Orders API to import sales from another system, create orders from a custom checkout, track fulfillment, record partial or full payment, schedule future orders, and retrieve receipts.
All endpoints use the /v1/orders prefix and authenticate with an API key.
Amounts are expressed in the business currency as decimal numbers, not minor units.
Endpoints
Order resource
Order reads and successful create, update, confirmation, and payment refresh operations return the same order shape.
{
"id": "5037c85a-44c6-4c3a-bd7d-3e21e73915e1",
"shortCode": "482193",
"status": "pending",
"paymentStatus": "partial",
"summary": "2x Jollof Rice",
"items": [
{
"id": "2b783a34-5b8b-43b7-a441-dcb8ae7db843",
"productId": "7a59174f-3207-42a6-8fab-5b8d8b2929fb",
"variantId": "f91718fb-1487-4ada-a44c-794b1ba5a3d3",
"productName": "Jollof Rice",
"variantName": "Large",
"quantity": 2,
"unitPrice": 3500,
"originalUnitPrice": 4000,
"discountAmount": 1000,
"subtotalAmount": 7000,
"taxAmount": 0,
"totalPrice": 7000,
"imageUrl": "https://cdn.example.com/jollof-rice.jpg",
"modifiers": [
{
"modifierGroupId": "4f7f9ff4-19e4-4cbb-b7c6-45275e70fc6f",
"modifierOptionId": "f494ef42-178a-4e16-bcf2-481a52d55833",
"name": "Extra chicken",
"priceDelta": 1000
}
],
"components": []
}
],
"totalAmount": 7000,
"subtotalAmount": 8000,
"discountAmount": 1000,
"serviceChargeAmount": 0,
"amountPaid": 4000,
"remainingAmount": 3000,
"currency": "NGN",
"paymentMethod": "transfer",
"customer": {
"id": "3cadb42d-571b-4979-94b4-c094c60820c4",
"name": "Ada Lovelace",
"phoneNumber": "+2348012345678",
"whatsappNumber": "+2348012345678"
},
"store": {
"id": "837ceae4-43c2-4bbb-830c-1099d36b9d05",
"name": "Lekki"
},
"channel": "checkout",
"channelFulfilledAt": null,
"isAutomated": false,
"deposit": null,
"scheduledFor": null,
"scheduledConfirmedAt": null,
"serviceMode": "takeaway",
"tableSessionId": null,
"tableLabel": null,
"kitchenTicketId": null,
"kitchenTicketStatus": null,
"recordedBy": {
"id": "4e3d75bb-4208-4dd4-a292-2c27ecb99185",
"name": "API integration"
},
"servedBy": null,
"tags": [],
"notes": "Pack cutlery separately",
"receiptUrl": null,
"cancellation": null,
"occurredAt": "2026-07-14T10:30:00.000Z",
"date": "2026-07-14T10:30:00.000Z",
"createdAt": "2026-07-14T10:30:02.000Z",
"entrySource": "live",
"recoverySessionId": null,
"academicTerm": null
}Status values
paymentStatus is calculated from the order totals:
Setting an order status to refunded does not move money. The public update endpoint
therefore does not accept refunded. Use the applicable payment provider refund
workflow, then consume the resulting order or transaction update.
List orders
GET /v1/orders?status=pending,scheduled&channel=whatsapp,voice&limit=25Query parameters
Comma-separated filters use lowercase values.
Response
{
"message": "Orders retrieved",
"data": [],
"meta": {
"total": 0,
"page": 1,
"perPage": 25,
"totalPages": 1,
"hasMore": false
},
"summary": {
"todayOrders": 4,
"todayRevenue": 42000,
"totalOrders": 138,
"filteredOrders": 12,
"totalRevenue": 184000,
"averageOrderValue": 15333.33,
"pendingOrdersCount": 6,
"pendingOutstandingAmount": 27000,
"completedOrdersCount": 7,
"completedRevenue": 157000,
"cancelledOrdersCount": 1,
"cancelledRevenue": 0
}
}filteredOrders and the revenue fields respect the supplied filters. totalOrders and
pendingOrdersCount are business workload counts and are not reduced by every list filter.
Retrieve an order
GET /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1This endpoint returns the complete order resource, including line items, customer, store, payment, scheduling, restaurant, staff, deposit, and cancellation data.
A missing order, a deleted order, or an order belonging to another business returns:
{
"error": "not_found",
"message": "Order not found."
}Create an order
POST /v1/orders
Idempotency-Key: 90ed7ca0-dd71-4550-8bea-62b99d551d41
Content-Type: application/jsonAlways use a unique UUID as the Idempotency-Key. Repeating a successful request with the
same key returns the original order with 200 OK. A new order returns 201 Created.
Core fields
Valid channels are in_person, whatsapp, instagram, tiktok, telegram, twitter,
storefront, dashboard, checkout, voice, and other.
Line item fields
Catalog-backed lines should include product_id. Fee lines can omit it and use
line_type: "fee" with a custom_price.
Modifier fields
Restaurant and hotel fields
School field
academic_term_id associates the order with an academic term. It is optional for other
business types.
Product and variant example
{
"items": [
{
"product_id": "7a59174f-3207-42a6-8fab-5b8d8b2929fb",
"variant_id": "f91718fb-1487-4ada-a44c-794b1ba5a3d3",
"product_name": "Jollof Rice",
"quantity": 2,
"modifiers": [
{
"modifier_group_id": "4f7f9ff4-19e4-4cbb-b7c6-45275e70fc6f",
"modifier_option_id": "f494ef42-178a-4e16-bcf2-481a52d55833",
"name": "Extra chicken",
"price_delta": 1000
}
]
}
],
"payment_method": "transfer",
"amount_paid": 4000,
"discount_amount": 1000,
"customer_id": "3cadb42d-571b-4979-94b4-c094c60820c4",
"store_id": "837ceae4-43c2-4bbb-830c-1099d36b9d05",
"channel": "checkout",
"service_mode": "takeaway",
"notes": "Pack cutlery separately"
}send_to_kitchen defaults to true for a restaurant or hotel business, or any order with a
service_mode or service_charge_amount — the order above is sent to the kitchen
immediately without needing to set it. Pass send_to_kitchen: false to hold an order back,
for example while its bank transfer payment is still pending, then call Send order to
kitchen once it clears:
{
"items": [
{
"product_id": "7a59174f-3207-42a6-8fab-5b8d8b2929fb",
"product_name": "Jollof Rice",
"quantity": 2
}
],
"payment_method": "transfer",
"amount_paid": 0,
"service_mode": "takeaway",
"send_to_kitchen": false
}Fee line example
{
"items": [
{
"product_name": "Delivery fee",
"quantity": 1,
"custom_price": 1500,
"line_type": "fee"
}
],
"payment_method": "cash"
}Scheduled order example
{
"items": [
{
"product_id": "7a59174f-3207-42a6-8fab-5b8d8b2929fb",
"product_name": "Event platter",
"quantity": 3
}
],
"scheduled_for": "2026-08-20T18:30:00.000Z",
"customer_name": "Grace Hopper",
"channel": "voice"
}scheduled_for must be a valid future timestamp. The new order enters scheduled status.
Update an order
PATCH /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1
Content-Type: application/jsonOnly supplied fields are changed.
Record a partial payment
{
"payment_method": "transfer",
"amount_paid": 4000
}amount_paid is cumulative. If an order total is 7000 and a second payment brings the
collected amount to 7000, send 7000, not the payment delta.
Cancel an order
{
"status": "cancelled",
"cancellation_reason": "Customer changed their mind"
}The response includes the stored cancellation audit block. Completed automated bank
transfer orders cannot be cancelled through this endpoint because a payment reversal may
be required.
Mark a channel order fulfilled
{
"channel_fulfilled": true
}Send false to return the order to the channel fulfillment queue.
Confirm a scheduled order
POST /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1/confirm
Idempotency-Key: b802662c-3b05-4f9b-859c-14468eb6d485
Content-Type: application/json{
"payment_method": "pos",
"amount_paid": 7000
}A fully paid order becomes completed. A partially paid order becomes pending.
Only orders currently in scheduled status can be confirmed.
Send order to kitchen
POST /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1/send-to-kitchen
Idempotency-Key: 6c2e1a4f-9d8b-4a3e-b7c6-5f1e8a2d3c90No request body. Creates the order’s kitchen ticket — the same effect as the dashboard’s
Send to Kitchen button, including the initial-stage customer WhatsApp notification and
a kitchen_ticket.created webhook.
Use it for an order created with send_to_kitchen: false, the usual case for a business
that only accepts bank transfer: the order is recorded immediately so payment can be
chased, but kitchen prep shouldn’t start until a payment webhook confirms it cleared.
Calling this endpoint is that trigger.
An order created with send_to_kitchen: true (the default) already has a kitchen ticket
and does not need this call. If the order already has one — check kitchenTicketId — this
endpoint returns 409.
Response
{
"message": "Order sent to kitchen",
"data": {
"id": "5037c85a-44c6-4c3a-bd7d-3e21e73915e1",
"shortCode": "482193",
"kitchenTicketId": "d2f6a1c4-8b3e-4f7a-9e1d-6c2b5a8f3e90",
"kitchenTicketStatus": "queued",
"...": "remaining fields match the order object"
},
"meta": {
"notification": {
"status": "sent",
"customerName": "Ada Lovelace",
"customerPhone": "+2348012345678",
"messageId": "wamid.HBg..."
}
}
}data is the full order resource. meta.notification reports the
initial-stage WhatsApp message the same way Update kitchen
status’s response does — see that section’s notification.status
and notification.reason tables, which apply here unchanged.
Idempotency
Repeating a request with the same Idempotency-Key returns the original response and
creates no second ticket. Without a key, a repeat call is not silently absorbed the way
kitchen-status absorbs an unchanged status — it returns 409, because a second real
ticket is never created for one order:
{
"error": "request_failed",
"message": "This order has already been sent to the kitchen."
}Update kitchen status
POST /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1/kitchen-status
Idempotency-Key: 4b1d2c8e-7f3a-4e9b-9c2d-1a6f5e8b3c70
Content-Type: application/json{
"status": "preparing"
}Moves the order’s kitchen ticket to a prep stage. This is the same transition staff make
with the Preparing, Ready, and Served buttons on the Kitchen board, with the
same effects: the ticket moves on the board, the customer receives the WhatsApp stage
message the business has configured, and a
kitchen_ticket.status_updated webhook is sent. Use it when the
kitchen is run from an external system such as a kitchen display, a delivery aggregator,
or your own POS.
Stages are not enforced in order; a ticket can move forward or back to any stage. Only the kitchen ticket is affected, never a bar ticket on the same order.
The order must already have a kitchen ticket, which it gets from send_to_kitchen: true
at creation or from staff sending it to the kitchen. If kitchenTicketId on the order is
null, this endpoint returns 404.
Response
{
"message": "Kitchen status updated",
"data": {
"id": "5037c85a-44c6-4c3a-bd7d-3e21e73915e1",
"shortCode": "482193",
"kitchenTicketId": "d2f6a1c4-8b3e-4f7a-9e1d-6c2b5a8f3e90",
"kitchenTicketStatus": "preparing"
},
"meta": {
"notification": {
"status": "sent",
"customerName": "Ada Lovelace",
"customerPhone": "+2348012345678",
"messageId": "wamid.HBg..."
}
}
}data is the full order resource with kitchenTicketStatus reflecting
the new stage. meta.notification reports what happened with the customer’s WhatsApp
stage message; Cloove only sends one when the business has enabled it for that stage.
A skipped or failed notification never fails the request. The stage is saved before
the message is attempted, so 200 OK always means the ticket is at the requested stage.
Idempotency
Repeating a request with the same Idempotency-Key returns the original response and
does not send another WhatsApp message or webhook. Repeating the same status without a
key is also safe: it returns 200 OK with notification.reason set to status_unchanged
and emits no webhook.
A 404 with the message This order has no associated kitchen ticket means the order
was never sent to the kitchen. Create orders with send_to_kitchen: true when an
external system will drive their prep status.
Refresh an automated payment
POST /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1/requeryUse this endpoint for an order with isAutomated: true when your application needs to
refresh its virtual account payment state. The response returns the updated order resource.
Do not poll continuously. Prefer webhooks for normal payment updates and requery only after a customer reports payment or when recovering from a missed event.
Generate a receipt
POST /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1/receipt{
"message": "Order receipt generated",
"data": {
"url": "https://storage.example.com/receipts/signed-receipt.pdf"
}
}Receipt URLs can be signed and time-limited. Store the order ID and generate another URL when a previous link expires.
Delete an order
DELETE /v1/orders/5037c85a-44c6-4c3a-bd7d-3e21e73915e1Deletion soft-deletes the order, restores tracked variant inventory, and removes its active debt record. It is intended for incorrectly recorded orders.
Orders with automated deposits cannot be deleted and return 409 Conflict:
{
"error": "order_not_deletable",
"message": "Orders with automated deposits (like bank transfers) cannot be deleted."
}Use cancellation for a valid order that will not be fulfilled. Use deletion only when the record itself should not remain in sales reporting.
Errors
See API conventions for the shared error envelope, retry rules, pagination, and idempotency behavior.
Creating an order emits an order.created webhook. Status and payment changes may emit
additional order or payment events. Sending an order to the kitchen emits
kitchen_ticket.created, and kitchen stage changes emit kitchen_ticket.status_updated.
Verify webhook signatures and process event IDs idempotently.