Laayo Partner API v1
One signed REST API for every POS. JSON in, JSON out. Every success body is wrapped as { "data": ... }, and every error as { "error": { "code", "message", "fields" } }.
Base URL https://posconnect.laayo.com.np. Every path starts with /v1. Sandbox keys use the same URL and only see Laayo test stores.
Bodies are at most 512 KB. Each key can make 600 requests a minute. Times are RFC 3339 in Nepal time, for example 2026-11-01T12:41:08+05:45.
429 and 503 answers carry Retry-After in seconds. Wait that long, then sign the request again with a new timestamp. While Laayo pauses the partner API, every call answers 503 api_paused with Retry-After: 300; webhooks keep coming.
{ "data": { "store_id": 9001, "state": "open" } }Signing
Every request carries X-Laayo-Key, X-Laayo-Timestamp (unix seconds, within 5 minutes of our clock) and X-Laayo-Signature: lowercase hex HMAC-SHA256 with your secret of
timestamp + '.' + METHOD + '.' + path with query + '.' + raw body
METHOD is upper case. The path includes the query exactly as sent. The body is empty for GET. Each signature is accepted once, so identical requests in the same second are refused as 401 replayed: retry after 1 second with a new timestamp.
Webhooks we send you are signed with the same secret over timestamp + '.' + raw body.
1762019400.GET./v1/orders/123.
1762019400.PUT./v1/stores/5/status.{"state":"open"}Errors
A store or order that is not linked to your key is always 404 not_found, so nobody can probe which stores exist.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_body | The body is not valid JSON, or the connection dropped while sending it. |
| 400 | invalid_code | The 6 digit code is wrong, already used, or older than 15 minutes. |
| 401 | unauthorized | A signing header is missing, the key ID is unknown, or the signature does not match. The console request log shows which one. |
| 401 | stale_timestamp | X-Laayo-Timestamp is more than 5 minutes from our clock, or not unix seconds. |
| 401 | replayed | We already accepted a request with this exact signature. Two identical requests in the same second sign the same. |
| 403 | ip_not_allowed | The key has an IP allowlist and the call came from another address. |
| 403 | partner_suspended | Laayo paused this partner account. |
| 403 | sandbox_live_store | A sandbox key tried to link a store that is not a Laayo test store. |
| 404 | not_found | The store or order does not exist, or it is not linked to your key. Both answer the same, on purpose. |
| 404 | no_route | The method and path match no endpoint. |
| 409 | owner_takeover | The owner switched to Laayo’s own controls, for up to 1 hour. Store, item and order writes from the POS are paused. |
| 409 | store_linked_elsewhere | The store already has an active link to another POS. |
| 409 | order_closed | The order is delivered, cancelled or refunded. |
| 409 | accept_first | For example ready before the order was accepted. |
| 409 | rider_confirms_first | In this town a Laayo rider confirms delivery orders before the restaurant. rider_assigned and rider_accepted arrive when a rider takes the delivery offer; that is not the confirmation yet, so accept, preparing and ready still answer this. |
| 409 | order_changed | Someone moved the order at the same moment as your call. |
| 409 | needs_laayo_app | This order needs a step the Partner API does not do in v1. error.reason says which one, for example store_notification_settings_missing: the store has never had its Laayo notification settings set up. |
| 409 | scheduled_not_active | A scheduled order cannot be accepted before its time window. |
| 409 | prep_time_unknown | Busy needs the store’s normal prep time in minutes, and this store’s is set another way. |
| 409 | diff_closed | The menu upload was already reviewed or replaced by a newer one. |
| 413 | body_too_large | The body is over 512 KB. |
| 415 | unsupported_media_type | A body was sent without Content-Type: application/json. |
| 422 | validation_failed | A field is missing, has the wrong type or is out of range, for example more than 500 items. fields names each one. |
| 429 | rate_limited | More than 600 requests in a minute on one key. The answer carries Retry-After in seconds. |
| 429 | too_many_codes | More than 10 wrong link codes in an hour on one key. Retry-After says how long. |
| 500 | server_error | Something broke at Laayo. |
| 501 | not_implemented | Rejecting orders from the POS is coming. In v1 this endpoint always answers 501. |
| 503 | api_paused | Laayo switched the partner API off for a short time, for example during an incident. Every /v1 call answers this with Retry-After: 300. Webhooks keep coming. |
| 503 | unavailable | A dependency at Laayo is briefly unavailable. |
Link a store
Exchange the 6 digit code the owner reads from the Laayo vendor app for access to that store.
Parameters
Errors
More in the guide: /docs/linking
POST /v1/stores/link
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{ "code": "731058", "external_store_id": "TK-01" }{
"data": {
"store_id": 9001,
"store_name": "Laayo Test Kitchen",
"external_store_id": "TK-01",
"link_id": 41,
"linked_at": "2026-11-01T12:30:04+05:45"
}
}Unlink a store
End the link from your side. A busy store goes back to its normal prep time.
Parameters
Errors
More in the guide: /docs/linking
POST /v1/stores/9001/unlink X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{ "data": { "store_id": 9001, "unlinked": true } }Get store status
What customers see right now: open or not and why, the delivery time, busy minutes and owner takeover.
Parameters
Errors
More in the guide: /docs/store-status
GET /v1/stores/9001/status X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": {
"store_id": 9001,
"state": "busy",
"customers_see": "35-50 min",
"customer_view": {
"open_now": true,
"reason": "open",
"delivery_time": "35-50 min",
"closed_until": null,
"closed_until_type": null,
"busy_extra_minutes": 15
},
"owner_takeover": { "active": false, "until": null }
}
}Set store status
Open, close or mark the store busy. open and closed are exactly the restaurant’s own toggle in the Laayo app.
Parameters
Errors
More in the guide: /docs/store-status
PUT /v1/stores/9001/status
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{ "state": "busy", "extra_prep_minutes": 15 }{
"data": {
"store_id": 9001,
"state": "busy",
"customers_see": "35-50 min",
"customer_view": {
"open_now": true,
"reason": "open",
"delivery_time": "35-50 min",
"closed_until": null,
"closed_until_type": null,
"busy_extra_minutes": 15
},
"owner_takeover": { "active": false, "until": null }
}
}Send a heartbeat
Tell Laayo the POS is running. Send one every 60 seconds. After 10 minutes without one, the link shows offline.
Parameters
Errors
More in the guide: /docs/pos-offline
POST /v1/heartbeat
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{ "store_ids": [9001], "pos_version": "nRestro 4.12" }{
"data": {
"server_time": "2026-11-01T12:31:00+05:45",
"stores": [ { "store_id": 9001, "online": true } ],
"next_heartbeat_seconds": 60
}
}Set items sold out or back
Mark up to 500 items sold out or available in one call, by your ID or by Laayo item ID. back_at brings an item back by itself.
Parameters
Errors
More in the guide: /docs/sold-out
PUT /v1/stores/9001/items/availability
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{ "items": [
{ "external_item_id": "MOMO-C-10", "available": false,
"back_at": "2026-11-02T10:00:00+05:45" }
] }{
"data": {
"updated": 1,
"items": [
{ "ref": "MOMO-C-10", "laayo_item_id": 55102, "result": "updated",
"back_at": "2026-11-02T10:00:00+05:45" }
]
}
}Get the item map
Your item IDs and the Laayo items they point to, for this store.
Parameters
Errors
More in the guide: /docs/item-mapping
GET /v1/stores/9001/item-map X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": {
"store_id": 9001,
"mappings": [
{ "external_item_id": "MOMO-C-10", "laayo_item_id": 55102 },
{ "external_item_id": "MOMO-V-10", "laayo_item_id": 55101 }
]
}
}Map items
Add or change pairs of your item ID and a Laayo item ID, and remove pairs. Send only what changes.
Parameters
Errors
More in the guide: /docs/item-mapping
PUT /v1/stores/9001/item-map
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{
"mappings": [
{ "external_item_id": "MOMO-C-10", "laayo_item_id": 55102 },
{ "external_item_id": "MOMO-V-10", "laayo_item_id": 55101 }
],
"remove": [ "OLD-SKU-7" ]
}{ "data": { "saved": 2, "removed": 1 } }Get an order
One order of a linked store, with items, your item IDs, the masked customer and its timeline.
Parameters
Errors
More in the guide: /docs/receiving-orders
GET /v1/orders/104812 X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": {
"laayo_order_id": "104812",
"laayo_store_id": 9001,
"external_store_id": "TK-01",
"status": "confirmed",
"order_type": "delivery",
"payment_method": "cash_on_delivery",
"payment_status": "unpaid",
"order_amount": 545,
"delivery_charge": 60,
"total_tax": 0,
"created_at": "2026-11-01T12:41:08+05:45",
"scheduled": false,
"schedule_at": null,
"prep_minutes": "15",
"your_order_ref": "KOT 48",
"note": "Less spicy",
"customer": { "name": "Sita", "phone": "+977*******521", "address": "Near Birtamod chowk" },
"rider": { "name": "Ram" },
"items": [
{ "id": 88213, "external_item_id": "MOMO-C-10", "name": "Chicken momo",
"quantity": 2, "price": 180, "variation": null,
"addons": [ { "name": "Extra achar", "quantity": 1, "price": 20 } ], "note": null }
],
"timeline": { "confirmed": "2026-11-01T12:42:30+05:45", "processing": null,
"handover": null, "picked_up": null, "delivered": null, "canceled": null }
}
}Accept an order
The kitchen took the order. Forward only: repeating it, or calling it on an order that is already further, answers 200 with changed false.
Parameters
Errors
More in the guide: /docs/order-status
POST /v1/orders/104812/accept
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{ "prep_minutes": 15, "your_order_ref": "KOT 48" }{
"data": { "order_id": 104812, "action": "accept", "status": "confirmed",
"changed": true, "prep_minutes": 15 }
}Mark preparing
Cooking started. Moves the customer’s live tracking forward.
Parameters
Errors
More in the guide: /docs/order-status
POST /v1/orders/104812/preparing X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": { "order_id": 104812, "action": "preparing", "status": "processing", "changed": true }
}Mark ready
The food is packed. The customer sees it at once, and the answer includes the rider when one is assigned.
Parameters
Errors
More in the guide: /docs/order-status
POST /v1/orders/104812/ready X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": { "order_id": 104812, "action": "ready", "status": "handover", "changed": true,
"rider": { "name": "Ram", "arrived_at_store": false, "eta_minutes": 4 } }
}Reject an order
Rejecting orders from the POS is coming. In v1 this answers 501, and the restaurant denies orders in the Laayo vendor app.
Parameters
Errors
More in the guide: /docs/order-status
POST /v1/orders/104812/reject X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"error": { "code": "not_implemented",
"message": "Rejecting from the POS is not available yet. The restaurant can deny the order in the Laayo app." }
}Poll for events
The same events the webhooks carry, for a POS with no public address. Holds the call open up to wait seconds when nothing is new.
Parameters
Errors
More in the guide: /docs/polling
GET /v1/events?after=5521&wait=25 X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": {
"events": [
{ "event": "order.created",
"event_id": "5521",
"idempotency_key": "order.created:104812",
"occurred_at": "2026-11-01T12:41:11+05:45",
"data": { "laayo_order_id": "104812", "status": "pending", "...": "..." } }
],
"next_cursor": "5521"
}
}Your account
Your key, profile, webhook URL, IP allowlist and whether an old secret is still valid after a rotation.
Parameters
None. Only the three signing headers.
Errors
GET /v1/partner/me X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": {
"partner_id": 7, "name": "nRestro", "key_id": "lpk_8f2c41a9e07b5d13c6a2f904",
"status": "active", "sandbox": true,
"webhook_url": "https://pos.example.com/laayo/webhook",
"rate_limit_per_minute": 600, "linked_stores": 1,
"profile": { "company_name": "nRestro Pvt. Ltd.", "support_phone": "9801234567",
"website": "https://nrestro.example", "logo_url": null, "contact_email": "support@nrestro.example" },
"ip_allowlist": [ "203.0.113.24" ],
"last_used_at": "2026-11-01T12:30:58+05:45",
"previous_secret_valid_until": null,
"created_at": "2026-10-20T10:02:00+05:45"
}
}Update your profile
Company name, the support phone restaurants see, website, logo URL and contact email.
Parameters
Errors
PUT /v1/partner/profile
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{ "support_phone": "9801234567", "website": "https://nrestro.example" }{ "data": { "partner_id": 7, "...": "same as GET /v1/partner/me" } }List linked stores
Every store linked to your key, with online or offline, busy and owner takeover state.
Parameters
None. Only the three signing headers.
Errors
GET /v1/partner/stores X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": [
{ "store_id": 9001, "name": "Laayo Test Kitchen", "external_store_id": "TK-01",
"online": true, "last_heartbeat_at": "2026-11-01T12:31:00+05:45", "offline_since": null,
"pos_version": "nRestro 4.12", "linked_at": "2026-10-31T17:30:04+05:45",
"owner_takeover_until": null, "busy_extra_minutes": null }
]
}Set the webhook URL
Where Laayo POSTs events. https only. Send null to stop webhooks and poll instead.
Parameters
Errors
More in the guide: /docs/webhook-events
PUT /v1/partner/webhook
Content-Type: application/json
X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904
X-Laayo-Timestamp: 1762003860
X-Laayo-Signature: 5f0c2a8e9d41…
{ "url": "https://pos.example.com/laayo/webhook" }{ "data": { "webhook_url": "https://pos.example.com/laayo/webhook" } }Send a test event
Queues a signed test event to your webhook URL. Watch it arrive in the deliveries list.
Parameters
None. Only the three signing headers.
Errors
More in the guide: /docs/webhook-events
POST /v1/partner/webhook/test X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{ "data": { "event_id": "5530", "idempotency_key": "test:7:1762003860" } }List webhook deliveries
Recent events with their delivery status and every attempt.
Parameters
Errors
More in the guide: /docs/retries
GET /v1/partner/webhooks/deliveries X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": [
{ "event_id": "5521", "event": "order.created", "idempotency_key": "order.created:104812",
"store_id": 9001, "status": "delivered", "attempts": 1, "last_status_code": 200,
"last_error": null, "created_at": "2026-11-01T12:41:11+05:45",
"next_attempt_at": null, "delivered_at": "2026-11-01T12:41:12+05:45" }
]
}Rotate your secret
A new secret, shown once. The old one keeps working for 24 hours, so you can roll it out without downtime.
Parameters
None. Only the three signing headers.
Errors
More in the guide: /docs/signing
POST /v1/partner/secret/rotate X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": {
"key_id": "lpk_8f2c41a9e07b5d13c6a2f904",
"secret": "lps_…",
"previous_secret_valid_until": "2026-11-02T12:35:00+05:45",
"note": "Shown once. Store it now; the old secret keeps working for 24 hours."
}
}Go-live checks
The six go-live checks, computed from your sandbox history.
Parameters
None. Only the three signing headers.
Errors
More in the guide: /docs/go-live
GET /v1/partner/go-live X-Laayo-Key: lpk_8f2c41a9e07b5d13c6a2f904 X-Laayo-Timestamp: 1762003860 X-Laayo-Signature: 5f0c2a8e9d41…
{
"data": {
"ready": false,
"passed": "5/6",
"checks": [
{ "id": "signed_request", "label": "Signed requests work", "passed": true,
"passed_at": "2026-10-21T11:04:52+05:45" },
{ "id": "order_flow", "label": "An order taken from accept to ready", "passed": false,
"passed_at": null,
"hint": "Accept, then mark ready, one test order (POST /v1/orders/{id}/accept, /ready)." }
]
}
}Webhook events
Laayo POSTs each event to your webhook URL. Answer 2xx within 10 seconds. A failed delivery waits 30 seconds, 1 minute, 2 minutes and longer, for about 24 hours, then the event is set aside. The same events are available with GET /v1/events.
| Header | Meaning |
|---|---|
| X-Laayo-Event | Event name, for example order.created |
| X-Laayo-Event-Id | Unique per event |
| X-Laayo-Idempotency-Key | Use it to drop duplicates. A retry carries the same key. |
| X-Laayo-Key | Your key ID |
| X-Laayo-Timestamp | Unix seconds when this attempt was signed |
| X-Laayo-Signature | Lowercase hex HMAC-SHA256 of timestamp + '.' + raw body, with your secret |
| X-Laayo-Attempt | 1 on the first try, then 2, 3 and so on |
order.created
A customer placed an order at a linked store. data is the full order, as in GET /v1/orders/{id}. Sandbox test orders carry "test": true and ids from 9000000001 up.
{
"event": "order.created",
"idempotency_key": "order.created:104812",
"occurred_at": "2026-11-01T12:41:11+05:45",
"data": { "laayo_order_id": "104812", "external_store_id": "TK-01", "status": "pending",
"items": [ { "external_item_id": "MOMO-C-10", "quantity": 2, "...": "..." } ], "...": "..." }
}order.updated
The order moved. type says how: accepted, preparing, ready, rider_accepted, rider_assigned, rider_arrived, picked_up, delivered, cancelled, refunded or refund_requested. Includes changes made in the Laayo vendor app. Two quick changes can arrive as one event with the latest status, so read data.status and do not count events.
{
"event": "order.updated",
"type": "rider_arrived",
"idempotency_key": "order.updated:104812:rider_arrived",
"occurred_at": "2026-11-01T12:58:40+05:45",
"data": { "laayo_order_id": "104812", "status": "handover", "rider": { "name": "Ram" }, "...": "..." }
}payout.paid
A payout reached the restaurant. data lists every order in it with Laayo’s commission on each, and your_order_ref when you sent one. You can turn this event off in the console under Webhooks; order events cannot be turned off.
{
"event": "payout.paid",
"idempotency_key": "payout.paid:2210",
"occurred_at": "2026-10-31T16:00:04+05:45",
"data": {
"laayo_store_id": 27, "external_store_id": "DBZ-01", "disbursement_id": 2210,
"payout_channel": "bank", "paid_at": "2026-10-31T16:00:00+05:45",
"gross_amount": 1000, "total_commission": 100, "total_store_amount": 900,
"fee": 0, "tds": 13, "net_payable": 887,
"orders": [ { "laayo_order_id": "104755", "your_order_ref": "KOT 41", "order_amount": 1000,
"admin_commission": 100, "store_amount": 900, "delivery_charge": 0 } ]
}
}test
Sent by Send test event in the console, or POST /v1/partner/webhook/test.
{
"event": "test",
"idempotency_key": "test:7:1762003860",
"occurred_at": "2026-11-01T12:51:00+05:45",
"data": { "message": "Test event from Laayo. Answer 2xx to acknowledge.", "partner_id": 7 }
}