One REST API over four UAE food-delivery apps — Careem Food, Talabat, InstaShop and Deliveroo. Find restaurants near any UAE location and fetch their full menu (prices, images, ratings, offers). You pick the app with fda_name; the endpoints, parameters, auth and response shapes are identical across all four.
https://fda.nenodata.comhttps://fda.nenodata.com/docs (Swagger) · https://fda.nenodata.com/redoc (reference)Every /api/v1/* call needs your API key in a header:
X-API-Key: YOUR_KEY
Missing or wrong key → 401 unauthorized.
Check your usage: sign in with your login ID and password at https://fda.nenodata.com/portal to see requests per day, this month's total, each call's outcome and a CSV export. You can change your password there.
Every request body must also include fda_name (the app to query). Supported values: "careem", "talabat", "instashop" and "deliveroo" (call GET /api/v1/providers for the current list). The endpoints, parameters, auth and response shapes are identical across providers — only the fda_name changes.
fda_name |
What it covers | id you pass to /menu |
Not published by that app |
|---|---|---|---|
careem |
Careem Food across the UAE | number | — |
talabat |
Talabat across the UAE | number | restaurant phone; from_price for option-priced items |
instashop |
InstaShop's whole marketplace — restaurants plus grocery, pharmacy, bakery, flowers | string (opaque id) | — |
deliveroo |
Deliveroo restaurants in Dubai, Abu Dhabi, Sharjah, Al Ain, Ajman & RAK | string (slug, e.g. "joe-and-the-juice-galleria-mall") |
restaurant phone; cuisines and delivery fee on listings (they come with the menu instead) |
Talabat ("talabat"): works for any UAE area (by area name or latlng) and needs no upstream token. Response fields match Careem, with two exceptions Talabat's public data doesn't expose: restaurant phone and, for items with selectable options, the per-item from_price (those show "Price on Selection"). Everything else — ratings, cuisines, offers, images, distance, and full flat-priced menus — is the same.
InstaShop ("instashop"): works for any UAE area (by area name or latlng). InstaShop is a marketplace of shops across verticals — restaurants, grocery, pharmacy, bakery, flowers and more — so /restaurants returns every nearby shop (each with cuisines, rating, phone, delivery fee, logo and coordinates); filter client-side if you only want food. Each shop's id is an opaque string — pass it straight to /menu, which returns the full catalog: categories, items and prices. (No token or extra parameters needed; the service handles InstaShop's regional requirements for you.)
Deliveroo ("deliveroo"): works for any UAE location (by area name or latlng) across Dubai, Abu Dhabi, Sharjah, Al Ain, Ajman and Ras Al Khaimah, and needs no upstream token. Each restaurant's id is a slug (e.g. "joe-and-the-juice-galleria-mall") — pass it straight to /menu. Listings are restaurants only (Deliveroo's grocery and convenience partners are filtered out) and return name, rating, distance, ETA, offers and image; cuisines and the delivery fee come with the menu instead (Deliveroo does not put them on the listing), and restaurant phone is not published at all. Note that Deliveroo shows a platform-wide "Spend AED 50, Get free delivery" badge on most restaurants, so has_offer is usually true — use discount_percentage to find real discounts. Add "full": true to get the location's complete listing (thousands of stores) instead of the ~80-restaurant discovery feed.
The workflow never changes. Same endpoints, same request fields,
same response shape, same error codes, for every app — swap fda_name
and nothing else moves:
GET /areas → POST /restaurants → take an `id` → POST /menu
What differs is only what each app publishes. Nothing below is a limitation of this API — it is data the app itself does not expose.
| Feature | careem | talabat | instashop | deliveroo |
|---|---|---|---|---|
/restaurants, /menu, /offers, /areas |
yes | yes | yes | yes |
area / latlng / pagination / full |
yes | yes | yes | yes |
id type |
number | number | string | string (slug) |
| Cuisines on the listing | yes | yes | yes | no — on the menu instead |
| Delivery fee on the listing | yes | yes | yes | no — on the menu instead |
| Area name on the listing | yes | no | yes | yes |
Restaurant phone |
yes | no | yes | no |
| Restaurant coordinates | yes | yes | yes | yes |
strict_area |
yes | no — no area field | yes | yes |
min_discount (a % filter) |
yes | no — no % published | no — no % published | yes |
from_price for option-priced items |
yes | no | no | yes |
Non-food shops in /restaurants |
— | — | yes — grocery, pharmacy… | — filtered out |
Three of those are worth a sentence each, because they surprise people:
min_discount only bites on careem and deliveroo. It
filters on a percentage, and the other two never publish one. Measured on one Dubai
area: careem 195 of 200 offers carry a numeric %, deliveroo 61 of 200,
talabat 0 of 193 (every offer reads "Super Saver"),
instashop 0 of 79 (every one reads "Deal"). Their offers
are real — they just have no rate attached, so any min_discount
value returns an empty list. To find any offer on those two, call
/offers without min_discount, or read
has_offer / offers[] from a normal
/restaurants response.strict_area returns nothing on talabat, whose listing
API carries no area name at all, so there is nothing to match a requested area
against.has_offer is almost always true on
deliveroo, because it badges most restaurants with a platform-wide
free-delivery voucher. Sort by discount_percentage instead if you want
genuine price cuts.| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/restaurants |
Restaurants in an area (paginated) |
| POST | /api/v1/offers |
Only restaurants with active offers |
| POST | /api/v1/restaurants/all |
Complete stored catalog — every restaurant, instant (Careem) |
| POST | /api/v1/menu |
Full menu for one restaurant |
| GET | /api/v1/areas |
Supported area names |
| GET | /api/v1/providers |
Supported apps (fda_name values) |
| GET | /health |
Health check (no key needed) |
POST /api/v1/restaurants
Request body:
| Field | Required | Description |
|---|---|---|
fda_name |
✅ | "careem", "talabat", "instashop" or "deliveroo" |
area |
one of these | Area name, e.g. "Al Barsha" (see /api/v1/areas) |
latlng |
one of these | Exact coordinates "lat,lng" |
zipcode |
one of these | Postal hint (best-effort) |
full |
optional | true = the app's complete listing for the location (hundreds to thousands), instead of its short discovery feed. Slower on the first call, then cached. Default false. |
strict_area |
optional | true = only restaurants that truly belong to that area (no neighbours). Best paired with full. Default false. |
page |
optional | Page number, starts at 1. Default 1. |
page_size |
optional | Results per page (1–200). Omit = all in one page. |
curl -X POST https://fda.nenodata.com/api/v1/restaurants \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_KEY" \
-d '{"fda_name":"careem","area":"Al Barsha","strict_area":true,"page":1,"page_size":20}'
Response (trimmed):
{
"provider": "careem",
"location": { "lat": "25.11280", "lng": "55.19630", "source": "preset" },
"count": 20,
"pagination": { "page": 1, "page_size": 20, "total": 34,
"total_pages": 2, "has_next": true, "has_prev": false },
"restaurants": [
{
"id": 922373,
"name": "JJ Chicken",
"rating": 4.8,
"reviews_count": 7306,
"cuisines": ["Lebanese", "Sandwiches"],
"area": "Al Barsha 1",
"distance": "(1.5 km)",
"distance_km": 1.5,
"eta": "30 - 40 mins",
"delivery_fee": 9.9,
"currency": "AED",
"price_level": 2,
"careem_plus": true,
"image_url": "https://.../logo.jpg"
}
]
}
Use each restaurant's id to fetch its menu.
Coverage: by default you get the app's ranked discovery feed (~60 on Careem, ~70-150
on Deliveroo, which varies its feed). For every restaurant serving the location, add "full": true —
Bur Dubai returns ~1,180 on Careem and a Dubai coordinate returns ~3,400 on
Deliveroo. Combine with page/page_size to page through the full set, and with
strict_area for a clean per-area list.
POST /api/v1/offers — same inputs as /restaurants, but returns only
restaurants that currently have an active offer/promotion. It scans the
location's complete listing (not just the ~60 feed), so offer-running chains are
included.
| Field | Description |
|---|---|
fda_name, area/latlng/zipcode |
same as /restaurants |
min_discount |
optional — keep only offers of at least this %, e.g. 30 |
page, page_size, strict_area |
same as /restaurants |
curl -X POST https://fda.nenodata.com/api/v1/offers \
-H "Content-Type: application/json" -H "X-API-Key: YOUR_KEY" \
-d '{"fda_name":"careem","area":"downtown","min_discount":30,"page_size":50}'
Works for every provider. Each restaurant includes offer fields:
{
"name": "Zaatar W Zeit",
"has_offer": true,
"offer_text": "30% off on select items",
"offers": ["30% off — On Select items"],
"discount_percentage": 30
}
Important — how offers work:
- discount_percentage / offer_text are the app's advertised headline. An
offer like "30% off on select items" or "up to 50% off" applies to some
items, not necessarily the whole menu.
- Use these for filtering/sorting offers. For exact, per-dish pricing, call
/api/v1/menu — it returns each item's original_price and
discount_percentage, plus restaurant-level promotions with min_basket_value,
cap, and valid_until.
- min_discount matches on the headline %, so it may include "up to X%" offers.
- It only works on careem and deliveroo. talabat and instashop publish offer badges ("Super Saver", "Deal") with no percentage, so any min_discount returns an empty list there — omit it and filter on has_offer / offers[] instead.
POST /api/v1/restaurants/all — returns the complete list of every
Careem restaurant across the UAE (currently ~15,000), served instantly from our
database. Use this when you want the whole catalog, not a single location.
The catalog is refreshed by a scheduled crawl (weekly), so responses are fast and
carry no rate-limit risk. Each response includes a crawled_at timestamp (and a
stale flag) so you know how fresh the data is. For live, real-time data for one
specific location, use POST /api/v1/restaurants instead.
| Field | Description |
|---|---|
fda_name |
required — "careem" (only Careem has a stored catalog today) |
page, page_size |
pagination, in the JSON body — same as /restaurants. Omit page_size for everything in one response. |
has_offer |
optional — true keeps only restaurants with an active offer |
min_discount |
optional — keep only offers of at least this %, e.g. 30 |
area |
optional — returns the complete list of restaurants for that area (the same set POST /restaurants with full:true returns for it, but instant). Area names are alias-aware, e.g. "al_barsha", "marina"; see GET /api/v1/areas. |
# one page of 100
curl -X POST https://fda.nenodata.com/api/v1/restaurants/all \
-H "Content-Type: application/json" -H "X-API-Key: YOUR_KEY" \
-d '{"fda_name":"careem","page":1,"page_size":100}'
# only restaurants with 30%+ offers
curl -X POST https://fda.nenodata.com/api/v1/restaurants/all \
-H "Content-Type: application/json" -H "X-API-Key: YOUR_KEY" \
-d '{"fda_name":"careem","has_offer":true,"min_discount":30,"page_size":100}'
# only restaurants in one area
curl -X POST https://fda.nenodata.com/api/v1/restaurants/all \
-H "Content-Type: application/json" -H "X-API-Key: YOUR_KEY" \
-d '{"fda_name":"careem","area":"al_barsha","page_size":100}'
The response is the same shape as /restaurants, plus catalog fields:
{
"provider": "careem",
"source": "stored_catalog",
"crawled_at": "2026-09-21T19:15:41Z",
"stale": false,
"count": 100,
"pagination": { "page": 1, "page_size": 100, "total": 15394, "total_pages": 154, "has_next": true, "has_prev": false },
"restaurants": [ /* … */ ]
}
To pull the whole catalog, loop page (1, 2, 3, …) with a fixed
page_size until pagination.has_next is false. Paginating is recommended over
omitting page_size, since the full set is a large (multi-MB) response.
POST /api/v1/menu
| Field | Required | Description |
|---|---|---|
fda_name |
✅ | "careem", "talabat", "instashop" or "deliveroo" |
restaurant_id |
✅ | The id from the restaurants call — a number on Careem/Talabat, a string on InstaShop and Deliveroo |
area / latlng |
optional | Delivery location (affects availability) |
curl -X POST https://fda.nenodata.com/api/v1/menu \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_KEY" \
-d '{"fda_name":"careem","restaurant_id":922373}'
Response (trimmed):
{
"provider": "careem",
"restaurant_id": "922373",
"restaurant_name": "JJ Chicken",
"logo_url": "https://.../logo.jpg",
"banner_url": "https://.../banner.jpg",
"rating": 4.8,
"reviews_count": 7306,
"cuisine": "Lebanese, Sandwiches",
"area": "Motor City",
"lat": 25.0476,
"lng": 55.2467,
"phone": "+971600545554",
"delivery_fee": 9.9,
"eta": "40 - 50 mins",
"deliverable": true,
"currency": "AED",
"category_count": 24,
"item_count": 134,
"categories": [
{
"name": "Bestsellers",
"items": [
{
"id": "4992997791",
"name": "Grilled Chicken Breast Platter",
"description": "Two pieces of grilled chicken breast ...",
"price": "AED 60",
"price_value": 60.0,
"from_price": null,
"original_price": null,
"image_url": "https://.../item.png",
"available": true
}
]
}
]
}
Good to know:
- price_value is the number; price is the display text.
- Items priced by choice show price: "Price on Selection" with a from_price (cheapest option).
- If deliverable is false, the restaurant is out of range for that location and prices may be absent — query with an area/latlng near the restaurant.
Offers in the menu response:
- has_offer + promotions[] — restaurant-level offers, each with text,
percentage, min_basket_value (minimum order), cap (max discount), and
valid_until (expiry).
- Per item: original_price and discount_percentage are set when that dish is
discounted — this is the exact, authoritative per-item pricing.
⚠️ The restaurant's headline offer is not the price you pay. A real example: Zaatar W Zeit advertises "30% off on select items", so /offers reports discount_percentage: 30 — but its menu has 7 discounted items out of 146.
/restaurants & /offers |
/menu |
|
|---|---|---|
discount_percentage means |
the advertised banner | that dish's real discount |
| Use it for | filtering and sorting | quoting a price |
So: shortlist with /offers, then read actual prices from /menu. If you show prices to an end user, take them from /menu — quoting the headline would have been wrong for 139 of those 146 dishes. This applies to every app.
GET /api/v1/areas?fda_name=careem
curl "https://fda.nenodata.com/api/v1/areas?fda_name=careem" -H "X-API-Key: YOUR_KEY"
{
"provider": "careem",
"count": 47,
"areas": [
{ "name": "al_barsha", "label": "Al Barsha", "aliases": [], "lat": "25.11280", "lng": "55.19630" }
]
}
Pass the name value as area. fda_name defaults to careem here; pass ?fda_name=talabat (or instashop / deliveroo) for that app's list. Covers Dubai, Abu Dhabi, Al Ain, Sharjah, Ajman & RAK (filter with ?emirate=abu_dhabi). Any other UAE area also works (geocoded).
All errors share the same shape:
{ "error": "restaurant_not_found", "detail": "restaurant_id=999 not found" }
| HTTP | error |
Meaning |
|---|---|---|
| 401 | unauthorized |
Missing/invalid API key |
| 400 | unknown_provider |
fda_name not supported |
| 422 | (validation) | Missing/invalid body field |
| 404 | location_not_found |
Area/zipcode couldn't be resolved |
| 404 | restaurant_not_found |
Unknown restaurant id |
| 502 | upstream_auth_failed |
Upstream token expired — contact the provider |
| 502 | upstream_error |
Temporary upstream issue — retry |
/areas → pick an area → /restaurants → take an id → /menu.fda_name. Ids are not interchangeable between apps — always take the id from that app's own /restaurants response."strict_area": true.strict_area is on.