Grab APIfood.grab.com ↗
Search GrabFood restaurants by location and retrieve full menus with prices, categories, and modifier options via 2 structured API endpoints.
What is the Grab API?
The GrabFood API provides 2 endpoints to query restaurants and menus across GrabFood-supported markets in Southeast Asia. Use search_restaurants to find and page through nearby restaurants with ratings, delivery fees, and estimated delivery times, then call get_merchant_menu with a merchant ID to retrieve every menu item—including prices, categories, availability flags, and modifier options—for that restaurant.
curl -X GET 'https://api.parse.bot/scraper/8c488b01-45f9-416b-a0f2-11dd465bf552/search_restaurants?KEYWORD=burger&keyword=burger&PAGE_SIZE=3&page_size=5&GUEST_TOKEN=REDACTED_TOKEN&guest_token=REDACTED_TOKEN' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace food-grab-com-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""
GrabFood Indonesia API Client
Search restaurants and retrieve full menu listings from GrabFood Indonesia.
Get your API key from: https://parse.bot/settings
"""
import os
import requests
from typing import Optional, Any, Dict, List
class ParseClient:
"""Client for interacting with the GrabFood Indonesia API via Parse.bot."""
def __init__(self, api_key: Optional[str] = None):
"""Initialize the Parse API client.
Args:
api_key: API key for authentication. If not provided, reads from PARSE_API_KEY env var.
"""
self.base_url = "https://api.parse.bot"
self.scraper_id = "8c488b01-45f9-416b-a0f2-11dd465bf552"
self.api_key = api_key or os.getenv("PARSE_API_KEY")
if not self.api_key:
raise ValueError("API key must be provided or set in PARSE_API_KEY environment variable")
def _call(self, endpoint: str, method: str = "POST", **params) -> Dict[str, Any]:
"""Make an API call to the Parse.bot scraper.
Args:
endpoint: The endpoint name (e.g., 'search_restaurants')
method: HTTP method ('GET' or 'POST')
**params: Parameters to pass to the endpoint
Returns:
The JSON response from the API
Raises:
requests.exceptions.RequestException: If the API call fails
"""
url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
headers = {
"X-API-Key": self.api_key,
"Content-Type": "application/json"
}
if method.upper() == "GET":
response = requests.get(url, headers=headers, params=params)
else:
response = requests.post(url, headers=headers, json=params)
response.raise_for_status()
return response.json()
def search_restaurants(
self,
guest_token: str,
latlng: str = "-6.1767352,106.826504",
keyword: str = "",
offset: int = 0,
page_size: int = 32,
country_code: str = "ID"
) -> Dict[str, Any]:
"""Search for GrabFood restaurants by location and optional keyword.
Args:
guest_token: Guest authentication token from browser sessionStorage
latlng: Latitude,longitude for location-based search
keyword: Search keyword to filter restaurants
offset: Pagination offset
page_size: Number of results per page
country_code: Country code (default: ID for Indonesia)
Returns:
Dictionary containing total_count, offset, page_size, and restaurants list
"""
return self._call(
"search_restaurants",
method="GET",
guest_token=guest_token,
latlng=latlng,
keyword=keyword,
offset=offset,
page_size=page_size,
country_code=country_code
)
def get_merchant_menu(
self,
guest_token: str,
merchant_id: str,
latlng: str = "-6.1767352,106.826504"
) -> Dict[str, Any]:
"""Get the full menu for a specific GrabFood merchant/restaurant.
Args:
guest_token: Guest authentication token from browser sessionStorage
merchant_id: Merchant ID from search results
latlng: Latitude,longitude for location context
Returns:
Dictionary containing merchant info and menu_items list
"""
return self._call(
"get_merchant_menu",
method="GET",
guest_token=guest_token,
merchant_id=merchant_id,
latlng=latlng
)
def format_price(price_str: str) -> float:
"""Convert price display string to numeric value for comparison."""
return float(price_str.replace("Rp", "").replace(".", "").strip())
def analyze_restaurant_menu(client: ParseClient, guest_token: str, restaurant: Dict[str, Any]) -> None:
"""Analyze and display menu analysis for a single restaurant."""
print(f"\n{'='*80}")
print(f"📊 MENU ANALYSIS: {restaurant['name']}")
print(f"{'='*80}")
menu_data = client.get_merchant_menu(
guest_token=guest_token,
merchant_id=restaurant['id']
)
# Basic info
print(f"📍 Location: {menu_data['address']}")
print(f"🍽️ Cuisine: {menu_data['cuisine']}")
print(f"📋 Total Menu Items: {menu_data['total_menu_items']}")
# Availability stats
available_count = sum(1 for item in menu_data['menu_items'] if item['available'])
top_sellers = [item for item in menu_data['menu_items'] if item['top_seller']]
print(f"\n📊 Statistics:")
print(f" • Available items: {available_count}/{len(menu_data['menu_items'])}")
print(f" • Top sellers: {len(top_sellers)}")
# Price analysis
if menu_data['menu_items']:
prices = []
discounted_items = []
for item in menu_data['menu_items']:
if item['price_minor_unit']:
prices.append(item['price_minor_unit'])
if item['discounted_price_display']:
discounted_items.append(item)
if prices:
min_price = min(prices) / 100_000
max_price = max(prices) / 100_000
avg_price = sum(prices) / len(prices) / 100_000
print(f" • Price range: Rp {min_price:.0f}K - Rp {max_price:.0f}K")
print(f" • Average price: Rp {avg_price:.0f}K")
print(f" • Items on sale: {len(discounted_items)}")
# Display top sellers
if top_sellers:
print(f"\n🔥 Top Sellers (showing first 3):")
for item in top_sellers[:3]:
price = item['price_display']
if item['discounted_price_display']:
price = f"{item['discounted_price_display']} (was {item['price_display']})"
print(f" • {item['name']}: {price}")
# Show categories and sample items
print(f"\n📂 Menu Categories and Samples:")
items_by_category = {}
for item in menu_data['menu_items']:
category = item['category']
if category not in items_by_category:
items_by_category[category] = []
items_by_category[category].append(item)
for category in menu_data['menu_categories'][:5]: # Show first 5 categories
if category in items_by_category:
print(f"\n {category} ({len(items_by_category[category])} items)")
for item in items_by_category[category][:2]: # Show first 2 items per category
available_mark = "✓" if item['available'] else "✗"
print(f" {available_mark} {item['name'][:50]}")
if __name__ == "__main__":
# Initialize the client
client = ParseClient()
# Replace with your actual guest token from GrabFood browser session
# You can extract this from browser DevTools: sessionStorage.getItem('guest_token')
GUEST_TOKEN = "your_guest_token_here"
print("🚀 GrabFood Indonesia Restaurant Analyzer")
print("=" * 80)
try:
# Step 1: Search for restaurants with a keyword
print("\n🔍 Step 1: Searching for fried chicken restaurants in Jakarta...")
search_results = client.search_restaurants(
guest_token=GUEST_TOKEN,
keyword="ayam goreng",
page_size=5
)
total_found = search_results['total_count']
restaurants = search_results['restaurants']
print(f"✓ Found {total_found} total restaurants (showing {len(restaurants)} results)")
# Step 2: Display search results summary
print("\n" + "=" * 80)
print("📋 SEARCH RESULTS SUMMARY")
print("=" * 80)
for i, restaurant in enumerate(restaurants, 1):
print(f"\n{i}. {restaurant['name']}")
print(f" ID: {restaurant['id']}")
print(f" ⭐ Rating: {restaurant['rating']}/5 ({restaurant['vote_count']:,} votes)")
print(f" 📏 Distance: {restaurant['distance_km']} km")
print(f" ⏱️ Delivery: {restaurant['estimated_delivery_time']} min")
print(f" 💵 Delivery Fee: {restaurant['delivery_fee_display']}")
print(f" 🍗 Cuisine: {', '.join(restaurant['cuisine'])}")
if restaurant.get('promo'):
print(f" 🎉 Promo: {restaurant['promo']}")
print(f" 🕌 Halal: {'Yes' if restaurant['halal'] else 'No'}")
# Step 3: Analyze menus for top restaurants
print("\n\n" + "=" * 80)
print("📊 DETAILED MENU ANALYSIS")
print("=" * 80)
# Analyze top 2 restaurants
for restaurant in restaurants[:2]:
try:
analyze_restaurant_menu(client, GUEST_TOKEN, restaurant)
except Exception as e:
print(f"\n⚠️ Could not retrieve menu for {restaurant['name']}: {str(e)}")
# Step 4: Comparison summary
print("\n\n" + "=" * 80)
print("📊 COMPARISON SUMMARY")
print("=" * 80)
print("\nRanking by Rating:")
sorted_by_rating = sorted(restaurants, key=lambda x: x['rating'], reverse=True)
for i, restaurant in enumerate(sorted_by_rating[:3], 1):
print(f" {i}. {restaurant['name']}: ⭐ {restaurant['rating']} ({restaurant['vote_count']} votes)")
print("\nRanking by Delivery Time:")
sorted_by_time = sorted(restaurants, key=lambda x: x['estimated_delivery_time'])
for i, restaurant in enumerate(sorted_by_time[:3], 1):
print(f" {i}. {restaurant['name']}: ⏱️ {restaurant['estimated_delivery_time']} min")
print("\nRanking by Delivery Fee:")
def extract_fee(fee_str):
return float(fee_str.replace("Rp", "").replace(".", "").strip())
sorted_by_fee = sorted(restaurants, key=lambda x: extract_fee(x['delivery_fee_display']))
for i, restaurant in enumerate(sorted_by_fee[:3], 1):
print(f" {i}. {restaurant['name']}: {restaurant['delivery_fee_display']}")
print("\n✅ Analysis completed successfully!")
except ValueError as e:
print(f"❌ Configuration Error: {e}")
print(" Please set your API key: export PARSE_API_KEY='your_api_key_here'")
except requests.exceptions.RequestException as e:
print(f"❌ API Error: {e}")
print(" Please verify your guest token and try again.")
except Exception as e:
print(f"❌ Unexpected error: {e}")Search and list GrabFood restaurants by location with optional keyword filter. Returns paginated results with restaurant details including name, cuisine, rating, delivery fee, and location.
| Param | Type | Description |
|---|---|---|
| latlng | string | Latitude,longitude for location-based search (comma-separated, e.g. '-6.1767352,106.826504'). |
| offset | integer | Pagination offset (increment by page_size for next page). |
| keyword | string | Search keyword to filter restaurants by name or cuisine. |
| page_size | integer | Number of results per page. |
| guest_tokenrequired | string | Guest authentication JWT from browser sessionStorage('guest_token') on food.grab.com. Valid for 30 days. |
| country_code | string | ISO country code for the GrabFood region (e.g. 'ID' for Indonesia, 'SG' for Singapore, 'TH' for Thailand). |
{
"type": "object",
"fields": {
"offset": "integer current pagination offset",
"page_size": "integer page size used",
"restaurants": "array of restaurant objects with id, name, cuisine, rating, vote_count, distance_km, estimated_delivery_time, delivery_fee_display, photo_url, halal, promo, latitude, longitude",
"total_count": "integer total number of matching restaurants"
},
"sample": {
"data": {
"offset": 0,
"page_size": 5,
"restaurants": [
{
"id": "6-C7ACCXAKGAVXNA",
"name": "Wallace - Tangki",
"halal": true,
"promo": "Diskon Rp38.120",
"rating": 4.6,
"cuisine": [
"Ayam Goreng",
"Nasi Ayam"
],
"latitude": -6.147417791414867,
"longitude": 106.82382539472837,
"photo_url": "https://huawei-food-cms.grab.com/compressed_webp/merchants/6-C7ACCXAKGAVXNA/hero/bbad5422-247f-47fb-b58f-82ab1fd45aeb__store_cover__2025__05__28__05__55__02.webp",
"vote_count": 867,
"distance_km": 4.99,
"delivery_fee_display": "Rp11.000",
"estimated_delivery_time": 50
}
],
"total_count": 767
},
"status": "success"
}
}About the Grab API
Restaurant Search
The search_restaurants endpoint accepts a latlng parameter (latitude/longitude, comma-separated) and an optional keyword to filter by restaurant name or cuisine type. Results are paginated via offset and page_size. Each restaurant object in the restaurants array includes id, name, cuisine, rating, vote_count, distance_km, estimated_delivery_time, and delivery_fee_display. The total_count field tells you how many records match so you can calculate how many pages to fetch. A country_code parameter (e.g. SG, TH, ID) scopes results to the correct regional GrabFood instance.
Merchant Menu Retrieval
Once you have a merchant id from search results, pass it as merchant_id to get_merchant_menu. The response returns menu_items—an array covering every item across all categories—with fields like name, description, category, price_display, price_minor_unit, discounted_price_disp, and available. The menu_categories array lists category names in order, and total_menu_items gives the total item count. The address and cuisine fields on the response describe the merchant itself.
Authentication and Coverage
Both endpoints require a guest_token—a JWT retrieved from sessionStorage on food.grab.com in any modern browser. Tokens are valid for 30 days. The API covers GrabFood markets where the service operates, including Indonesia, Singapore, Thailand, and other Southeast Asian countries. The latlng parameter drives location context for both search and menu requests, so accurate coordinates are important for correct delivery fee and availability data.
The Grab API is a managed, monitored endpoint for food.grab.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when food.grab.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official food.grab.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Aggregate restaurant listings and delivery fees across multiple GrabFood cities for a price-comparison dashboard.
- Build a menu database by iterating
search_restaurantsresults and callingget_merchant_menufor each merchant ID. - Track rating and
vote_countchanges for specific restaurants over time to monitor reputation trends. - Identify which cuisines and restaurants are available near a given latitude/longitude for a delivery coverage map.
- Extract discounted item prices (
discounted_price_dispvsprice_display) to surface deals across restaurants. - Populate a food-ordering assistant with structured category and modifier data from
menu_itemsfor a given merchant. - Analyze estimated delivery times and
delivery_fee_displayacross a city grid to study logistics patterns.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 100 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does GrabFood have an official public developer API?+
What does `search_restaurants` return beyond a restaurant name and rating?+
id (needed for menu lookups), cuisine type, vote_count, distance_km from the supplied coordinates, estimated_delivery_time, and delivery_fee_display. The total_count field on the response lets you paginate through all matching results using offset and page_size.Does `get_merchant_menu` return modifier groups or add-on options for menu items?+
menu_items array includes modifier options at the item level. Full nested modifier-group structures (e.g. size choices, toppings with sub-options and min/max selection rules) are included where the merchant has configured them.Is order placement or cart management supported?+
search_restaurants and menu retrieval via get_merchant_menu. Order creation, cart management, and checkout are not exposed. You can fork this API on Parse and revise it to add those endpoints if your use case requires transactional capabilities.How current is the menu and pricing data?+
latlng and merchant_id. Menu availability (available field per item) and pricing can vary by time of day or merchant configuration, so results should be treated as point-in-time. There is no built-in caching layer between your call and the source.