Food Delivery API — Usage Guide (Careem, Talabat, InstaShop & Deliveroo)

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.


1. Authentication

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.


What is the same, and what differs

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:

2. Endpoints at a glance

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)

3. Get restaurants in an area

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.


3b. Get only restaurants with offers

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.


3c. Get ALL restaurants (stored catalog)

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.

4. Get a restaurant's menu

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.


5. List supported areas

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).


6. Errors

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

7. Quick tips