Orders API
Create, manage, and track the full order lifecycle — from placement to completion — through the Ordeliya API.
Overview
The Orders API is the core of the Ordeliya platform. Every order is scoped to the authenticated store. Tenant users (Owner, Admin, Manager, Staff) can view and manage all orders. Customers can only view their own orders through the storefront endpoints.
All monetary values are in minor units (integer cents/ore). 24900 = 249.00 DKK.
GET /orders
List orders with optional filters. Returns a paginated list sorted by createdAt descending.
Auth: Bearer Token or API Key (read:orders)
curl "https://api.ordeliya.com/orders?status=RECEIVED&limit=10" \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter: RECEIVED, CONFIRMED, PREPARING, READY, ON_THE_WAY, COMPLETED, CANCELLED |
fulfillmentType | string | — | Filter: DELIVERY, PICKUP, CURBSIDE |
paymentStatus | string | — | Filter: PENDING, COMPLETED, FAILED, REFUNDED |
source | string | — | Filter: WEB, APP, POS, PHONE |
customerId | string | — | Filter by customer ID |
from | ISO 8601 | — | Orders created after this date |
to | ISO 8601 | — | Orders created before this date |
page | integer | 1 | Page number |
limit | integer | 20 | Items per page (max: 100) |
sort | string | createdAt | Sort field |
order | string | desc | Sort direction: asc or desc |
Response 200 OK
{
"success": true,
"data": [
{
"id": "ord_v7k3m9n2",
"orderId": "2026-0147",
"status": "RECEIVED",
"fulfillmentType": "DELIVERY",
"source": "WEB",
"paymentStatus": "COMPLETED",
"paymentProvider": "STRIPE",
"customer": {
"id": "cust_n3k7m2",
"name": "Maria Nielsen",
"phone": "+4520123456",
"email": "maria@example.dk"
},
"items": [
{
"id": "oi_x1y2z3",
"productId": "prod_8kx2m4n7",
"productName": "Margherita Pizza",
"variantId": "var_lg01",
"variantName": "Large",
"quantity": 2,
"unitPriceMinor": 11900,
"totalMinor": 23800,
"options": [
{
"optionGroupName": "Extras",
"choiceName": "Extra Mozzarella",
"priceMinor": 1500
}
],
"notes": "Extra crispy"
},
{
"id": "oi_a4b5c6",
"productId": "prod_3jn8v5q2",
"productName": "Garlic Bread",
"variantId": null,
"variantName": null,
"quantity": 1,
"unitPriceMinor": 3900,
"totalMinor": 3900,
"options": [],
"notes": null
}
],
"subtotalMinor": 30700,
"taxMinor": 7675,
"deliveryFeeMinor": 2900,
"discountMinor": 0,
"totalMinor": 33600,
"currency": "DKK",
"notes": "3rd floor, code 4521",
"deliveryAddress": {
"street": "Vesterbrogade 42",
"zipcode": "1620",
"city": "Copenhagen V",
"country": "DK"
},
"scheduledAt": null,
"createdAt": "2026-03-15T18:42:11.000Z",
"updatedAt": "2026-03-15T18:42:11.000Z"
}
],
"meta": {
"total": 1247,
"page": 1,
"limit": 10,
"totalPages": 125,
"requestId": "req_k8j7h6g5f4d3"
}
}
GET /orders/:id
Get full order details including items, customer, delivery address, payment info, and status timeline.
Auth: Bearer Token or API Key (read:orders)
curl https://api.ordeliya.com/orders/ord_v7k3m9n2 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
Returns the same order object as the list endpoint, plus the timeline array:
{
"success": true,
"data": {
"id": "ord_v7k3m9n2",
"orderId": "2026-0147",
"status": "PREPARING",
"timeline": [
{ "status": "RECEIVED", "timestamp": "2026-03-15T18:42:11.000Z", "actor": "system" },
{ "status": "CONFIRMED", "timestamp": "2026-03-15T18:43:22.000Z", "actor": "usr_4k7m2n8v" },
{ "status": "PREPARING", "timestamp": "2026-03-15T18:45:01.000Z", "actor": "usr_s7t8a9f0" }
]
}
}
Error 404 Not Found
{
"success": false,
"error": {
"statusCode": 404,
"message": "Order not found"
}
}
The order might exist but belong to a different store. Orders are strictly scoped — you can only access orders from your authenticated store.
POST /orders
Create a new order. Idempotency key is strongly recommended to prevent duplicate orders.
Auth: Bearer Token or API Key (write:orders)
curl -X POST https://api.ordeliya.com/orders \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: pos_device01_txn_20260315_001" \
-d '{
"fulfillmentType": "DELIVERY",
"source": "POS",
"customerId": "cust_n3k7m2",
"items": [
{
"productId": "prod_8kx2m4n7",
"variantId": "var_lg01",
"quantity": 1,
"options": [
{ "optionChoiceId": "opt_c7h8e9e0" }
],
"notes": "Well done"
},
{
"productId": "prod_3jn8v5q2",
"quantity": 2
}
],
"deliveryAddress": {
"street": "Nørrebrogade 15",
"zipcode": "2200",
"city": "Copenhagen N",
"country": "DK"
},
"notes": "Please ring doorbell twice",
"scheduledAt": "2026-03-15T20:00:00.000Z"
}'
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
fulfillmentType | string | yes | DELIVERY, PICKUP, or CURBSIDE |
source | string | yes | WEB, APP, POS, or PHONE |
customerId | string | no | Link to existing customer |
customerName | string | no | Guest name (if no customerId) |
customerPhone | string | no | Guest phone (if no customerId) |
customerEmail | string | no | Guest email (if no customerId) |
items | array | yes | At least one item required |
items[].productId | string | yes | Product ID |
items[].variantId | string | no | Variant ID (if product has variants) |
items[].quantity | integer | yes | Min: 1 |
items[].options | array | no | Option choices for modifiers |
items[].notes | string | no | Item-level special instructions |
deliveryAddress | object | conditional | Required when fulfillmentType is DELIVERY |
notes | string | no | Order-level notes |
scheduledAt | ISO 8601 | no | Future time for scheduled orders (null = ASAP) |
Response 201 Created
{
"success": true,
"data": {
"id": "ord_q2w3e4r5",
"orderId": "2026-0148",
"status": "RECEIVED",
"fulfillmentType": "DELIVERY",
"source": "POS",
"customer": {
"id": "cust_n3k7m2",
"name": "Maria Nielsen",
"phone": "+4520123456",
"email": "maria@example.dk"
},
"items": [
{
"id": "oi_d7e8f9",
"productId": "prod_8kx2m4n7",
"productName": "Margherita Pizza",
"variantId": "var_lg01",
"variantName": "Large",
"quantity": 1,
"unitPriceMinor": 11900,
"totalMinor": 13400,
"options": [
{
"optionGroupName": "Extras",
"choiceName": "Extra Mozzarella",
"priceMinor": 1500
}
],
"notes": "Well done"
},
{
"id": "oi_g0h1i2",
"productId": "prod_3jn8v5q2",
"productName": "Garlic Bread",
"variantId": null,
"variantName": null,
"quantity": 2,
"unitPriceMinor": 3900,
"totalMinor": 7800,
"options": [],
"notes": null
}
],
"subtotalMinor": 21200,
"taxMinor": 5300,
"deliveryFeeMinor": 2900,
"discountMinor": 0,
"totalMinor": 24100,
"currency": "DKK",
"paymentStatus": "PENDING",
"deliveryAddress": {
"street": "Nørrebrogade 15",
"zipcode": "2200",
"city": "Copenhagen N",
"country": "DK"
},
"notes": "Please ring doorbell twice",
"scheduledAt": "2026-03-15T20:00:00.000Z",
"createdAt": "2026-03-15T19:15:44.000Z"
}
}
Error 422 Unprocessable Entity
{
"success": false,
"error": {
"statusCode": 422,
"message": "Validation failed",
"errors": [
{ "field": "items[0].productId", "message": "Product not found or inactive" },
{ "field": "deliveryAddress", "message": "Required for DELIVERY fulfillment type" }
]
}
}
PATCH /orders/:id/status
Advance the order through the status workflow. Only valid transitions are allowed.
Auth: Bearer Token or API Key (write:orders)
curl -X PATCH https://api.ordeliya.com/orders/ord_v7k3m9n2/status \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"status": "CONFIRMED",
"note": "Order confirmed by kitchen"
}'
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
status | string | yes | Target status (see flow below) |
note | string | no | Internal note for the timeline |
Response 200 OK
{
"success": true,
"data": {
"id": "ord_v7k3m9n2",
"orderId": "2026-0147",
"status": "CONFIRMED",
"previousStatus": "RECEIVED",
"updatedAt": "2026-03-15T18:43:22.000Z"
}
}
Error 400 Bad Request — Invalid Transition
{
"success": false,
"error": {
"statusCode": 400,
"message": "Invalid status transition from COMPLETED to PREPARING"
}
}
DELETE /orders/:id
Soft-delete an order. The order is marked as archived but retained for reporting. Only RECEIVED or CANCELLED orders can be deleted.
Auth: Bearer Token (write:orders, Owner or Admin role)
curl -X DELETE https://api.ordeliya.com/orders/ord_v7k3m9n2 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 204 No Content
No response body. The order is archived.
Error 400 Bad Request
{
"success": false,
"error": {
"statusCode": 400,
"message": "Cannot delete order with status CONFIRMED. Cancel the order first."
}
}
GET /orders/:id/timeline
Get the full status history of an order.
Auth: Bearer Token or API Key (read:orders)
curl https://api.ordeliya.com/orders/ord_v7k3m9n2/timeline \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
{
"success": true,
"data": [
{
"status": "RECEIVED",
"timestamp": "2026-03-15T18:42:11.000Z",
"actor": "system",
"note": "Order placed via storefront"
},
{
"status": "CONFIRMED",
"timestamp": "2026-03-15T18:43:22.000Z",
"actor": "usr_4k7m2n8v",
"actorName": "Anders Jensen",
"note": "Order confirmed by kitchen"
},
{
"status": "PREPARING",
"timestamp": "2026-03-15T18:45:01.000Z",
"actor": "usr_s7t8a9f0",
"actorName": "Sofia Larsen",
"note": null
},
{
"status": "READY",
"timestamp": "2026-03-15T19:05:33.000Z",
"actor": "usr_s7t8a9f0",
"actorName": "Sofia Larsen",
"note": "Food packed and ready"
},
{
"status": "ON_THE_WAY",
"timestamp": "2026-03-15T19:08:45.000Z",
"actor": "system",
"note": "Driver assigned: Jakob"
},
{
"status": "COMPLETED",
"timestamp": "2026-03-15T19:28:12.000Z",
"actor": "system",
"note": "Delivered successfully"
}
]
}
GET /orders/stats
Get aggregate order statistics for the current store. Useful for dashboard widgets.
Auth: Bearer Token or API Key (read:orders)
curl https://api.ordeliya.com/orders/stats \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
{
"success": true,
"data": {
"totalOrders": 1247,
"todayOrders": 23,
"pendingOrders": 5,
"totalRevenueMinor": 1456700,
"todayRevenueMinor": 42300,
"averageOrderMinor": 11680,
"statusBreakdown": {
"RECEIVED": 3,
"CONFIRMED": 2,
"PREPARING": 4,
"READY": 1,
"ON_THE_WAY": 2,
"COMPLETED": 1218,
"CANCELLED": 17,
"REFUNDED": 0
}
}
}
Refunds
POST /refunds
Create a refund request for an order. Refunds go through an approval workflow: PENDING → APPROVED → PROCESSED (or REJECTED).
Auth: Bearer Token (write:orders, Manager role or above)
curl -X POST https://api.ordeliya.com/refunds \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"orderId": "ord_v7k3m9n2",
"type": "PARTIAL",
"reason": "QUALITY_ISSUE",
"reasonText": "Customer reported cold pizza",
"refundAmountMinor": 8900,
"items": [
{
"productName": "Margherita Pizza",
"qty": 1,
"refundAmountMinor": 8900
}
]
}'
| Field | Type | Required | Description |
|---|---|---|---|
orderId | string | yes | The order to refund |
type | string | yes | FULL or PARTIAL |
reason | string | yes | CUSTOMER_REQUEST, QUALITY_ISSUE, DUPLICATE_ORDER, OTHER |
reasonText | string | no | Free-text explanation |
refundAmountMinor | integer | yes | Amount to refund in minor units |
items | array | no | Line-item breakdown for partial refunds |
Response 201 Created
{
"success": true,
"data": {
"id": "ref_m3n4o5p6",
"orderId": "ord_v7k3m9n2",
"type": "PARTIAL",
"reason": "QUALITY_ISSUE",
"reasonText": "Customer reported cold pizza",
"refundAmountMinor": 8900,
"currency": "DKK",
"status": "PENDING",
"items": [
{
"productName": "Margherita Pizza",
"qty": 1,
"refundAmountMinor": 8900
}
],
"createdAt": "2026-03-15T20:15:00.000Z"
}
}
GET /refunds
List all refunds for the current store.
Auth: Bearer Token or API Key (read:orders)
curl "https://api.ordeliya.com/refunds?status=PENDING&page=1&limit=20" \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by status: PENDING, APPROVED, REJECTED, PROCESSED |
orderId | string | — | Filter by order ID |
page | integer | 1 | Page number |
limit | integer | 20 | Items per page |
Response 200 OK
{
"success": true,
"data": [
{
"id": "ref_m3n4o5p6",
"orderId": "ord_v7k3m9n2",
"type": "PARTIAL",
"reason": "QUALITY_ISSUE",
"refundAmountMinor": 8900,
"currency": "DKK",
"status": "PENDING",
"createdAt": "2026-03-15T20:15:00.000Z"
}
],
"meta": {
"total": 1,
"page": 1,
"limit": 20,
"totalPages": 1
}
}
GET /refunds/:id
Get a single refund by ID.
Auth: Bearer Token or API Key (read:orders)
curl https://api.ordeliya.com/refunds/ref_m3n4o5p6 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
PATCH /refunds/:id/approve
Approve a pending refund. Only PENDING refunds can be approved.
Auth: Bearer Token (Admin or Owner role)
curl -X PATCH https://api.ordeliya.com/refunds/ref_m3n4o5p6/approve \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
{
"success": true,
"data": {
"id": "ref_m3n4o5p6",
"status": "APPROVED",
"approvedAt": "2026-03-15T20:30:00.000Z"
}
}
PATCH /refunds/:id/reject
Reject a pending refund.
Auth: Bearer Token (Admin or Owner role)
curl -X PATCH https://api.ordeliya.com/refunds/ref_m3n4o5p6/reject \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
PATCH /refunds/:id/process
Mark an approved refund as processed (money returned to customer).
Auth: Bearer Token (Admin or Owner role)
curl -X PATCH https://api.ordeliya.com/refunds/ref_m3n4o5p6/process \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
{
"success": true,
"data": {
"id": "ref_m3n4o5p6",
"status": "PROCESSED",
"processedAt": "2026-03-15T21:00:00.000Z"
}
}
Order Status Flow
Orders follow a strict state machine. Only valid transitions are allowed.
┌─────────────┐
│ CANCELLED │
└──────▲───────┘
│
┌──────────┐ ┌───────────┐ ┌────┴──────┐ ┌─────────┐ ┌───────────┐
│ RECEIVED │──▸│ CONFIRMED │──▸│ PREPARING │──▸│ READY │──▸│ COMPLETED │
└──────────┘ └───────────┘ └───────────┘ └────┬────┘ └───────────┘
│
┌─────▼──────┐
│ ON_THE_WAY │──▸ COMPLETED
└────────────┘
Valid transitions:
| From | To |
|---|---|
RECEIVED | CONFIRMED, CANCELLED |
CONFIRMED | PREPARING, CANCELLED |
PREPARING | READY, CANCELLED |
READY | ON_THE_WAY, COMPLETED, CANCELLED |
ON_THE_WAY | COMPLETED, CANCELLED |
COMPLETED | REFUNDED |
CANCELLED | — (terminal state) |
REFUNDED | — (terminal state) |
Order Sources
| Source | Description | Typical Use |
|---|---|---|
WEB | Customer storefront order | Online ordering |
APP | Mobile app order | Native app |
POS | Point of sale terminal | In-store |
PHONE | Manual phone order | Call-in orders |
Fulfillment Types
| Type | Description | Delivery Address |
|---|---|---|
DELIVERY | Delivered to customer | Required |
PICKUP | Customer picks up at store | Not needed |
CURBSIDE | Customer picks up at curb | Not needed |
Payment Status
| Status | Description |
|---|---|
PENDING | Awaiting payment (cash orders, pay-at-pickup) |
COMPLETED | Payment received and confirmed |
FAILED | Payment attempt failed |
REFUNDED | Full refund issued |
Money Format
All prices are integers in minor units:
| Value | Currency | Display |
|---|---|---|
8900 | DKK | 89,00 kr |
1999 | EUR | 19,99 EUR |
24900 | TRY | 249,00 TL |
0 | DKK | 0,00 kr (free) |
Fields ending in Minor contain money values: subtotalMinor, taxMinor, deliveryFeeMinor, discountMinor, totalMinor, unitPriceMinor.
Tax is calculated based on the store's taxRateBps (basis points, e.g., 2500 = 25% Danish VAT) and taxInclusive setting.