    // Feeds declaration
    syntax = "proto3";

    package ext.travel.ttd.proto.feeds.v1;

    message ProductFeed {
      // Metadata for this feed.
      // Required.
      FeedMetadata feed_metadata = 1;

      // List of the products.
      // Optional. When unset in all shards, all products will be deleted.
      repeated Product products = 2;
    }


    export message FeedMetadata {
      // The current shard ID, zero-based. Shards do not need to be transferred in
      // order. Processing will start only after a full set of shards was uploaded.
      // Required when total_shards_count > 1.
      uint32 shard_id = 1;

      // Total number of shards in this transfer.
      // Required. Must be >= 1.
      uint32 total_shards_count = 2;

      // An arbitrary number used to link multiple shards to the same transfer.
      // Required when total_shards_count > 1.
      // Must be the same for all shards within the transfer.
      uint64 nonce = 3;

      enum ProcessingInstruction {
        // For compatibility, don't use.
        PROCESS_AS_UNSPECIFIED = 0;

        // This Feed upload should be processed as a complete snapshot replacing any
        // previously uploaded data of this type.
        // Supported feed types: product.
        PROCESS_AS_SNAPSHOT = 1;

        // This Feed upload should be processed as an upsert, updating or adding
        // data to the previous uploads. Supported feed types: reviews,
        // availability.
        PROCESS_AS_UPSERT = 2;
      }

      // Processing instruction for this upload.
      // Required.
      ProcessingInstruction processing_instruction = 4;

      // Maximal share of currently active products that are allowed to be removed
      // by an upload. If more products will be removed by this transfer, the whole
      // transfer will be rejected.
      // This is a safeguard against unintentional take down of a significant part
      // of the inventory. Can be set to 1.0 to allow complete inventory take down.
      // Optional.
      double max_removal_share = 5;
    }


    message Product {
      // An opaque string from the partner which uniquely identifies the product.
      // Allowed characters are alphanumeric, _, and -. Max length: 255.
      // Required.
      string id = 1;

      // The title of the product in plain text, e.g. "Horseback riding on the
      // moon". See definition of "LocalizedTextSet" message for the details on the
      // localization.
      // Recommended to not exceed length of 50 in any language. Max length: 150.
      // Required.
      LocalizedTextSet title = 2;

      // The description of the product. Limited formatting options are allowed in
      // the HTML format. Supported tags:
      //   * h1-h5
      //   * ul, ol, li
      //   * strong, italic, em
      //   * p, br
      // Other tags are not supported and will be removed. CSS, tables, style
      // property, `a` links are not supported. Images are not allowed, use the
      // related_media field instead.
      // Important notes:
      //   * Try not to use other tags except for the supported ones mentioned
      //     above, because the contents within unsupported tags will be stripped,
      //     and may lead to an undesirable user experience.
      //   * Try avoid deep nested structures like more than 3 different heading
      //     levels or nested lists. Keeping the structure flat, simple, and
      //     straightforward, helps to create a better user experience.
      //   * Do not duplicate info from the product_features field below in the
      //     description as both would normally be shown side by side.
      // Recommended to not exceed length of 10000 in any language. Max length:
      // 16000.
      // Recommended.
      LocalizedTextSet description = 3;

      // Structured details about the product features.
      // Max number of features: 100.
      // Recommended.
      repeated TextFeature product_features = 4;

      // Aggregated product rating.
      // Recommended.
      Rating rating = 5;

      // Related media such as photos or videos.
      // Max number of media: 30.
      // Recommended.
      repeated Media related_media = 6;

      // Whether Google should make use of the order in which related media are
      // listed in the feed or not. The media order would be used to influence
      // the final image order for the product in the UI.
      // Optional, default is false.
      bool use_media_order = 13;

      // Options available for this product.
      // Max number of options: 20.
      // At least one is required.
      repeated Option options = 7;

      // Operator details.
      // Optional.
      Operator operator = 8;

      // Inventory type of this product.
      enum InventoryType {
        // Default inventory type.
        INVENTORY_TYPE_DEFAULT = 0;

        // Product is an official ticket to a point of interest. To learn what
        // qualifies as official inventory, refer to the policy doc.
        INVENTORY_TYPE_OFFICIAL = 1;

        // Product is coming directly from the operator or their official
        // Connectivity Provider / ResTech.
        INVENTORY_TYPE_OPERATOR_DIRECT = 2;
      }

      // Optional.
      InventoryType inventory_type = 9;

      // Should contain only distinct values of InventoryType.
      // Max number of inventory types: 2.
      // Optional.
      repeated InventoryType inventory_types = 12;

      // Confirmation type of this product.
      enum ConfirmationType {
        // Type of confirmation is unknown.
        CONFIRMATION_TYPE_UNKNOWN = 0;

        // Booking is confirmed to the end user immediately.
        CONFIRMATION_TYPE_INSTANT = 1;

        // Booking is confirmed to the end user within 24 hours.
        CONFIRMATION_TYPE_24HRS = 2 [features.enforce_naming_style = STYLE_LEGACY];

        // Booking is confirmed to the end user within 48 hours.
        CONFIRMATION_TYPE_48HRS = 3 [features.enforce_naming_style = STYLE_LEGACY];
      }

      // Optional.
      ConfirmationType confirmation_type = 10;

      // Possible fulfillment types -- ways to obtain a document to confirm the
      // booking. Combinations are possible, e.g. mobile + printed, or
      // printed at home + in-person pickup is available.
      // At least one field must be true.
      message FulfillmentType {
        // Confirmation on a mobile phone, e.g. with a QR code.
        bool mobile = 1;

        // Printable confirmation.
        bool print_at_home = 2;

        // Admission documents to be picked up in person.
        bool pickup = 3;
      }

      // Recommended.
      FulfillmentType fulfillment_type = 11;

      // Provider brand name.
      // Recommended to not exceed length of 50 in any language.
      // Max length: 100.
      // Optional.
      LocalizedTextSet brand_name = 14;
    }


    message Option {
      // Option ID. Must be unique within a product.
      // Required.
      string id = 1;

      // The title of the option in plain text, e.g. "Sunset tour".
      //
      // If there is only a single option, the option title may be the same as the
      // product title. If multiple product options are presented, the title must be
      // unique to the option.
      // Recommended to not exceed length of 50 in any language.
      // Max length: 150.
      // Required.
      LocalizedTextSet title = 2;

      // The description of the option. Limited formatting options are allowed in
      // the HTML format, see product description field for more details.
      // Recommended to not exceed length of 10000 in any language.
      // Max length: 16000.
      // Recommended.
      LocalizedTextSet description = 3;

      // Landing page URL for this option. The page should include a button to start
      // the booking/checkout flow.
      // Required.
      DeepLink landing_page = 5;

      // Link to a list view at a higher level of available tickets and tours,
      // prominently showing this option possibly among other options.
      // Recommended.
      DeepLink landing_page_list_view = 6;

      // Additional structured details about the option features. Should not
      // duplicate the details from the product level.
      // Max number of features: 100.
      // Optional.
      repeated TextFeature option_features = 7;

      // Cancellation policy for this option.
      // Recommended.
      CancellationPolicy cancellation_policy = 8;

      // Relevant categories for this Option. Refer to the documentation for valid
      // and mutually exclusive values.
      // Max number of categories: 100.
      // Optional.
      repeated Category option_categories = 9;

      // List of locations related to this option.
      // Max number of locations: 100.
      // Recommended.
      repeated RelatedLocation related_locations = 10;

      // If true, the option is a *typical ticket* that a user would expect to buy
      // to participate in the experience, whether it's an attraction admission or
      // a slot in a tour.
      // Optional, default is false.
      bool base_ticket = 11;

      // All possible prices for this option.
      // Note: With Feed Spec v1 only single Adult price is supported. If multiple
      // price options were provided, the lowest price, possibly with notion
      // "from ..." would be displayed.
      // At least one is required.
      repeated PriceOption price_options = 12;

      // Duration of the option in seconds, where applicable, e.g. for guided tours,
      // boat trips etc. This should reflect the duration of experience (not
      // validity time).
      // Optional.
      uint32 duration_sec = 16;

      // Languages of the option. Only where relevant -- when it's important for
      // the end user to understand and/or read in the language to enjoy the
      // experience. E.g. relevant for a guided tour, but not for a mini-golf pass.
      // Max number of languages: 100.
      // Recommended.
      repeated Language languages = 14;

      // Meeting point -- the start location. Only add where relevant and
      // beneficial, e.g. the place where participant will meet the tour guide to
      // start a walking tour, the place where participant will be picked up for a
      // city tour, the dock where a cruise trip will start.
      // Optional.
      Location meeting_point = 15;
    }


    message TextFeature {
      enum TextFeatureType {
        // Don't use, for backwards compatibility only.
        TEXT_FEATURE_UNKNOWN = 0;

        // Feature is an inclusion.
        TEXT_FEATURE_INCLUSION = 1;

        // Feature is an exclusion.
        TEXT_FEATURE_EXCLUSION = 2;

        // Feature is a highlight.
        TEXT_FEATURE_HIGHLIGHT = 3;

        // Feature is a "must know".
        TEXT_FEATURE_MUST_KNOW = 4;

        // Feature represents information about safety measures.
        TEXT_FEATURE_SAFETY_INFORMATION = 5;
      }

      // Feature type.
      // Required.
      TextFeatureType feature_type = 1;

      // LocalizedTextSet content of the feature. Values support both plain-text
      // and HTML-like text for basic formatting. Supported HTML formatting tags:
      //
      // Phrase tags: <br>, <strong>, <em>, <i>:
      //   Only the four tags mentioned above are supported. <br> can be used to
      //   break lines in paragraphs, and <strong>/<em>/<i> can be used to highlight
      //   important text. Any other phrase tags will be ignored.
      //
      // All other tags and custom styles are not allowed and will be removed. Any
      // URLs, anchors, and links will be stripped, and will never be displayed to
      // end-users.
      // Recommended to not exceed length of 1000 in any language. Max length: 2000.
      // Required.
      LocalizedTextSet value = 2;
    }


    message Rating {
      // Average rating value.
      // The value must be in the range of [1, 5] and can be omitted if and only if
      // the rating_count is zero.
      double average_value = 1;

      // Number of ratings used in calculating the value.
      // Required.
      uint64 rating_count = 2;
    }


    message Media {
      // URL of this media source. Google will crawl the media hosted at this URL.
      // Max length: 2000.
      // Required.
      string url = 1;

      enum MediaType {
        // Don't use, for backwards compatibility only.
        MEDIA_TYPE_UNSPECIFIED = 0;

        // Indicates the media provided by the url is a photo.
        MEDIA_TYPE_PHOTO = 1;
      }

      // Type of this media source.
      // Required.
      MediaType type = 2;

      // Attribution information about the source of the media. Note that if
      // the attribution is required to display with the media to give credit to
      // photographer or agency, this field must be set.
      // Recommended to not exceed length of 1000 in any language.
      // Max length: 2000.
      // Optional.
      LocalizedTextSet attribution = 3;
    }


    message Category {
      // Refer to the documentation for the valid values list.
      // Required.
      string label = 1;
    }


    // Defines relation between an option and a location.
    message RelatedLocation {
      // Location related to an option. Can be a POI (e.g. Eiffel tower),
      // neighbourhood (e.g. Old Town) or an address / arbitrary map point.
      // Required.
      Location location = 1;

      enum RelationType {
        RELATION_TYPE_UNSPECIFIED = 0;

        // Relation grants admission to this related location.
        RELATION_TYPE_ADMISSION_TICKET = 1;

        // Relation declares an additional service which doesn't get the user into
        // the related location, e.g. a parking ticket, a temporary exhibition, etc.
        RELATION_TYPE_SUPPLEMENTARY_ADDON = 2;

        // Location is related but relation does not allow admission or admission is
        // irrelevant, e.g. location is a square highlighted in a city tour.
        RELATION_TYPE_RELATED_NO_ADMISSION = 3;
      }

      // Relation type of an option for the given location.
      // Required.
      RelationType relation_type = 2;
    }


    // Deep link definition. Can include value parameters that will be expanded on
    // serve time.
    message DeepLink {
      // Landing page URL template for desktop. If both `url` and `localized_url`
      // are provided, the former is used as a fallback in case
      // no URL matches the user's locale.
      // Max length: 2000.
      // Either `url` or `localized_url` is required.
      string url = 1;

      // Localized landing page URL template for desktop. If both `url` and
      // `localized_url` are provided, the former is used as a fallback in case
      // no URL matches the user's locale.
      // Max length: 2000.
      // Max number of locales: 50.
      // Either `url` or `localized_url` is required.
      LocalizedTextSet localized_url = 3;

      reserved 2, 4;
    }


    message Operator {
      // Operator name.
      // Recommended to not exceed length of 50 in any language. Max length: 100.
      // Required.

      LocalizedTextSet name = 1;

      // Operator business name as it is registered in Google Business Profile and
      // appearing on Google Maps.
      // Recommended to not exceed length of 50 in any language.
      // Max length: 100.
      // Required.
      LocalizedTextSet google_business_profile_name = 4;

      // Operator phone number. Prefer full international phone number format.
      // Max length: 64.
      // Optional.
      string phone_number = 2;

      // List of operator locations.
      // Max number of locations: 1.
      // Optional.
      repeated Location locations = 3;
    }


    message Language {
      // A BCP 47 compliant language code such as "en" or "de-CH".
      string code = 1;
    }


    export message PriceOption {
      // Unique ID within the price set.
      // Max length: 255.
      // Required.
      string id = 1;

      // Short description of the price option, e.g. "Adult weekday".
      // Max length: 150.
      // Required.
      string title = 2;

      // Price value, must match the final price on the checkout page, including all
      // taxes and charges, see price policy. Currency will be converted to the user
      // currency on rendering.
      // Required when is_free is false.
      google.type.Money price = 3;

      // Admission or ticket is free. Must be set to true for zero-price options.
      // Optional, default is false.
      bool is_free = 4;

      // List of geographical regions this price is applicable to. If empty,
      // applicable to all locations.
      // Optional.
      repeated GeoCriterion geo_criteria = 5;

      // Break down of fees and taxes included in the price above.
      // Optional.
      PriceFeesAndTaxes fees_and_taxes = 6;
    }


    message GeoCriterion {
      // 2-letter country code as defined in ISO 3166-1.
      // Required.
      string country_code = 1;

      // If true, criterion is negative (the country code is excluded).
      bool is_negative = 2;
    }


    message PriceFeesAndTaxes {
      // Booking fees included in the final product price for a single ticket.
      // Optional.
      google.type.Money per_ticket_fee = 1;

      // State taxes included in the final product price for a single ticket.
      // Optional.
      google.type.Money per_ticket_tax = 2;
    }


    export message Location {
      // At least one of (location, description) must be set, and we highly
      // recommend populating location wherever possible.
      //
      // To emphasize, both fields can be populated together, e.g. you can set
      // Central Park New York for the location and "In front of the 72 Street
      // Station" for the description.
      GeoLocation location = 1;

      // Additional description in human-readable form, e.g.
      //     "On the left side of the fountain on the Palace square".
      // At least one of (location, description) must be set.
      // Recommended to not exceed length of 1000 in any language. Max length: 2000.
      LocalizedTextSet description = 2;
    }


    message GeoLocation {
      // Required (exactly one variant from oneof).
      // See
      // https://developers.google.com/travel/things-to-do/guides/partner-integration/location
      // for detailed guidelines.
      oneof value {
        // Place ID as defined by the Places API:
        //   https://developers.google.com/places/web-service/place-id
        //
        // Uniquely identifies a POI on Google.
        // It can be sourced using the Places API endpoints, for instance Place
        // Search or Place Autocomplete, or manually using the Find Location Matches
        // tool in Things to Do Center.
        string place_id = 1;

        // Legacy single-line address.
        // Components are expected to be comma-separated, with the first component
        // being the place name as it is displayed on Google.
        // For higher matching accuracy, use the street address shown on Google for
        // the place.
        //
        // Examples:
        // - "Colosseum, Piazza del Colosseo, 1, 00184 Roma RM, Italy"
        // - "The British Museum, Great Russell St, London WC1B 3DG, United Kingdom"
        //
        // Max length: 200.
        //
        // Deprecated: use `place_info` for higher matching accuracy, which provides
        // a separate field for the place name and supports both structured and
        // unstructured address formats.
        string address = 3 [deprecated = true];

        // Structured place information.
        PlaceInfo place_info = 4;

        // Business Profile ID, as found in the Google Business Profile settings
        // page. Use this field when sourcing locations directly from the place
        // owner, who has access to the Google Business Profile for the place and
        // can provide such ID.
        uint64 business_profile_id = 5;

        // Geographic coordinates.
        // This field can only be used to determine a city or geographical region,
        // as it is too ambiguous to identify a specific place or businesses.
        // Use `place_info` instead to match to a specific place by name and
        // coordinates.
        google.type.LatLng lat_lng = 2;
      }
    }


    message PlaceInfo {
      // Place or business name.
      // For higher matching accuracy, this should be the same as the name shown on
      // Google for the place. For places with a claimed Google Business Profile,
      // this should be the same as the business name configured in the business
      // profile.
      // Max length: 100.
      // Required.
      string name = 1;

      // Phone number, including the international prefix.
      // For higher matching accuracy, this should be the same as the phone number
      // shown on Google for the place.
      // It can include commonly used separator characters.
      // Examples: "+1 212-363-3200", "+91 562 222 7261".
      // Max length: 30.
      // Optional.
      string phone_number = 2;

      // Website URL shown on Google for the place, preferably the URL linked from
      // the business listing in Google Maps or Search for the place.
      // Max length: 1000.
      // Optional.
      string website_url = 3;

      // Geographic coordinates of the place.
      // If left empty, Google will infer the coordinates from the address.
      // Optional, but either `coordinates` or one of `address_type` must be
      // provided.
      google.type.LatLng coordinates = 4;

      // Optional, but either `coordinates` or one of `address_type` must be
      // provided.
      oneof address_type {
        // Structured address.
        // Prefer this format whenever possible for higher matching accuracy.
        StructuredAddress structured_address = 5;

        // Unstructured address.
        // It should not include the place or business name, which must instead be
        // provided separately using the `name` field.
        //
        // Examples:
        // - `name`: "Colosseum", `unstructured_address`: "Piazza del Colosseo, 1,
        // 00184 Roma RM, Italy".
        // - `name`: "The British Museum", `unstructured_address`: "Great Russell
        // St, London WC1B 3DG, United Kingdom".
        //
        // Max length: 400.
        string unstructured_address = 6;
      }
    }


    message StructuredAddress {
      // Street address, including house number and any other component that cannot
      // be provided using the more specific fields defined below. It should not
      // include the place or business name, which must instead be provided
      // separately using the `name` field under `PlaceInfo`. It should also not
      // include postal code, locality or country as those should be provided using
      // the corresponding fields below.
      //
      // Examples:
      // - "Piazza del Colosseo, 1" for the Colosseum.
      // - "Great Russell St" for The British Museum.
      // - "Champ de Mars, 5 Av. Anatole France" for the Eiffel Tower.
      //
      // Max length: 200.
      // Required.
      string street_address = 1;

      // Locality, generally referring to the city/town portion of an address.
      // Examples: "New York", "Rome", "London", "Tokyo".
      // In regions of the world where localities are not well defined or do not fit
      // into this structure well, leave empty.
      // Max length: 80.
      // Optional.
      string locality = 2;

      // Highest administrative subdivision used for postal addresses of the
      // specific country or region. This can be a state, a region, a province, an
      // oblast, a prefecture, etc.
      // It can be an abbreviation or a full name, depending on how the region is
      // usually represented in the postal addresses of the specific country. For
      // example, "CA" or "California" for US addresses, "RM" for Rome province in
      // Italy.
      // Many countries don't use an administrative area in postal addresses. For
      // instance, this field should not be used for addresses in Switzerland.
      // Max length: 80.
      // Optional.
      string administrative_area = 3;

      // The postal code or zip code.
      // Examples: "75007", "WC1B 3DG", etc.
      // Required if the country supports postal codes, otherwise it should be left
      // empty.
      // Max length: 30.
      // Optional.
      string postal_code = 4;

      // Country code, as defined by Unicode's "CLDR", itself based on the ISO 3166
      // alpha-2 standard. See
      // https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html.
      //
      // Examples: "US" for the United States, "FR" for France, "GB" for the United
      // Kingdom, etc.
      // Max length: 2.
      // Required.
      string country_code = 5;
    }


    // Values of the localized fields.
    export message LocalizedTextSet {
      // Per-locale LocalizedText values.
      // Maximum repeatedness: 50
      repeated google.type.LocalizedText localized_texts = 1;
    }


    // Cancellation policy for a product.
    export message CancellationPolicy {
      // Defines a single refund condition. Multiple refund conditions could be
      // used together to describe "refund steps" as various durations before the
      // service start time.
      message RefundCondition {
        // Duration in seconds before the start time, until when the customer can
        // receive a refund for part of the service's cost specified in
        // `refund_percent`.
        // When unset or set to 0 the service can be cancelled at any time.
        // Optional.
        uint32 min_duration_before_start_time_sec = 1;

        // The percent that can be refunded, as long as the service booking is
        // cancelled at least `min_duration_before_start_time` before the service
        // start time, in the range of [0, 100].
        // When unset or set to 0, the service is not refundable. When set to 100
        // this service is fully refundable.
        // Optional.
        uint32 refund_percent = 2;

        // A flat fee deducted on refund. Could be used separately, or in
        // combination with the refund_percent above. In the latter case, refund
        // percent applies first, then the fee is deducted.
        // Optional.
        google.type.Money refund_fee = 3;
      }

      // Zero or more refund conditions applicable to the policy.
      // Max number of refund conditions: 10.
      repeated RefundCondition refund_conditions = 1;
    }


    // Represents an amount of money with its currency type.
    message google.type.Money {
      // The three-letter currency code defined in ISO 4217.
      string currency_code = 1;

      // The whole units of the amount.
      // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar.
      int64 units = 2;

      // Number of nano (10^-9) units of the amount.
      // The value must be between -999,999,999 and +999,999,999 inclusive.
      // If `units` is positive, `nanos` must be positive or zero.
      // If `units` is zero, `nanos` can be positive, zero, or negative.
      // If `units` is negative, `nanos` must be negative or zero.
      // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.
      int32 nanos = 3;
    }

    // An object that represents a latitude/longitude pair. This is expressed as a
    // pair of doubles to represent degrees latitude and degrees longitude. Unless
    // specified otherwise, this must conform to the
    // <a href="http://www.unoosa.org/pdf/icg/2012/template/WGS_84.pdf">WGS84
    // standard</a>. Values must be within normalized ranges.
    message google.type.LatLng {
      // The latitude in degrees. It must be in the range [-90.0, +90.0].
      double latitude = 1;

      // The longitude in degrees. It must be in the range [-180.0, +180.0].
      double longitude = 2;
    }

    message google.type.LocalizedText {
      // Localized string in the language corresponding to `language_code' below.
      string text = 1;

      // The text's BCP-47 language code, such as "en-US" or "sr-Latn".
      //
      // For more information, see
      // http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
      string language_code = 2;
    }