A read-only REST API for orders, menu, tables, inventory, customers and analytics — with real-time webhooks and inbound order channels. Scoped keys, an OpenAPI spec, and a live explorer to try it in your browser.
The ScanSewa Developer API is a read-only REST API that lets partners and your own apps read a restaurant's live data — orders, menu, tables, inventory, customers and sales analytics — plus receive real-time webhooks and push orders in from external channels. Responses are JSON; every request is authenticated with a scoped API key.
Base URL
https://api.scansewa.com/api/public/v1Make your first authenticated call in under a minute.
curl "https://api.scansewa.com/api/public/v1/orders?status=Completed&limit=10" \
-H "Authorization: Bearer ssk_live_…"A successful response is { success: true, … } with HTTP 200. If you get 401, re-check the key; 403 means the key is missing that endpoint's scope.
Pass your key as a Bearer token, or use the x-api-key header. Keys are read-only and scoped to a single restaurant's data — keep them server-side and never ship them in a browser or mobile app.
Authorization: Bearer ssk_live_…x-api-key: ssk_live_…Key hygiene
401 on its next call.Each key is limited to the scopes you grant it. Calling an endpoint outside the key's scopes returns 403. Grant the minimum a given integration needs.
customers:read returns personal data — only grant it to integrations that genuinely need contact details, and handle the data per your privacy obligations.
Every key is limited to 120 requests per minute. Standard RateLimit-* headers are returned on each response; exceeding the cap returns 429.
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 41 // seconds until the window resetsWhen you receive a 429, wait until the window resets (see RateLimit-Reset) before retrying, and back off exponentially on repeated limits. Batch work into paginated list calls rather than many single-item lookups, and prefer webhooks over tight polling.
List endpoints are paginated. Pass ?limit= (1–100, default 50) and ?page= (default 1); each response includes total and hasMore so you know when to stop. Loop until hasMore is false rather than assuming a fixed page count.
limitItems per page, 1–100. Default 50.pagePage number, starting at 1.statusOrders only — Pending, Confirmed, Delivered, Completed, Cancelled.since / untilISO dates — filter by creation time (orders, analytics).qCustomers — search by name or phone.inStock / lowStock / active / occupiedBoolean filters on catalog, inventory, tables.// Walk every page of a list endpoint
async function fetchAll(path, key) {
const out = [];
let page = 1;
while (true) {
const res = await fetch(`${path}?limit=100&page=${page}`, {
headers: { Authorization: `Bearer ${key}` },
});
const body = await res.json();
out.push(...body.data);
if (!body.hasMore) break; // stop when the API says there's no more
page += 1;
}
return out;
}Errors use standard HTTP status codes with a JSON { success: false, message } body — read message for a human-readable reason.
{
"success": false,
"message": "Key lacks the required scope: analytics:read"
}Treat 401/403 as configuration problems (fix the key or scope, don't retry blindly). Retry 429 after the reset window, and retry 5xx with exponential backoff.
All endpoints are GET and relative to the base URL above. Query parameters are listed with each endpoint below.
/ordersList orders, newest first./orders/:idFetch a single order by id./menuList menu items./menu/:idFetch a single menu item./categoriesList menu categories./offersList offers / promotions./tablesList tables with occupancy./inventoryList stock items./customersList & search customers (contains personal data)./analytics/summaryRevenue, by-status, by-source and top items for a date range.{
"success": true,
"page": 1,
"limit": 50,
"total": 128,
"hasMore": true,
"data": [
{
"id": "665f…",
"status": "Completed",
"paymentStatus": "Paid",
"items": [
{ "menuItemId": "M-101", "name": "Margherita", "quantity": 2, "unitPrice": 450, "itemTotal": 900 }
],
"subtotal": 900,
"grandTotal": 1017,
"table": "T4",
"source": "dine-in",
"customerName": "Asha",
"createdAt": "2026-06-11T08:20:00.000Z"
}
]
}{
"success": true,
"data": {
"orders": 128,
"revenue": 184560,
"averageOrderValue": 1441.88,
"byStatus": [ { "status": "Completed", "count": 110, "revenue": 170200 } ],
"bySource": [ { "source": "dine-in", "count": 80, "revenue": 120300 } ],
"topItems": [ { "menuItemId": "M-101", "name": "Margherita", "quantity": 212, "revenue": 95400 } ]
}
}Every list and detail endpoint returns the objects below inside data. Fields marked optional may be null or absent. New fields may be added over time — see Versioning.
OrderidstringUnique order id.statusstringPending · Confirmed · Delivered · Completed · Cancelled.paymentStatusstring ?Paid, Unpaid or Partial when tracked.itemsOrderItem[]Line items — see OrderItem below.subtotalnumberSum of line items before tax, discounts and charges.grandTotalnumberFinal payable amount for the order.tablestring ?Table name/number for dine-in orders.sourcestring ?dine-in, QR, online or an inbound channel name.customerNamestring ?Customer name if captured.customerPhonestring ?Customer phone if captured (PII).notesstring ?Free-text order note.createdAtstring (ISO)When the order was placed.updatedAtstring (ISO)Last time the order changed.OrderItemmenuItemIdstringStable id of the menu item ordered.namestringItem name at time of order.quantityintegerUnits ordered.unitPricenumberPrice per unit at time of order.itemTotalnumberquantity × unitPrice.MenuItemidstringDatabase id of the menu item.menuItemIdstringStable id used across orders and channels.namestringDisplay name.pricenumberCurrent price.categoryIdstring ?Category the item belongs to.categoryNamestring ?Resolved category name.categoryTypestring ?e.g. Food, Beverage.foodTypestring ?Veg / Non-veg / Egg where set.descriptionstring ?Item description.imageUrlstring ?Public image URL.isInStockbooleanWhether the item is currently sellable.CategoryidstringCategory id.namestringCategory name.typestring ?Grouping type.descriptionstring ?Category description.isActivebooleanWhether the category is live.imageUrlstring ?Public image URL.OfferidstringOffer id.namestringOffer name.offerTypestring ?Combo, discount or bundle type.mrpnumberOriginal price before the offer.pricenumberOffer price.descriptionstring ?Offer description.isActivebooleanWhether the offer is running now.startDatestring (ISO) ?When the offer starts.endDatestring (ISO) ?When the offer ends.TableidstringTable id.nameOrNumberstringHuman label, e.g. "T4" or "Terrace 2".capacityinteger ?Seats.tableTypestring ?e.g. Regular, Booth.tableAreastring ?Floor / zone.occupiedbooleanWhether the table has a live order.InventoryItemidstringStock item id.namestringIngredient / stock name.quantitynumberQuantity on hand.unitstring ?kg, ltr, pcs, etc.pricePerUnitnumber ?Cost per unit.lowStockThresholdnumberLevel at which the item flags low.lowStockbooleanTrue when quantity ≤ threshold.categoryIdstring ?Inventory category.CustomeridstringCustomer id.namestring ?Customer name (PII).phonestring ?Phone number (PII).emailstring ?Email address (PII).addressstring ?Saved address (PII).createdAtstring (ISO)When the customer was first seen.Try any endpoint against the live API with one of your keys, then copy the request as cURL, JavaScript or Python.
Paste one of your API keys and call the real API — nothing is stored, the key stays in your browser.
Create a key in your ERP dashboard → Developer API.
https://api.scansewa.com/api/public/v1/orderscurl "https://api.scansewa.com/api/public/v1/orders" \
-H "Authorization: Bearer YOUR_API_KEY"Register an endpoint in your ERP dashboard to receive signed event POSTs instead of polling. Each delivery carries X-ScanSewa-Event, X-ScanSewa-Delivery and X-ScanSewa-Signature headers. Failed deliveries retry with backoff (30s → 2h, 6 attempts).
{
"id": "evt_9f2c…", // unique — use for idempotency
"type": "order.created",
"createdAt": "2026-06-11T08:20:00.000Z",
"data": { "id": "665f…", "status": "Pending", "grandTotal": 1017, "items": [ … ] }
}import crypto from "crypto";
// In your webhook route (any language / framework), read the RAW request body
// and the signature header, then verify before trusting the payload.
function verifyScanSewaWebhook(rawBody, signatureHeader) {
const parts = Object.fromEntries( // "t=...,v1=..."
(signatureHeader || "").split(",").map(p => p.split("="))
);
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET) // whsec_...
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1 || "")
);
}
// if verifyScanSewaWebhook(rawBody, header) is true:
// const event = JSON.parse(rawBody); // { id, type, createdAt, data }
// handle event.type, and dedupe on event.idRespond 2xx within 10s to acknowledge, and dedupe on event.id — the same event may arrive more than once. Do slow work (writes, emails) after you've acknowledged.
Channels let an external system — a delivery aggregator or your own app — push orders into ScanSewa. They land in the kitchen display, billing and reports like any other order. Create a channel in your ERP dashboard to get a signing secret and a unique URL, then POST signed orders:
POST https://api.scansewa.com/api/inbound/<channelId>/ordersimport crypto from "crypto";
const secret = process.env.SCANSEWA_CHANNEL_SECRET; // chsec_...
const body = JSON.stringify(order);
const t = Math.floor(Date.now() / 1000);
const v1 = crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
await fetch("https://api.scansewa.com/api/inbound/<channelId>/orders", {
method: "POST",
headers: { "Content-Type": "application/json", "X-ScanSewa-Signature": `t=${t},v1=${v1}` },
body,
});Items resolve to your menu by id, a mapping table or name. To get status changes back, register an order.updated webhook.
Common integrations, each as a short starting point. Combine endpoints and webhooks to fit your workflow.
Pull each day’s completed, paid orders into your books or ERP.
Show revenue, order count, top items and channel split for any range.
Keep a website, kiosk or delivery listing in step with the live menu.
Notify a channel when any ingredient drops below its threshold.
Let an external app or delivery channel drop orders straight into the kitchen.
Prefer webhooks to polling
Subscribe to order events for near-real-time updates instead of polling on a tight loop — it’s faster and stays well under the rate limit.
Always paginate to the end
Loop with ?page= until hasMore is false. Never assume a single page holds every record.
Make handlers idempotent
Dedupe on order id and webhook event.id — the same order or event can be delivered more than once.
Back off on 429 / 5xx
Respect RateLimit-Reset, then retry with exponential backoff. Don’t hammer a failing endpoint.
Keep keys server-side
Call the API from your backend. Never embed a key in a browser, mobile app or public repo.
Minimise PII you store
Only request customers:read when needed, and store contact data no longer than your use requires.
The API is versioned in the path — the current stable version is v1. We treat these as non-breaking and may ship them without a new version: adding a new endpoint, adding a new field to a response, or adding a new optional query parameter or webhook event.
Build tolerantly — ignore fields you don't recognise rather than failing on them, and don't depend on the ordering of array results unless documented. Any breaking change would ship under a new version path (e.g. /v2), and we'd give advance notice and a migration window before retiring an older one.
The whole API is described by an OpenAPI 3.0 spec — import it into Postman, Insomnia or Swagger UI, or generate a typed client in your language of choice. No key is needed to fetch it.
https://api.scansewa.com/api/public/v1/openapi.json Download openapi.jsonGenerate a client with your usual toolchain (for example, an OpenAPI generator) and you get typed models for every object in the Response objects reference above.
Ready to build?
Create an API key from your ERP dashboard in under a minute.
Notable, backwards-compatible additions to the Developer API, newest first.
Stuck on an integration, or need an endpoint we don't expose yet? We're happy to help.
Found a security issue? See our responsible-disclosure program. For data handling, read the Privacy Policy.
Create a scoped API key from your ScanSewa dashboard and connect the tools you already use — or let us help you build a custom integration.