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://api.scrapeguys.com · 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 is gated twice 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."}; on an environment where the Goibibo scraper is switched off you get 400 {"error": "ota 'goibibo' is not currently enabled"} before any job is created. Contact us to enable it.

Submit a job

FieldTypeRequiredDefaultNotes
otastringRequired—Set to "goibibo" explicitly (alias goibibo.com) — this endpoint defaults to Agoda when ota is omitted. A numeric channel code ("5" or bare 5) may be sent instead — added 21 September 2026, see Addressing an OTA by number. The code is accepted on this endpoint only — Rate Shop holds Goibibo out under its number exactly as under its name.
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 — when Goibibo names the property and that name agrees with the Place ID's own — so the same place_id returns the same property on every later request. Otherwise each request resolves again. 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_indateRequired—YYYY-MM-DD; not past, ≤ 1 year.
check_outdateRequired—After 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_typestringOptionaldesktopdesktop or mobile: accepted and echoed, but has no effect on Goibibo — every scrape is the Android app route regardless of what you send. app_android is rejected with a 400 (there is no separate route to select). 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://api.scrapeguys.com/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-12-15", "check_out": "2026-12-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/" }
Changed 16 October 2026 — a misspelt field is a 400. Fields this endpoint does not know are still ignored, so your own extras (client_ref, request_id, …) are harmless. But a key that is a near miss of a real field you did not send — a case or separator variant (checkIn, check-in) or a letter or two off (adult, room, child_ages) — is refused with a 400 naming the field it probably meant. Before this, "room": 2 priced one room with nothing in the response to say so.

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. A pin is written only when Goibibo names the property and that name agrees with the Place ID's own; otherwise the job still returns prices and the next request resolves again. 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's format is checked at submit (digits only): a malformed id is rejected with 400 before any job is created or billed. A well-formed id from another OTA cannot be told apart and is priced as Goibibo's own id — Agoda, MakeMyTrip and Goibibo ids are all plain numbers.

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 — desktop or mobile are accepted; app_android, the app route Agoda and Booking.com offer, and anything else 400 — 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://api.scrapeguys.com/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, tax_required, tax_scope, 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; tax_required / tax_scope echo what you submitted). 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", "ota_code": 5, "hotel_name": "The Taj Mahal Palace, Mumbai", "origin_hotel_name": "The Taj Mahal Palace, Mumbai", "hotel_id": "200601010111111111", "currency": "INR", "check_in": "2026-12-15", "check_out": "2026-12-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", "cheapest_fitting_meal_plan_code": "CP", "cheapest_fitting_tax_status": "excluded", "source": "http", "scraped_at": "2026-10-15T12: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 Dec 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": false, "tax_status": "excluded", "tax_amount": 3924.0, "tax_basis": "tax_and_fees", "tax_and_fees": 3924.0, "fee_amount": null, "meal_plan_code": "CP", "meal_plan_code_raw": "MP_CP", "source_url": null, "source_url_scope": null } ] } ] } }

Object fields

ObjectFields
HotelRoomPricingota, ota_code, 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, cheapest_fitting_meal_plan_code, cheapest_fitting_tax_status, cheapest_room, source ("http"), scraped_at, sold_out, error, truncated, suppliers[], rate_channels[] — suppliers[]/rate_channels[] are always empty; those are Agoda-only channel fields. cheapest_fitting_tax_status is the tax_status of the offer cheapest_fitting_price was read from; cheapest_room is the cheapest fitting rate priced all-in for one room for one night — see Agoda's Response schema for both. ota_code is the numeric channel code ota also accepts on submit (see Addressing an OTA by number) — 5 here, present on failed jobs too; added 21 September 2026, before which Goibibo had none and this was always null. children_ages is always [] and children_per_room always 0 — 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_id — tags[] is always empty (Agoda-only). bed is the OTA's own wording and case; only spacing mechanics are cleaned up centrally, so the same bed can't read as two different strings depending on which OTA sent it. view is one of a fixed vocabulary, identical across every OTA: "Sea view", "City view", "Garden view", "Courtyard view", "Pool view", "Lake view", "Valley view", "Bay view", "Jungle view", "Hill view", "Mountain view", matched on whole phrases only — never a bare noun, so a room called "Suite with Plunge Pool" is not a pool view. Synonyms fold onto one token ("Ocean View" → Sea view, "Swimming Pool View" → Pool view); Hill view and Mountain view deliberately do not fold into each other. null means no view in the vocabulary was named — never a raw upstream string. Same precedence as MakeMyTrip (shares this parser): the origin's own structured view field wins over the room name when both are present.
RoomOfferprice, currency, price_text, price_basis, price_before_discount, board, free_cancellation, cancellation_text, refundable, occupancy, occupancy_source ("priced"), max_children, rooms_booked, fits_occupancy, badges[], rate_token, raw_label, supplier_id, rate_channel_id, device_identity, tax_included, tax_status, tax_amount, tax_basis, fee_amount, tax_and_fees, pay_later, payment_text, tax_percentage, 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, and rooms_booked, pay_later and payment_text are Agoda- or Booking.com-only; all always null here. price_before_discount carries the pre-discount list price when Goibibo states a discount, as on MakeMyTrip, and is null otherwise. tax_percentage is always null on Goibibo, itemized or not — this backend states amounts, never a rate. tax_basis, fee_amount and tax_and_fees are populated free — see Taxes and fees below.
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_statustax_included as a word: included, excluded or unknown — never null. Always excluded on Goibibo, whose basis is fixed. Added 13 October 2026.
tax_amountReal — read the same way as on MakeMyTrip (shared parser): the response states the tax figure directly, per night. On an ordinary job it's taxes and fees combined, same as MakeMyTrip and Booking.com. Unlike either of those, Goibibo's tax line states its own components, so a tax_required job splits it into tax alone here — at no extra request. See Taxes and fees below.
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 (+ cheapest_fitting_meal_plan_code, added 23 September 2026, so you're not comparing a breakfast rate against a room-only one) to normalize across OTAs rather than assuming.

Taxes and fees — the split costs nothing here

Goibibo's room grid states its own tax line's components, so tax_amount — taxes and fees combined, the same shared contract every OTA here publishes — can be split into tax alone and the fee beside it without a second request. Agoda pays one booking-form request per rate for the same answer and Expedia two (a prepare-checkout call, then the checkout page); an itemized Goibibo job issues exactly as many requests as an ordinary one, and is one unit of quota like any other job.

FieldQuestion it answersCost
tax_and_feesWhat does this rate cost on top of price?free, on every offer
tax_amount + fee_amountHow much of that is tax, and how much is a fee?free — opt in with tax_required, no extra request
The identity that holds either way
price + tax_and_fees = the all-in, always price + tax_amount + fee_amount = the same all-in, to the paise, on rates that split

Ask for the split the same way as on Agoda:

Request body — the two fields that opt in
{ "tax_required": true, "tax_scope": "one_fare_per_room" }
tax_scopeWhat gets itemizedExtra requests
one_fare_per_roomThe cheapest fare of each room type that can seat the partyNone
all_fares_all_roomsEvery fare of every room typeNone

Measured 20 Sep 2026 over 306 live rates across 10 properties x 2 stay windows: 82 (27%) carry a fee inside tax_amount — a median 46% of the figure, up to 58% — e.g. a 2,333.88 combined figure that was 1,034.00 Hotel GST beside 765.00 hotelier service charge and 534.88 Goibibo service fee. Those are exactly the rows where tax_amount / price reads noticeably higher than the rest, which is why publishing the combined figure with nothing saying so was a real gap.

FieldMeaning
tax_amountCombined tax + fee on an ordinary job — unchanged. With tax_required, the tax alone, with the fee moved to fee_amount.
fee_amountWith tax_required only. The hotelier's service charge plus Goibibo's own service fee, read straight from the tax line's own components. 0.0 where the whole figure is tax; null on every non-itemized rate.
tax_and_feesFree, on every offer. The combined figure, held steady even after tax_required splits tax_amount — so price + tax_and_fees is the all-in whether or not a rate was itemized.
tax_basisFree, on every offer. tax_and_fees — the figure has a fee inside it. tax_only — it does not. unknown — Goibibo itemized a tax but stated components we couldn't read. null — no tax stated at all.
Not every rate resolves. A rate whose tax-line components the origin didn't state, or that don't add up to the line they belong to, keeps its combined tax_amount and reports tax_basis: "unknown" — it never goes null the way an unresolved Agoda itemization does, because the combined figure was already there before you asked.

tax_percentage stays null on Goibibo, itemized or not — this backend states amounts, never a rate. tax_scope sent without tax_required: true is still a 400, same rule as every other OTA on this endpoint.

MakeMyTrip shares this parser and now publishes the same free tax_basis — but its tax_amount stays combined, and tax_required is still a 400 there. Booking.com has no components to read at all on its default route, so tax_required is a 400 there too — but its app_android route does itemize, free, from the app's own room list.

Result outcomes

Submitting can 4xx (validation 400, auth 401, missing scope 403, throttling 429) or, rarely, a 5xx if we couldn't accept the job on our side — retry the submit. 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


Next: Destination Search →