Case Studies & ProjectsAPI Researcher
FoodPanda Pakistan — Mapping a Private API and Building a Deal Scanner
By Burhan Moin
Motivation
FoodPanda Pakistan runs limited-time flash deals that go live at unpredictable times and sell out within minutes. The official app provides no notification system for these deals beyond generic push alerts. I wanted a way to be alerted the instant a deal matching my criteria went live — and to query the full restaurant and menu catalogue in ways the app UI does not allow.
Traffic Interception Setup
I set up Charles Proxy on macOS with SSL proxying enabled and installed the Charles root certificate on an Android device running the FoodPanda app. This gave me full visibility into every HTTPS request the app made.
Environment:
Proxy: Charles Proxy 4.x (macOS)
Device: Android 13, certificate trust installed
Target: FoodPanda Pakistan (pk.fd-api.com)
SSL Proxying hosts:
*.fd-api.com
*.foodpanda.pkAPI Surface Mapping
Authentication flow
The app authenticates with a POST to /api/v5/oauth/token using a username/password grant. The response includes a bearer token with a 30-day expiry. All subsequent requests carry Authorization: Bearer <token>.
import requests
BASE = "https://pk.fd-api.com"
def login(email: str, password: str) -> str:
r = requests.post(f"{BASE}/api/v5/oauth/token", json={
"grant_type": "password",
"username": email,
"password": password,
"client_id": "panda", # hardcoded in APK
"client_secret": "...", # extracted from APK resources
})
return r.json()["access_token"]Deals endpoint
Flash deals are served from /api/v5/vendors with a specific campaign_id parameter that the app populates when the deals banner is active. By polling this endpoint I could detect new deals before the push notification system delivered them.
def get_live_deals(token: str, lat: float, lon: float) -> list:
r = requests.get(f"{BASE}/api/v5/vendors", headers=auth(token), params={
"latitude": lat,
"longitude": lon,
"language_id": 1,
"dynamic_pricing": 0,
"configuration": "Panda",
"country_code": "PK",
"use_new_discovery": True,
"campaign_id": "promotion", # deals filter
})
vendors = r.json().get("data", {}).get("items", [])
return [v for v in vendors if v.get("is_in_promotion")]Full menu and price-range query
The /api/v5/vendors/{vendor_id}/menus endpoint returns the complete menu tree including every item, variant, price, and availability flag. I wrapped this into a search function that queries across any number of restaurants simultaneously.
def search_menu(token: str, vendor_ids: list[str],
query: str = "", min_price: float = 0, max_price: float = 9999) -> list:
results = []
for vid in vendor_ids:
r = requests.get(f"{BASE}/api/v5/vendors/{vid}/menus",
headers=auth(token))
for category in r.json().get("data", {}).get("product_groups", []):
for item in category.get("products", []):
price = item.get("product_variations", [{}])[0].get("price", 0)
if (query.lower() in item["name"].lower()
and min_price <= price <= max_price):
results.append({
"restaurant": vid, "name": item["name"],
"price": price, "available": item.get("is_available"),
})
return sorted(results, key=lambda x: x["price"])The Deal Scanner CLI
I combined everything into a persistent CLI daemon that polls the deals endpoint every 30 seconds and sends a desktop notification (macOS osascript) the moment a deal matching my filter list appears.
import time, subprocess
POLL_INTERVAL = 30 # seconds
KEYWORDS = ["pizza", "burger", "biryani"]
def notify(restaurant: str, deal: str):
subprocess.run(["osascript", "-e",
f'display notification "{deal}" with title "FoodPanda Deal: {restaurant}"'])
def run(token, lat, lon):
seen = set()
while True:
deals = get_live_deals(token, lat, lon)
for d in deals:
key = d["id"]
if key not in seen:
seen.add(key)
name = d.get("name", "")
if any(k in name.lower() for k in KEYWORDS):
notify(name, d.get("tag", "New deal live"))
time.sleep(POLL_INTERVAL)Results
The scanner has been running for several months. I have been alerted to deals averaging 2–4 minutes earlier than the official push notifications. The menu search function covered over 800 restaurants across Karachi in a single query — returning results sorted by price in under 3 seconds.
[ Screenshots of deal alerts and CLI output to be added here ]
Key Takeaways
The FoodPanda API has no additional rate-limiting or anomaly detection beyond the standard bearer-token authentication. Client credentials embedded in the APK (client_id, client_secret) are static and not rotated. Any actor with Burp Suite or Charles and 20 minutes can fully map this API surface. Proper mitigation requires certificate pinning, rotating client credentials, and server-side anomaly detection on request patterns.