MakeMyTrip Bus
Live bus fares for one route and date from MakeMyTrip: every private bus with its fare range, seats left, timings and operator. Same keys, quotas and POST-and-poll job contract as the hotel and flight APIs, selected with "source": "makemytrip" — which is also the default.
/api/v1/bus-scraper/scrape/— submit/api/v1/bus-scraper/scrape/{job_id}/— poll/api/v1/bus-scraper/locations/?q=— resolve a placeBase URL: https://api.scrapeguys.com · Auth: X-API-Key: gsk_… with the bus:makemytrip scope · See the Overview.
GET /api/v1/scraper/usage/; there is no separate bus usage endpoint. A job is one unit, billed when it reaches a terminal status — including when it fails.bus:makemytrip scope. Without it every call returns 403 This API key does not have the 'bus:makemytrip' scope. There is no wildcard and existing keys are not granted it automatically — ask your ScrapeGuys contact to add it.Submit a search
curl -X POST 'https://api.scrapeguys.com/api/v1/bus-scraper/scrape/' \
-H 'Content-Type: application/json' -H 'X-API-Key: gsk_...' \
-d '{"origin":"Delhi","destination":"Manali","journey_date":"2026-11-12"}'
# {"job_id":"0c7d...","status":"pending","source":"makemytrip","poll_url":"/api/v1/bus-scraper/scrape/0c7d.../"}
# the whole route in one job
curl -X POST 'https://api.scrapeguys.com/api/v1/bus-scraper/scrape/' \
-H 'Content-Type: application/json' -H 'X-API-Key: gsk_...' \
-d '{"origin":"MMTCC1199","destination":"MMTCC1543","journey_date":"2026-11-12","paginate":true,"page_size":50}'
# resolve a place once, then reuse the code
curl 'https://api.scrapeguys.com/api/v1/bus-scraper/locations/?q=Manali' -H 'X-API-Key: gsk_...'
Request parameters
| Field | Supported | Notes |
|---|---|---|
origin, destination | Required | A place name, or a MakeMyTrip code (MMTCC… city, MMTLC… area or stop). Must differ. A name costs one extra request while it is learned; pass a code, or look it up once via /locations/, to skip that. |
journey_date | Required | YYYY-MM-DD, not in the past, up to 120 days out — see booking horizon. |
page_size | Supported | 1–50, default 20. |
paginate, max_pages | Supported | paginate: true walks every page in one job, bounded by max_pages (default 20). |
cursor | Supported | pagination.end_cursor from a previous answer, for one further page. Cannot be combined with paginate. |
source | Supported | makemytrip (default, and today the only bus supply). |
return_date | Not supported | One-way only. Submit a second job for the return leg. |
adults, children, passengers | Not supported | Fares are per seat, not per passenger. |
sort, sort_by, filters | Not supported | Results are departure-first and unfiltered; sort and filter them yourself. |
currency | INR only |
400 whose message names the remedy, for example {"return_date": "Bus search is one-way. Submit a second job for the return leg; there is no round-trip pricing on this API."}Booking horizon
{"journey_date": "journey_date is beyond the 120-day booking horizon (latest 2027-02-26); the supplier reports no buses for any date past it, which is indistinguishable from a route with no service."}
Past the horizon MakeMyTrip answers every date with "no buses available", exactly as it does for a route nobody runs. A job there could only come back empty while still costing a unit, so it is refused up front.
Poll for results
Poll every few seconds until status is completed or failed (pending → processing → terminal). There is no webhook.
{
"id": "0c7d...", "status": "completed", "source": "makemytrip",
"origin": "Delhi", "destination": "Manali", "journey_date": "2026-11-12",
"origin_code": "MMTCC1199", "destination_code": "MMTCC1543",
"bus_count": 20, "complete": false, "duration_seconds": 3.4, "bandwidth_kb": 10.72,
"result": {
"buses": [{
"index": 0, "trip_key": "39_MMTCC1199_MMTCC1543_12-11-2026_1001653357940006260",
"operator": "Deltin Travels Pvt Ltd", "bus_type": "Volvo 9600 A/C Semi Sleeper (2+2)",
"dep_time": "10:00", "arr_time": "21:50", "duration": "11h 50m",
"dep_ts": 1794461400, "arr_ts": 1794504000, "duration_min": 710,
"min_price": 529, "max_price": 799, "original_price": 529, "discounted_price": 529,
"seats_left": 38, "window_seats": 18, "single_seats": 0,
"is_ac": true, "is_sleeper": false, "is_seater": true, "is_primo": false,
"is_next_day": false, "is_electric": false, "is_premium": true,
"has_live_tracking": false, "is_rtc": false,
"mmt_ids": {"trip_key": "39_...", "operator_id": "31517", "vendor_trip_id": "1001653357940006260", "service_id": "6260"}
}],
"route": {"src": {"code": "MMTCC1199", "name": "Delhi", "type": "CITY"}, "dest": {"code": "MMTCC1543", "name": "Manali", "type": "CITY"}},
"totals": {"total_bus_count": 115, "primo_bus_count": 13, "rtc_bus_count": 9, "private_bus_count": 106, "minimum_fare": 529, "minimum_duration_minutes": 650},
"pagination": {"has_next_page": true, "end_cursor": "...", "pages_fetched": 1},
"alternates": {"routes": [], "dates": []},
"warnings": [],
"search_context": {"source": "makemytrip", "origin_code": "MMTCC1199", "destination_code": "MMTCC1543", "journey_date": "2026-11-12", "no_results": false, "complete": false},
"error": null, "error_category": ""
},
"error_message": "", "error_category": ""
}
min_price/max_priceare per seat. A listing is a quote at a moment, not a held price.indexis the position in the published, departure-ordered list, not the supplier's own (unstable) ordering.search_context.no_results— MakeMyTrip served the request and runs nothing on it. This is acompletedjob, not a failure; checkno_results, noterror.alternatesmay name a nearby destination or another date, best-effort only.search_context.complete— whether you received the whole market.falsemeans a further page remains (pagination.has_next_page) or the headline count exceeds what was returned;warningssays which. It is also a filterable column on the job.bandwidth_kbis what the bytes cost on the wire, not the size of the JSON.
Two things about MakeMyTrip's own numbers
total_bus_count is larger than the list you get
total_bus_count (115 on Delhi → Manali) is private_bus_count (106) plus rtc_bus_count (9). Only private buses are returned: state-transport (RTC) inventory sits behind a grouped widget the search response does not expand. On a route with RTC service a complete answer is therefore still short of the headline — search_context.complete is false and warnings says so.
RTC is not always the whole gap. Measured on Delhi → Dehradun: total_bus_count 275, private_bus_count 270, rtc_bus_count 5, and a walk to the last page returned 252 — 18 more missing than RTC explains. The warning accounts for the RTC share and states plainly that the remainder is unexplained, rather than attributing the whole gap to RTC. Treat total_bus_count as MakeMyTrip's headline, not a number to reconcile against, and use buses.length for what was actually returned.
A paginated walk can be offered the same bus twice
Measured: at page_size: 20 a second page repeated a trip_key from the first; at 50 on the same route none did. A paginate: true walk de-duplicates by trip_key and records the number dropped in warnings. If you page yourself with cursor, de-duplicate on trip_key or you will double-count a bus and, if summing, a fare.
What a bus carries
Beyond the common fields: is_primo (MakeMyTrip's vetted-operator tier), is_rtc (state transport rather than a private operator), is_premium, is_electric, has_live_tracking, is_next_day (arrives the day after departure), window_seats, single_seats, and original_price / discounted_price when a deal applies. mmt_ids carries trip_key, operator_id, vendor_trip_id and service_id, published now for the seat-map and fare-breakup steps planned for later.
Resolve a place
curl 'https://api.scrapeguys.com/api/v1/bus-scraper/locations/?q=Delhi' -H 'X-API-Key: gsk_...'
{"query":"Delhi","source":"makemytrip","cached":false,
"locations":[{"code":"MMTCC1199","name":"Delhi","type":"city","display_name":"Delhi"}]}
Synchronous and counted against your rate limit. An empty locations list is a successful answer: MakeMyTrip has no bus location for that text, which is also why a search would refuse it. A 502 means the supplier could not be reached; there is no job to poll.
Errors
| error_category | On MakeMyTrip bus | What to do |
|---|---|---|
invalid_search | A place with no bus location (e.g. "Kashmiri Gate"), or a code pair MakeMyTrip rejects. | Fix the request; retrying cannot help. |
invalid_input | Malformed in a way validation did not catch. | Fix the request. |
transient | A network fault, a 429, a MakeMyTrip 5xx, or no exit available. | Resubmit. |
blocked | The supplier refused us (403/428). Expected to be rare. | Resubmit; if it persists, tell us. |
rotation | MakeMyTrip sent an error code or payload shape we do not know. | Tell us; retrying will not help until we ship a fix. |
error | Anything else, including the route being disabled. | Tell us. |
Status codes: 202 accepted · 200 poll/lookup · 400 refused request or disabled route · 401 bad key · 403 missing scope · 404 unknown job · 429 rate limit or quota · 502 supplier unreachable on /locations/.
The route can be switched off; while it is, every submit and lookup returns {"error": "the MakeMyTrip bus scraper is not currently enabled"} and costs nothing — no job row and no rate-limit slot.
What is supported
| Capability | Status |
|---|---|
| Listing: every private bus with fares, seats, timings, operator | Supported |
| Place → code lookup | Supported |
| Seat map, fare breakup, boarding/dropping points, cancellation policy | Planned — each bus already carries the ids they will need |
| RTC (state transport) buses | Planned — counted in the headline, not yet returned |
| Round trips, per-passenger pricing, booking | Not offered |