Create an order
This guide walks through the recommended flow to create an order through the API: read the menu, resolve the client (and address, for delivery), price delivery if needed, create the order, add its items and payment, and handle the common rejections along the way.
The flow at a glance
Step 1 — read the menu
Resolve which product variants the order needs. Either read the full menu
(GET /v1/menu) or browse it lean:
GET /v1/menu/categories,
GET /v1/menu/products, and
GET /v1/menu/products/{id} for one product's
variants and modifier categories. You need a variant_id (and, for any add-ons, a
modifier_id) per item you're about to add to the order.
Required scope: menu:read
Step 2 — find or create the client (and address)
GET /v1/clients, filtered by phone:
GET /v1/clients?filter[phone_number]=987654321&filter[country_calling_code]=51
If no client matches, create one:
POST /v1/clients
{
"name": "Maria Lopez",
"phone_number": "987654321",
"country_calling_code": "51"
}
Required scopes: clients:read (lookup), clients:write (create).
For a DELIVERY order, resolve a delivery address the same way —
list the client's saved addresses or
create one:
POST /v1/addresses
{
"client_id": "9c8e720f-31b1-485a-85fa-0aaf552faaa9",
"address": "Av. Larco 345",
"latitude": -12.1218,
"longitude": -77.0297
}
Required scopes: addresses:read (lookup), addresses:write (create).
Step 3 — price delivery (DELIVERY orders only)
Don't guess the delivery fee: resolve it from the merchant's own pricing configuration. See
Calculate delivery prices for the full flow
(GET /v1/delivery/pricing + GET /v1/delivery/quote) — it ends with the delivery_price you
pass below.
Step 4 — create the order
POST /v1/orders
Required scope: orders:write. Required header: Idempotency-Key — a unique value per
order attempt; retrying the same key with the same body replays the original result instead of
creating a second order.
curl -X POST https://public-api.olaclick.app/v1/orders \
-H "Authorization: Bearer olk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 018f1c33-5a2b-7e4d-9c1a-3b6f8e2d4a70" \
-d '{
"service_type": "DELIVERY",
"client_id": "9c8e720f-31b1-485a-85fa-0aaf552faaa9",
"address_id": "6b2f1a04-1c7e-4a2b-9f11-2d8e0b3c4d55",
"delivery_price": 6.5
}'
| Field | Applies to | Notes |
|---|---|---|
service_type | all | DELIVERY, ONSITE, TABLE or TAKEAWAY. Required. |
status | all | INITIATED (default) or PENDING. See below. |
client_id | all | An existing client of your company. |
table_id | TABLE only | Rejected on any other service_type. |
address_id, delivery_price | DELIVERY only | Rejected on any other service_type. |
comment, order_title, packaging_price, scheduled_delivery_date | all | Optional. |
The response (201) is the created order. See the REST API Reference for the full response shape.
Draft (INITIATED) vs PENDING
By default the order is created as INITIATED — a draft the merchant does not see yet, and
drafts are never returned by GET /v1/orders: keep the id from the create response and read
the draft with GET /v1/orders/{id}. Add its items (and
any discounts or coupon) while it's still a draft, then confirm it with
PATCH /v1/orders/{id}/status and
{"status": "PENDING"} (or PREPARING to skip the merchant's own confirmation step).
Confirming assigns the order its daily number and makes it visible to the merchant.
Registering a payment also confirms a draft: the first payment on an INITIATED order
(even a partial one) moves it to PENDING and assigns its daily number, without the
PATCH. So add items, discounts and coupons first, and the payment last.
To skip the draft entirely, send "status": "PENDING" on creation — the order is confirmed
right away.
Step 5 — items, payments, discounts, coupons
POST /v1/orders/{id}/combos
{
"combos": [
{
"variant_id": "3f6a9c2e-1b4d-4e7a-8c9f-2d5e7a1b3c40",
"quantity": 2,
"modifiers": [{ "modifier_id": "8e2b4d6f-9a0c-4e1b-85d7-9f2a4c6e8b10", "quantity": 1 }]
}
]
}
Then register a payment (payment_method_id comes
from GET /v1/company/payment-methods):
POST /v1/orders/{id}/payments
{
"payment_method_id": "4a7c9e1b-3d6f-4b2a-9e8c-1f3a5b7d9c20",
"bill_amount": 25.5
}
POST /v1/orders/{id}/discounts
{ "type": "PERCENTAGE", "value": 10, "description": "Loyalty 10%" }
Or a coupon code (the order needs a client
attached first):
POST /v1/orders/{id}/coupon
{ "code": "WELCOME10" }
Required scope for all of the above: orders:write. All of them work only on orders your
company created through the API (combos, discounts); payments also accept orders placed on
the storefront or the POS — see each endpoint's reference page for the exact rule.
Common 4xx errors
| Status | Code | When |
|---|---|---|
| 422 | validation_failed | A field doesn't apply to the service_type sent (e.g. table_id on a DELIVERY order), a required field is missing, or a value is out of range (e.g. a discount value over 100 for PERCENTAGE). |
| 400 | idempotency_key_required | POST /v1/orders without an Idempotency-Key header, or with an empty one. |
| 400 | idempotency_key_invalid | The key is longer than 255 characters. |
| 422 | idempotency_conflict | The same Idempotency-Key was reused with a different request body. |
| 409 | idempotency_in_progress | A request with that key is still being processed; retry after the Retry-After header. |
| 422 | order_not_modifiable | Acting on an order from a channel this endpoint doesn't allow to edit (e.g. adding items to an order placed on a food app). |
| 422 | validation_failed (field status) | Cancelling (POST /v1/orders/{id}/cancel with a reason) an order that still has active payments. Remove the payments first, then cancel. |
| 422 | coupon_not_applicable | The coupon exists but isn't eligible for this order (usage limit reached, once-per-customer, wrong service type, restricted to new customers). |
| 404 | not_found | Unknown order id or coupon code. |
| 403 | insufficient_scope | The API key is missing a required scope. |
Track the order
Once confirmed, don't poll for status changes — register a webhook and let OlaClick push them to you. See Receive order updates with webhooks.
Doing it through the MCP server
| Tool | What it does |
|---|---|
list_products / get_product | Browse the menu for variant_ids |
get_client / create_client | Resolve the client |
list_addresses / create_address | Resolve the delivery address |
get_delivery_pricing / get_delivery_quote | Price delivery |
create_order | Create the order (an idempotency_key is generated for you if omitted) |
add_order_combos | Add items |
update_order_status | Confirm or advance the order |
add_order_payment / add_order_discount / apply_order_coupon | Payments, discounts, coupons |
Needed scopes: menu:read, clients:read/clients:write, addresses:read/addresses:write,
orders:read/orders:write.
Example prompts:
"Create a delivery order for client 9c8e720f-31b1-485a-85fa-0aaf552faaa9 with 2 of variant 3f6a9c2e-1b4d-4e7a-8c9f-2d5e7a1b3c40, then confirm it"
"Add a 10% loyalty discount to order 7a1e9c3a-4b2d-4f7e-9a1b-3c6d8e2f5a90"
See the MCP Server page for every tool's full parameters.