Grab APIfood.grab.com ↗
Search GrabFood restaurants by location and retrieve full menus with prices, categories, and modifier options via two structured endpoints.
What is the Grab API?
The GrabFood API gives developers access to two endpoints — search_restaurants and get_merchant_menu — covering restaurant listings and full menu data across GrabFood regions including Indonesia, Singapore, and Thailand. search_restaurants returns up to paginated batches of restaurants with ratings, delivery fees, estimated delivery times, and distance, while get_merchant_menu returns every menu item across all categories for a given merchant, including prices in both display and minor-unit formats.
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 coordinate pair (e.g. -6.1767352,106.826504), an optional keyword to filter by restaurant name or cuisine, and a country_code for regional targeting. 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 matches exist so you can calculate how many pages to walk.
Merchant Menus
The get_merchant_menu endpoint takes a merchant_id sourced from search_restaurants results (e.g. IDGFSTI00001mce) and returns a complete menu. Response fields include merchant_name, address, cuisine, menu_categories (array of category name strings), and menu_items. Each item carries id, name, description, category, available, price_display, price_minor_unit, and discounted_price_disp where applicable. total_menu_items gives a count of all items returned.
Authentication
Both endpoints require a guest_token — a JWT available from sessionStorage on food.grab.com — which remains valid for 30 days. No account login is required to obtain it; a regular browser visit to the GrabFood site is sufficient.
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?+
- Build a delivery fee comparison tool across nearby restaurants using
delivery_fee_displayanddistance_km. - Aggregate GrabFood menu prices by cuisine type for market research or competitive pricing analysis.
- Power a restaurant discovery app filtered by rating and estimated delivery time for a given coordinate.
- Monitor menu item availability and price changes for a set of tracked merchant IDs over time.
- Extract
discounted_price_dispvalues to identify current promotions across a city's restaurant listings. - Seed a food database with structured menu categories, item descriptions, and prices for multiple regions.
- Build a keyword-based search interface over GrabFood inventory using the
keywordparameter insearch_restaurants.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. 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 `get_merchant_menu` return beyond item names and prices?+
get_merchant_menu returns structured data for every item: name, description, category, price_display, price_minor_unit, discounted_price_disp, and an available boolean. Items are organized under menu_categories, and the total_menu_items integer tells you the full item count without needing to count the array manually.Which GrabFood regions does the API cover?+
country_code parameter supports GrabFood markets including Indonesia (ID), Singapore (SG), and Thailand (TH). Coverage is tied to what the latlng coordinate resolves to in a given region, so results depend on both the coordinate and the country code supplied.Can I retrieve order history or user reviews for a restaurant?+
rating and vote_count) and menu data. Individual user reviews, review text, and order history are not exposed. You can fork this API on Parse and revise it to add those endpoints if that data is needed.Does the menu endpoint return item images?+
get_merchant_menu endpoint description references image data in the menu item objects. Fields confirmed in the response schema are id, name, description, category, available, price_display, price_minor_unit, and discounted_price_disp. If image URLs are a strict requirement and are not present in current responses, you can fork the API on Parse and extend the response mapping to surface them.