Requests and responses
The JSON envelope every response shares, pagination, money, times, caching and versions.
On this page
Requests
- Every call is HTTPS. Reads are
GETwith their options in the query string. - List endpoints refuse a query parameter they do not know with
400 invalid_request, so a typo such astown=nairobifails loudly instead of being ignored. - If a parameter is repeated (
?city=nairobi&city=mombasa), the first one is used. - Writes, when they ship, take a JSON body with
Content-Type: application/jsonand anIdempotency-Keyheader.
The response envelope
Every response, success or failure, is a JSON object with ok first. Check it before anything else.
A list
{
"ok": true,
"data": {
"items": [ { "id": "9b2f6c1e-…", "trading_name": "Sokoni Grill House", "…": "…" } ],
"next_cursor": "MjAyNi0xMC0yMVQwODoxNTowMCswMDowMHw5YjJmNmMxZS00YTdk…"
},
"meta": { "api_version": "v1", "limit": 25 }
}One object
{
"ok": true,
"data": {
"merchant": { "id": "9b2f6c1e-…", "trading_name": "Sokoni Grill House", "…": "…" },
"catalogue": [ { "name": "Nyama choma platter", "price_kes": 1450, "…": "…" } ]
},
"meta": { "api_version": "v1" }
}An error
{
"ok": false,
"error": {
"code": "invalid_request",
"message": "Unrecognized key(s) in object: 'town'",
"fields": { "_": "Unrecognized key(s) in object: 'town'" }
},
"meta": { "api_version": "v1" }
}meta.api_version is always v1 on these endpoints. On a list, meta.limit is the page size the server applied. See Errors for every error code.
Pagination
Lists are paged with a cursor, never an offset. Each list has a fixed order (merchants by when they went live, newest first; orders by when they were placed, newest first; experiences by start time, soonest first), and next_cursor marks where the page ended. Pass it back as cursor to get the next page, unchanged; it is opaque and its contents may change. When next_cursor is null, there are no more pages.
Pages hold 25 by default; ask for 1 to 100 with limit. Each page starts strictly after the last row of the one before, so a row is never returned twice or skipped, even when new rows arrive while you page. A cursor the endpoint did not issue is refused with 400 invalid_request.
// Follow next_cursor until it is null.
let cursor = null;
do {
const url = new URL('https://api.nexgapp.com/v1/orders');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { 'X-Api-Key': process.env.NEXG_API_KEY } });
const body = await res.json();
if (!body.ok) throw new Error(`${res.status} ${body.error.code}`);
await saveOrders(body.data.items); // upsert by order_id
cursor = body.data.next_cursor;
} while (cursor);Money
Amounts are integers, never decimals, so they add up exactly. Two units are in use, and the field name always says which.
| Field | Unit | Example |
|---|---|---|
price_kes | Whole Kenyan shillings | 1450 is KES 1,450 |
total_cents | Cents | 161625 is KES 1,616.25 |
pay_on_delivery_cap_kes | Whole Kenyan shillings | 5000 is KES 5,000 |
The currency is always KES. A price on an order is fixed when the order is placed, and NexG works it out itself: no request ever states a price.
Times and ids
- Times are ISO 8601 with an offset, and NexG sends them in UTC, for example
2026-11-10T09:42:17.204+00:00. Show them to people in Nairobi time (UTC+3). - When you send a time, include its offset:
2026-11-14T06:30:00+03:00. - Ids are UUIDs. Orders also have a
referencesuch asNX-261110-0003for people to read. Store both.
Caching
Public data may come from a shared cache, so a change at NexG, such as a merchant closing early, can take a minute or more to reach you. Your own orders and every error are never cached.
| Endpoints | Cache-Control | Fresh for |
|---|---|---|
| Merchants, a merchant, experiences | public, s-maxage=60, stale-while-revalidate=300 | 60 seconds |
| Zones, services | public, s-maxage=300, stale-while-revalidate=3600 | 5 minutes |
| Your orders, every error | no-store | Never cached |
Request ids
Every JSON error carries an X-Request-Id header, unique to that response. Log it with the failure. NexG logs the id of every server error (5xx) with what went wrong, so quoting it is the fastest way to have a failure looked into.