🎉 Developer Free Trial — 1 week of full API access, no credit card required. Claim your trial →
Scraper · OTA

Goibibo

Return the exact per-room-type price a real guest sees in Goibibo's own app — not a metasearch or commission-inflated affiliate price. Goibibo runs on MakeMyTrip's own backend under a different brand, so this source is resolved and fetched the same way the official Goibibo Android app does. Submit a job, receive a job_id, poll for the result.

POST /api/v1/scraper/ota-price/ — submit
GET /api/v1/scraper/ota-price/{job_id}/ — poll

Base URL: https://gumo.co.in · Auth: X-API-Key: gsk_… with the ota:goibibo scope · See the Overview.

Shared endpoint, one vendor per job. /api/v1/scraper/ota-price/ also prices Agoda, Booking.com, MakeMyTrip and Expedia — set "ota": "goibibo" (alias goibibo.com) to get this vendor; the endpoint defaults to Agoda when ota is omitted. The envelope shape is the same across all five; this page documents everything Goibibo-specific (one room per request, no children yet, Android-only). Not what you're after? Rate Shop does not offer Goibibo — see the note below.
Availability: Goibibo ships disabled by default while it completes its first production soak. If your key lacks the ota:goibibo scope you get 403 {"detail": "This API key does not have the 'ota:goibibo' scope."}; if the scraper is switched off platform-wide — the default today — you get 400 {"error": "ota 'goibibo' is not currently enabled"} before any job is created. Contact us to enable it.

Submit a job

FieldTypeRequiredDefaultNotes
otastringRequiredSet to "goibibo" explicitly (alias goibibo.com) — this endpoint defaults to Agoda when ota is omitted. Goibibo has no numeric channel code — see Addressing an OTA by number — and stays addressable by name only.
hotel_namestringConditional≤ 300 chars. Resolved via Goibibo's own app autosuggest, into the same hotelId space MakeMyTrip uses (one backend, two brands). Required unless place_id or ota_hotel_id is given — see Identifying the hotel.
citystringConditional≤ 200 chars. Required unless place_id or ota_hotel_id is given.
place_idstringOptional""Google Place ID. Identifies the property itself — the same id also works across Agoda, Booking.com and MakeMyTrip. Bridged to Goibibo's own id on first use, then pinned, so the same place_id returns the same property on every later request. See Identifying the hotel.
ota_hotel_idstringOptional""Goibibo's own numeric hotelId — the SAME id as MakeMyTrip's (e.g. "201709071828297454"), because Goibibo prices on MakeMyTrip's backend. Skips resolution entirely. The id is validated against "ota": "goibibo" at submit. See Identifying the hotel.
check_indateRequiredYYYY-MM-DD; not past, ≤ 1 year.
check_outdateRequiredAfter check_in; ≤ 30 nights.
adultsintOptional21–10. Total party size across all rooms, not per room.
roomsintOptional11–8 is accepted at submit like every other OTA here, but Goibibo only prices one room per request today — see the callout below for what actually happens when you ask for more.
currencystringOptionalINRINR only — same constraint as MakeMyTrip, the same backend. Other codes are rejected with 400 ("Unsupported for goibibo. Supported: INR.") before any job is created.
device_typestringOptionaldesktopAccepted and echoed, but has no effect on Goibibo — every scrape is the Android app route regardless of what you send. See Device pricing below.
One room per request — and it's not enforced at submit. A party the property can't seat in a single room (two rooms, or a room plus children) does not come back in Goibibo's normal room grid at all: the app re-splits it into a different combination behind the scenes, which is a different party than the one you asked for — so it is refused rather than priced. But rooms itself is validated the same 1–8 range as every other OTA here, so a value > 1 is not rejected at submit — the job is created, then completes as status: "failed" with result.error: "goibibo prices one room per request today (asked for N): a multi-room party comes back as a re-split combo, not the requested rooms". Keep rooms: 1 until multi-room pricing is measured and lifted.
Children aren't priced on Goibibo yet. Sending children_ages with "ota": "goibibo" is rejected with 400 before any job is created — for the same reason as the multi-room case above: a childful party doesn't come back in the normal room grid either, and one measured run even had the app rewrite the requested ages on the combo it substituted. Supported today: Agoda, Booking.com, Expedia and MakeMyTrip.
POST /api/v1/scraper/ota-price/ — Goibibo
curl -X POST https://gumo.co.in/api/v1/scraper/ota-price/ \
  -H "X-API-Key: gsk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "ota": "goibibo",
    "hotel_name": "The Taj Mahal Palace, Mumbai",
    "city": "Mumbai",
    "check_in": "2026-09-15",
    "check_out": "2026-09-17",
    "adults": 2,
    "rooms": 1
  }'
202 Accepted
{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "pending",
  "ota": "goibibo",
  "ota_hotel_id": "", "place_id": "",
  "poll_url": "/api/v1/scraper/ota-price/a1b2c3d4-e5f6-7890-abcd-ef1234567890/"
}

Identifying the hotel

A request identifies its hotel one of three ways, exactly as on MakeMyTrip — unsurprising, since the two share a backend.

By name (hotel_name + city) is the default — resolved through Goibibo's own app autosuggest and picked by us. It works without you knowing anything about Goibibo, and is what every existing integration uses.

By Google Place ID (place_id) is the recommended way to pin a portfolio. One id identifies the property across Agoda, Booking.com, MakeMyTrip and Goibibo. The first request for a place still goes through Goibibo's own autosuggest — there's no other way in — but the id that actually returned a price is then pinned, so every later request for that place_id addresses the same property directly. Send hotel_name alongside it if you have it — it's used as the search text for that one resolution; without it we look the name up from the Place ID itself.

By Goibibo's own id (ota_hotel_id, its numeric hotelId, e.g. "201709071828297454") is the fastest and most direct route once you hold it. The whole resolution step disappears, and the answer cannot drift to a different hotel between runs. It is the same id MakeMyTrip uses — if you already have a portfolio of MakeMyTrip hotelIds, they work here unchanged. The id is validated against "ota": "goibibo" at submit, so an id from a different OTA is rejected with 400 before any job is created or billed.

Where to get the id. Every result already returns the property's own id as result.hotel_id, so one name-based run per property gives you the id to send from then on.

Checking we priced the hotel you meant. Read result.origin_hotel_name — the origin's own name for the property it priced, sourced from the same deeplink field MakeMyTrip's parser always reads. That makes it a genuine statement from the origin, not an echo of your text, even though (see Response schema below) the URL it's read from is never published as source_url on a Goibibo result. result.hotel_name cannot answer this question at all — it is your own search text echoed back, not a confirmation signal.

What we do NOT do with your id. A submitted ota_hotel_id is used exactly as given and changes no state on our side — it's never written into our own name→id mapping.

Device pricing — device_type

device_type is accepted and validated on Goibibo — only desktop or mobile are accepted, anything else 400s — and echoed back on the job envelope, but it has no effect on which prices you get.

Goibibo is the Android-app route, always. This source works by sending the exact request the official Goibibo Android app itself sends — the one Goibibo surface not gated behind the anti-bot wall a plain web or iOS request hits. So every Goibibo scrape presents an Android app identity regardless of what you send for device_type; there is no separate desktop or iOS code path to switch to. (Contrast with Expedia, which is the same situation but on iOS/Android-flavored APP_PHONE rather than a device-specific build — and with MakeMyTrip, which is genuinely device-flat rather than device-locked.)

Poll for results

Poll the poll_url every ~2 seconds until status is completed or failed. Statuses: pending → processing → completed | failed. Job IDs are scoped to this endpoint — a Google Hotels job ID 404s here, and vice versa.

GET /api/v1/scraper/ota-price/{job_id}/
GET /ota-price/{job_id}/
curl https://gumo.co.in/api/v1/scraper/ota-price/YOUR_JOB_ID/ \
  -H "X-API-Key: gsk_your_api_key"
# Repeat every ~2 s until status is "completed" or "failed"

Response schema

The poll response is a job envelope: id, status, ota, hotel_name, city, ota_hotel_id, place_id, check_in, check_out, adults, children_ages, rooms, currency, device_type, result, error_message, created_at, started_at, completed_at, duration_seconds, bandwidth_kb — the same shape as every other OTA on this endpoint (children_ages is always [] here, since Goibibo rejects a non-empty one at submit). On completion, result holds the pricing object, which nests HotelRoomPricing → room_types[] → offers[].

200 OK — GET /ota-price/{job_id}/ (result, abridged)
{
  "id": "a1b2c3d4-...", "status": "completed", "error_message": "",
  "result": {
  "ota": "goibibo",
  "hotel_name": "The Taj Mahal Palace, Mumbai",
  "origin_hotel_name": "The Taj Mahal Palace, Mumbai",
  "hotel_id": "200601010111111111",
  "currency": "INR",
  "check_in": "2026-09-15", "check_out": "2026-09-17",
  "adults": 2, "rooms": 1,
  "nights": 2, "adults_per_room": 2, "price_scope": "per_room",
  "occupancy_honored": true,
  "cheapest_fitting_price": 21800.0, "cheapest_fitting_basis": "per_night_before_taxes",
  "source": "http", "scraped_at": "2026-09-14T12:00:00+00:00",
  "sold_out": false, "error": null, "truncated": false,
  "suppliers": [], "rate_channels": [],
  "room_types": [
    {
      "name": "Luxury Room Palace Wing", "sold_out": false,
      "room_size": "45 m²", "bed": "1 king bed", "view": "City view",
      "amenities": ["Free WiFi", "Parking"], "images": ["https://..."],
      "tags": [], "room_id": "991243155",
      "room_size_sqm": 45.0, "max_guests": 3,
      "offers": [
        {
          "price": 21800.0, "currency": "INR", "price_text": "₹ 21,800",
          "price_basis": "per_night_before_taxes", "price_before_discount": null,
          "board": "Breakfast included", "free_cancellation": true,
          "cancellation_text": "Free cancellation until Sep 13",
          "refundable": true, "occupancy": 2,
          "occupancy_source": "priced", "fits_occupancy": true,
          "badges": [], "rate_token": null,
          "supplier_id": null, "rate_channel_id": null, "device_identity": null,
          "tax_included": true, "tax_amount": 3924.0,
          "meal_plan_code": "CP", "meal_plan_code_raw": "MP_CP",
          "source_url": null, "source_url_scope": null
        }
      ]
    }
  ]
  }
}

Object fields

ObjectFields
HotelRoomPricingota, hotel_name, origin_hotel_name, hotel_id, currency, check_in, check_out, room_types[], adults, children_ages[], rooms, nights, adults_per_room, children_per_room, price_scope ("per_room"), occupancy_honored, cheapest_fitting_price, cheapest_fitting_basis, source ("http"), scraped_at, sold_out, error, truncated, suppliers[], rate_channels[]suppliers[]/rate_channels[] are always empty; those are Agoda-only channel fields. children_ages / children_per_room are always [] — Goibibo does not price children yet (see the callout under Submit a job).
RoomTypename, offers[], sold_out, room_size, room_size_sqm, max_guests, bed, view, description, amenities[], images[], tags[], room_idtags[] is always empty (Agoda-only).
RoomOfferprice, currency, price_text, price_basis, price_before_discount, board, free_cancellation, cancellation_text, refundable, occupancy, occupancy_source ("priced"), max_children, fits_occupancy, badges[], rate_token, raw_label, supplier_id, rate_channel_id, device_identity, tax_included, tax_amount, meal_plan_code, meal_plan_code_raw, source_url, source_url_scope — the channel/device fields are always null; those are Agoda-only. max_children is a Booking.com-only field, always null here. price_before_discount is always null — no usable strikethrough on this route.
source_url and source_url_scope are always null on Goibibo — deliberately, not for lack of a URL. The underlying response carries the same deeplink field MakeMyTrip's parser reads, but on Goibibo that URL points at makemytrip.com — Goibibo runs on MakeMyTrip's own backend — so publishing it under a Goibibo result would send you to a different OTA's page. result.origin_hotel_name is still read from that same field and is still genuine (see Identifying the hotel); only the URL itself is withheld.
FieldMeaning on Goibibo
tax_includedWhether price already contains taxes and fees — restates price_basis as a boolean.
tax_amountReal — read the same way as on MakeMyTrip (shared parser): the response states the tax figure directly, per night. There is no itemized split to ask for — tax_required is rejected with 400 on Goibibo, same as on MakeMyTrip and Booking.com.
meal_plan_codeRead from Goibibo's own meal-plan field (the same MP_EP / MP_CP / MP_MAP contract MakeMyTrip publishes), not derived from board. null only when the origin states no meal plan at all.
meal_plan_code_rawThe untouched upstream token behind the normalized meal_plan_code. Shared with MakeMyTrip; null on Agoda, Booking.com and Expedia.
Price basis on Goibibo. price_basis is a per-offer field. Goibibo is always per_night_before_taxes — the same fixed basis MakeMyTrip uses, and unlike Booking.com (whole-stay) or Agoda (usually per-night but derived per offer). Use the pre-computed cheapest_fitting_price + cheapest_fitting_basis to normalize across OTAs rather than assuming.

Result outcomes

Submitting can 4xx (validation 400, auth 401, missing scope 403, throttling 429). Once a job is accepted, polling always returns HTTP 200 — you distinguish scrape outcomes by fields, never by status code:

OutcomeHow to detect
Pricedstatus: "completed", result.error: null, result.room_types populated.
Sold outstatus: "completed", result.sold_out: true, result.room_types: [], result.error: null.
Failurestatus: "failed" with a human-readable error_message; result.error carries the same string, with result.room_types: []. Goibibo-specific strings include "unresolved (no Goibibo match for name+city)", "proxy quota (407): ...", "goibibo property unavailable (code=...): ..." (a delisted or invalid hotel id), and "goibibo prices one room per request today (asked for N): ..." (see the rooms callout under Submit a job).

A scrape failure is never a 4xx/5xx on the poll — it is a failed job with an explanation.

Goibibo-specific notes

  • Ships disabled by default while it completes its first production soak — contact us to enable it on your environment and key.
  • One room per request. A value > 1 is accepted at submit but fails the job, not rejected outright — see the callout under Submit a job.
  • No children yet. children_ages is rejected with 400 at submit.
  • Currency: INR only.
  • Device: ignored — every scrape is the Android app route, regardless of device_type.
  • Price basis: always per_night_before_taxes, shared with MakeMyTrip.
  • Identity: shares MakeMyTrip's numeric hotelId space — an id you already hold for MakeMyTrip works here unchanged.
  • Not offered on Rate Shop. /ota-price/ only, until it has proven itself on single-property jobs, where one failure costs one job rather than a whole shop.
  • source_url / source_url_scope are always null — see the callout under Response schema.

Next: Destination Search →