相較於 GetTripOptions 方法,GetBulkTripOptions
方法具有下列優點:
- 減少要求次數,並與上游 API (合作夥伴可用於自行取得資料) 保持一致,有助於更有效率地填入 Google 快取。
- 如果合作夥伴有一組預先確定的可用行程,就能更輕鬆地提供資料,不必詳細檢查行程。
- 對於較複雜的合作夥伴,仍會提供已知行程清單,盡可能比對合作夥伴的空房庫存和 Google 路線引擎產生的行程,盡可能在更多行程中顯示合作夥伴價格。
- 可擷取 Google 尚未放送的行程,以便日後調整 Google 路線引擎,更妥善地比對合作夥伴的庫存。
GetBulkTripOptionsRequest
Proto 定義
// Request sent to partners. message GetBulkTripOptionsRequest { // The MarketDates for which to return results. // Initially, only a single value will be supplied, representing one-way // trips. repeated MarketDate market_dates = 1; // List of itineraries matching market_dates that Google knows about and will // serve to end users. Partners should try to return results matching these // itineraries, as other itineraries will not be immediately useful other than // for analysis purposes. repeated ItineraryKey known_itineraries = 2; // If true, only the explicitly specified known_itineraries should be // returned. Not in use yet (always set to false). bool only_known_itineraries = 3; } message MarketDate { // ID of the stop where the traveler starts their journey. string origin_ticketing_stop_id = 1; // Stop ID of the ultimate journey destination of the traveler. string destination_ticketing_stop_id = 2; // Date of departure at the origin, in the timezone at the origin. .google.type.Date departure_date = 3; } // A full end-to-end journey for a traveler, including all vehicles used. message ItineraryKey { // All segments together should form a single trip for which a ticket can be // booked. repeated ext.travel.transport.partner.SegmentKey segment_keys = 1; }
JSON 範例
{ "market_dates": [ { "origin_ticketing_stop_id": "ZRH-1234", "destination_ticketing_stop_id": "WOL-2455", "departure_date": { "year": 2023, "month": 5, "day": 15 } } ], "known_itineraries": [ { "segment_keys": [ { "ticketing_trip_id": "123456", "from_ticketing_stop_time_id": "ZRH-1234", "to_ticketing_stop_time_id": "LUZ-1235", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 14, "minutes": 25, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 16, "minutes": 25, "seconds": 0, "nanos": 0, "utc_offset": "0s" } }, { "ticketing_trip_id": "988833", "from_ticketing_stop_time_id": "LUZ-1235", "to_ticketing_stop_time_id": "WOL-2455", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 18, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 20, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" } } ] }, { "segment_keys": [ { "ticketing_trip_id": "98765", "from_ticketing_stop_time_id": "ZRH-1234", "to_ticketing_stop_time_id": "ZUG-1235", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 14, "minutes": 30, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 16, "minutes": 30, "seconds": 0, "nanos": 0, "utc_offset": "0s" } }, { "ticketing_trip_id": "43210", "from_ticketing_stop_time_id": "ZUG-1235", "to_ticketing_stop_time_id": "WOL-2455", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 18, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 20, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" } } ] } ], "only_known_itineraries": false }
GetBulkTripOptionsResponse
Proto 定義
// Response received from partners. message GetBulkTripOptionsResponse { oneof response { BulkTripOptionsResult bulk_trip_options_result = 1; BulkTripOptionsError bulk_trip_options_error = 2; } } // A message representing a successful response. message BulkTripOptionsResult { // One response per entry in known_itineraries in the request, plus any // additional ones partners choose to return if only_known_itineraries is // false. repeated ItineraryResponse itinerary_responses = 1; } // A message for errors affecting the whole request. message BulkTripOptionsError { enum BulkTripOptionsErrorType { // An unexpected error happened during retrieval of this trip. // This indicates a bug that needs to be fixed by the partner. // This error is retriable. // Always returns HTTP 500 status code in response. INTERNAL_ERROR = 1; } BulkTripOptionsErrorType error_type = 1; // This is not user-visible and is for logging and debugging purposes only. string error_message = 2; } // A message container for an itinerary with a set of prices for that itinerary. message ItineraryResponse { // A single itinerary that this response relates to. ItineraryKey itinerary = 1; oneof response { // A set of prices for this itinerary. TripOptionSet trip_option_set = 2; // Error information about the retrieval of this itinerary. TripOptionsError trip_options_error = 3; } } // A container for multiple trip options. message TripOptionSet { // To reduce the amount of data transferred, segments.segment_key can be empty // inside these trip_options, since the segment_key is already present in // ItineraryResponse.itinerary.segment_keys. This requires the number of // segments in to each TripOption to match exactly the number of segments // inside ItineraryResponse.itinerary. We then assume that the n-th // trip_options.segments.segment_key matches the n-th itinerary.segment_key. repeated TripOption trip_options = 1; } // A travel option for users covering the whole end-to-end journey with the // partner. message TripOption { // A series of Segments that cover the whole trip option, one for each // SegmentKey in the request. repeated Segment segments = 1; // This is the lowest standard fare of all seats with the specified segments. // This field is optional if the series of segments is unavailable. // This "standard fare" should be the lowest seat price where no age or other // booking restrictions apply. The currency should be the local currency of // the origin of the first segment. Fare lowest_standard_fare = 2; // The availability status for bookings of this trip option at the // lowest_standard_fare. Availability availability = 3; // This is used to receive optional extra data via the Travel Transport API // and pass it into the ticketing deep link. // Booking links should still work without the booking token, but the token // can be used to ensure price consistency between Google Search results and // the partner booking page. string booking_token = 4 [features.field_presence = EXPLICIT]; } // A segment traveled on a single vehicle in a given Service Class. message Segment { SegmentKey segment_key = 1; // The service class used on this segment. ServiceClass service_class = 2; }
JSON 範例
{ "bulk_trip_options_result": { "itinerary_responses": [ { "itinerary": { "segment_keys": [ { "ticketing_trip_id": "123456", "from_ticketing_stop_time_id": "ZRH-1234", "to_ticketing_stop_time_id": "LUZ-1235", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 14, "minutes": 25, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 16, "minutes": 25, "seconds": 0, "nanos": 0, "utc_offset": "0s" } }, { "ticketing_trip_id": "988833", "from_ticketing_stop_time_id": "LUZ-1235", "to_ticketing_stop_time_id": "WOL-2455", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 18, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 20, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" } } ] }, "trip_option_set": { "trip_options": [ { "segments": [ { "service_class": { "type": "SECOND_CLASS" } }, { "service_class": { "type": "SECOND_CLASS" } } ], "lowest_standard_fare": { "total_amount": { "units": 10, "nanos": 0, "currency_code": "CHF" }, "line_items": [ { "line_item_type": "BASE_FARE", "amount": { "units": 9, "nanos": 750000000, "currency_code": "CHF" } }, { "line_item_type": "SERVICE_CHARGE", "amount": { "units": 0, "nanos": 250000000, "currency_code": "CHF" } } ] }, "availability": { "available": { "available_seat_count": 44, "total_seat_count": 200 } } }, { "segments": [ { "service_class": { "type": "FIRST_CLASS" } }, { "service_class": { "type": "FIRST_CLASS" } } ], "lowest_standard_fare": { "total_amount": { "units": 15, "nanos": 0, "currency_code": "CHF" }, "line_items": [ { "line_item_type": "BASE_FARE", "amount": { "units": 13, "nanos": 950000000, "currency_code": "CHF" } }, { "line_item_type": "SERVICE_CHARGE", "amount": { "units": 1, "nanos": 50000000, "currency_code": "CHF" } } ] }, "availability": { "available": { "available_seat_count": 10, "total_seat_count": 100 } } } ] } }, { "itinerary": { "segment_keys": [ { "ticketing_trip_id": "98765", "from_ticketing_stop_time_id": "ZRH-1234", "to_ticketing_stop_time_id": "ZUG-1235", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 14, "minutes": 30, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 16, "minutes": 30, "seconds": 0, "nanos": 0, "utc_offset": "0s" } }, { "ticketing_trip_id": "43210", "from_ticketing_stop_time_id": "ZUG-1235", "to_ticketing_stop_time_id": "WOL-2455", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 18, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 20, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" } } ] }, "trip_option_set": { "trip_options": [ { "segments": [ { "service_class": { "type": "SECOND_CLASS" } }, { "service_class": { "type": "SECOND_CLASS" } } ], "lowest_standard_fare": { "total_amount": { "units": 9, "nanos": 0, "currency_code": "CHF" }, "line_items": [ { "line_item_type": "BASE_FARE", "amount": { "units": 8, "nanos": 750000000, "currency_code": "CHF" } }, { "line_item_type": "SERVICE_CHARGE", "amount": { "units": 0, "nanos": 250000000, "currency_code": "CHF" } } ] }, "availability": { "available": { "available_seat_count": 30, "total_seat_count": 150 } } }, { "segments": [ { "service_class": { "type": "FIRST_CLASS" } }, { "service_class": { "type": "FIRST_CLASS" } } ], "lowest_standard_fare": { "total_amount": { "units": 14, "nanos": 0, "currency_code": "CHF" }, "line_items": [ { "line_item_type": "BASE_FARE", "amount": { "units": 12, "nanos": 950000000, "currency_code": "CHF" } }, { "line_item_type": "SERVICE_CHARGE", "amount": { "units": 1, "nanos": 50000000, "currency_code": "CHF" } } ] }, "availability": { "available": { "available_seat_count": 15, "total_seat_count": 60 } } } ] } } ] } }
JSON 錯誤範例
{ "bulk_trip_options_error": { "error_type": "INTERNAL_ERROR", "error_message": "Unexpected RoutingException during trip lookup." } }
JSON 部分錯誤
{ "bulk_trip_options_result": { "itinerary_responses": [ { "itinerary": { "segment_keys": [ { "ticketing_trip_id": "123456", "from_ticketing_stop_time_id": "ZRH-1234", "to_ticketing_stop_time_id": "LUZ-1235", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 14, "minutes": 25, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 16, "minutes": 25, "seconds": 0, "nanos": 0, "utc_offset": "0s" } }, { "ticketing_trip_id": "988833", "from_ticketing_stop_time_id": "LUZ-1235", "to_ticketing_stop_time_id": "WOL-2455", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 18, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 20, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" } } ] }, "trip_option_set": { "trip_options": [ { "segments": [ { "service_class": { "type": "SECOND_CLASS" } }, { "service_class": { "type": "SECOND_CLASS" } } ], "lowest_standard_fare": { "total_amount": { "units": 10, "nanos": 0, "currency_code": "CHF" }, "line_items": [ { "line_item_type": "BASE_FARE", "amount": { "units": 9, "nanos": 750000000, "currency_code": "CHF" } }, { "line_item_type": "SERVICE_CHARGE", "amount": { "units": 0, "nanos": 250000000, "currency_code": "CHF" } } ] }, "availability": { "available": { "available_seat_count": 44, "total_seat_count": 200 } } }, { "segments": [ { "service_class": { "type": "FIRST_CLASS" } }, { "service_class": { "type": "FIRST_CLASS" } } ], "lowest_standard_fare": { "total_amount": { "units": 15, "nanos": 0, "currency_code": "CHF" }, "line_items": [ { "line_item_type": "BASE_FARE", "amount": { "units": 13, "nanos": 950000000, "currency_code": "CHF" } }, { "line_item_type": "SERVICE_CHARGE", "amount": { "units": 1, "nanos": 50000000, "currency_code": "CHF" } } ] }, "availability": { "available": { "available_seat_count": 10, "total_seat_count": 100 } } } ] } }, { "itinerary": { "segment_keys": [ { "ticketing_trip_id": "98765", "from_ticketing_stop_time_id": "ZRH-1234", "to_ticketing_stop_time_id": "ZUG-1235", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 14, "minutes": 30, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 16, "minutes": 30, "seconds": 0, "nanos": 0, "utc_offset": "0s" } }, { "ticketing_trip_id": "43210", "from_ticketing_stop_time_id": "ZUG-1235", "to_ticketing_stop_time_id": "WOL-2455", "service_date": { "year": 2023, "month": 5, "day": 15 }, "boarding_time": { "year": 2023, "month": 5, "day": 15, "hours": 18, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" }, "arrival_time": { "year": 2023, "month": 5, "day": 15, "hours": 20, "minutes": 13, "seconds": 0, "nanos": 0, "utc_offset": "0s" } } ] }, "trip_options_error": { "error_type": "SEGMENT_KEY_NOT_FOUND", "error_message": "No matching segments found, no departures at 14:30" } } ] } }
常見情況
我們設想了下列情境,並為 API 提供特定要求形狀:
如果合作夥伴的技術能力較弱,建議只提供
market_date
。 這些合作夥伴可以對上游資料使用market_date
型查詢,並傳回對應至我們資料格式的所有結果。Google 可以篩選結果,只顯示預期行程。對於技術較為成熟的合作夥伴,請同時提供
market_date
和known_itineraries
。合作夥伴可以在內部資料庫中進行詳細查詢,以便準確計算預期行程的價格。系統會向使用者顯示所有要求的known_itineraries
,因此合作夥伴可以盡量顯示價格和預訂連結,提高行程曝光度。合作夥伴仍可選擇傳回其他行程,協助 Google 更準確地比對自家 (宣傳) 廣告空間。
錯誤報告
應分別回報每個行程的錯誤。因此,建議的回應是 BulkTripOptionsResult
(HTTP 200),表示 bulk_trip_options_result.itinerary_responses[i].trip_options_error
中有每趟行程的錯誤。如果整個要求意外失敗,請搭配使用 bulk_trip_options_error
和 BulkTripOptionsErrorType.INTERNAL_ERROR
(HTTP 500)。
詳情請參閱TripOptionsErrorType
如要瞭解 HTTP 回應中傳回的狀態碼,請參閱「狀態碼和錯誤處理」。
附註
MarketDate.departure_date
指定日曆日期。為更符合運算子 API,這項功能會使用來源時區。不過,SegmentKey 仍會使用世界標準時間指定登機和抵達時間,以確保與 GetTripOptions
一致。