Get 大量 TripOptions 方法

相較於 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_dateknown_itineraries。合作夥伴可以在內部資料庫中進行詳細查詢,以便準確計算預期行程的價格。系統會向使用者顯示所有要求的 known_itineraries,因此合作夥伴可以盡量顯示價格和預訂連結,提高行程曝光度。合作夥伴仍可選擇傳回其他行程,協助 Google 更準確地比對自家 (宣傳) 廣告空間。

錯誤報告

應分別回報每個行程的錯誤。因此,建議的回應是 BulkTripOptionsResult (HTTP 200),表示 bulk_trip_options_result.itinerary_responses[i].trip_options_error 中有每趟行程的錯誤。如果整個要求意外失敗,請搭配使用 bulk_trip_options_errorBulkTripOptionsErrorType.INTERNAL_ERROR (HTTP 500)。

詳情請參閱TripOptionsErrorType

如要瞭解 HTTP 回應中傳回的狀態碼,請參閱「狀態碼和錯誤處理」。

附註

MarketDate.departure_date 指定日曆日期。為更符合運算子 API,這項功能會使用來源時區。不過,SegmentKey 仍會使用世界標準時間指定登機和抵達時間,以確保與 GetTripOptions 一致。