Scraper · Bus

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.

POST/api/v1/bus-scraper/scrape/— submit
GET/api/v1/bus-scraper/scrape/{job_id}/— poll
GET/api/v1/bus-scraper/locations/?q=— resolve a place

Base URL: https://api.scrapeguys.com · Auth: X-API-Key: gsk_… with the bus:makemytrip scope · See the Overview.

Shared contract. Auth header, rate limits, quotas and the job lifecycle match the Overview. Bus jobs count against the same per-minute limit and daily/monthly quotas as your hotel usage and appear in 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.
Your key needs the 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

FieldSupportedNotes
origin, destinationRequiredA 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_dateRequiredYYYY-MM-DD, not in the past, up to 120 days out — see booking horizon.
page_sizeSupported1–50, default 20.
paginate, max_pagesSupportedpaginate: true walks every page in one job, bounded by max_pages (default 20).
cursorSupportedpagination.end_cursor from a previous answer, for one further page. Cannot be combined with paginate.
sourceSupportedmakemytrip (default, and today the only bus supply).
return_dateNot supportedOne-way only. Submit a second job for the return leg.
adults, children, passengersNot supportedFares are per seat, not per passenger.
sort, sort_by, filtersNot supportedResults are departure-first and unfiltered; sort and filter them yourself.
currencyINR only
Rejected at submit, not silently ignored. Every unsupported field is refused with a 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": ""
}

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_categoryOn MakeMyTrip busWhat to do
invalid_searchA place with no bus location (e.g. "Kashmiri Gate"), or a code pair MakeMyTrip rejects.Fix the request; retrying cannot help.
invalid_inputMalformed in a way validation did not catch.Fix the request.
transientA network fault, a 429, a MakeMyTrip 5xx, or no exit available.Resubmit.
blockedThe supplier refused us (403/428). Expected to be rare.Resubmit; if it persists, tell us.
rotationMakeMyTrip sent an error code or payload shape we do not know.Tell us; retrying will not help until we ship a fix.
errorAnything 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

CapabilityStatus
Listing: every private bus with fares, seats, timings, operatorSupported
Place → code lookupSupported
Seat map, fare breakup, boarding/dropping points, cancellation policyPlanned — each bus already carries the ids they will need
RTC (state transport) busesPlanned — counted in the headline, not yet returned
Round trips, per-passenger pricing, bookingNot offered