Adding a new test client that supports the Order-based booking flow
diff --git a/README.md b/README.md
index 2730405..f6454c1 100644
--- a/README.md
+++ b/README.md
@@ -5,15 +5,15 @@
 
 ## Testing Client
 
-Before using the test utility, the Go programming language must be installed
-on your workstation. A precompiled Go binary for your operating system can
-be [found here](https://golang.org/dl/)
+Before using either the order based or booking based test utilites, the Go
+programming language must be installed on your workstation. A precompiled Go 
+binary for your operating system can be [found here](https://golang.org/dl/)
 
 This guide will assume you're using the default GOPATH and subsequent GOBIN.
 For a comprehensive explanation of the GOPATH env variable please see
 [this document](https://golang.org/dl/) by the Go team.
 
-### Installing the utility with Linux
+### Installing the utilities with Linux
 
 First, build your Go directory structure. A comprehensive guide on the intended
 structure of Go code can be [found here.](https://golang.org/doc/code.html)
@@ -30,7 +30,7 @@
 
     source ~/.bashrc
 
-Next, retrieve the utility from the
+Next, retrieve the utilities from the
 [maps-booking-v3 repository](https://maps-booking.googlesource.com/)
 
     git clone https://maps-booking.googlesource.com/maps-booking-v3 $HOME/go/src/github.com/maps-booking-v3/
@@ -39,9 +39,9 @@
 
     cd $HOME/go
     go get -d ./...
-    go install $HOME/go/src/github.com/maps-booking-v3/testclient/main.go
+    go install $HOME/go/src/github.com/maps-booking-v3/testclient/<orderBasedTestClient.go or bookingBasedTestClient.go>
 
-### Installing the utility with Windows Powershell
+### Installing the utilities with Windows Powershell
 
 First, build your Go directory structure. A comprehensive guide on the intended
 structure of Go code can be [found here.](https://golang.org/doc/code.html)
@@ -57,7 +57,7 @@
     $env:GOPATH = (go env GOPATH)
     $env:GOBIN = (go env GOPATH) + "\bin"
 
-Next, retrieve the utility from the
+Next, retrieve the utilities from the
 [maps-booking-v3 repository](https://maps-booking.googlesource.com/)
 
     git clone https://maps-booking.googlesource.com/maps-booking-v3 $env:HOME\go\src\github.com\maps-booking-v3\
@@ -66,9 +66,9 @@
 
     cd $env:HOME\go
     go get -d .\...
-    go install $env:HOME\go\src\github.com\maps-booking-v3\testclient\main.go
+    go install $env:HOME\go\src\github.com\maps-booking-v3\testclient\<orderBasedTestClient.go or bookingBasedTestClient.go>
 
-### Using the utility
+### Using the utilities
 
 After following the install steps above an executable should now live in
 
@@ -81,6 +81,7 @@
 All available flags can be displayed using the '--help' flag. The currently
 accepted flags are:
 
+    bookingBasedTestClient.go
     -all_tests
         Whether to test all endpoints.
     -availability_feed string
@@ -121,6 +122,32 @@
     -full_server_name string
         Fully qualified domain name. Same name used to sign CN. Only necessary if ca_file is specified and the base URL differs from the server address.
 
+    orderBasedTestClient.go
+    -all_tests
+        Whether to test all endpoints syncronously, including ListOrders flow.
+    -availability_feed string
+        Absolute path to availability feed required for all tests except health. Feeds can be in either json or pb3 format
+    -ca_file string
+        Absolute path to your server's Certificate Authority root cert. Downloading all roots currently recommended by the Google Internet Authority is a suitable alternative https://pki.google.com/roots.pem. Leave blank to connect using http rather than https.
+    -check_order_test
+        Whether to test the CheckOrderFulfillability endpoint.
+    -create_order_test
+        Whether to test the CreateOrder endpoint.
+    -credentials_file string
+        File containing credentials for your server. Leave blank to bypass authentication. File should have exactly one line of the form 'username:password'.
+    -full_server_name string
+        Fully qualified domain name. Same name used to sign CN. Only necessary if ca_file is specified and the base URL differs from the server address.
+    -health_check_test
+        Whether to test the Health endpoint.
+    -num_test_slots int
+        Maximum number of slots to test from availability_feed. Slots will be selected randomly (default 10)
+    -output_dir string
+        Absolute path of dir to dump log file.
+    -server_addr string
+        Your http server's address in the format of host:port (default "example.com:80")
+    -service_feed string
+        Absolute path to service feed required for all tests except health. Feeds can be in either json or pb3 format
+
 Example Usage:
 
     bin/main -server_addr="localhost:443" -check_availability_test=true -output_dir="/tmp" -availability_feed="/tmp/test.json"
@@ -131,12 +158,12 @@
 
 It is important that as part of testing you verify all aspects of booking server
 invocation - authentication, availability, cancellation, and rescheduling tests.
-As the final step, you must be able to execute the test utility with the flag
--all_tests=true and it should successfully pass the entire series of tests.
+As the final step, you must be able to execute the designated utility with the
+flag -all_tests=true and it should successfully pass the entire series of tests.
 
 ### Parsing the output
 
-The test utility will output a file with the prefix 'http_test_client_log_'
+Both test utilities will output a file with the prefix 'http_test_client_log_'
 followed by a timestamp in RFC3339 format. The output file contains a
 complete log of all Requests and Responses sent/received by the testing tool as
 well as diffs of the expected response in the event of errors.
diff --git a/api/api.go b/api/api.go
index f637ff7..ca2d365 100644
--- a/api/api.go
+++ b/api/api.go
@@ -139,7 +139,7 @@
 	if err != nil {
 		return fmt.Errorf("could not complete health check: %v", err)
 	}
-	log.Printf("health check success!")
+	log.Println("health check success!")
 	return nil
 }
 
@@ -164,7 +164,7 @@
 	return bodyString, nil
 }
 
-// CheckAvailability beforms a maps booking availability check on all supplied availability slots. This function
+// CheckAvailability performs a maps booking availability check on all supplied availability slots. This function
 // will return all slots with a valid return.
 func CheckAvailability(a *fpb.Availability, conn *HTTPConnection) error {
 	slot, err := utils.BuildSlotFrom(a)
@@ -399,3 +399,169 @@
 	}
 	return CancelBooking(resp.GetBooking(), conn)
 }
+
+// CheckOrderFulfillability attempts to send a CheckOrderFulfillabilityRequest
+// to the connection endpoint and diff the results with what are expected.
+func CheckOrderFulfillability(merchantID string, lineItems []*mpb.LineItem, conn *HTTPConnection) error {
+	reqPB := &mpb.CheckOrderFulfillabilityRequest{
+		MerchantId: merchantID,
+		Item:       lineItems,
+	}
+	req, err := conn.marshaler.MarshalToString(reqPB)
+	if err != nil {
+		return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
+	}
+	httpResp, err := sendRequest("CheckOrderFulfillability", req, conn)
+	if err != nil {
+		return fmt.Errorf("invalid response. CheckOrderFulfillability yielded error: %v", err)
+	}
+	var resp mpb.CheckOrderFulfillabilityResponse
+	if err := jsonpb.UnmarshalString(httpResp, &resp); err != nil {
+		return fmt.Errorf("CheckOrderFulfillability: Could not parse HTTP response to pb3: %v", err)
+	}
+
+	// We ignore price for now. This is difficult to verify without knowing aggregator specific taxes/fees.
+	orderFulfillability := resp.GetFulfillability()
+	// TODO(ccawdrey): Add validation cases for other OrderFulFillability enums.
+	if diff := cmp.Diff(orderFulfillability.GetResult(), mpb.OrderFulfillability_CAN_FULFILL); diff != "" {
+		return fmt.Errorf("invalid response. CheckOrderFulfillability.Fulfillability.OrderFulfillabilityResult differ (-got +want)\n%s", diff)
+	}
+	if orderFulfillability.GetUnfulfillableReason() != "" {
+		return errors.New("invalid response. CheckOrderFulfillability.UnfulfillableReason should be empty")
+	}
+
+	var respLineItems []*mpb.LineItem
+	for _, lineItemFulfillability := range orderFulfillability.GetItemFulfillability() {
+		if diff := cmp.Diff(lineItemFulfillability.GetResult(), mpb.LineItemFulfillability_CAN_FULFILL); diff != "" {
+			return fmt.Errorf("invalid response. CheckOrderFulfillability.Fulfillability.ItemFulfillability.Result for LineItem %v -- differ (-got +want)\n%s", lineItemFulfillability.GetItem(), diff)
+		}
+		if lineItemFulfillability.GetUnfulfillableReason() != "" {
+			return errors.New("invalid response. CheckOrderFulfillability.Fulfillability.ItemFulfillability.UnfulfillableReason should be empty")
+		}
+
+		respLineItems = append(respLineItems, lineItemFulfillability.GetItem())
+	}
+
+	if err = utils.ValidateLineItems(respLineItems, lineItems, false); err != nil {
+		return fmt.Errorf("invalid response. CheckOrderFulfillability %v", err)
+	}
+
+	return nil
+}
+
+// CreateOrder will attempt to build an order from a merchant id and array of line orders.
+func CreateOrder(merchantID string, lineItems []*mpb.LineItem, conn *HTTPConnection) (*mpb.Order, error) {
+	gen := rand.New(rand.NewSource(time.Now().UnixNano()))
+
+	reqOrder := &mpb.Order{
+		UserInformation: &mpb.UserInformation{
+			UserId:     userID,
+			GivenName:  firstName,
+			FamilyName: lastName,
+			Telephone:  telephone,
+			Email:      email,
+		},
+		PaymentInformation: &mpb.PaymentInformation{
+			PrepaymentStatus: mpb.PrepaymentStatus_PREPAYMENT_NOT_PROVIDED,
+		},
+		MerchantId: merchantID,
+		Item:       lineItems,
+	}
+	reqPB := &mpb.CreateOrderRequest{
+		Order:            reqOrder,
+		IdempotencyToken: strconv.Itoa(gen.Intn(1000000)),
+	}
+
+	req, err := conn.marshaler.MarshalToString(reqPB)
+	if err != nil {
+		return nil, fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
+	}
+	httpResp, err := sendRequest("CreateOrder", req, conn)
+	if err != nil {
+		return nil, fmt.Errorf("invalid response. CreateOrder yielded error: %v", err)
+	}
+	var resp mpb.CreateOrderResponse
+	if err := jsonpb.UnmarshalString(httpResp, &resp); err != nil {
+		return nil, fmt.Errorf("CreateOrder: Could not parse HTTP response to pb3: %v", err)
+	}
+
+	if resp.GetOrderFailure() != nil {
+		return nil, fmt.Errorf("invalid response. CreateOrder contains OrderFailure for request %v", reqPB)
+	}
+
+	if err = utils.ValidateOrder(*resp.GetOrder(), *reqOrder); err != nil {
+		return nil, fmt.Errorf("invalid response. CreateOrder %v", err)
+	}
+
+	// Perform idempotency test.
+	log.Printf("Idempotency check")
+	idemHTTPResp, err := sendRequest("CreateOrder", req, conn)
+	if err != nil {
+		return nil, fmt.Errorf("invalid response. Idempotency check yielded error: %v", err)
+	}
+	var idemResp mpb.CreateOrderResponse
+	if err := jsonpb.UnmarshalString(idemHTTPResp, &idemResp); err != nil {
+		return nil, fmt.Errorf("CreateOrder idem: Could not parse HTTP response to pb3: %v", err)
+	}
+
+	if idemResp.GetOrderFailure() != nil {
+		return nil, errors.New("Idempotency check invalid. CreateOrder contains OrderFailure")
+	}
+	if err = utils.ValidateOrder(*idemResp.GetOrder(), *resp.GetOrder()); err != nil {
+		return nil, fmt.Errorf("Idempotency check invalid  %v", err)
+	}
+
+	return resp.GetOrder(), nil
+}
+
+func sendListOrdersRequest(reqPB *mpb.ListOrdersRequest, conn *HTTPConnection) (mpb.ListOrdersResponse, error) {
+	req, err := conn.marshaler.MarshalToString(reqPB)
+	if err != nil {
+		return mpb.ListOrdersResponse{}, fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
+	}
+	httpResp, err := sendRequest("ListOrders", req, conn)
+	if err != nil {
+		return mpb.ListOrdersResponse{}, fmt.Errorf("invalid response. ListOrders yielded error: %v", err)
+	}
+	var resp mpb.ListOrdersResponse
+	if err := jsonpb.UnmarshalString(httpResp, &resp); err != nil {
+		return resp, fmt.Errorf("ListOrders: Could not parse HTTP response to pb3: %v", err)
+	}
+
+	return resp, nil
+}
+
+// ListOrders first checks that the number and contents of the server's order state
+// are consistent with what the test client assumes is present.
+func ListOrders(orders []*mpb.Order, conn *HTTPConnection) error {
+	if len(orders) == 0 {
+		return errors.New("at least one order must be present for ListOrders to succeed")
+	}
+
+	// UserId check first.
+	reqPB := &mpb.ListOrdersRequest{
+		UserId: userID,
+	}
+	respUser, err := sendListOrdersRequest(reqPB, conn)
+	if err != nil {
+		return err
+	}
+	if err = utils.ValidateOrders(respUser.GetOrder(), orders); err != nil {
+		return fmt.Errorf("invalid response. ListOrders %v for request %v", err, reqPB)
+	}
+
+	// Still here? OrderId check.
+	reqPB.Reset()
+	for _, order := range orders {
+		reqPB.OrderId = append(reqPB.OrderId, order.GetOrderId())
+	}
+	respOrder, err := sendListOrdersRequest(reqPB, conn)
+	if err != nil {
+		return err
+	}
+	if err = utils.ValidateOrders(respOrder.GetOrder(), orders); err != nil {
+		return fmt.Errorf("invalid response. ListOrders %v for request %v", err, reqPB)
+	}
+
+	return nil
+}
diff --git a/feeds/availability_feed.pb.go b/feeds/availability_feed.pb.go
deleted file mode 100644
index c41f821..0000000
--- a/feeds/availability_feed.pb.go
+++ /dev/null
@@ -1,637 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// source: availability_feed.proto
-
-/*
-Package maps_booking_feeds is a generated protocol buffer package.
-
-It is generated from these files:
-	availability_feed.proto
-
-It has these top-level messages:
-	FeedMetadata
-	AvailabilityFeed
-	ServiceAvailability
-	Availability
-	Resources
-	TimeRange
-*/
-package maps_booking_feeds
-
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
-
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
-
-type FeedMetadata_ProcessingInstruction int32
-
-const (
-	// By default we will assume that this feed is an incremental feed.
-	FeedMetadata_PROCESS_UNKNOWN FeedMetadata_ProcessingInstruction = 0
-	// This Feed message is one shard of a complete feed. Anything previously
-	// supplied by this partner will be deleted; the contents of this feed
-	// represent the entire state of the world.
-	FeedMetadata_PROCESS_AS_COMPLETE FeedMetadata_ProcessingInstruction = 1
-	// This Feed message is one shard of an incremental feed. Existing entities
-	// will be left untouched except as modified in this feed.
-	FeedMetadata_PROCESS_AS_INCREMENTAL FeedMetadata_ProcessingInstruction = 2
-)
-
-var FeedMetadata_ProcessingInstruction_name = map[int32]string{
-	0: "PROCESS_UNKNOWN",
-	1: "PROCESS_AS_COMPLETE",
-	2: "PROCESS_AS_INCREMENTAL",
-}
-var FeedMetadata_ProcessingInstruction_value = map[string]int32{
-	"PROCESS_UNKNOWN":        0,
-	"PROCESS_AS_COMPLETE":    1,
-	"PROCESS_AS_INCREMENTAL": 2,
-}
-
-func (x FeedMetadata_ProcessingInstruction) String() string {
-	return proto.EnumName(FeedMetadata_ProcessingInstruction_name, int32(x))
-}
-func (FeedMetadata_ProcessingInstruction) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor0, []int{0, 0}
-}
-
-type FeedMetadata struct {
-	// Instructs us how to process the feed: either as a shard of a complete feed,
-	// or as a shard of an incremental update.
-	ProcessingInstruction FeedMetadata_ProcessingInstruction `protobuf:"varint,1,opt,name=processing_instruction,json=processingInstruction,enum=maps.booking.feeds.FeedMetadata_ProcessingInstruction" json:"processing_instruction,omitempty"`
-	// The current shard and total number of shards for this feed.
-	//
-	// Shard number is assumed to be zero-based.
-	//
-	// There does not need to be any relationship to the file name.
-	//
-	// Shards do not need to be transferred in order, and they may not be
-	// processed in order.
-	ShardNumber int32 `protobuf:"varint,2,opt,name=shard_number,json=shardNumber" json:"shard_number,omitempty"`
-	TotalShards int32 `protobuf:"varint,3,opt,name=total_shards,json=totalShards" json:"total_shards,omitempty"`
-	// An identifier that must be consistent across all shards in a feed.
-	// This value must be globally unique across each feed type.
-	//
-	// This value ensures that complete feeds spanning multiple shards are
-	// processed together correctly.
-	//
-	// Clients only need to set this value when the processing_instruction is set
-	// to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by
-	// total_shards).
-	//
-	// Feeds that span multiple shards must set this nonce to the same value.
-	Nonce uint64 `protobuf:"varint,5,opt,name=nonce" json:"nonce,omitempty"`
-	// The timestamp at which this feed shard was generated.
-	//
-	// In Unix time format (seconds since the epoch).
-	GenerationTimestamp int64 `protobuf:"varint,4,opt,name=generation_timestamp,json=generationTimestamp" json:"generation_timestamp,omitempty"`
-}
-
-func (m *FeedMetadata) Reset()                    { *m = FeedMetadata{} }
-func (m *FeedMetadata) String() string            { return proto.CompactTextString(m) }
-func (*FeedMetadata) ProtoMessage()               {}
-func (*FeedMetadata) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
-
-func (m *FeedMetadata) GetProcessingInstruction() FeedMetadata_ProcessingInstruction {
-	if m != nil {
-		return m.ProcessingInstruction
-	}
-	return FeedMetadata_PROCESS_UNKNOWN
-}
-
-func (m *FeedMetadata) GetShardNumber() int32 {
-	if m != nil {
-		return m.ShardNumber
-	}
-	return 0
-}
-
-func (m *FeedMetadata) GetTotalShards() int32 {
-	if m != nil {
-		return m.TotalShards
-	}
-	return 0
-}
-
-func (m *FeedMetadata) GetNonce() uint64 {
-	if m != nil {
-		return m.Nonce
-	}
-	return 0
-}
-
-func (m *FeedMetadata) GetGenerationTimestamp() int64 {
-	if m != nil {
-		return m.GenerationTimestamp
-	}
-	return 0
-}
-
-type AvailabilityFeed struct {
-	Metadata            *FeedMetadata          `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"`
-	ServiceAvailability []*ServiceAvailability `protobuf:"bytes,2,rep,name=service_availability,json=serviceAvailability" json:"service_availability,omitempty"`
-}
-
-func (m *AvailabilityFeed) Reset()                    { *m = AvailabilityFeed{} }
-func (m *AvailabilityFeed) String() string            { return proto.CompactTextString(m) }
-func (*AvailabilityFeed) ProtoMessage()               {}
-func (*AvailabilityFeed) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
-
-func (m *AvailabilityFeed) GetMetadata() *FeedMetadata {
-	if m != nil {
-		return m.Metadata
-	}
-	return nil
-}
-
-func (m *AvailabilityFeed) GetServiceAvailability() []*ServiceAvailability {
-	if m != nil {
-		return m.ServiceAvailability
-	}
-	return nil
-}
-
-type ServiceAvailability struct {
-	// If provided, we will consider the Availability entities provided to be a
-	// complete snapshot from [start_timestamp_restrict, end_timestamp_restrict).
-	// That is, all existing availability will be deleted if the following
-	// condition holds true:
-	//
-	//    start_timestamp_restrict <= Availability.start_sec &&
-	//    Availability.start_sec < end_timestamp_restrict
-	//
-	// If a resource_restrict message is set, the condition is further restricted:
-	//
-	//    Availability.resource.staff_id == resource_restrict.staff_id &&
-	//    Availability.resource.room_id == resource_restrict.room_id
-	//
-	// These fields are typically used to provide a complete update of
-	// availability in a given time range.
-	//
-	// Setting start_timestamp_restrict while leaving end_timestamp_restrict unset
-	// is interpreted to mean all time beginning at start_timestamp_restrict.
-	//
-	// Setting end_timestamp_restrict while leaving start_timestamp_restrict unset
-	// is interpreted to mean all time up to the end_timestamp_restrict.
-	//
-	// In Unix time format (seconds since the epoch).
-	StartTimestampRestrict int64 `protobuf:"varint,1,opt,name=start_timestamp_restrict,json=startTimestampRestrict" json:"start_timestamp_restrict,omitempty"`
-	EndTimestampRestrict   int64 `protobuf:"varint,2,opt,name=end_timestamp_restrict,json=endTimestampRestrict" json:"end_timestamp_restrict,omitempty"`
-	// If provided, the timestamp restricts will be applied only to the given
-	// merchant or service.
-	//
-	// These fields are typically used to provide complete snapshot of
-	// availability in a given range (defined above) for a specific merchant or
-	// service.
-	//
-	// Leaving these fields unset, or setting these to the empty string or null,
-	// is interpreted to mean that no restrict is intended.
-	MerchantIdRestrict string `protobuf:"bytes,3,opt,name=merchant_id_restrict,json=merchantIdRestrict" json:"merchant_id_restrict,omitempty"`
-	ServiceIdRestrict  string `protobuf:"bytes,4,opt,name=service_id_restrict,json=serviceIdRestrict" json:"service_id_restrict,omitempty"`
-	// Setting resources_restrict further restricts the scope of the update to
-	// just this set of resources. All id fields of the resources must match
-	// exactly.
-	ResourcesRestrict *Resources      `protobuf:"bytes,6,opt,name=resources_restrict,json=resourcesRestrict" json:"resources_restrict,omitempty"`
-	Availability      []*Availability `protobuf:"bytes,5,rep,name=availability" json:"availability,omitempty"`
-}
-
-func (m *ServiceAvailability) Reset()                    { *m = ServiceAvailability{} }
-func (m *ServiceAvailability) String() string            { return proto.CompactTextString(m) }
-func (*ServiceAvailability) ProtoMessage()               {}
-func (*ServiceAvailability) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
-
-func (m *ServiceAvailability) GetStartTimestampRestrict() int64 {
-	if m != nil {
-		return m.StartTimestampRestrict
-	}
-	return 0
-}
-
-func (m *ServiceAvailability) GetEndTimestampRestrict() int64 {
-	if m != nil {
-		return m.EndTimestampRestrict
-	}
-	return 0
-}
-
-func (m *ServiceAvailability) GetMerchantIdRestrict() string {
-	if m != nil {
-		return m.MerchantIdRestrict
-	}
-	return ""
-}
-
-func (m *ServiceAvailability) GetServiceIdRestrict() string {
-	if m != nil {
-		return m.ServiceIdRestrict
-	}
-	return ""
-}
-
-func (m *ServiceAvailability) GetResourcesRestrict() *Resources {
-	if m != nil {
-		return m.ResourcesRestrict
-	}
-	return nil
-}
-
-func (m *ServiceAvailability) GetAvailability() []*Availability {
-	if m != nil {
-		return m.Availability
-	}
-	return nil
-}
-
-// An availability of the merchant's service, indicating time and number
-// of spots.
-// The availability feed should be a list of this message.
-// Please note that it's up to the partner to call out all the possible
-// availabilities.
-// If a massage therapist is available 9am-12pm, and they provide
-// one-hour massage sessions, the aggregator should provide the feed as
-//   availability {start_sec: 9am, duration: 60 minutes, ...}
-//   availability {start_sec: 10am, duration: 60 minutes, ...}
-//   availability {start_sec: 11am, duration: 60 minutes, ...}
-// instead of
-//   availability {start_sec: 9am, duration: 180 minutes, ...}
-//
-type Availability struct {
-	// An opaque string from an aggregator to identify a merchant.
-	MerchantId string `protobuf:"bytes,1,opt,name=merchant_id,json=merchantId" json:"merchant_id,omitempty"`
-	// An opaque string from aggregator to identify a service of the
-	// merchant.
-	ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId" json:"service_id,omitempty"`
-	// Start time of this availability, using epoch time in seconds.
-	StartSec int64 `protobuf:"varint,3,opt,name=start_sec,json=startSec" json:"start_sec,omitempty"`
-	// Duration of the service in seconds, e.g. 30 minutes for a chair massage.
-	DurationSec int64 `protobuf:"varint,4,opt,name=duration_sec,json=durationSec" json:"duration_sec,omitempty"`
-	// Number of total spots and open spots of this availability.
-	// E.g. a Yoga class of 10 spots with 3 booked.
-	//   availability {spots_total: 10, spots_open: 7 ...}
-	// E.g. a chair massage session which was already booked.
-	//   availability {spots_total: 1, spots_open: 0 ...}
-	//
-	// Note: If sending requests using the availability compression format defined
-	//       below, these two fields will be inferred. A Recurrence
-	//       implies spots_total=1 and spots_open=1. A ScheduleException implies
-	//       spots_total=1 and spots_open=0.
-	SpotsTotal int64 `protobuf:"varint,5,opt,name=spots_total,json=spotsTotal" json:"spots_total,omitempty"`
-	SpotsOpen  int64 `protobuf:"varint,6,opt,name=spots_open,json=spotsOpen" json:"spots_open,omitempty"`
-	// An optional opaque string to identify this availability slot. If set, it
-	// will be included in the requests that book/update/cancel appointments.
-	AvailabilityTag string `protobuf:"bytes,7,opt,name=availability_tag,json=availabilityTag" json:"availability_tag,omitempty"`
-	// Optional resources used to disambiguate this availability slot from
-	// others when different staff, room, or party_size values are part
-	// of the service.
-	//
-	// E.g. the same Yoga class with two 2 instructors.
-	//  availability { resources { staff_id: "1" staff_name: "Amy" }
-	//                 spots_total: 10 spots_open: 7 }
-	//  availability { resources { staff_id: "2" staff_name: "John" }
-	//                 spots_total: 5 spots_open: 2 }
-	Resources *Resources `protobuf:"bytes,8,opt,name=resources" json:"resources,omitempty"`
-	// A list of ids referencing the payment options which can be used to pay
-	// for this slot. The actual payment options are defined at the Merchant
-	// level, and can also be shared among multiple Merchants.
-	//
-	// This field overrides any payment_option_ids specified in the service
-	// message. Similarly payment_option_ids specified here do NOT have to be
-	// present in the service message, though must be defined at the
-	// Merchant level.
-	PaymentOptionId []string `protobuf:"bytes,9,rep,name=payment_option_id,json=paymentOptionId" json:"payment_option_id,omitempty"`
-	// The recurrence information for the availability, representing more than one
-	// start time. A recurrence should contain appointments for one working day.
-	Recurrence *Availability_Recurrence `protobuf:"bytes,10,opt,name=recurrence" json:"recurrence,omitempty"`
-	// When this service cannot be scheduled. To limit the number of
-	// schedule_exception messages consider joining adjacent exceptions.
-	ScheduleException []*Availability_ScheduleException `protobuf:"bytes,11,rep,name=schedule_exception,json=scheduleException" json:"schedule_exception,omitempty"`
-}
-
-func (m *Availability) Reset()                    { *m = Availability{} }
-func (m *Availability) String() string            { return proto.CompactTextString(m) }
-func (*Availability) ProtoMessage()               {}
-func (*Availability) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
-
-func (m *Availability) GetMerchantId() string {
-	if m != nil {
-		return m.MerchantId
-	}
-	return ""
-}
-
-func (m *Availability) GetServiceId() string {
-	if m != nil {
-		return m.ServiceId
-	}
-	return ""
-}
-
-func (m *Availability) GetStartSec() int64 {
-	if m != nil {
-		return m.StartSec
-	}
-	return 0
-}
-
-func (m *Availability) GetDurationSec() int64 {
-	if m != nil {
-		return m.DurationSec
-	}
-	return 0
-}
-
-func (m *Availability) GetSpotsTotal() int64 {
-	if m != nil {
-		return m.SpotsTotal
-	}
-	return 0
-}
-
-func (m *Availability) GetSpotsOpen() int64 {
-	if m != nil {
-		return m.SpotsOpen
-	}
-	return 0
-}
-
-func (m *Availability) GetAvailabilityTag() string {
-	if m != nil {
-		return m.AvailabilityTag
-	}
-	return ""
-}
-
-func (m *Availability) GetResources() *Resources {
-	if m != nil {
-		return m.Resources
-	}
-	return nil
-}
-
-func (m *Availability) GetPaymentOptionId() []string {
-	if m != nil {
-		return m.PaymentOptionId
-	}
-	return nil
-}
-
-func (m *Availability) GetRecurrence() *Availability_Recurrence {
-	if m != nil {
-		return m.Recurrence
-	}
-	return nil
-}
-
-func (m *Availability) GetScheduleException() []*Availability_ScheduleException {
-	if m != nil {
-		return m.ScheduleException
-	}
-	return nil
-}
-
-// Recurrence messages are optional, but allow for a more compact
-// representation of consistently repeating availability slots. They typically
-// represent a day's working schedule.
-// ScheduleException messages are then used to represent booked/unavailable
-// time ranges within the work day.
-//
-// Requirements:
-//   1. The expansion of availability slots or recurrences must NOT create
-//      identical slots. If the ids, start_sec, duration_sec, and resources
-//      match, slots are considered identical.
-//   2. Do NOT mix the standard availability format and recurrence within the
-//      slots of a single service. Recurrence benefits merchants/services that
-//      offer appointments. The standard format is geared towards
-//      merchants/services with regularly scheduled classes.
-type Availability_Recurrence struct {
-	// The inclusive maximum UTC timestamp the availability repeats until.
-	RepeatUntilSec int64 `protobuf:"varint,1,opt,name=repeat_until_sec,json=repeatUntilSec" json:"repeat_until_sec,omitempty"`
-	// Defines the time between successive availability slots.
-	//
-	// E.g. An availability with a duration of 20 min, a repeat_every_sec of
-	// 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will
-	// yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am,
-	// 11-11:20am.
-	RepeatEverySec int32 `protobuf:"varint,2,opt,name=repeat_every_sec,json=repeatEverySec" json:"repeat_every_sec,omitempty"`
-}
-
-func (m *Availability_Recurrence) Reset()                    { *m = Availability_Recurrence{} }
-func (m *Availability_Recurrence) String() string            { return proto.CompactTextString(m) }
-func (*Availability_Recurrence) ProtoMessage()               {}
-func (*Availability_Recurrence) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3, 0} }
-
-func (m *Availability_Recurrence) GetRepeatUntilSec() int64 {
-	if m != nil {
-		return m.RepeatUntilSec
-	}
-	return 0
-}
-
-func (m *Availability_Recurrence) GetRepeatEverySec() int32 {
-	if m != nil {
-		return m.RepeatEverySec
-	}
-	return 0
-}
-
-// ScheduleException messages are used to represent booked/unavailable time
-// ranges within the work day. As time slots are booked, the list of
-// exceptions should grow to reflect the newly unavailable time ranges.
-// The recurrence itself shouldn't be modified.
-type Availability_ScheduleException struct {
-	// The time range of the exception.
-	TimeRange *TimeRange `protobuf:"bytes,1,opt,name=time_range,json=timeRange" json:"time_range,omitempty"`
-}
-
-func (m *Availability_ScheduleException) Reset()         { *m = Availability_ScheduleException{} }
-func (m *Availability_ScheduleException) String() string { return proto.CompactTextString(m) }
-func (*Availability_ScheduleException) ProtoMessage()    {}
-func (*Availability_ScheduleException) Descriptor() ([]byte, []int) {
-	return fileDescriptor0, []int{3, 1}
-}
-
-func (m *Availability_ScheduleException) GetTimeRange() *TimeRange {
-	if m != nil {
-		return m.TimeRange
-	}
-	return nil
-}
-
-// A resource is used to disambiguate availability slots from one another when
-// different staff, room or party_size values are part of the service.
-// Multiple slots for the same service and time interval can co-exist when they
-// have different resources.
-type Resources struct {
-	// Optional id for a staff member providing the service. This field identifies
-	// the staff member across all merchants, services, and availability records.
-	// It also needs to be stable over time to allow correlation with past
-	// bookings.
-	// This field must be present if staff_name is present.
-	StaffId string `protobuf:"bytes,1,opt,name=staff_id,json=staffId" json:"staff_id,omitempty"`
-	// Optional name of a staff member providing the service. This field will be
-	// displayed to users making a booking, and should be human readable, as
-	// opposed to an opaque identifier.
-	// This field must be present if staff_id is present.
-	StaffName string `protobuf:"bytes,2,opt,name=staff_name,json=staffName" json:"staff_name,omitempty"`
-	// An optional id for the room the service is located in. This field
-	// identifies the room across all merchants, services, and availability
-	// records. It also needs to be stable over time to allow correlation with
-	// past bookings.
-	// This field must be present if room_name is present.
-	RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId" json:"room_id,omitempty"`
-	// An optional name for the room the service is located in. This
-	// field will be displayed to users making a booking, and should be human
-	// readable, as opposed to an opaque identifier.
-	// This field must be present if room_id is present.
-	RoomName string `protobuf:"bytes,4,opt,name=room_name,json=roomName" json:"room_name,omitempty"`
-	// Applicable only for Dining: The party size which can be accommodated
-	// during this time slot. A restaurant can be associated with multiple Slots
-	// for the same time, each specifying a different party_size, if for instance
-	// 2, 3, or 4 people can be seated with a reservation.
-	PartySize int32 `protobuf:"varint,5,opt,name=party_size,json=partySize" json:"party_size,omitempty"`
-}
-
-func (m *Resources) Reset()                    { *m = Resources{} }
-func (m *Resources) String() string            { return proto.CompactTextString(m) }
-func (*Resources) ProtoMessage()               {}
-func (*Resources) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
-
-func (m *Resources) GetStaffId() string {
-	if m != nil {
-		return m.StaffId
-	}
-	return ""
-}
-
-func (m *Resources) GetStaffName() string {
-	if m != nil {
-		return m.StaffName
-	}
-	return ""
-}
-
-func (m *Resources) GetRoomId() string {
-	if m != nil {
-		return m.RoomId
-	}
-	return ""
-}
-
-func (m *Resources) GetRoomName() string {
-	if m != nil {
-		return m.RoomName
-	}
-	return ""
-}
-
-func (m *Resources) GetPartySize() int32 {
-	if m != nil {
-		return m.PartySize
-	}
-	return 0
-}
-
-type TimeRange struct {
-	BeginSec int64 `protobuf:"varint,1,opt,name=begin_sec,json=beginSec" json:"begin_sec,omitempty"`
-	EndSec   int64 `protobuf:"varint,2,opt,name=end_sec,json=endSec" json:"end_sec,omitempty"`
-}
-
-func (m *TimeRange) Reset()                    { *m = TimeRange{} }
-func (m *TimeRange) String() string            { return proto.CompactTextString(m) }
-func (*TimeRange) ProtoMessage()               {}
-func (*TimeRange) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
-
-func (m *TimeRange) GetBeginSec() int64 {
-	if m != nil {
-		return m.BeginSec
-	}
-	return 0
-}
-
-func (m *TimeRange) GetEndSec() int64 {
-	if m != nil {
-		return m.EndSec
-	}
-	return 0
-}
-
-func init() {
-	proto.RegisterType((*FeedMetadata)(nil), "maps.booking.feeds.FeedMetadata")
-	proto.RegisterType((*AvailabilityFeed)(nil), "maps.booking.feeds.AvailabilityFeed")
-	proto.RegisterType((*ServiceAvailability)(nil), "maps.booking.feeds.ServiceAvailability")
-	proto.RegisterType((*Availability)(nil), "maps.booking.feeds.Availability")
-	proto.RegisterType((*Availability_Recurrence)(nil), "maps.booking.feeds.Availability.Recurrence")
-	proto.RegisterType((*Availability_ScheduleException)(nil), "maps.booking.feeds.Availability.ScheduleException")
-	proto.RegisterType((*Resources)(nil), "maps.booking.feeds.Resources")
-	proto.RegisterType((*TimeRange)(nil), "maps.booking.feeds.TimeRange")
-	proto.RegisterEnum("maps.booking.feeds.FeedMetadata_ProcessingInstruction", FeedMetadata_ProcessingInstruction_name, FeedMetadata_ProcessingInstruction_value)
-}
-
-func init() { proto.RegisterFile("availability_feed.proto", fileDescriptor0) }
-
-var fileDescriptor0 = []byte{
-	// 859 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xdb, 0x6e, 0x23, 0x45,
-	0x10, 0xc5, 0x76, 0x9c, 0x64, 0xca, 0xd1, 0xae, 0xdd, 0xf6, 0x26, 0x43, 0x50, 0xb4, 0xc6, 0x2f,
-	0x18, 0x90, 0x2c, 0x08, 0x08, 0x21, 0xb1, 0x2f, 0x51, 0x30, 0x52, 0xb4, 0x89, 0x1d, 0x7a, 0xbc,
-	0x42, 0xe2, 0x65, 0x68, 0xcf, 0x54, 0x9c, 0x11, 0x9e, 0x9e, 0x51, 0x77, 0x3b, 0x22, 0xfb, 0x29,
-	0x7c, 0x03, 0xff, 0xc1, 0xe7, 0xf0, 0x01, 0xbc, 0xa0, 0xae, 0xb9, 0x9a, 0x58, 0xca, 0xbe, 0x79,
-	0x4e, 0x9d, 0xd3, 0x97, 0x73, 0xaa, 0xda, 0x70, 0x22, 0x1e, 0x44, 0xb4, 0x16, 0xcb, 0x68, 0x1d,
-	0x99, 0x47, 0xff, 0x0e, 0x31, 0x9c, 0xa4, 0x2a, 0x31, 0x09, 0x63, 0xb1, 0x48, 0xf5, 0x64, 0x99,
-	0x24, 0xbf, 0x47, 0x72, 0x35, 0xb1, 0x05, 0x3d, 0xfa, 0xa7, 0x09, 0x47, 0x3f, 0x21, 0x86, 0x37,
-	0x68, 0x44, 0x28, 0x8c, 0x60, 0x31, 0x1c, 0xa7, 0x2a, 0x09, 0x50, 0xeb, 0x48, 0xae, 0xfc, 0x48,
-	0x6a, 0xa3, 0x36, 0x81, 0x89, 0x12, 0xe9, 0x36, 0x86, 0x8d, 0xf1, 0x8b, 0xf3, 0xef, 0x26, 0x4f,
-	0x57, 0x99, 0xd4, 0x57, 0x98, 0xdc, 0x96, 0xf2, 0xab, 0x4a, 0xcd, 0x5f, 0xa5, 0xbb, 0x60, 0xf6,
-	0x29, 0x1c, 0xe9, 0x7b, 0xa1, 0x42, 0x5f, 0x6e, 0xe2, 0x25, 0x2a, 0xb7, 0x39, 0x6c, 0x8c, 0xdb,
-	0xbc, 0x43, 0xd8, 0x8c, 0x20, 0x4b, 0x31, 0x89, 0x11, 0x6b, 0x9f, 0x40, 0xed, 0xb6, 0x32, 0x0a,
-	0x61, 0x1e, 0x41, 0x6c, 0x00, 0x6d, 0x99, 0xc8, 0x00, 0xdd, 0xf6, 0xb0, 0x31, 0xde, 0xe3, 0xd9,
-	0x07, 0xfb, 0x1a, 0x06, 0x2b, 0x94, 0xa8, 0x84, 0xdd, 0xc9, 0x37, 0x51, 0x8c, 0xda, 0x88, 0x38,
-	0x75, 0xf7, 0x86, 0x8d, 0x71, 0x8b, 0xf7, 0xab, 0xda, 0xa2, 0x28, 0x8d, 0x04, 0xbc, 0xda, 0x79,
-	0x7c, 0xd6, 0x87, 0x97, 0xb7, 0x7c, 0x7e, 0x39, 0xf5, 0x3c, 0xff, 0xdd, 0xec, 0xed, 0x6c, 0xfe,
-	0xcb, 0xac, 0xfb, 0x11, 0x3b, 0x81, 0x7e, 0x01, 0x5e, 0x78, 0xfe, 0xe5, 0xfc, 0xe6, 0xf6, 0x7a,
-	0xba, 0x98, 0x76, 0x1b, 0xec, 0x14, 0x8e, 0x6b, 0x85, 0xab, 0xd9, 0x25, 0x9f, 0xde, 0x4c, 0x67,
-	0x8b, 0x8b, 0xeb, 0x6e, 0x73, 0xf4, 0x57, 0x03, 0xba, 0x17, 0xb5, 0x84, 0xac, 0x77, 0xec, 0x0d,
-	0x1c, 0xc6, 0xb9, 0x7f, 0xe4, 0x73, 0xe7, 0x7c, 0xf8, 0x9c, 0xcf, 0xbc, 0x54, 0xb0, 0x5f, 0x61,
-	0xa0, 0x51, 0x3d, 0x44, 0x01, 0xfa, 0xf5, 0xec, 0xdd, 0xe6, 0xb0, 0x35, 0xee, 0x9c, 0x7f, 0xb6,
-	0x6b, 0x25, 0x2f, 0xe3, 0xd7, 0x0f, 0xc2, 0xfb, 0xfa, 0x29, 0x38, 0xfa, 0xb7, 0x09, 0xfd, 0x1d,
-	0x64, 0xf6, 0x3d, 0xb8, 0xda, 0x08, 0x65, 0x2a, 0x5f, 0x7d, 0x85, 0xda, 0xa8, 0x28, 0x30, 0x74,
-	0x83, 0x16, 0x3f, 0xa6, 0x7a, 0xe9, 0x2d, 0xcf, 0xab, 0xec, 0x5b, 0x38, 0x46, 0x19, 0xee, 0xd2,
-	0x35, 0x49, 0x37, 0x40, 0x19, 0x3e, 0x55, 0x7d, 0x05, 0x83, 0x18, 0x55, 0x70, 0x2f, 0xa4, 0xf1,
-	0xa3, 0xb0, 0xd2, 0xd8, 0x6e, 0x70, 0x38, 0x2b, 0x6a, 0x57, 0x61, 0xa9, 0x98, 0x40, 0x71, 0xa1,
-	0x2d, 0xc1, 0x1e, 0x09, 0x7a, 0x79, 0xa9, 0xc6, 0xbf, 0x06, 0xa6, 0x50, 0x27, 0x1b, 0x15, 0xa0,
-	0xae, 0xe8, 0xfb, 0x94, 0xc6, 0xd9, 0x2e, 0x0f, 0x79, 0xc1, 0xe6, 0xbd, 0x52, 0x58, 0xae, 0xf6,
-	0x23, 0x1c, 0x6d, 0x65, 0xd1, 0xa6, 0x2c, 0x76, 0xa6, 0xba, 0x15, 0xc2, 0x96, 0x6a, 0xf4, 0x77,
-	0x1b, 0x8e, 0xb6, 0x6c, 0x7f, 0x0d, 0x9d, 0x9a, 0x0d, 0xe4, 0xb4, 0xc3, 0xa1, 0xba, 0x3d, 0x3b,
-	0x03, 0xa8, 0x6e, 0x4d, 0x8e, 0x3a, 0xdc, 0x29, 0x2f, 0xcb, 0x3e, 0x01, 0x27, 0x8b, 0x4d, 0x63,
-	0x40, 0xde, 0xb5, 0xf8, 0x21, 0x01, 0x1e, 0x06, 0x76, 0xd2, 0xc2, 0x4d, 0x3e, 0x2e, 0xb6, 0x9e,
-	0x0d, 0x4a, 0xa7, 0xc0, 0x2c, 0xe5, 0x35, 0x74, 0x74, 0x9a, 0x18, 0xed, 0xd3, 0xf8, 0xd1, 0xbc,
-	0xb5, 0x38, 0x10, 0xb4, 0xb0, 0x08, 0xed, 0x4f, 0x84, 0x24, 0x45, 0x49, 0xee, 0xb5, 0xb8, 0x43,
-	0xc8, 0x3c, 0x45, 0xc9, 0x3e, 0x87, 0xee, 0xd6, 0xf3, 0x64, 0xc4, 0xca, 0x3d, 0xa0, 0x43, 0xbe,
-	0xac, 0xe3, 0x0b, 0xb1, 0x62, 0x3f, 0x80, 0x53, 0xda, 0xea, 0x1e, 0x7e, 0x48, 0x0c, 0x15, 0x9f,
-	0x7d, 0x01, 0xbd, 0x54, 0x3c, 0xc6, 0x28, 0x8d, 0x9f, 0xa4, 0x74, 0xa1, 0x28, 0x74, 0x9d, 0x61,
-	0xcb, 0x6e, 0x94, 0x17, 0xe6, 0x84, 0x5f, 0x85, 0xec, 0x2d, 0x80, 0xc2, 0x60, 0xa3, 0x14, 0xda,
-	0x27, 0x04, 0x68, 0xa7, 0x2f, 0x9f, 0x0b, 0x6a, 0xc2, 0x4b, 0x09, 0xaf, 0xc9, 0x99, 0x00, 0xa6,
-	0x83, 0x7b, 0x0c, 0x37, 0x6b, 0xf4, 0xf1, 0x8f, 0x00, 0x69, 0x13, 0xb7, 0x43, 0xe9, 0x9f, 0x3f,
-	0xbb, 0xa8, 0x97, 0x4b, 0xa7, 0x85, 0x92, 0xf7, 0xf4, 0xff, 0xa1, 0xd3, 0xdf, 0x00, 0xaa, 0xcd,
-	0xd9, 0x18, 0xba, 0x0a, 0x53, 0x14, 0xc6, 0xdf, 0x48, 0x13, 0xad, 0x29, 0xb8, 0x6c, 0x00, 0x5f,
-	0x64, 0xf8, 0x3b, 0x0b, 0xdb, 0xec, 0x2a, 0x26, 0x3e, 0xa0, 0x7a, 0x24, 0x66, 0xf6, 0xde, 0xe6,
-	0xcc, 0xa9, 0x85, 0x3d, 0x0c, 0x4e, 0x7f, 0x86, 0xde, 0x93, 0x93, 0xb0, 0x37, 0x00, 0x76, 0x66,
-	0x7d, 0x25, 0xe4, 0x0a, 0xf3, 0x57, 0x6a, 0x67, 0x20, 0x76, 0x78, 0xb9, 0x25, 0x71, 0xc7, 0x14,
-	0x3f, 0x47, 0x7f, 0x36, 0xc0, 0x29, 0x93, 0x62, 0x1f, 0x83, 0xed, 0xba, 0xbb, 0xbb, 0xaa, 0x87,
-	0x0f, 0xe8, 0x3b, 0x6f, 0x60, 0x2a, 0x49, 0x11, 0x63, 0xd9, 0xc0, 0x16, 0x99, 0x89, 0x18, 0xd9,
-	0x09, 0x1c, 0xa8, 0x24, 0x89, 0xad, 0x30, 0x1b, 0xfd, 0x7d, 0xfb, 0x99, 0x75, 0x36, 0x15, 0x48,
-	0x96, 0x0d, 0xf9, 0xa1, 0x05, 0x48, 0x75, 0x06, 0x90, 0x0a, 0x65, 0x1e, 0x7d, 0x1d, 0xbd, 0xcf,
-	0xfe, 0x25, 0xda, 0xdc, 0x21, 0xc4, 0x8b, 0xde, 0xe3, 0xe8, 0x02, 0x9c, 0xf2, 0xd0, 0x76, 0xa1,
-	0x25, 0xae, 0x22, 0x59, 0x73, 0xf2, 0x90, 0x00, 0xeb, 0xe1, 0x09, 0x1c, 0xd8, 0xc7, 0xab, 0xb0,
-	0xae, 0xc5, 0xf7, 0x51, 0x86, 0x1e, 0x06, 0xcb, 0x7d, 0xfa, 0x8f, 0xfd, 0xe6, 0xbf, 0x00, 0x00,
-	0x00, 0xff, 0xff, 0x08, 0xf3, 0x9e, 0x1d, 0x7e, 0x07, 0x00, 0x00,
-}
diff --git a/feeds/availability_feed.proto b/feeds/availability_feed.proto
deleted file mode 100644
index e25bd0e..0000000
--- a/feeds/availability_feed.proto
+++ /dev/null
@@ -1,249 +0,0 @@
-syntax = "proto3";
-
-package maps.booking.feeds;
-
-message FeedMetadata {
-  enum ProcessingInstruction {
-    // By default we will assume that this feed is an incremental feed.
-    PROCESS_UNKNOWN = 0;
-
-    // This Feed message is one shard of a complete feed. Anything previously
-    // supplied by this partner will be deleted; the contents of this feed
-    // represent the entire state of the world.
-    PROCESS_AS_COMPLETE = 1;
-
-    // This Feed message is one shard of an incremental feed. Existing entities
-    // will be left untouched except as modified in this feed.
-    PROCESS_AS_INCREMENTAL = 2;
-  }
-
-  // Instructs us how to process the feed: either as a shard of a complete feed,
-  // or as a shard of an incremental update.
-  ProcessingInstruction processing_instruction = 1;
-
-  // The current shard and total number of shards for this feed.
-  //
-  // Shard number is assumed to be zero-based.
-  //
-  // There does not need to be any relationship to the file name.
-  //
-  // Shards do not need to be transferred in order, and they may not be
-  // processed in order.
-  int32 shard_number = 2;
-  int32 total_shards = 3;
-
-  // An identifier that must be consistent across all shards in a feed.
-  // This value must be globally unique across each feed type.
-  //
-  // This value ensures that complete feeds spanning multiple shards are
-  // processed together correctly.
-  //
-  // Clients only need to set this value when the processing_instruction is set
-  // to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by
-  // total_shards).
-  //
-  // Feeds that span multiple shards must set this nonce to the same value.
-  uint64 nonce = 5;
-
-  // The timestamp at which this feed shard was generated.
-  //
-  // In Unix time format (seconds since the epoch).
-  int64 generation_timestamp = 4;
-}
-
-message AvailabilityFeed {
-  FeedMetadata metadata = 1;
-  repeated ServiceAvailability service_availability = 2;
-}
-
-message ServiceAvailability {
-  // If provided, we will consider the Availability entities provided to be a
-  // complete snapshot from [start_timestamp_restrict, end_timestamp_restrict).
-  // That is, all existing availability will be deleted if the following
-  // condition holds true:
-  //
-  //    start_timestamp_restrict <= Availability.start_sec &&
-  //    Availability.start_sec < end_timestamp_restrict
-  //
-  // If a resource_restrict message is set, the condition is further restricted:
-  //
-  //    Availability.resource.staff_id == resource_restrict.staff_id &&
-  //    Availability.resource.room_id == resource_restrict.room_id
-  //
-  // These fields are typically used to provide a complete update of
-  // availability in a given time range.
-  //
-  // Setting start_timestamp_restrict while leaving end_timestamp_restrict unset
-  // is interpreted to mean all time beginning at start_timestamp_restrict.
-  //
-  // Setting end_timestamp_restrict while leaving start_timestamp_restrict unset
-  // is interpreted to mean all time up to the end_timestamp_restrict.
-  //
-  // In Unix time format (seconds since the epoch).
-  int64 start_timestamp_restrict = 1;
-  int64 end_timestamp_restrict = 2;
-
-  // If provided, the timestamp restricts will be applied only to the given
-  // merchant or service.
-  //
-  // These fields are typically used to provide complete snapshot of
-  // availability in a given range (defined above) for a specific merchant or
-  // service.
-  //
-  // Leaving these fields unset, or setting these to the empty string or null,
-  // is interpreted to mean that no restrict is intended.
-  string merchant_id_restrict = 3;
-  string service_id_restrict = 4;
-
-  // Setting resources_restrict further restricts the scope of the update to
-  // just this set of resources. All id fields of the resources must match
-  // exactly.
-  Resources resources_restrict = 6;
-
-  repeated Availability availability = 5;
-}
-
-// An availability of the merchant's service, indicating time and number
-// of spots.
-// The availability feed should be a list of this message.
-// Please note that it's up to the partner to call out all the possible
-// availabilities.
-// If a massage therapist is available 9am-12pm, and they provide
-// one-hour massage sessions, the aggregator should provide the feed as
-//   availability {start_sec: 9am, duration: 60 minutes, ...}
-//   availability {start_sec: 10am, duration: 60 minutes, ...}
-//   availability {start_sec: 11am, duration: 60 minutes, ...}
-// instead of
-//   availability {start_sec: 9am, duration: 180 minutes, ...}
-//
-message Availability {
-  // An opaque string from an aggregator to identify a merchant.
-  string merchant_id = 1;
-  // An opaque string from aggregator to identify a service of the
-  // merchant.
-  string service_id = 2;
-  // Start time of this availability, using epoch time in seconds.
-  int64 start_sec = 3;
-  // Duration of the service in seconds, e.g. 30 minutes for a chair massage.
-  int64 duration_sec = 4;
-  // Number of total spots and open spots of this availability.
-  // E.g. a Yoga class of 10 spots with 3 booked.
-  //   availability {spots_total: 10, spots_open: 7 ...}
-  // E.g. a chair massage session which was already booked.
-  //   availability {spots_total: 1, spots_open: 0 ...}
-  //
-  // Note: If sending requests using the availability compression format defined
-  //       below, these two fields will be inferred. A Recurrence
-  //       implies spots_total=1 and spots_open=1. A ScheduleException implies
-  //       spots_total=1 and spots_open=0.
-  int64 spots_total = 5;
-  int64 spots_open = 6;
-  // An optional opaque string to identify this availability slot. If set, it
-  // will be included in the requests that book/update/cancel appointments.
-  string availability_tag = 7;
-
-  // Optional resources used to disambiguate this availability slot from
-  // others when different staff, room, or party_size values are part
-  // of the service.
-  //
-  // E.g. the same Yoga class with two 2 instructors.
-  //  availability { resources { staff_id: "1" staff_name: "Amy" }
-  //                 spots_total: 10 spots_open: 7 }
-  //  availability { resources { staff_id: "2" staff_name: "John" }
-  //                 spots_total: 5 spots_open: 2 }
-  Resources resources = 8;
-
-  // A list of ids referencing the payment options which can be used to pay
-  // for this slot. The actual payment options are defined at the Merchant
-  // level, and can also be shared among multiple Merchants.
-  //
-  // This field overrides any payment_option_ids specified in the service
-  // message. Similarly payment_option_ids specified here do NOT have to be
-  // present in the service message, though must be defined at the
-  // Merchant level.
-  repeated string payment_option_id = 9;
-
-  // Recurrence messages are optional, but allow for a more compact
-  // representation of consistently repeating availability slots. They typically
-  // represent a day's working schedule.
-  // ScheduleException messages are then used to represent booked/unavailable
-  // time ranges within the work day.
-  //
-  // Requirements:
-  //   1. The expansion of availability slots or recurrences must NOT create
-  //      identical slots. If the ids, start_sec, duration_sec, and resources
-  //      match, slots are considered identical.
-  //   2. Do NOT mix the standard availability format and recurrence within the
-  //      slots of a single service. Recurrence benefits merchants/services that
-  //      offer appointments. The standard format is geared towards
-  //      merchants/services with regularly scheduled classes.
-  message Recurrence {
-    // The inclusive maximum UTC timestamp the availability repeats until.
-    int64 repeat_until_sec = 1;
-    // Defines the time between successive availability slots.
-    //
-    // E.g. An availability with a duration of 20 min, a repeat_every_sec of
-    // 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will
-    // yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am,
-    // 11-11:20am.
-    int32 repeat_every_sec = 2;
-  }
-  // The recurrence information for the availability, representing more than one
-  // start time. A recurrence should contain appointments for one working day.
-  Recurrence recurrence = 10;
-
-  // ScheduleException messages are used to represent booked/unavailable time
-  // ranges within the work day. As time slots are booked, the list of
-  // exceptions should grow to reflect the newly unavailable time ranges.
-  // The recurrence itself shouldn't be modified.
-  message ScheduleException {
-    // The time range of the exception.
-    TimeRange time_range = 1;
-  }
-  // When this service cannot be scheduled. To limit the number of
-  // schedule_exception messages consider joining adjacent exceptions.
-  repeated ScheduleException schedule_exception = 11;
-}
-
-// A resource is used to disambiguate availability slots from one another when
-// different staff, room or party_size values are part of the service.
-// Multiple slots for the same service and time interval can co-exist when they
-// have different resources.
-message Resources {
-  // Optional id for a staff member providing the service. This field identifies
-  // the staff member across all merchants, services, and availability records.
-  // It also needs to be stable over time to allow correlation with past
-  // bookings.
-  // This field must be present if staff_name is present.
-  string staff_id = 1;
-
-  // Optional name of a staff member providing the service. This field will be
-  // displayed to users making a booking, and should be human readable, as
-  // opposed to an opaque identifier.
-  // This field must be present if staff_id is present.
-  string staff_name = 2;
-
-  // An optional id for the room the service is located in. This field
-  // identifies the room across all merchants, services, and availability
-  // records. It also needs to be stable over time to allow correlation with
-  // past bookings.
-  // This field must be present if room_name is present.
-  string room_id = 3;
-
-  // An optional name for the room the service is located in. This
-  // field will be displayed to users making a booking, and should be human
-  // readable, as opposed to an opaque identifier.
-  // This field must be present if room_id is present.
-  string room_name = 4;
-
-  // Applicable only for Dining: The party size which can be accommodated
-  // during this time slot. A restaurant can be associated with multiple Slots
-  // for the same time, each specifying a different party_size, if for instance
-  // 2, 3, or 4 people can be seated with a reservation.
-  int32 party_size = 5;
-}
-
-message TimeRange {
-  int64 begin_sec = 1;
-  int64 end_sec = 2;
-}
\ No newline at end of file
diff --git a/feeds/feeds.pb.go b/feeds/feeds.pb.go
new file mode 100644
index 0000000..9902bb9
--- /dev/null
+++ b/feeds/feeds.pb.go
@@ -0,0 +1,2181 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: feeds.proto
+
+/*
+Package maps_booking_feeds is a generated protocol buffer package.
+
+It is generated from these files:
+	feeds.proto
+
+It has these top-level messages:
+	FeedMetadata
+	AvailabilityFeed
+	ServiceAvailability
+	Availability
+	Resources
+	TimeRange
+	ServiceFeed
+	Service
+	Price
+	SchedulingRules
+	TaxRate
+	ServiceIntakeFormField
+	ServiceIntakeForm
+	Deposit
+	NoShowFee
+	PriceType
+	RequireCreditCard
+	ActionLink
+	ActionPlatform
+	TicketType
+	RelatedMedia
+	ServiceAttributeValueId
+*/
+
+package maps_booking_feeds
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+// Defines how a total price is determined from an availability.
+type PriceType int32
+
+const (
+	// The price is for a fixed amount. This is the default value if the field is
+	// not set.
+	PriceType_FIXED_RATE_DEFAULT PriceType = 0
+	// The price specified is per person, and the total price is calculated
+	// according to the party size specified in Resources as
+	// price_micros * party_size. A PER_PERSON price must be accompanied by a
+	// party size in the availability resources. If it is not, a party size of one
+	// is used.
+	PriceType_PER_PERSON PriceType = 1
+)
+
+var PriceType_name = map[int32]string{
+	0: "FIXED_RATE_DEFAULT",
+	1: "PER_PERSON",
+}
+var PriceType_value = map[string]int32{
+	"FIXED_RATE_DEFAULT": 0,
+	"PER_PERSON":         1,
+}
+
+func (x PriceType) String() string {
+	return proto.EnumName(PriceType_name, int32(x))
+}
+func (PriceType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{0}
+}
+
+// Defines whether a credit card is required in order to book an appointment.
+type RequireCreditCard int32
+
+const (
+	// The credit card requirement is not explicitly specified and the
+	// behaviour is identical to the one specified for CONDITIONAL.
+	RequireCreditCard_REQUIRE_CREDIT_CARD_UNSPECIFIED RequireCreditCard = 0
+	// Google will require a credit card for the booking if any of the following
+	// conditions are met:
+	// * the availability has a price and the prepayment_type is REQUIRED
+	// * the no_show_fee is set
+	// * the deposit field is set.
+	RequireCreditCard_REQUIRE_CREDIT_CARD_CONDITIONAL RequireCreditCard = 1
+	// A credit card is always required in order to book this availability
+	// regardless of other field values.
+	RequireCreditCard_REQUIRE_CREDIT_CARD_ALWAYS RequireCreditCard = 2
+)
+
+var RequireCreditCard_name = map[int32]string{
+	0: "REQUIRE_CREDIT_CARD_UNSPECIFIED",
+	1: "REQUIRE_CREDIT_CARD_CONDITIONAL",
+	2: "REQUIRE_CREDIT_CARD_ALWAYS",
+}
+var RequireCreditCard_value = map[string]int32{
+	"REQUIRE_CREDIT_CARD_UNSPECIFIED": 0,
+	"REQUIRE_CREDIT_CARD_CONDITIONAL": 1,
+	"REQUIRE_CREDIT_CARD_ALWAYS":      2,
+}
+
+func (x RequireCreditCard) String() string {
+	return proto.EnumName(RequireCreditCard_name, int32(x))
+}
+func (RequireCreditCard) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{1}
+}
+
+// The platform that the action is performed on. Web application is the general
+// fallback. It is recommended to have at least one ActionLink with
+// ACTION_PLATFORM_WEB_APPLICATION. Links with Android and iOS as platform are
+// only used on the respective system.
+type ActionPlatform int32
+
+const (
+	// The platform is unspecified.
+	ActionPlatform_ACTION_PLATFORM_UNSPECIFIED ActionPlatform = 0
+	// The action platform is web in general.
+	ActionPlatform_ACTION_PLATFORM_WEB_APPLICATION ActionPlatform = 1
+	// The action platform is web on mobile devices.
+	ActionPlatform_ACTION_PLATFORM_MOBILE_WEB ActionPlatform = 2
+	// The action platform is Android OS.
+	ActionPlatform_ACTION_PLATFORM_ANDROID ActionPlatform = 3
+	// The action platform is iOS.
+	ActionPlatform_ACTION_PLATFORM_IOS ActionPlatform = 4
+)
+
+var ActionPlatform_name = map[int32]string{
+	0: "ACTION_PLATFORM_UNSPECIFIED",
+	1: "ACTION_PLATFORM_WEB_APPLICATION",
+	2: "ACTION_PLATFORM_MOBILE_WEB",
+	3: "ACTION_PLATFORM_ANDROID",
+	4: "ACTION_PLATFORM_IOS",
+}
+var ActionPlatform_value = map[string]int32{
+	"ACTION_PLATFORM_UNSPECIFIED":     0,
+	"ACTION_PLATFORM_WEB_APPLICATION": 1,
+	"ACTION_PLATFORM_MOBILE_WEB":      2,
+	"ACTION_PLATFORM_ANDROID":         3,
+	"ACTION_PLATFORM_IOS":             4,
+}
+
+func (x ActionPlatform) String() string {
+	return proto.EnumName(ActionPlatform_name, int32(x))
+}
+func (ActionPlatform) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{2}
+}
+
+type FeedMetadata_ProcessingInstruction int32
+
+const (
+	// By default we will assume that this feed is an incremental feed.
+	FeedMetadata_PROCESS_UNKNOWN FeedMetadata_ProcessingInstruction = 0
+	// This Feed message is one shard of a complete feed. Anything previously
+	// supplied by this partner will be deleted; the contents of this feed
+	// represent the entire state of the world.
+	FeedMetadata_PROCESS_AS_COMPLETE FeedMetadata_ProcessingInstruction = 1
+	// This Feed message is one shard of an incremental feed. Existing entities
+	// will be left untouched except as modified in this feed.
+	FeedMetadata_PROCESS_AS_INCREMENTAL FeedMetadata_ProcessingInstruction = 2
+)
+
+var FeedMetadata_ProcessingInstruction_name = map[int32]string{
+	0: "PROCESS_UNKNOWN",
+	1: "PROCESS_AS_COMPLETE",
+	2: "PROCESS_AS_INCREMENTAL",
+}
+var FeedMetadata_ProcessingInstruction_value = map[string]int32{
+	"PROCESS_UNKNOWN":        0,
+	"PROCESS_AS_COMPLETE":    1,
+	"PROCESS_AS_INCREMENTAL": 2,
+}
+
+func (x FeedMetadata_ProcessingInstruction) String() string {
+	return proto.EnumName(FeedMetadata_ProcessingInstruction_name, int32(x))
+}
+func (FeedMetadata_ProcessingInstruction) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{0, 0}
+}
+
+// Enum to indicate the prepayment type.
+type Service_PrepaymentType int32
+
+const (
+	// By default we will assume that the prepayment is NOT_SUPPORTED.
+	Service_PREPAYMENT_TYPE_UNSPECIFIED Service_PrepaymentType = 0
+	// The user has to pay this service at the booking time.
+	Service_REQUIRED Service_PrepaymentType = 1
+	// The user can choose to pre-pay this service at the booking time or later,
+	// but it is not required in order to book.
+	Service_OPTIONAL Service_PrepaymentType = 2
+	// The prepayment is not supported for this service.
+	Service_NOT_SUPPORTED Service_PrepaymentType = 3
+)
+
+var Service_PrepaymentType_name = map[int32]string{
+	0: "PREPAYMENT_TYPE_UNSPECIFIED",
+	1: "REQUIRED",
+	2: "OPTIONAL",
+	3: "NOT_SUPPORTED",
+}
+var Service_PrepaymentType_value = map[string]int32{
+	"PREPAYMENT_TYPE_UNSPECIFIED": 0,
+	"REQUIRED":                    1,
+	"OPTIONAL":                    2,
+	"NOT_SUPPORTED":               3,
+}
+
+func (x Service_PrepaymentType) String() string {
+	return proto.EnumName(Service_PrepaymentType_name, int32(x))
+}
+func (Service_PrepaymentType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{7, 0}
+}
+
+type Service_ServiceType int32
+
+const (
+	Service_SERVICE_TYPE_UNSPECIFIED        Service_ServiceType = 0
+	Service_SERVICE_TYPE_DINING_RESERVATION Service_ServiceType = 1
+	Service_SERVICE_TYPE_FOOD_ORDERING      Service_ServiceType = 2
+	Service_SERVICE_TYPE_EVENT_TICKET       Service_ServiceType = 3
+	Service_SERVICE_TYPE_TRIP_TOUR          Service_ServiceType = 4
+)
+
+var Service_ServiceType_name = map[int32]string{
+	0: "SERVICE_TYPE_UNSPECIFIED",
+	1: "SERVICE_TYPE_DINING_RESERVATION",
+	2: "SERVICE_TYPE_FOOD_ORDERING",
+	3: "SERVICE_TYPE_EVENT_TICKET",
+	4: "SERVICE_TYPE_TRIP_TOUR",
+}
+var Service_ServiceType_value = map[string]int32{
+	"SERVICE_TYPE_UNSPECIFIED":        0,
+	"SERVICE_TYPE_DINING_RESERVATION": 1,
+	"SERVICE_TYPE_FOOD_ORDERING":      2,
+	"SERVICE_TYPE_EVENT_TICKET":       3,
+	"SERVICE_TYPE_TRIP_TOUR":          4,
+}
+
+func (x Service_ServiceType) String() string {
+	return proto.EnumName(Service_ServiceType_name, int32(x))
+}
+func (Service_ServiceType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{7, 1}
+}
+
+// Enum to indicate the type of field.
+type ServiceIntakeFormField_FieldType int32
+
+const (
+	// Fields of unspecified or unknown type will be ignored.
+	ServiceIntakeFormField_FIELD_TYPE_UNSPECIFIED ServiceIntakeFormField_FieldType = 0
+	// A one-line input field for text.
+	ServiceIntakeFormField_SHORT_ANSWER ServiceIntakeFormField_FieldType = 1
+	// A multi-line input field for text.
+	ServiceIntakeFormField_PARAGRAPH ServiceIntakeFormField_FieldType = 2
+	// A set of radio buttons that requires one choice from many options.
+	ServiceIntakeFormField_MULTIPLE_CHOICE ServiceIntakeFormField_FieldType = 3
+	// One or more enumerated items with checkboxes.
+	ServiceIntakeFormField_CHECKBOXES ServiceIntakeFormField_FieldType = 4
+	// A selection from a dropdown.
+	ServiceIntakeFormField_DROPDOWN ServiceIntakeFormField_FieldType = 5
+	// A yes/no button.
+	ServiceIntakeFormField_BOOLEAN ServiceIntakeFormField_FieldType = 6
+)
+
+var ServiceIntakeFormField_FieldType_name = map[int32]string{
+	0: "FIELD_TYPE_UNSPECIFIED",
+	1: "SHORT_ANSWER",
+	2: "PARAGRAPH",
+	3: "MULTIPLE_CHOICE",
+	4: "CHECKBOXES",
+	5: "DROPDOWN",
+	6: "BOOLEAN",
+}
+var ServiceIntakeFormField_FieldType_value = map[string]int32{
+	"FIELD_TYPE_UNSPECIFIED": 0,
+	"SHORT_ANSWER":           1,
+	"PARAGRAPH":              2,
+	"MULTIPLE_CHOICE":        3,
+	"CHECKBOXES":             4,
+	"DROPDOWN":               5,
+	"BOOLEAN":                6,
+}
+
+func (x ServiceIntakeFormField_FieldType) String() string {
+	return proto.EnumName(ServiceIntakeFormField_FieldType_name, int32(x))
+}
+func (ServiceIntakeFormField_FieldType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{11, 0}
+}
+
+// Enum to indicate the type of this media source. Only photos are supported.
+// Please reach out to Google if supporting animations or videos is
+// important to your feature and we will find the best solution for you.
+type RelatedMedia_MediaType int32
+
+const (
+	// Unused.
+	RelatedMedia_TYPE_UNSPECIFIED RelatedMedia_MediaType = 0
+	// Indicates the media provided by the url is a photo.
+	RelatedMedia_PHOTO RelatedMedia_MediaType = 1
+)
+
+var RelatedMedia_MediaType_name = map[int32]string{
+	0: "TYPE_UNSPECIFIED",
+	1: "PHOTO",
+}
+var RelatedMedia_MediaType_value = map[string]int32{
+	"TYPE_UNSPECIFIED": 0,
+	"PHOTO":            1,
+}
+
+func (x RelatedMedia_MediaType) String() string {
+	return proto.EnumName(RelatedMedia_MediaType_name, int32(x))
+}
+func (RelatedMedia_MediaType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{17, 0}
+}
+
+type FeedMetadata struct {
+	// Instructs us how to process the feed: either as a shard of a complete feed,
+	// or as a shard of an incremental update.
+	ProcessingInstruction FeedMetadata_ProcessingInstruction `protobuf:"varint,1,opt,name=processing_instruction,json=processingInstruction,enum=maps.booking.feeds.FeedMetadata_ProcessingInstruction" json:"processing_instruction,omitempty"`
+	// The current shard and total number of shards for this feed.
+	//
+	// Shard number is assumed to be zero-based.
+	//
+	// There does not need to be any relationship to the file name.
+	//
+	// Shards do not need to be transferred in order, and they may not be
+	// processed in order.
+	ShardNumber int32 `protobuf:"varint,2,opt,name=shard_number,json=shardNumber" json:"shard_number,omitempty"`
+	TotalShards int32 `protobuf:"varint,3,opt,name=total_shards,json=totalShards" json:"total_shards,omitempty"`
+	// An identifier that must be consistent across all shards in a feed.
+	// This value must be globally unique across each feed type.
+	//
+	// This value ensures that complete feeds spanning multiple shards are
+	// processed together correctly.
+	//
+	// Clients only need to set this value when the processing_instruction is set
+	// to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by
+	// total_shards).
+	//
+	// Feeds that span multiple shards must set this nonce to the same value.
+	Nonce uint64 `protobuf:"varint,5,opt,name=nonce" json:"nonce,omitempty"`
+	// The timestamp at which this feed shard was generated.
+	//
+	// In Unix time format (seconds since the epoch).
+	GenerationTimestamp  int64    `protobuf:"varint,4,opt,name=generation_timestamp,json=generationTimestamp" json:"generation_timestamp,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *FeedMetadata) Reset()         { *m = FeedMetadata{} }
+func (m *FeedMetadata) String() string { return proto.CompactTextString(m) }
+func (*FeedMetadata) ProtoMessage()    {}
+func (*FeedMetadata) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{0}
+}
+func (m *FeedMetadata) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_FeedMetadata.Unmarshal(m, b)
+}
+func (m *FeedMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_FeedMetadata.Marshal(b, m, deterministic)
+}
+func (dst *FeedMetadata) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_FeedMetadata.Merge(dst, src)
+}
+func (m *FeedMetadata) XXX_Size() int {
+	return xxx_messageInfo_FeedMetadata.Size(m)
+}
+func (m *FeedMetadata) XXX_DiscardUnknown() {
+	xxx_messageInfo_FeedMetadata.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_FeedMetadata proto.InternalMessageInfo
+
+func (m *FeedMetadata) GetProcessingInstruction() FeedMetadata_ProcessingInstruction {
+	if m != nil {
+		return m.ProcessingInstruction
+	}
+	return FeedMetadata_PROCESS_UNKNOWN
+}
+
+func (m *FeedMetadata) GetShardNumber() int32 {
+	if m != nil {
+		return m.ShardNumber
+	}
+	return 0
+}
+
+func (m *FeedMetadata) GetTotalShards() int32 {
+	if m != nil {
+		return m.TotalShards
+	}
+	return 0
+}
+
+func (m *FeedMetadata) GetNonce() uint64 {
+	if m != nil {
+		return m.Nonce
+	}
+	return 0
+}
+
+func (m *FeedMetadata) GetGenerationTimestamp() int64 {
+	if m != nil {
+		return m.GenerationTimestamp
+	}
+	return 0
+}
+
+type AvailabilityFeed struct {
+	Metadata             *FeedMetadata          `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"`
+	ServiceAvailability  []*ServiceAvailability `protobuf:"bytes,2,rep,name=service_availability,json=serviceAvailability" json:"service_availability,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}               `json:"-"`
+	XXX_unrecognized     []byte                 `json:"-"`
+	XXX_sizecache        int32                  `json:"-"`
+}
+
+func (m *AvailabilityFeed) Reset()         { *m = AvailabilityFeed{} }
+func (m *AvailabilityFeed) String() string { return proto.CompactTextString(m) }
+func (*AvailabilityFeed) ProtoMessage()    {}
+func (*AvailabilityFeed) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{1}
+}
+func (m *AvailabilityFeed) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AvailabilityFeed.Unmarshal(m, b)
+}
+func (m *AvailabilityFeed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AvailabilityFeed.Marshal(b, m, deterministic)
+}
+func (dst *AvailabilityFeed) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AvailabilityFeed.Merge(dst, src)
+}
+func (m *AvailabilityFeed) XXX_Size() int {
+	return xxx_messageInfo_AvailabilityFeed.Size(m)
+}
+func (m *AvailabilityFeed) XXX_DiscardUnknown() {
+	xxx_messageInfo_AvailabilityFeed.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AvailabilityFeed proto.InternalMessageInfo
+
+func (m *AvailabilityFeed) GetMetadata() *FeedMetadata {
+	if m != nil {
+		return m.Metadata
+	}
+	return nil
+}
+
+func (m *AvailabilityFeed) GetServiceAvailability() []*ServiceAvailability {
+	if m != nil {
+		return m.ServiceAvailability
+	}
+	return nil
+}
+
+type ServiceAvailability struct {
+	// If provided, we will consider the Availability entities provided to be a
+	// complete snapshot from [start_timestamp_restrict, end_timestamp_restrict).
+	// That is, all existing availability will be deleted if the following
+	// condition holds true:
+	//
+	//    start_timestamp_restrict <= Availability.start_sec &&
+	//    Availability.start_sec < end_timestamp_restrict
+	//
+	// If a resource_restrict message is set, the condition is further restricted:
+	//
+	//    Availability.resource.staff_id == resource_restrict.staff_id &&
+	//    Availability.resource.room_id == resource_restrict.room_id
+	//
+	// These fields are typically used to provide a complete update of
+	// availability in a given time range.
+	//
+	// Setting start_timestamp_restrict while leaving end_timestamp_restrict unset
+	// is interpreted to mean all time beginning at start_timestamp_restrict.
+	//
+	// Setting end_timestamp_restrict while leaving start_timestamp_restrict unset
+	// is interpreted to mean all time up to the end_timestamp_restrict.
+	//
+	// In Unix time format (seconds since the epoch).
+	StartTimestampRestrict int64 `protobuf:"varint,1,opt,name=start_timestamp_restrict,json=startTimestampRestrict" json:"start_timestamp_restrict,omitempty"`
+	EndTimestampRestrict   int64 `protobuf:"varint,2,opt,name=end_timestamp_restrict,json=endTimestampRestrict" json:"end_timestamp_restrict,omitempty"`
+	// If provided, the timestamp restricts will be applied only to the given
+	// merchant or service.
+	//
+	// These fields are typically used to provide complete snapshot of
+	// availability in a given range (defined above) for a specific merchant or
+	// service.
+	//
+	// Leaving these fields unset, or setting these to the empty string or null,
+	// is interpreted to mean that no restrict is intended.
+	MerchantIdRestrict string `protobuf:"bytes,3,opt,name=merchant_id_restrict,json=merchantIdRestrict" json:"merchant_id_restrict,omitempty"`
+	ServiceIdRestrict  string `protobuf:"bytes,4,opt,name=service_id_restrict,json=serviceIdRestrict" json:"service_id_restrict,omitempty"`
+	// Setting resources_restrict further restricts the scope of the update to
+	// just this set of resources. All id fields of the resources must match
+	// exactly.
+	ResourcesRestrict    *Resources      `protobuf:"bytes,6,opt,name=resources_restrict,json=resourcesRestrict" json:"resources_restrict,omitempty"`
+	Availability         []*Availability `protobuf:"bytes,5,rep,name=availability" json:"availability,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}        `json:"-"`
+	XXX_unrecognized     []byte          `json:"-"`
+	XXX_sizecache        int32           `json:"-"`
+}
+
+func (m *ServiceAvailability) Reset()         { *m = ServiceAvailability{} }
+func (m *ServiceAvailability) String() string { return proto.CompactTextString(m) }
+func (*ServiceAvailability) ProtoMessage()    {}
+func (*ServiceAvailability) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{2}
+}
+func (m *ServiceAvailability) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ServiceAvailability.Unmarshal(m, b)
+}
+func (m *ServiceAvailability) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ServiceAvailability.Marshal(b, m, deterministic)
+}
+func (dst *ServiceAvailability) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ServiceAvailability.Merge(dst, src)
+}
+func (m *ServiceAvailability) XXX_Size() int {
+	return xxx_messageInfo_ServiceAvailability.Size(m)
+}
+func (m *ServiceAvailability) XXX_DiscardUnknown() {
+	xxx_messageInfo_ServiceAvailability.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ServiceAvailability proto.InternalMessageInfo
+
+func (m *ServiceAvailability) GetStartTimestampRestrict() int64 {
+	if m != nil {
+		return m.StartTimestampRestrict
+	}
+	return 0
+}
+
+func (m *ServiceAvailability) GetEndTimestampRestrict() int64 {
+	if m != nil {
+		return m.EndTimestampRestrict
+	}
+	return 0
+}
+
+func (m *ServiceAvailability) GetMerchantIdRestrict() string {
+	if m != nil {
+		return m.MerchantIdRestrict
+	}
+	return ""
+}
+
+func (m *ServiceAvailability) GetServiceIdRestrict() string {
+	if m != nil {
+		return m.ServiceIdRestrict
+	}
+	return ""
+}
+
+func (m *ServiceAvailability) GetResourcesRestrict() *Resources {
+	if m != nil {
+		return m.ResourcesRestrict
+	}
+	return nil
+}
+
+func (m *ServiceAvailability) GetAvailability() []*Availability {
+	if m != nil {
+		return m.Availability
+	}
+	return nil
+}
+
+// An availability of the merchant's service, indicating time and number
+// of spots.
+// The availability feed should be a list of this message.
+// Please note that it's up to the partner to call out all the possible
+// availabilities.
+// If a massage therapist is available 9am-12pm, and they provide
+// one-hour massage sessions, the aggregator should provide the feed as
+//   availability {start_sec: 9am, duration: 60 minutes, ...}
+//   availability {start_sec: 10am, duration: 60 minutes, ...}
+//   availability {start_sec: 11am, duration: 60 minutes, ...}
+// instead of
+//   availability {start_sec: 9am, duration: 180 minutes, ...}
+//
+type Availability struct {
+	// An opaque string from an aggregator to identify a merchant.
+	MerchantId string `protobuf:"bytes,1,opt,name=merchant_id,json=merchantId" json:"merchant_id,omitempty"`
+	// An opaque string from aggregator to identify a service of the
+	// merchant.
+	ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId" json:"service_id,omitempty"`
+	// Start time of this availability, using epoch time in seconds.
+	StartSec int64 `protobuf:"varint,3,opt,name=start_sec,json=startSec" json:"start_sec,omitempty"`
+	// Duration of the service in seconds, e.g. 30 minutes for a chair massage.
+	DurationSec int64 `protobuf:"varint,4,opt,name=duration_sec,json=durationSec" json:"duration_sec,omitempty"`
+	// Number of total spots and open spots of this availability.
+	// E.g. a Yoga class of 10 spots with 3 booked.
+	//   availability {spots_total: 10, spots_open: 7 ...}
+	// E.g. a chair massage session which was already booked.
+	//   availability {spots_total: 1, spots_open: 0 ...}
+	//
+	// Note: If sending requests using the availability compression format defined
+	//       below, these two fields will be inferred. A Recurrence
+	//       implies spots_total=1 and spots_open=1. A ScheduleException implies
+	//       spots_total=1 and spots_open=0.
+	SpotsTotal int64 `protobuf:"varint,5,opt,name=spots_total,json=spotsTotal" json:"spots_total,omitempty"`
+	SpotsOpen  int64 `protobuf:"varint,6,opt,name=spots_open,json=spotsOpen" json:"spots_open,omitempty"`
+	// An optional opaque string to identify this availability slot. If set, it
+	// will be included in the requests that book/update/cancel appointments.
+	AvailabilityTag string `protobuf:"bytes,7,opt,name=availability_tag,json=availabilityTag" json:"availability_tag,omitempty"`
+	// Optional resources used to disambiguate this availability slot from
+	// others when different staff, room, or party_size values are part
+	// of the service.
+	//
+	// E.g. the same Yoga class with two 2 instructors.
+	//  availability { resources { staff_id: "1" staff_name: "Amy" }
+	//                 spots_total: 10 spots_open: 7 }
+	//  availability { resources { staff_id: "2" staff_name: "John" }
+	//                 spots_total: 5 spots_open: 2 }
+	Resources *Resources `protobuf:"bytes,8,opt,name=resources" json:"resources,omitempty"`
+	// A list of ids referencing the payment options which can be used to pay
+	// for this slot. The actual payment options are defined at the Merchant
+	// level, and can also be shared among multiple Merchants.
+	//
+	// This field overrides any payment_option_ids specified in the service
+	// message. Similarly payment_option_ids specified here do NOT have to be
+	// present in the service message, though must be defined at the
+	// Merchant level.
+	PaymentOptionId []string `protobuf:"bytes,9,rep,name=payment_option_id,json=paymentOptionId" json:"payment_option_id,omitempty"`
+	// The recurrence information for the availability, representing more than one
+	// start time. A recurrence should contain appointments for one working day.
+	Recurrence *Availability_Recurrence `protobuf:"bytes,10,opt,name=recurrence" json:"recurrence,omitempty"`
+	// When this service cannot be scheduled. To limit the number of
+	// schedule_exception messages consider joining adjacent exceptions.
+	ScheduleException    []*Availability_ScheduleException `protobuf:"bytes,11,rep,name=schedule_exception,json=scheduleException" json:"schedule_exception,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                          `json:"-"`
+	XXX_unrecognized     []byte                            `json:"-"`
+	XXX_sizecache        int32                             `json:"-"`
+}
+
+func (m *Availability) Reset()         { *m = Availability{} }
+func (m *Availability) String() string { return proto.CompactTextString(m) }
+func (*Availability) ProtoMessage()    {}
+func (*Availability) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{3}
+}
+func (m *Availability) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Availability.Unmarshal(m, b)
+}
+func (m *Availability) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Availability.Marshal(b, m, deterministic)
+}
+func (dst *Availability) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Availability.Merge(dst, src)
+}
+func (m *Availability) XXX_Size() int {
+	return xxx_messageInfo_Availability.Size(m)
+}
+func (m *Availability) XXX_DiscardUnknown() {
+	xxx_messageInfo_Availability.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Availability proto.InternalMessageInfo
+
+func (m *Availability) GetMerchantId() string {
+	if m != nil {
+		return m.MerchantId
+	}
+	return ""
+}
+
+func (m *Availability) GetServiceId() string {
+	if m != nil {
+		return m.ServiceId
+	}
+	return ""
+}
+
+func (m *Availability) GetStartSec() int64 {
+	if m != nil {
+		return m.StartSec
+	}
+	return 0
+}
+
+func (m *Availability) GetDurationSec() int64 {
+	if m != nil {
+		return m.DurationSec
+	}
+	return 0
+}
+
+func (m *Availability) GetSpotsTotal() int64 {
+	if m != nil {
+		return m.SpotsTotal
+	}
+	return 0
+}
+
+func (m *Availability) GetSpotsOpen() int64 {
+	if m != nil {
+		return m.SpotsOpen
+	}
+	return 0
+}
+
+func (m *Availability) GetAvailabilityTag() string {
+	if m != nil {
+		return m.AvailabilityTag
+	}
+	return ""
+}
+
+func (m *Availability) GetResources() *Resources {
+	if m != nil {
+		return m.Resources
+	}
+	return nil
+}
+
+func (m *Availability) GetPaymentOptionId() []string {
+	if m != nil {
+		return m.PaymentOptionId
+	}
+	return nil
+}
+
+func (m *Availability) GetRecurrence() *Availability_Recurrence {
+	if m != nil {
+		return m.Recurrence
+	}
+	return nil
+}
+
+func (m *Availability) GetScheduleException() []*Availability_ScheduleException {
+	if m != nil {
+		return m.ScheduleException
+	}
+	return nil
+}
+
+// Recurrence messages are optional, but allow for a more compact
+// representation of consistently repeating availability slots. They typically
+// represent a day's working schedule.
+// ScheduleException messages are then used to represent booked/unavailable
+// time ranges within the work day.
+//
+// Requirements:
+//   1. The expansion of availability slots or recurrences must NOT create
+//      identical slots. If the ids, start_sec, duration_sec, and resources
+//      match, slots are considered identical.
+//   2. Do NOT mix the standard availability format and recurrence within the
+//      slots of a single service. Recurrence benefits merchants/services that
+//      offer appointments. The standard format is geared towards
+//      merchants/services with regularly scheduled classes.
+type Availability_Recurrence struct {
+	// The inclusive maximum UTC timestamp the availability repeats until.
+	RepeatUntilSec int64 `protobuf:"varint,1,opt,name=repeat_until_sec,json=repeatUntilSec" json:"repeat_until_sec,omitempty"`
+	// Defines the time between successive availability slots.
+	//
+	// E.g. An availability with a duration of 20 min, a repeat_every_sec of
+	// 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will
+	// yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am,
+	// 11-11:20am.
+	RepeatEverySec       int32    `protobuf:"varint,2,opt,name=repeat_every_sec,json=repeatEverySec" json:"repeat_every_sec,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Availability_Recurrence) Reset()         { *m = Availability_Recurrence{} }
+func (m *Availability_Recurrence) String() string { return proto.CompactTextString(m) }
+func (*Availability_Recurrence) ProtoMessage()    {}
+func (*Availability_Recurrence) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{3, 0}
+}
+func (m *Availability_Recurrence) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Availability_Recurrence.Unmarshal(m, b)
+}
+func (m *Availability_Recurrence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Availability_Recurrence.Marshal(b, m, deterministic)
+}
+func (dst *Availability_Recurrence) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Availability_Recurrence.Merge(dst, src)
+}
+func (m *Availability_Recurrence) XXX_Size() int {
+	return xxx_messageInfo_Availability_Recurrence.Size(m)
+}
+func (m *Availability_Recurrence) XXX_DiscardUnknown() {
+	xxx_messageInfo_Availability_Recurrence.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Availability_Recurrence proto.InternalMessageInfo
+
+func (m *Availability_Recurrence) GetRepeatUntilSec() int64 {
+	if m != nil {
+		return m.RepeatUntilSec
+	}
+	return 0
+}
+
+func (m *Availability_Recurrence) GetRepeatEverySec() int32 {
+	if m != nil {
+		return m.RepeatEverySec
+	}
+	return 0
+}
+
+// ScheduleException messages are used to represent booked/unavailable time
+// ranges within the work day. As time slots are booked, the list of
+// exceptions should grow to reflect the newly unavailable time ranges.
+// The recurrence itself shouldn't be modified.
+type Availability_ScheduleException struct {
+	// The time range of the exception.
+	TimeRange            *TimeRange `protobuf:"bytes,1,opt,name=time_range,json=timeRange" json:"time_range,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *Availability_ScheduleException) Reset()         { *m = Availability_ScheduleException{} }
+func (m *Availability_ScheduleException) String() string { return proto.CompactTextString(m) }
+func (*Availability_ScheduleException) ProtoMessage()    {}
+func (*Availability_ScheduleException) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{3, 1}
+}
+func (m *Availability_ScheduleException) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Availability_ScheduleException.Unmarshal(m, b)
+}
+func (m *Availability_ScheduleException) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Availability_ScheduleException.Marshal(b, m, deterministic)
+}
+func (dst *Availability_ScheduleException) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Availability_ScheduleException.Merge(dst, src)
+}
+func (m *Availability_ScheduleException) XXX_Size() int {
+	return xxx_messageInfo_Availability_ScheduleException.Size(m)
+}
+func (m *Availability_ScheduleException) XXX_DiscardUnknown() {
+	xxx_messageInfo_Availability_ScheduleException.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Availability_ScheduleException proto.InternalMessageInfo
+
+func (m *Availability_ScheduleException) GetTimeRange() *TimeRange {
+	if m != nil {
+		return m.TimeRange
+	}
+	return nil
+}
+
+// A resource is used to disambiguate availability slots from one another when
+// different staff, room or party_size values are part of the service.
+// Multiple slots for the same service and time interval can co-exist when they
+// have different resources.
+type Resources struct {
+	// Optional id for a staff member providing the service. This field identifies
+	// the staff member across all merchants, services, and availability records.
+	// It also needs to be stable over time to allow correlation with past
+	// bookings.
+	// This field must be present if staff_name is present.
+	StaffId string `protobuf:"bytes,1,opt,name=staff_id,json=staffId" json:"staff_id,omitempty"`
+	// Optional name of a staff member providing the service. This field will be
+	// displayed to users making a booking, and should be human readable, as
+	// opposed to an opaque identifier.
+	// This field must be present if staff_id is present.
+	StaffName string `protobuf:"bytes,2,opt,name=staff_name,json=staffName" json:"staff_name,omitempty"`
+	// An optional id for the room the service is located in. This field
+	// identifies the room across all merchants, services, and availability
+	// records. It also needs to be stable over time to allow correlation with
+	// past bookings.
+	// This field must be present if room_name is present.
+	RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId" json:"room_id,omitempty"`
+	// An optional name for the room the service is located in. This
+	// field will be displayed to users making a booking, and should be human
+	// readable, as opposed to an opaque identifier.
+	// This field must be present if room_id is present.
+	RoomName string `protobuf:"bytes,4,opt,name=room_name,json=roomName" json:"room_name,omitempty"`
+	// Applicable only for Dining: The party size which can be accommodated
+	// during this time slot. A restaurant can be associated with multiple Slots
+	// for the same time, each specifying a different party_size, if for instance
+	// 2, 3, or 4 people can be seated with a reservation.
+	PartySize            int32    `protobuf:"varint,5,opt,name=party_size,json=partySize" json:"party_size,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Resources) Reset()         { *m = Resources{} }
+func (m *Resources) String() string { return proto.CompactTextString(m) }
+func (*Resources) ProtoMessage()    {}
+func (*Resources) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{4}
+}
+func (m *Resources) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Resources.Unmarshal(m, b)
+}
+func (m *Resources) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Resources.Marshal(b, m, deterministic)
+}
+func (dst *Resources) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Resources.Merge(dst, src)
+}
+func (m *Resources) XXX_Size() int {
+	return xxx_messageInfo_Resources.Size(m)
+}
+func (m *Resources) XXX_DiscardUnknown() {
+	xxx_messageInfo_Resources.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Resources proto.InternalMessageInfo
+
+func (m *Resources) GetStaffId() string {
+	if m != nil {
+		return m.StaffId
+	}
+	return ""
+}
+
+func (m *Resources) GetStaffName() string {
+	if m != nil {
+		return m.StaffName
+	}
+	return ""
+}
+
+func (m *Resources) GetRoomId() string {
+	if m != nil {
+		return m.RoomId
+	}
+	return ""
+}
+
+func (m *Resources) GetRoomName() string {
+	if m != nil {
+		return m.RoomName
+	}
+	return ""
+}
+
+func (m *Resources) GetPartySize() int32 {
+	if m != nil {
+		return m.PartySize
+	}
+	return 0
+}
+
+type TimeRange struct {
+	BeginSec             int64    `protobuf:"varint,1,opt,name=begin_sec,json=beginSec" json:"begin_sec,omitempty"`
+	EndSec               int64    `protobuf:"varint,2,opt,name=end_sec,json=endSec" json:"end_sec,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *TimeRange) Reset()         { *m = TimeRange{} }
+func (m *TimeRange) String() string { return proto.CompactTextString(m) }
+func (*TimeRange) ProtoMessage()    {}
+func (*TimeRange) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{5}
+}
+func (m *TimeRange) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_TimeRange.Unmarshal(m, b)
+}
+func (m *TimeRange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_TimeRange.Marshal(b, m, deterministic)
+}
+func (dst *TimeRange) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_TimeRange.Merge(dst, src)
+}
+func (m *TimeRange) XXX_Size() int {
+	return xxx_messageInfo_TimeRange.Size(m)
+}
+func (m *TimeRange) XXX_DiscardUnknown() {
+	xxx_messageInfo_TimeRange.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_TimeRange proto.InternalMessageInfo
+
+func (m *TimeRange) GetBeginSec() int64 {
+	if m != nil {
+		return m.BeginSec
+	}
+	return 0
+}
+
+func (m *TimeRange) GetEndSec() int64 {
+	if m != nil {
+		return m.EndSec
+	}
+	return 0
+}
+
+type ServiceFeed struct {
+	Metadata             *FeedMetadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"`
+	Service              []*Service    `protobuf:"bytes,2,rep,name=service" json:"service,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *ServiceFeed) Reset()         { *m = ServiceFeed{} }
+func (m *ServiceFeed) String() string { return proto.CompactTextString(m) }
+func (*ServiceFeed) ProtoMessage()    {}
+func (*ServiceFeed) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{6}
+}
+func (m *ServiceFeed) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ServiceFeed.Unmarshal(m, b)
+}
+func (m *ServiceFeed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ServiceFeed.Marshal(b, m, deterministic)
+}
+func (dst *ServiceFeed) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ServiceFeed.Merge(dst, src)
+}
+func (m *ServiceFeed) XXX_Size() int {
+	return xxx_messageInfo_ServiceFeed.Size(m)
+}
+func (m *ServiceFeed) XXX_DiscardUnknown() {
+	xxx_messageInfo_ServiceFeed.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ServiceFeed proto.InternalMessageInfo
+
+func (m *ServiceFeed) GetMetadata() *FeedMetadata {
+	if m != nil {
+		return m.Metadata
+	}
+	return nil
+}
+
+func (m *ServiceFeed) GetService() []*Service {
+	if m != nil {
+		return m.Service
+	}
+	return nil
+}
+
+// The definition of a service provided by a merchant.
+type Service struct {
+	// An opaque string from an aggregator partner which uniquely identifies a
+	// merchant. (required)
+	MerchantId string `protobuf:"bytes,1,opt,name=merchant_id,json=merchantId" json:"merchant_id,omitempty"`
+	// An opaque string from an aggregator partner which uniquely identifies the
+	// service. (required)
+	ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId" json:"service_id,omitempty"`
+	// The name of the service, suitable for display to users, e.g. "Men's
+	// haircut". (required)
+	Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"`
+	// The description of the service, suitable for display to users.
+	//
+	// This field now supports both plain text and HTML-like formatting rules to
+	// display structural contents to end-users. Unlike plain text sections,
+	// customized layouts can be created here using headings, paragraphs, lists
+	// and some phrase tags. Please read the following instructions and notes
+	// carefully to create better user-experience.
+	//
+	// Supported HTML-like formatting tags:
+	//
+	// Heading tags: <h1>, <h2>, <h3>, <h4>, <h5>, <h6>
+	//   Heading tags can be used to display titles and sub-titles. For example,
+	//   <h1>Itinerary</h1> will display the inline text as the most important
+	//   heading of the section. Note that any inner HTML tags, styles or
+	//   attributes will be ignored. For example, <h1 style=".."> will be treated
+	//   the same as <h1>. Only pure text wil be preserved.
+	//
+	// Paragraph tag: <p>:
+	//   Paragraph tag can be used to wrap detailed introduction or contents. Any
+	//   inner tags, styles or attributes will be ignored with a few exceptions:
+	//   <br>, <strong> and <em>. Please see the phrase tag section below for more
+	//   details.
+	//
+	// List tags: <ul>, <ol>, <li>
+	//   <ul> tag can be used with <li> tag to display unordered lists, and <ol>
+	//   tag can be used with <li> to display ordered lists. This is a good tool
+	//   to display checklists, highlights or any other lists that fit your
+	//   use-cases.
+	// Example: To show a list of features of a cruise trip:
+	//  <ol>
+	//    <li>Wonderful ocean view and chances to play with wildlife.</li>
+	//    <li>Carefully designed travel arrangements and services.</li>
+	//    <li>Gauranteed lowest price.</li>
+	// </ol>
+	// Note that only <li> children under <ul> or <ol> tags will be converted. All
+	// other children will be dropped. Also any inner tags, attributes and styles
+	// will be ignored, only pure text contents are preserved.
+	//
+	// Division tag: <div>
+	//   All supported inner tags of the <div> tag will be parsed with the rules
+	//   stated above. But <div> tag itself does not mean any grouping or
+	//   indenting here. Also any inner attributes and styles will be ignored.
+	//
+	// Phrase tags: <br>, <strong>, <em>:
+	//   Only the three tags mentioned above are supported. <br> can be used to
+	//   break lines in paragraphs, and <strong>/<em> can be used to highlight
+	//   important text. Any other phrase tags will be ignored.
+	//
+	// Unsupported tags:
+	//   * <html>, <header> and <body> tags are not allowed.
+	//   * Any URLs, anchors, links will be striped, and will never be displayed
+	//   to end-users. If you want to use photos to create a rich user experience,
+	//   please use the "related_media" field below to send your photo urls
+	//   * Any other tags not mentioned above are not supported (for example
+	//   <table>, <td> ...).
+	//
+	// Important notes:
+	//   * Try not to use other tags except for the supported ones mentioned
+	//   above, because contents within unsupported tags will be excluded from UI
+	//   rendering and may lead to undesired 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.
+	//   * If the currently supported layouts are not enough for your use cases,
+	//   please contact Google with your requests and we will find the best
+	//   approach for you.
+	//
+	// (required)
+	Description string `protobuf:"bytes,4,opt,name=description" json:"description,omitempty"`
+	// The price of the service. (optional, overridden when payment options or
+	// ticket types present)
+	Price *Price `protobuf:"bytes,5,opt,name=price" json:"price,omitempty"`
+	// Rules to book/cancel an appointment. (optional)
+	Rules *SchedulingRules `protobuf:"bytes,6,opt,name=rules" json:"rules,omitempty"`
+	// Intake forms to customize the service. (optional)
+	Form []*ServiceIntakeForm `protobuf:"bytes,7,rep,name=form" json:"form,omitempty"`
+	// Whether a prepayment is required, optional or not supported. (optional)
+	PrepaymentType Service_PrepaymentType `protobuf:"varint,8,opt,name=prepayment_type,json=prepaymentType,enum=maps.booking.feeds.Service_PrepaymentType" json:"prepayment_type,omitempty"`
+	// The service's tax rate. If present this field overrides any tax_rate set at
+	// the merchant level. An empty message (i.e. tax_rate { }) will reset the
+	// applied tax rate to zero. (optional)
+	TaxRate *TaxRate `protobuf:"bytes,9,opt,name=tax_rate,json=taxRate" json:"tax_rate,omitempty"`
+	// A list of ids referencing the payment options which can be used to pay
+	// for this service. The actual payment options are defined at the Merchant
+	// level, and can also be shared among multiple Merchants. (optional)
+	PaymentOptionId []string `protobuf:"bytes,10,rep,name=payment_option_id,json=paymentOptionId" json:"payment_option_id,omitempty"`
+	// Defines how a deposit may be charged to the user. Can be overridden at the
+	// availability level. (optional)
+	Deposit *Deposit `protobuf:"bytes,11,opt,name=deposit" json:"deposit,omitempty"`
+	// Defines a no show fee that may be charged to the user. Can be overridden
+	// at the availability level. (optional)
+	NoShowFee *NoShowFee `protobuf:"bytes,12,opt,name=no_show_fee,json=noShowFee" json:"no_show_fee,omitempty"`
+	// Indicates whether the user must provide a credit card in order to book this
+	// service.
+	// This value can be overridden at the availability level. (optional)
+	RequireCreditCard RequireCreditCard `protobuf:"varint,13,opt,name=require_credit_card,json=requireCreditCard,enum=maps.booking.feeds.RequireCreditCard" json:"require_credit_card,omitempty"`
+	// An action link related to this service.
+	ActionLink []*ActionLink `protobuf:"bytes,14,rep,name=action_link,json=actionLink" json:"action_link,omitempty"`
+	// The predefined type of this service. Currently, only used with action_link.
+	Type Service_ServiceType `protobuf:"varint,15,opt,name=type,enum=maps.booking.feeds.Service_ServiceType" json:"type,omitempty"`
+	// Types of tickets that can be booked/purchased for this service, if tickets
+	// are supported. (optional)
+	TicketType []*TicketType `protobuf:"bytes,16,rep,name=ticket_type,json=ticketType" json:"ticket_type,omitempty"`
+	// Photos related to this service. Google will crawl these media to
+	// ensure that they are displayed to end-users in the most efficient way.
+	// (optional)
+	RelatedMedia []*RelatedMedia `protobuf:"bytes,17,rep,name=related_media,json=relatedMedia" json:"related_media,omitempty"`
+	// Service attribute values that apply to this service (optional).
+	// Each Service may have zero or more values for each service attribute
+	// defined in the corresponding Merchant.
+	ServiceAttributeValueId []*ServiceAttributeValueId `protobuf:"bytes,18,rep,name=service_attribute_value_id,json=serviceAttributeValueId" json:"service_attribute_value_id,omitempty"`
+	XXX_NoUnkeyedLiteral    struct{}                   `json:"-"`
+	XXX_unrecognized        []byte                     `json:"-"`
+	XXX_sizecache           int32                      `json:"-"`
+}
+
+func (m *Service) Reset()         { *m = Service{} }
+func (m *Service) String() string { return proto.CompactTextString(m) }
+func (*Service) ProtoMessage()    {}
+func (*Service) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{7}
+}
+func (m *Service) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Service.Unmarshal(m, b)
+}
+func (m *Service) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Service.Marshal(b, m, deterministic)
+}
+func (dst *Service) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Service.Merge(dst, src)
+}
+func (m *Service) XXX_Size() int {
+	return xxx_messageInfo_Service.Size(m)
+}
+func (m *Service) XXX_DiscardUnknown() {
+	xxx_messageInfo_Service.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Service proto.InternalMessageInfo
+
+func (m *Service) GetMerchantId() string {
+	if m != nil {
+		return m.MerchantId
+	}
+	return ""
+}
+
+func (m *Service) GetServiceId() string {
+	if m != nil {
+		return m.ServiceId
+	}
+	return ""
+}
+
+func (m *Service) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Service) GetDescription() string {
+	if m != nil {
+		return m.Description
+	}
+	return ""
+}
+
+func (m *Service) GetPrice() *Price {
+	if m != nil {
+		return m.Price
+	}
+	return nil
+}
+
+func (m *Service) GetRules() *SchedulingRules {
+	if m != nil {
+		return m.Rules
+	}
+	return nil
+}
+
+func (m *Service) GetForm() []*ServiceIntakeForm {
+	if m != nil {
+		return m.Form
+	}
+	return nil
+}
+
+func (m *Service) GetPrepaymentType() Service_PrepaymentType {
+	if m != nil {
+		return m.PrepaymentType
+	}
+	return Service_PREPAYMENT_TYPE_UNSPECIFIED
+}
+
+func (m *Service) GetTaxRate() *TaxRate {
+	if m != nil {
+		return m.TaxRate
+	}
+	return nil
+}
+
+func (m *Service) GetPaymentOptionId() []string {
+	if m != nil {
+		return m.PaymentOptionId
+	}
+	return nil
+}
+
+func (m *Service) GetDeposit() *Deposit {
+	if m != nil {
+		return m.Deposit
+	}
+	return nil
+}
+
+func (m *Service) GetNoShowFee() *NoShowFee {
+	if m != nil {
+		return m.NoShowFee
+	}
+	return nil
+}
+
+func (m *Service) GetRequireCreditCard() RequireCreditCard {
+	if m != nil {
+		return m.RequireCreditCard
+	}
+	return RequireCreditCard_REQUIRE_CREDIT_CARD_UNSPECIFIED
+}
+
+func (m *Service) GetActionLink() []*ActionLink {
+	if m != nil {
+		return m.ActionLink
+	}
+	return nil
+}
+
+func (m *Service) GetType() Service_ServiceType {
+	if m != nil {
+		return m.Type
+	}
+	return Service_SERVICE_TYPE_UNSPECIFIED
+}
+
+func (m *Service) GetTicketType() []*TicketType {
+	if m != nil {
+		return m.TicketType
+	}
+	return nil
+}
+
+func (m *Service) GetRelatedMedia() []*RelatedMedia {
+	if m != nil {
+		return m.RelatedMedia
+	}
+	return nil
+}
+
+func (m *Service) GetServiceAttributeValueId() []*ServiceAttributeValueId {
+	if m != nil {
+		return m.ServiceAttributeValueId
+	}
+	return nil
+}
+
+// The price of a service or a fee.
+type Price struct {
+	// The price in micro-units of the currency.
+	// Fractions of smallest currency unit will be rounded using nearest even
+	// rounding. (e.g. For USD 2.5 cents rounded to 2 cents, 3.5 cents rounded to
+	// 4 cents, 0.5 cents rounded to 0 cents, 2.51 cents rounded to 3 cents).
+	// (required)
+	PriceMicros int64 `protobuf:"varint,1,opt,name=price_micros,json=priceMicros" json:"price_micros,omitempty"`
+	// The currency of the price that is defined in ISO 4217. (required)
+	CurrencyCode string `protobuf:"bytes,2,opt,name=currency_code,json=currencyCode" json:"currency_code,omitempty"`
+	// An optional and opaque string that identifies the pricing option that is
+	// associated with the extended price. (optional)
+	PricingOptionTag     string   `protobuf:"bytes,3,opt,name=pricing_option_tag,json=pricingOptionTag" json:"pricing_option_tag,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Price) Reset()         { *m = Price{} }
+func (m *Price) String() string { return proto.CompactTextString(m) }
+func (*Price) ProtoMessage()    {}
+func (*Price) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{8}
+}
+func (m *Price) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Price.Unmarshal(m, b)
+}
+func (m *Price) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Price.Marshal(b, m, deterministic)
+}
+func (dst *Price) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Price.Merge(dst, src)
+}
+func (m *Price) XXX_Size() int {
+	return xxx_messageInfo_Price.Size(m)
+}
+func (m *Price) XXX_DiscardUnknown() {
+	xxx_messageInfo_Price.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Price proto.InternalMessageInfo
+
+func (m *Price) GetPriceMicros() int64 {
+	if m != nil {
+		return m.PriceMicros
+	}
+	return 0
+}
+
+func (m *Price) GetCurrencyCode() string {
+	if m != nil {
+		return m.CurrencyCode
+	}
+	return ""
+}
+
+func (m *Price) GetPricingOptionTag() string {
+	if m != nil {
+		return m.PricingOptionTag
+	}
+	return ""
+}
+
+// The scheduling rules for a service.
+type SchedulingRules struct {
+	// The minimum advance notice in seconds required to book an appointment.
+	// (optional)
+	MinAdvanceBooking int64 `protobuf:"varint,1,opt,name=min_advance_booking,json=minAdvanceBooking" json:"min_advance_booking,omitempty"`
+	// The minimum advance notice in seconds required to cancel a booked
+	// appointment online. (optional)
+	MinAdvanceOnlineCanceling int64 `protobuf:"varint,2,opt,name=min_advance_online_canceling,json=minAdvanceOnlineCanceling" json:"min_advance_online_canceling,omitempty"`
+	// The fee for canceling within the minimum advance notice period.
+	LateCancellationFee *Price `protobuf:"bytes,3,opt,name=late_cancellation_fee,json=lateCancellationFee" json:"late_cancellation_fee,omitempty"` // Deprecated: Do not use.
+	// The fee for no-show without canceling.
+	NoshowFee            *Price   `protobuf:"bytes,4,opt,name=noshow_fee,json=noshowFee" json:"noshow_fee,omitempty"` // Deprecated: Do not use.
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *SchedulingRules) Reset()         { *m = SchedulingRules{} }
+func (m *SchedulingRules) String() string { return proto.CompactTextString(m) }
+func (*SchedulingRules) ProtoMessage()    {}
+func (*SchedulingRules) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{9}
+}
+func (m *SchedulingRules) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_SchedulingRules.Unmarshal(m, b)
+}
+func (m *SchedulingRules) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_SchedulingRules.Marshal(b, m, deterministic)
+}
+func (dst *SchedulingRules) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SchedulingRules.Merge(dst, src)
+}
+func (m *SchedulingRules) XXX_Size() int {
+	return xxx_messageInfo_SchedulingRules.Size(m)
+}
+func (m *SchedulingRules) XXX_DiscardUnknown() {
+	xxx_messageInfo_SchedulingRules.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SchedulingRules proto.InternalMessageInfo
+
+func (m *SchedulingRules) GetMinAdvanceBooking() int64 {
+	if m != nil {
+		return m.MinAdvanceBooking
+	}
+	return 0
+}
+
+func (m *SchedulingRules) GetMinAdvanceOnlineCanceling() int64 {
+	if m != nil {
+		return m.MinAdvanceOnlineCanceling
+	}
+	return 0
+}
+
+// Deprecated: Do not use.
+func (m *SchedulingRules) GetLateCancellationFee() *Price {
+	if m != nil {
+		return m.LateCancellationFee
+	}
+	return nil
+}
+
+// Deprecated: Do not use.
+func (m *SchedulingRules) GetNoshowFee() *Price {
+	if m != nil {
+		return m.NoshowFee
+	}
+	return nil
+}
+
+// A tax rate applied when charging the user for a service, and which can be set
+// on either a per merchant, or per service basis.
+type TaxRate struct {
+	// A tax rate in millionths of one percent, effectively giving 6 decimals of
+	// precision. For example, if the tax rate is 7.253%, this field should be set
+	// to 7253000.
+	//
+	// If this field is left unset or set to 0, the total price charged to a user
+	// for any service provided by this merchant is the exact price specified by
+	// Service.price. The service price is assumed to be exempt from or already
+	// inclusive of applicable taxes. Taxes will not be shown to the user as a
+	// separate line item.
+	//
+	// If this field is set to any nonzero value, the total price charged to a
+	// user for any service provided by this merchant will include the service
+	// price plus the tax assessed using the tax rate provided here. Fractions of
+	// the smallest currency unit (for example, fractions of one cent) will be
+	// rounded using nearest even rounding. Taxes will be shown to the user as a
+	// separate line item. (required)
+	MicroPercent         int32    `protobuf:"varint,1,opt,name=micro_percent,json=microPercent" json:"micro_percent,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *TaxRate) Reset()         { *m = TaxRate{} }
+func (m *TaxRate) String() string { return proto.CompactTextString(m) }
+func (*TaxRate) ProtoMessage()    {}
+func (*TaxRate) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{10}
+}
+func (m *TaxRate) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_TaxRate.Unmarshal(m, b)
+}
+func (m *TaxRate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_TaxRate.Marshal(b, m, deterministic)
+}
+func (dst *TaxRate) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_TaxRate.Merge(dst, src)
+}
+func (m *TaxRate) XXX_Size() int {
+	return xxx_messageInfo_TaxRate.Size(m)
+}
+func (m *TaxRate) XXX_DiscardUnknown() {
+	xxx_messageInfo_TaxRate.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_TaxRate proto.InternalMessageInfo
+
+func (m *TaxRate) GetMicroPercent() int32 {
+	if m != nil {
+		return m.MicroPercent
+	}
+	return 0
+}
+
+// Defines a field that is included in a ServiceIntakeForm.
+type ServiceIntakeFormField struct {
+	// The type of this field. (required)
+	Type ServiceIntakeFormField_FieldType `protobuf:"varint,1,opt,name=type,enum=maps.booking.feeds.ServiceIntakeFormField_FieldType" json:"type,omitempty"`
+	// The text shown to the user for this field. (required)
+	Label string `protobuf:"bytes,2,opt,name=label" json:"label,omitempty"`
+	// For MULTIPLE_CHOICE, CHECKBOXES, or DROPDOWN, the values to enumerate.
+	// (optional)
+	Value []string `protobuf:"bytes,3,rep,name=value" json:"value,omitempty"`
+	// Indicates whether an answer to this field is required by a user. (optional)
+	IsRequired           bool     `protobuf:"varint,4,opt,name=is_required,json=isRequired" json:"is_required,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ServiceIntakeFormField) Reset()         { *m = ServiceIntakeFormField{} }
+func (m *ServiceIntakeFormField) String() string { return proto.CompactTextString(m) }
+func (*ServiceIntakeFormField) ProtoMessage()    {}
+func (*ServiceIntakeFormField) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{11}
+}
+func (m *ServiceIntakeFormField) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ServiceIntakeFormField.Unmarshal(m, b)
+}
+func (m *ServiceIntakeFormField) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ServiceIntakeFormField.Marshal(b, m, deterministic)
+}
+func (dst *ServiceIntakeFormField) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ServiceIntakeFormField.Merge(dst, src)
+}
+func (m *ServiceIntakeFormField) XXX_Size() int {
+	return xxx_messageInfo_ServiceIntakeFormField.Size(m)
+}
+func (m *ServiceIntakeFormField) XXX_DiscardUnknown() {
+	xxx_messageInfo_ServiceIntakeFormField.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ServiceIntakeFormField proto.InternalMessageInfo
+
+func (m *ServiceIntakeFormField) GetType() ServiceIntakeFormField_FieldType {
+	if m != nil {
+		return m.Type
+	}
+	return ServiceIntakeFormField_FIELD_TYPE_UNSPECIFIED
+}
+
+func (m *ServiceIntakeFormField) GetLabel() string {
+	if m != nil {
+		return m.Label
+	}
+	return ""
+}
+
+func (m *ServiceIntakeFormField) GetValue() []string {
+	if m != nil {
+		return m.Value
+	}
+	return nil
+}
+
+func (m *ServiceIntakeFormField) GetIsRequired() bool {
+	if m != nil {
+		return m.IsRequired
+	}
+	return false
+}
+
+// Defines an intake form that customizes the service provided by a merchant.
+type ServiceIntakeForm struct {
+	// Fields that will be displayed to the user. (required)
+	Field []*ServiceIntakeFormField `protobuf:"bytes,1,rep,name=field" json:"field,omitempty"`
+	// If true, this form will be shown to first time customers.
+	// (one of first_time_customers or returning_customers required)
+	FirstTimeCustomers bool `protobuf:"varint,2,opt,name=first_time_customers,json=firstTimeCustomers" json:"first_time_customers,omitempty"`
+	// If true, this form will be shown to repeat customers.
+	// (one of first_time_customers or returning_customers required)
+	ReturningCustomers   bool     `protobuf:"varint,3,opt,name=returning_customers,json=returningCustomers" json:"returning_customers,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ServiceIntakeForm) Reset()         { *m = ServiceIntakeForm{} }
+func (m *ServiceIntakeForm) String() string { return proto.CompactTextString(m) }
+func (*ServiceIntakeForm) ProtoMessage()    {}
+func (*ServiceIntakeForm) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{12}
+}
+func (m *ServiceIntakeForm) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ServiceIntakeForm.Unmarshal(m, b)
+}
+func (m *ServiceIntakeForm) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ServiceIntakeForm.Marshal(b, m, deterministic)
+}
+func (dst *ServiceIntakeForm) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ServiceIntakeForm.Merge(dst, src)
+}
+func (m *ServiceIntakeForm) XXX_Size() int {
+	return xxx_messageInfo_ServiceIntakeForm.Size(m)
+}
+func (m *ServiceIntakeForm) XXX_DiscardUnknown() {
+	xxx_messageInfo_ServiceIntakeForm.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ServiceIntakeForm proto.InternalMessageInfo
+
+func (m *ServiceIntakeForm) GetField() []*ServiceIntakeFormField {
+	if m != nil {
+		return m.Field
+	}
+	return nil
+}
+
+func (m *ServiceIntakeForm) GetFirstTimeCustomers() bool {
+	if m != nil {
+		return m.FirstTimeCustomers
+	}
+	return false
+}
+
+func (m *ServiceIntakeForm) GetReturningCustomers() bool {
+	if m != nil {
+		return m.ReturningCustomers
+	}
+	return false
+}
+
+// A deposit that the user may be charged or have a hold on their credit card
+// for.
+type Deposit struct {
+	// Deposit amount.
+	Deposit *Price `protobuf:"bytes,1,opt,name=deposit" json:"deposit,omitempty"`
+	// Minimum advance cancellation for the deposit.
+	MinAdvanceCancellationSec int64 `protobuf:"varint,2,opt,name=min_advance_cancellation_sec,json=minAdvanceCancellationSec" json:"min_advance_cancellation_sec,omitempty"`
+	// Defines how the deposit is determined from the availability.
+	DepositType          PriceType `protobuf:"varint,3,opt,name=deposit_type,json=depositType,enum=maps.booking.feeds.PriceType" json:"deposit_type,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
+	XXX_unrecognized     []byte    `json:"-"`
+	XXX_sizecache        int32     `json:"-"`
+}
+
+func (m *Deposit) Reset()         { *m = Deposit{} }
+func (m *Deposit) String() string { return proto.CompactTextString(m) }
+func (*Deposit) ProtoMessage()    {}
+func (*Deposit) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{13}
+}
+func (m *Deposit) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Deposit.Unmarshal(m, b)
+}
+func (m *Deposit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Deposit.Marshal(b, m, deterministic)
+}
+func (dst *Deposit) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Deposit.Merge(dst, src)
+}
+func (m *Deposit) XXX_Size() int {
+	return xxx_messageInfo_Deposit.Size(m)
+}
+func (m *Deposit) XXX_DiscardUnknown() {
+	xxx_messageInfo_Deposit.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Deposit proto.InternalMessageInfo
+
+func (m *Deposit) GetDeposit() *Price {
+	if m != nil {
+		return m.Deposit
+	}
+	return nil
+}
+
+func (m *Deposit) GetMinAdvanceCancellationSec() int64 {
+	if m != nil {
+		return m.MinAdvanceCancellationSec
+	}
+	return 0
+}
+
+func (m *Deposit) GetDepositType() PriceType {
+	if m != nil {
+		return m.DepositType
+	}
+	return PriceType_FIXED_RATE_DEFAULT
+}
+
+// A fee that a user may be charged if they have made a booking but do not
+// show up.
+type NoShowFee struct {
+	// The amount the user may be charged if they do not show up for their
+	// reservation.
+	Fee *Price `protobuf:"bytes,1,opt,name=fee" json:"fee,omitempty"`
+	// Defines how the fee is determined from the availability.
+	FeeType              PriceType `protobuf:"varint,3,opt,name=fee_type,json=feeType,enum=maps.booking.feeds.PriceType" json:"fee_type,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
+	XXX_unrecognized     []byte    `json:"-"`
+	XXX_sizecache        int32     `json:"-"`
+}
+
+func (m *NoShowFee) Reset()         { *m = NoShowFee{} }
+func (m *NoShowFee) String() string { return proto.CompactTextString(m) }
+func (*NoShowFee) ProtoMessage()    {}
+func (*NoShowFee) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{14}
+}
+func (m *NoShowFee) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_NoShowFee.Unmarshal(m, b)
+}
+func (m *NoShowFee) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_NoShowFee.Marshal(b, m, deterministic)
+}
+func (dst *NoShowFee) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NoShowFee.Merge(dst, src)
+}
+func (m *NoShowFee) XXX_Size() int {
+	return xxx_messageInfo_NoShowFee.Size(m)
+}
+func (m *NoShowFee) XXX_DiscardUnknown() {
+	xxx_messageInfo_NoShowFee.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_NoShowFee proto.InternalMessageInfo
+
+func (m *NoShowFee) GetFee() *Price {
+	if m != nil {
+		return m.Fee
+	}
+	return nil
+}
+
+func (m *NoShowFee) GetFeeType() PriceType {
+	if m != nil {
+		return m.FeeType
+	}
+	return PriceType_FIXED_RATE_DEFAULT
+}
+
+// An action URL with associated language, list of countries restricted to, and
+// optional platform that indicates which platform this action should be
+// performed on.
+type ActionLink struct {
+	// The entry point URL for this action link.
+	Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"`
+	// The BCP-47 language tag identifying the language in which the content
+	// from this URI is available.
+	Language string `protobuf:"bytes,2,opt,name=language" json:"language,omitempty"`
+	// ISO 3166-1 alpha-2 country code. Leave empty for unrestricted visibility.
+	RestrictedCountry []string `protobuf:"bytes,3,rep,name=restricted_country,json=restrictedCountry" json:"restricted_country,omitempty"`
+	// The platform that this action should be performed on. If this field is
+	// unset, ACTION_PLATFORM_WEB_APPLICATION will be used as fallback.
+	Platform             ActionPlatform `protobuf:"varint,4,opt,name=platform,enum=maps.booking.feeds.ActionPlatform" json:"platform,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *ActionLink) Reset()         { *m = ActionLink{} }
+func (m *ActionLink) String() string { return proto.CompactTextString(m) }
+func (*ActionLink) ProtoMessage()    {}
+func (*ActionLink) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{15}
+}
+func (m *ActionLink) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ActionLink.Unmarshal(m, b)
+}
+func (m *ActionLink) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ActionLink.Marshal(b, m, deterministic)
+}
+func (dst *ActionLink) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ActionLink.Merge(dst, src)
+}
+func (m *ActionLink) XXX_Size() int {
+	return xxx_messageInfo_ActionLink.Size(m)
+}
+func (m *ActionLink) XXX_DiscardUnknown() {
+	xxx_messageInfo_ActionLink.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ActionLink proto.InternalMessageInfo
+
+func (m *ActionLink) GetUrl() string {
+	if m != nil {
+		return m.Url
+	}
+	return ""
+}
+
+func (m *ActionLink) GetLanguage() string {
+	if m != nil {
+		return m.Language
+	}
+	return ""
+}
+
+func (m *ActionLink) GetRestrictedCountry() []string {
+	if m != nil {
+		return m.RestrictedCountry
+	}
+	return nil
+}
+
+func (m *ActionLink) GetPlatform() ActionPlatform {
+	if m != nil {
+		return m.Platform
+	}
+	return ActionPlatform_ACTION_PLATFORM_UNSPECIFIED
+}
+
+// TicketType is used to differentiate among tickets (where a ticket can be a
+// spot on a raft trip, an admission to a museum, etc.) with different prices
+// and/or availabilities due to different user types or different service
+// attributes.
+type TicketType struct {
+	// The ticket id is used to differentiate among different ticket types of the
+	// same service, and is only expected to be unique within a service.
+	TicketTypeId string `protobuf:"bytes,1,opt,name=ticket_type_id,json=ticketTypeId" json:"ticket_type_id,omitempty"`
+	// This can be user visible, e.g., “adult”, "child", “veteran”, “Row J”, etc.
+	ShortDescription string `protobuf:"bytes,2,opt,name=short_description,json=shortDescription" json:"short_description,omitempty"`
+	// The price of a single ticket of this type, exclusive of any taxes. The tax
+	// rate of Service is applied to its tickets.
+	Price                *Price   `protobuf:"bytes,3,opt,name=price" json:"price,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *TicketType) Reset()         { *m = TicketType{} }
+func (m *TicketType) String() string { return proto.CompactTextString(m) }
+func (*TicketType) ProtoMessage()    {}
+func (*TicketType) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{16}
+}
+func (m *TicketType) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_TicketType.Unmarshal(m, b)
+}
+func (m *TicketType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_TicketType.Marshal(b, m, deterministic)
+}
+func (dst *TicketType) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_TicketType.Merge(dst, src)
+}
+func (m *TicketType) XXX_Size() int {
+	return xxx_messageInfo_TicketType.Size(m)
+}
+func (m *TicketType) XXX_DiscardUnknown() {
+	xxx_messageInfo_TicketType.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_TicketType proto.InternalMessageInfo
+
+func (m *TicketType) GetTicketTypeId() string {
+	if m != nil {
+		return m.TicketTypeId
+	}
+	return ""
+}
+
+func (m *TicketType) GetShortDescription() string {
+	if m != nil {
+		return m.ShortDescription
+	}
+	return ""
+}
+
+func (m *TicketType) GetPrice() *Price {
+	if m != nil {
+		return m.Price
+	}
+	return nil
+}
+
+// Defines a media source. This field only contains the URL of this media source
+// (only photos are supported for now). Google will crawl the media data to
+// ensure that they are displayed to end-users in the most efficient way.
+type RelatedMedia struct {
+	// URL of this media source.
+	Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"`
+	// Type of this media source.
+	Type RelatedMedia_MediaType `protobuf:"varint,2,opt,name=type,enum=maps.booking.feeds.RelatedMedia_MediaType" json:"type,omitempty"`
+	// Caption of the media, only plain text is supported. (optional)
+	Caption              string   `protobuf:"bytes,3,opt,name=caption" json:"caption,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *RelatedMedia) Reset()         { *m = RelatedMedia{} }
+func (m *RelatedMedia) String() string { return proto.CompactTextString(m) }
+func (*RelatedMedia) ProtoMessage()    {}
+func (*RelatedMedia) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{17}
+}
+func (m *RelatedMedia) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_RelatedMedia.Unmarshal(m, b)
+}
+func (m *RelatedMedia) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_RelatedMedia.Marshal(b, m, deterministic)
+}
+func (dst *RelatedMedia) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RelatedMedia.Merge(dst, src)
+}
+func (m *RelatedMedia) XXX_Size() int {
+	return xxx_messageInfo_RelatedMedia.Size(m)
+}
+func (m *RelatedMedia) XXX_DiscardUnknown() {
+	xxx_messageInfo_RelatedMedia.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RelatedMedia proto.InternalMessageInfo
+
+func (m *RelatedMedia) GetUrl() string {
+	if m != nil {
+		return m.Url
+	}
+	return ""
+}
+
+func (m *RelatedMedia) GetType() RelatedMedia_MediaType {
+	if m != nil {
+		return m.Type
+	}
+	return RelatedMedia_TYPE_UNSPECIFIED
+}
+
+func (m *RelatedMedia) GetCaption() string {
+	if m != nil {
+		return m.Caption
+	}
+	return ""
+}
+
+// Identifies a particular value of a service attribute to be applied to a
+// Service.
+type ServiceAttributeValueId struct {
+	// ID of an attribute as defined in Merchant.service_attribute, e.g.
+	// "service-type".
+	AttributeId string `protobuf:"bytes,1,opt,name=attribute_id,json=attributeId" json:"attribute_id,omitempty"`
+	// ID of the value for this attribute, e.g. "haircut". Must match a value_id
+	// in the service attribute definition.
+	ValueId              string   `protobuf:"bytes,2,opt,name=value_id,json=valueId" json:"value_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ServiceAttributeValueId) Reset()         { *m = ServiceAttributeValueId{} }
+func (m *ServiceAttributeValueId) String() string { return proto.CompactTextString(m) }
+func (*ServiceAttributeValueId) ProtoMessage()    {}
+func (*ServiceAttributeValueId) Descriptor() ([]byte, []int) {
+	return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{18}
+}
+func (m *ServiceAttributeValueId) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ServiceAttributeValueId.Unmarshal(m, b)
+}
+func (m *ServiceAttributeValueId) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ServiceAttributeValueId.Marshal(b, m, deterministic)
+}
+func (dst *ServiceAttributeValueId) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ServiceAttributeValueId.Merge(dst, src)
+}
+func (m *ServiceAttributeValueId) XXX_Size() int {
+	return xxx_messageInfo_ServiceAttributeValueId.Size(m)
+}
+func (m *ServiceAttributeValueId) XXX_DiscardUnknown() {
+	xxx_messageInfo_ServiceAttributeValueId.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ServiceAttributeValueId proto.InternalMessageInfo
+
+func (m *ServiceAttributeValueId) GetAttributeId() string {
+	if m != nil {
+		return m.AttributeId
+	}
+	return ""
+}
+
+func (m *ServiceAttributeValueId) GetValueId() string {
+	if m != nil {
+		return m.ValueId
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*FeedMetadata)(nil), "maps.booking.feeds.FeedMetadata")
+	proto.RegisterType((*AvailabilityFeed)(nil), "maps.booking.feeds.AvailabilityFeed")
+	proto.RegisterType((*ServiceAvailability)(nil), "maps.booking.feeds.ServiceAvailability")
+	proto.RegisterType((*Availability)(nil), "maps.booking.feeds.Availability")
+	proto.RegisterType((*Availability_Recurrence)(nil), "maps.booking.feeds.Availability.Recurrence")
+	proto.RegisterType((*Availability_ScheduleException)(nil), "maps.booking.feeds.Availability.ScheduleException")
+	proto.RegisterType((*Resources)(nil), "maps.booking.feeds.Resources")
+	proto.RegisterType((*TimeRange)(nil), "maps.booking.feeds.TimeRange")
+	proto.RegisterType((*ServiceFeed)(nil), "maps.booking.feeds.ServiceFeed")
+	proto.RegisterType((*Service)(nil), "maps.booking.feeds.Service")
+	proto.RegisterType((*Price)(nil), "maps.booking.feeds.Price")
+	proto.RegisterType((*SchedulingRules)(nil), "maps.booking.feeds.SchedulingRules")
+	proto.RegisterType((*TaxRate)(nil), "maps.booking.feeds.TaxRate")
+	proto.RegisterType((*ServiceIntakeFormField)(nil), "maps.booking.feeds.ServiceIntakeFormField")
+	proto.RegisterType((*ServiceIntakeForm)(nil), "maps.booking.feeds.ServiceIntakeForm")
+	proto.RegisterType((*Deposit)(nil), "maps.booking.feeds.Deposit")
+	proto.RegisterType((*NoShowFee)(nil), "maps.booking.feeds.NoShowFee")
+	proto.RegisterType((*ActionLink)(nil), "maps.booking.feeds.ActionLink")
+	proto.RegisterType((*TicketType)(nil), "maps.booking.feeds.TicketType")
+	proto.RegisterType((*RelatedMedia)(nil), "maps.booking.feeds.RelatedMedia")
+	proto.RegisterType((*ServiceAttributeValueId)(nil), "maps.booking.feeds.ServiceAttributeValueId")
+	proto.RegisterEnum("maps.booking.feeds.PriceType", PriceType_name, PriceType_value)
+	proto.RegisterEnum("maps.booking.feeds.RequireCreditCard", RequireCreditCard_name, RequireCreditCard_value)
+	proto.RegisterEnum("maps.booking.feeds.ActionPlatform", ActionPlatform_name, ActionPlatform_value)
+	proto.RegisterEnum("maps.booking.feeds.FeedMetadata_ProcessingInstruction", FeedMetadata_ProcessingInstruction_name, FeedMetadata_ProcessingInstruction_value)
+	proto.RegisterEnum("maps.booking.feeds.Service_PrepaymentType", Service_PrepaymentType_name, Service_PrepaymentType_value)
+	proto.RegisterEnum("maps.booking.feeds.Service_ServiceType", Service_ServiceType_name, Service_ServiceType_value)
+	proto.RegisterEnum("maps.booking.feeds.ServiceIntakeFormField_FieldType", ServiceIntakeFormField_FieldType_name, ServiceIntakeFormField_FieldType_value)
+	proto.RegisterEnum("maps.booking.feeds.RelatedMedia_MediaType", RelatedMedia_MediaType_name, RelatedMedia_MediaType_value)
+}
+
+func init() { proto.RegisterFile("feeds.proto", fileDescriptor_feeds_0b3f05d3b8d0e7eb) }
+
+var fileDescriptor_feeds_0b3f05d3b8d0e7eb = []byte{
+	// 2250 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x58, 0x4f, 0x73, 0xdb, 0xc6,
+	0x15, 0x0f, 0x48, 0xd1, 0x24, 0x1f, 0x69, 0x19, 0x5c, 0xc9, 0x12, 0x2c, 0xc7, 0xb1, 0x0a, 0x37,
+	0x53, 0x55, 0x4e, 0x99, 0x56, 0x4e, 0x32, 0xc9, 0xc4, 0x4d, 0x42, 0x81, 0x90, 0x85, 0x31, 0x45,
+	0xc0, 0x4b, 0xc8, 0x8e, 0x7b, 0xd9, 0x40, 0xc0, 0x4a, 0xc2, 0x98, 0x04, 0xd8, 0x05, 0xa8, 0x58,
+	0x99, 0xc9, 0xa5, 0xa7, 0x1e, 0x3b, 0xd3, 0x5b, 0x3f, 0x41, 0x3b, 0xf5, 0x07, 0xe8, 0xf4, 0xd6,
+	0x53, 0x3f, 0x4e, 0x3f, 0x40, 0x2f, 0x9d, 0x5d, 0x80, 0x00, 0x28, 0x41, 0x96, 0x3b, 0xed, 0x85,
+	0xc2, 0xbe, 0xf7, 0x7e, 0xfb, 0xe7, 0xbd, 0x7d, 0xbf, 0xf7, 0x56, 0xd0, 0x3a, 0xa6, 0xd4, 0x8b,
+	0xba, 0x53, 0x16, 0xc6, 0x21, 0x42, 0x13, 0x67, 0x1a, 0x75, 0x8f, 0xc2, 0xf0, 0x95, 0x1f, 0x9c,
+	0x74, 0x85, 0x46, 0xfd, 0x57, 0x05, 0xda, 0x7b, 0x94, 0x7a, 0x07, 0x34, 0x76, 0x3c, 0x27, 0x76,
+	0xd0, 0x04, 0xd6, 0xa6, 0x2c, 0x74, 0x69, 0x14, 0xf9, 0xc1, 0x09, 0xf1, 0x83, 0x28, 0x66, 0x33,
+	0x37, 0xf6, 0xc3, 0x40, 0x91, 0x36, 0xa5, 0xad, 0xe5, 0x9d, 0xcf, 0xba, 0x97, 0x67, 0xe9, 0x16,
+	0x67, 0xe8, 0x5a, 0x19, 0xdc, 0xc8, 0xd1, 0xf8, 0xf6, 0xb4, 0x4c, 0x8c, 0x7e, 0x02, 0xed, 0xe8,
+	0xd4, 0x61, 0x1e, 0x09, 0x66, 0x93, 0x23, 0xca, 0x94, 0xca, 0xa6, 0xb4, 0x55, 0xc3, 0x2d, 0x21,
+	0x1b, 0x0a, 0x11, 0x37, 0x89, 0xc3, 0xd8, 0x19, 0x13, 0x21, 0x8c, 0x94, 0x6a, 0x62, 0x22, 0x64,
+	0x23, 0x21, 0x42, 0xab, 0x50, 0x0b, 0xc2, 0xc0, 0xa5, 0x4a, 0x6d, 0x53, 0xda, 0x5a, 0xc2, 0xc9,
+	0x00, 0xfd, 0x0a, 0x56, 0x4f, 0x68, 0x40, 0x99, 0xc3, 0x57, 0x22, 0xb1, 0x3f, 0xa1, 0x51, 0xec,
+	0x4c, 0xa6, 0xca, 0xd2, 0xa6, 0xb4, 0x55, 0xc5, 0x2b, 0xb9, 0xce, 0x9e, 0xab, 0x54, 0x07, 0x6e,
+	0x97, 0x6e, 0x1f, 0xad, 0xc0, 0x2d, 0x0b, 0x9b, 0x9a, 0x3e, 0x1a, 0x91, 0xc3, 0xe1, 0xd3, 0xa1,
+	0xf9, 0x62, 0x28, 0xbf, 0x87, 0xd6, 0x61, 0x65, 0x2e, 0xec, 0x8d, 0x88, 0x66, 0x1e, 0x58, 0x03,
+	0xdd, 0xd6, 0x65, 0x09, 0x6d, 0xc0, 0x5a, 0x41, 0x61, 0x0c, 0x35, 0xac, 0x1f, 0xe8, 0x43, 0xbb,
+	0x37, 0x90, 0x2b, 0xea, 0x1b, 0x09, 0xe4, 0xde, 0x99, 0xe3, 0x8f, 0x9d, 0x23, 0x7f, 0xec, 0xc7,
+	0xe7, 0xdc, 0x77, 0xe8, 0x31, 0x34, 0x26, 0xa9, 0xff, 0x84, 0x9f, 0x5b, 0x3b, 0x9b, 0xd7, 0xf9,
+	0x19, 0x67, 0x08, 0xf4, 0x1b, 0x58, 0x8d, 0x28, 0x3b, 0xf3, 0x5d, 0x4a, 0x9c, 0xc2, 0xcc, 0x4a,
+	0x65, 0xb3, 0xba, 0xd5, 0xda, 0xf9, 0x59, 0xd9, 0x4c, 0xa3, 0xc4, 0xbe, 0xb8, 0x11, 0xbc, 0x12,
+	0x5d, 0x16, 0xaa, 0xff, 0xae, 0xc0, 0x4a, 0x89, 0x31, 0xfa, 0x1c, 0x94, 0x28, 0x76, 0x58, 0x9c,
+	0xfb, 0x95, 0x30, 0x1a, 0xc5, 0xcc, 0x77, 0x63, 0x71, 0x82, 0x2a, 0x5e, 0x13, 0xfa, 0xcc, 0xb7,
+	0x38, 0xd5, 0xa2, 0x4f, 0x60, 0x8d, 0x06, 0x5e, 0x19, 0xae, 0x22, 0x70, 0xab, 0x34, 0xf0, 0x2e,
+	0xa3, 0x7e, 0x09, 0xab, 0x13, 0xca, 0xdc, 0x53, 0x27, 0x88, 0x89, 0xef, 0xe5, 0x18, 0x7e, 0x1b,
+	0x9a, 0x18, 0xcd, 0x75, 0x86, 0x97, 0x21, 0xba, 0x30, 0x3f, 0xd0, 0x02, 0x60, 0x49, 0x00, 0x3a,
+	0xa9, 0xaa, 0x60, 0x3f, 0x00, 0xc4, 0x68, 0x14, 0xce, 0x98, 0x4b, 0xa3, 0xdc, 0xfc, 0x86, 0x88,
+	0xc6, 0xbd, 0x32, 0x1f, 0xe2, 0xb9, 0x35, 0xee, 0x64, 0xc0, 0x6c, 0xb6, 0x3e, 0xb4, 0x17, 0x62,
+	0x51, 0x13, 0xb1, 0x28, 0x8d, 0xea, 0x42, 0x10, 0x16, 0x50, 0xea, 0x3f, 0x6b, 0xd0, 0x5e, 0x70,
+	0xfb, 0x7d, 0x68, 0x15, 0xdc, 0x20, 0x3c, 0xdd, 0xc4, 0x90, 0x9f, 0x1e, 0xdd, 0x03, 0xc8, 0x4f,
+	0x2d, 0x3c, 0xda, 0xc4, 0xcd, 0xec, 0xb0, 0xe8, 0x2e, 0x34, 0x93, 0xb0, 0x45, 0xd4, 0x15, 0xbe,
+	0xab, 0xe2, 0x86, 0x10, 0x8c, 0xa8, 0xcb, 0x33, 0xcd, 0x9b, 0xa5, 0xe9, 0xc2, 0xf5, 0x49, 0xa2,
+	0xb4, 0xe6, 0x32, 0x6e, 0x72, 0x1f, 0x5a, 0xd1, 0x34, 0x8c, 0x23, 0x22, 0xd2, 0x4f, 0xe4, 0x5b,
+	0x15, 0x83, 0x10, 0xd9, 0x5c, 0x22, 0xd6, 0x17, 0x06, 0xe1, 0x94, 0x06, 0xc2, 0x7b, 0x55, 0xdc,
+	0x14, 0x12, 0x73, 0x4a, 0x03, 0xf4, 0x73, 0x90, 0x8b, 0x07, 0x24, 0xb1, 0x73, 0xa2, 0xd4, 0xc5,
+	0x26, 0x6f, 0x15, 0xe5, 0xb6, 0x73, 0x82, 0xbe, 0x84, 0x66, 0xe6, 0x56, 0xa5, 0xf1, 0x2e, 0x61,
+	0xc8, 0xed, 0xd1, 0x36, 0x74, 0xa6, 0xce, 0xf9, 0x84, 0x06, 0x31, 0x09, 0xa7, 0xe2, 0x40, 0xbe,
+	0xa7, 0x34, 0x37, 0xab, 0x7c, 0xa1, 0x54, 0x61, 0x0a, 0xb9, 0xe1, 0xa1, 0xa7, 0x00, 0x8c, 0xba,
+	0x33, 0xc6, 0x28, 0xa7, 0x10, 0x10, 0x2b, 0x3d, 0xbc, 0x2e, 0x50, 0x5d, 0x9c, 0x41, 0x70, 0x01,
+	0x8e, 0x1c, 0x40, 0x91, 0x7b, 0x4a, 0xbd, 0xd9, 0x98, 0x12, 0xfa, 0xda, 0xa5, 0x62, 0x11, 0xa5,
+	0x25, 0xa2, 0xbf, 0x73, 0xed, 0xa4, 0xa3, 0x14, 0xaa, 0xcf, 0x91, 0xb8, 0x13, 0x5d, 0x14, 0x6d,
+	0x7c, 0x07, 0x90, 0x2f, 0x8e, 0xb6, 0x40, 0x66, 0x74, 0x4a, 0x9d, 0x98, 0xcc, 0x82, 0xd8, 0x1f,
+	0x8b, 0xc0, 0x25, 0x09, 0xb8, 0x9c, 0xc8, 0x0f, 0xb9, 0x98, 0xc7, 0x2e, 0xb7, 0xa4, 0x67, 0x94,
+	0x9d, 0x0b, 0xcb, 0x84, 0x6f, 0x53, 0x4b, 0x9d, 0x8b, 0x47, 0xd4, 0xdd, 0x78, 0x06, 0x9d, 0x4b,
+	0x3b, 0x41, 0x8f, 0x01, 0x78, 0xce, 0x12, 0xe6, 0x04, 0x27, 0x34, 0x65, 0xa9, 0xd2, 0x80, 0xf0,
+	0xe4, 0xc5, 0xdc, 0x08, 0x37, 0xe3, 0xf9, 0xa7, 0xfa, 0x27, 0x09, 0x9a, 0x59, 0xa4, 0xd0, 0x1d,
+	0xe0, 0xb7, 0xee, 0xf8, 0x38, 0xbf, 0xc3, 0x75, 0x31, 0x4e, 0x2f, 0xb0, 0x50, 0x05, 0xce, 0x84,
+	0x66, 0x17, 0x98, 0x4b, 0x86, 0xce, 0x84, 0xa2, 0x75, 0xa8, 0xb3, 0x30, 0x9c, 0x70, 0x60, 0x92,
+	0xfa, 0x37, 0xf8, 0x30, 0xb9, 0xd9, 0x42, 0x21, 0x60, 0x49, 0x92, 0x37, 0xb8, 0x40, 0xa0, 0xee,
+	0x01, 0x4c, 0x1d, 0x16, 0x9f, 0x93, 0xc8, 0xff, 0x21, 0xa9, 0x12, 0x35, 0xdc, 0x14, 0x92, 0x91,
+	0xff, 0x03, 0x55, 0x7b, 0xd0, 0xcc, 0x36, 0xcd, 0x27, 0x3a, 0xa2, 0x27, 0x7e, 0x50, 0xf0, 0x64,
+	0x43, 0x08, 0xb8, 0x0f, 0xd7, 0xa1, 0xce, 0xc9, 0x6b, 0xee, 0xba, 0x2a, 0xbe, 0x41, 0x03, 0x6f,
+	0x44, 0x5d, 0xf5, 0x77, 0x12, 0xb4, 0x52, 0x9e, 0xfc, 0x3f, 0x30, 0xfa, 0xa7, 0x50, 0x4f, 0x73,
+	0x36, 0x25, 0xf1, 0xbb, 0x6f, 0x21, 0x71, 0x3c, 0xb7, 0x55, 0xdf, 0x00, 0xd4, 0x53, 0xe1, 0xff,
+	0xcc, 0x14, 0x08, 0x96, 0x84, 0x2b, 0x13, 0x2f, 0x8b, 0x6f, 0xb4, 0x09, 0x2d, 0x8f, 0x46, 0x2e,
+	0xf3, 0x93, 0x5b, 0x9d, 0x78, 0xb9, 0x28, 0x42, 0x1f, 0x43, 0x6d, 0xca, 0xfc, 0xb4, 0x12, 0xb7,
+	0x76, 0xee, 0x94, 0x6d, 0xdb, 0xe2, 0x06, 0x38, 0xb1, 0x43, 0x5f, 0x40, 0x8d, 0xcd, 0xc6, 0x34,
+	0x4a, 0x89, 0xf6, 0x41, 0xe9, 0x39, 0x93, 0xbb, 0xe8, 0x07, 0x27, 0x98, 0x9b, 0xe2, 0x04, 0x81,
+	0xbe, 0x80, 0xa5, 0xe3, 0x90, 0x4d, 0x94, 0xba, 0xf0, 0xd0, 0x87, 0x6f, 0xf1, 0x90, 0x11, 0xc4,
+	0xce, 0x2b, 0xba, 0x17, 0xb2, 0x09, 0x16, 0x10, 0x34, 0x82, 0x5b, 0x53, 0x46, 0xe7, 0x0c, 0x11,
+	0x9f, 0x4f, 0xa9, 0x60, 0x98, 0xe5, 0x9d, 0xed, 0xb7, 0xcc, 0xd2, 0xb5, 0x32, 0x88, 0x7d, 0x3e,
+	0xa5, 0x78, 0x79, 0xba, 0x30, 0x46, 0x9f, 0x41, 0x23, 0x76, 0x5e, 0x13, 0xe6, 0xc4, 0x54, 0x69,
+	0x8a, 0xd3, 0x94, 0x46, 0xcd, 0x76, 0x5e, 0x63, 0x27, 0xa6, 0xb8, 0x1e, 0x27, 0x1f, 0xe5, 0x5c,
+	0x05, 0xe5, 0x5c, 0xf5, 0x29, 0xd4, 0x3d, 0x3a, 0x0d, 0x23, 0x3f, 0x56, 0x5a, 0x57, 0x2f, 0xd1,
+	0x4f, 0x4c, 0xf0, 0xdc, 0x16, 0xfd, 0x1a, 0x5a, 0x41, 0x48, 0xa2, 0xd3, 0xf0, 0x7b, 0x72, 0x4c,
+	0xa9, 0xd2, 0xbe, 0x3a, 0x79, 0x87, 0xe1, 0xe8, 0x34, 0xfc, 0x7e, 0x8f, 0x52, 0xdc, 0x0c, 0xe6,
+	0x9f, 0xe8, 0x10, 0x56, 0x18, 0xfd, 0xed, 0xcc, 0x67, 0x94, 0xb8, 0x8c, 0x7a, 0x7e, 0x4c, 0x5c,
+	0x87, 0x79, 0xca, 0x4d, 0xe1, 0xb2, 0x0f, 0xcb, 0x49, 0x59, 0x98, 0x6b, 0xc2, 0x5a, 0x73, 0x98,
+	0xc7, 0x6b, 0xe4, 0x05, 0x11, 0xfa, 0x1a, 0x5a, 0x8e, 0x68, 0xaf, 0xc8, 0xd8, 0x0f, 0x5e, 0x29,
+	0xcb, 0x22, 0x8e, 0x1f, 0x94, 0x92, 0xa4, 0x30, 0x1b, 0xf8, 0xc1, 0x2b, 0x0c, 0x4e, 0xf6, 0x8d,
+	0xbe, 0x84, 0x25, 0x11, 0xbb, 0x5b, 0x62, 0x23, 0x6f, 0x6b, 0x74, 0xe6, 0x7f, 0x45, 0xe0, 0x04,
+	0x88, 0xaf, 0x1e, 0xfb, 0xee, 0x2b, 0x9a, 0xc6, 0x5f, 0xbe, 0x7a, 0x75, 0x5b, 0x98, 0x09, 0x28,
+	0xc4, 0xd9, 0x37, 0xd2, 0xe1, 0x26, 0xa3, 0x63, 0x27, 0xa6, 0x1e, 0x99, 0x50, 0xcf, 0x77, 0x94,
+	0xce, 0xd5, 0x35, 0x1e, 0x27, 0x86, 0x07, 0xdc, 0x0e, 0xb7, 0x59, 0x61, 0x84, 0x4e, 0x61, 0x23,
+	0xeb, 0xde, 0xe2, 0x98, 0xf9, 0x47, 0xb3, 0x98, 0x92, 0x33, 0x67, 0x3c, 0x13, 0x79, 0x89, 0xc4,
+	0x9c, 0x0f, 0xdf, 0xd6, 0xc3, 0xcd, 0x41, 0xcf, 0x39, 0xc6, 0xf0, 0xf0, 0x7a, 0x54, 0xae, 0x50,
+	0xbf, 0x83, 0xe5, 0xc5, 0x2b, 0x8c, 0xee, 0xc3, 0x5d, 0x0b, 0xeb, 0x56, 0xef, 0x25, 0x6f, 0x4f,
+	0x89, 0xfd, 0xd2, 0xd2, 0xc9, 0xe1, 0x70, 0x64, 0xe9, 0x9a, 0xb1, 0x67, 0xe8, 0x7d, 0xf9, 0x3d,
+	0xd4, 0x86, 0x06, 0xd6, 0x9f, 0x1d, 0x1a, 0x58, 0xef, 0xcb, 0x12, 0x1f, 0x99, 0x96, 0x6d, 0x98,
+	0x43, 0xde, 0xc9, 0xa2, 0x0e, 0xdc, 0x1c, 0x9a, 0x36, 0x19, 0x1d, 0x5a, 0x96, 0x89, 0x6d, 0xbd,
+	0x2f, 0x57, 0xd5, 0xbf, 0xe6, 0x2c, 0x28, 0xe6, 0x7f, 0x1f, 0x94, 0x91, 0x8e, 0x9f, 0x1b, 0x9a,
+	0x5e, 0x36, 0xf9, 0x03, 0xb8, 0xbf, 0xa0, 0xed, 0x1b, 0x43, 0x63, 0xf8, 0x84, 0x60, 0x9d, 0x4b,
+	0x7b, 0x7c, 0x1d, 0x59, 0x42, 0x1f, 0xc0, 0xc6, 0x82, 0xd1, 0x9e, 0x69, 0xf6, 0x89, 0x89, 0xfb,
+	0x3a, 0x36, 0x86, 0x4f, 0xe4, 0x0a, 0xba, 0x07, 0x77, 0x16, 0xf4, 0xfa, 0x73, 0x71, 0x14, 0x43,
+	0x7b, 0xaa, 0xdb, 0x72, 0x95, 0xb7, 0xe2, 0x0b, 0x6a, 0x1b, 0x1b, 0x16, 0xb1, 0xcd, 0x43, 0x2c,
+	0x2f, 0xa9, 0x3f, 0x42, 0x4d, 0x70, 0x11, 0x6f, 0x7c, 0x04, 0x1b, 0x91, 0x89, 0xef, 0xb2, 0x30,
+	0x4a, 0x59, 0xbf, 0x25, 0x64, 0x07, 0x42, 0x84, 0x1e, 0xc0, 0xcd, 0xb4, 0xe4, 0x9e, 0x13, 0x37,
+	0xf4, 0xe6, 0x95, 0xa9, 0x3d, 0x17, 0x6a, 0xa1, 0x47, 0xd1, 0x47, 0x80, 0x38, 0x86, 0xbf, 0x9c,
+	0xd2, 0x4c, 0xe6, 0xfd, 0x4d, 0xc2, 0xa0, 0x72, 0xaa, 0x49, 0x52, 0xd9, 0x76, 0x4e, 0xd4, 0x3f,
+	0x56, 0xe0, 0xd6, 0x05, 0x6a, 0xe3, 0x4d, 0xeb, 0xc4, 0x0f, 0x88, 0xe3, 0x9d, 0x39, 0x81, 0x4b,
+	0x49, 0x1a, 0xf0, 0x74, 0x43, 0x9d, 0x89, 0x1f, 0xf4, 0x12, 0xcd, 0x6e, 0xa2, 0x40, 0x5f, 0xc3,
+	0xfb, 0x45, 0xfb, 0x30, 0x18, 0xfb, 0x01, 0x25, 0x2e, 0x1f, 0xf0, 0x49, 0xd3, 0x22, 0x75, 0x27,
+	0x07, 0x9a, 0xc2, 0x42, 0x9b, 0x1b, 0xa0, 0x67, 0x70, 0x9b, 0xdf, 0xc5, 0x14, 0x32, 0x4e, 0x9a,
+	0x3f, 0xce, 0x11, 0xd5, 0x6b, 0x08, 0x7c, 0xb7, 0xa2, 0x48, 0x78, 0x85, 0x63, 0xb5, 0x02, 0x94,
+	0xb3, 0xc5, 0x63, 0x80, 0x20, 0xcc, 0xb8, 0x66, 0xe9, 0x5d, 0xe6, 0x69, 0x26, 0x80, 0x3d, 0x4a,
+	0xd5, 0x2e, 0xd4, 0x53, 0x86, 0xe4, 0x3e, 0x17, 0x01, 0x21, 0x53, 0xca, 0x5c, 0x1a, 0x24, 0x0f,
+	0x8b, 0x1a, 0x6e, 0x0b, 0xa1, 0x95, 0xc8, 0xd4, 0xbf, 0x57, 0x60, 0xed, 0x12, 0xcd, 0xef, 0xf9,
+	0x74, 0xec, 0xa1, 0xfd, 0x94, 0x1e, 0x92, 0x97, 0xeb, 0x27, 0xef, 0x54, 0x20, 0x04, 0xb2, 0x2b,
+	0x7e, 0x0b, 0x5c, 0xb1, 0x0a, 0xb5, 0xb1, 0x73, 0x44, 0xc7, 0x69, 0xd4, 0x93, 0x01, 0x97, 0x8a,
+	0x3c, 0x55, 0xaa, 0x82, 0xac, 0x93, 0x01, 0x2f, 0xbc, 0x3e, 0x7f, 0x40, 0x08, 0xb6, 0xf3, 0xc4,
+	0xf9, 0x1b, 0x18, 0xfc, 0x28, 0xa5, 0x44, 0x4f, 0xfd, 0xbd, 0x04, 0xcd, 0x6c, 0x01, 0x7e, 0x41,
+	0xf7, 0x0c, 0x7d, 0xd0, 0x2f, 0x4b, 0x10, 0x19, 0xda, 0xa3, 0x7d, 0x13, 0xdb, 0xa4, 0x37, 0x1c,
+	0xbd, 0xd0, 0xb1, 0x2c, 0xa1, 0x9b, 0xd0, 0xb4, 0x7a, 0xb8, 0xf7, 0x04, 0xf7, 0xac, 0x7d, 0xb9,
+	0xc2, 0x9f, 0xa5, 0x07, 0x87, 0x03, 0xdb, 0xb0, 0x06, 0x3a, 0xd1, 0xf6, 0x4d, 0x43, 0xd3, 0xe5,
+	0x2a, 0x5a, 0x06, 0xd0, 0xf6, 0x75, 0xed, 0xe9, 0xae, 0xf9, 0xad, 0x3e, 0x92, 0x97, 0x78, 0xd6,
+	0xf6, 0xb1, 0x69, 0xf5, 0xf9, 0xa3, 0xb5, 0x86, 0x5a, 0x50, 0xdf, 0x35, 0xcd, 0x81, 0xde, 0x1b,
+	0xca, 0x37, 0xd4, 0xbf, 0x49, 0xd0, 0xb9, 0xe4, 0x02, 0xf4, 0x0d, 0xd4, 0x8e, 0xf9, 0xfe, 0x14,
+	0x49, 0x90, 0xcf, 0xf6, 0xbb, 0x3b, 0x0e, 0x27, 0x40, 0xfe, 0x5a, 0x3b, 0xf6, 0x59, 0x94, 0xbc,
+	0x0e, 0x89, 0x3b, 0x8b, 0xe2, 0x70, 0x42, 0x59, 0x24, 0xdc, 0xd7, 0xc0, 0x48, 0xe8, 0x78, 0xc7,
+	0xa5, 0xcd, 0x35, 0xe8, 0x63, 0x5e, 0x62, 0xe2, 0x19, 0x0b, 0x78, 0xf2, 0xe4, 0x80, 0x6a, 0x02,
+	0xc8, 0x54, 0x19, 0x40, 0xfd, 0x87, 0x04, 0xf5, 0xb4, 0xce, 0xa1, 0x47, 0x79, 0x55, 0x94, 0xae,
+	0xeb, 0x3b, 0xb2, 0x9a, 0x78, 0x21, 0x75, 0x16, 0x12, 0x20, 0xef, 0xef, 0x0a, 0xa9, 0x53, 0xbc,
+	0xe7, 0xbc, 0x17, 0xfc, 0x06, 0xda, 0xe9, 0x5c, 0x49, 0x05, 0xa9, 0x8a, 0x6b, 0x76, 0xef, 0xca,
+	0xa5, 0xc5, 0x7d, 0x6a, 0xa5, 0x10, 0x3e, 0x50, 0x19, 0x34, 0xb3, 0x7a, 0x8b, 0x1e, 0x42, 0x95,
+	0xe7, 0xcb, 0xb5, 0x07, 0xe0, 0x56, 0xe8, 0x73, 0x68, 0x1c, 0x53, 0xfa, 0x5f, 0xac, 0x5b, 0x3f,
+	0xa6, 0xe2, 0x43, 0xfd, 0x8b, 0x04, 0x90, 0x97, 0x53, 0x24, 0x43, 0x75, 0xc6, 0xc6, 0x69, 0x7b,
+	0xc8, 0x3f, 0xd1, 0x06, 0x34, 0xc6, 0x4e, 0x70, 0x32, 0x73, 0x4e, 0xe6, 0x24, 0x97, 0x8d, 0xd1,
+	0x2f, 0xc4, 0x1b, 0x59, 0xbc, 0x70, 0xa9, 0x47, 0xdc, 0x70, 0x16, 0xc4, 0xec, 0x3c, 0xbd, 0xfe,
+	0x9d, 0x5c, 0xa3, 0x25, 0x0a, 0xf4, 0x15, 0x34, 0xa6, 0x63, 0x27, 0x16, 0x5d, 0xda, 0x92, 0xd8,
+	0xa5, 0x7a, 0x75, 0x75, 0xb7, 0x52, 0x4b, 0x9c, 0x61, 0xd4, 0x3f, 0x48, 0x00, 0x79, 0xf1, 0x45,
+	0x3f, 0x85, 0xe5, 0x42, 0xc5, 0xce, 0xbb, 0xda, 0x76, 0x5e, 0x94, 0x0d, 0x0f, 0x3d, 0x84, 0x4e,
+	0x74, 0x1a, 0xb2, 0x98, 0x14, 0x5b, 0xd5, 0xe4, 0x20, 0xb2, 0x50, 0xf4, 0xcb, 0xfa, 0xd5, 0xea,
+	0xbb, 0xf5, 0xab, 0xea, 0x9f, 0x25, 0x68, 0x17, 0x8b, 0x79, 0x89, 0x03, 0xbf, 0x4a, 0x69, 0xa7,
+	0x72, 0x75, 0x47, 0x59, 0x9c, 0xa1, 0x2b, 0x7e, 0x0b, 0x64, 0xa3, 0x40, 0xdd, 0x75, 0x92, 0x6d,
+	0x27, 0xa5, 0x63, 0x3e, 0x54, 0x3f, 0x82, 0x66, 0x66, 0x8c, 0x56, 0x41, 0x2e, 0xa1, 0x8c, 0x26,
+	0xd4, 0xac, 0x7d, 0xd3, 0x36, 0x65, 0x49, 0x7d, 0x01, 0xeb, 0x57, 0xb4, 0x08, 0xbc, 0xe0, 0xe5,
+	0xbd, 0x46, 0xe6, 0xc7, 0x56, 0x26, 0x33, 0x3c, 0xfe, 0x44, 0xcb, 0x9a, 0x90, 0xc4, 0x7b, 0xf5,
+	0xb3, 0x04, 0xbd, 0xfd, 0x08, 0x9a, 0xd9, 0xc5, 0x42, 0x6b, 0x80, 0xf6, 0x8c, 0x6f, 0xf5, 0x3e,
+	0xc1, 0x3d, 0x5b, 0x27, 0x7d, 0x7d, 0xaf, 0x77, 0x38, 0xb0, 0xe5, 0xf7, 0x38, 0x0b, 0x59, 0x3a,
+	0x26, 0x96, 0x8e, 0x47, 0xbc, 0x8e, 0x6f, 0xff, 0x08, 0x9d, 0x4b, 0x4d, 0x21, 0xef, 0x00, 0xd2,
+	0xf6, 0x82, 0x68, 0x58, 0xef, 0x1b, 0x36, 0xd1, 0x7a, 0xb8, 0x7f, 0xb9, 0x4d, 0x28, 0x33, 0xd2,
+	0xcc, 0x61, 0xdf, 0x48, 0x9b, 0x11, 0xd1, 0x26, 0x94, 0x19, 0xf5, 0x06, 0x2f, 0x7a, 0x2f, 0x47,
+	0x72, 0x65, 0xfb, 0x8d, 0x04, 0xcb, 0x8b, 0xf7, 0x8c, 0x37, 0x3f, 0x3d, 0x8d, 0x4f, 0x40, 0xac,
+	0x41, 0xcf, 0xde, 0x33, 0xf1, 0xc1, 0xe5, 0x85, 0x2f, 0x1a, 0xbc, 0xd0, 0x77, 0x49, 0xcf, 0xb2,
+	0x06, 0x86, 0x56, 0xe8, 0x4f, 0x2e, 0x1a, 0x1d, 0x98, 0xbb, 0xc6, 0x40, 0xe7, 0xb6, 0x72, 0x05,
+	0xdd, 0x85, 0xf5, 0x8b, 0xfa, 0xde, 0xb0, 0x8f, 0x4d, 0xa3, 0x2f, 0x57, 0xd1, 0x3a, 0xac, 0x5c,
+	0x54, 0x1a, 0xe6, 0x48, 0x5e, 0x3a, 0xba, 0x21, 0xfe, 0x65, 0xfb, 0xe8, 0x3f, 0x01, 0x00, 0x00,
+	0xff, 0xff, 0xcc, 0x96, 0x13, 0xc4, 0xc1, 0x15, 0x00, 0x00,
+}
diff --git a/feeds/feeds.proto b/feeds/feeds.proto
new file mode 100644
index 0000000..55e946e
--- /dev/null
+++ b/feeds/feeds.proto
@@ -0,0 +1,658 @@
+syntax = "proto3";
+
+package maps.booking.feeds;
+
+message FeedMetadata {
+  enum ProcessingInstruction {
+    // By default we will assume that this feed is an incremental feed.
+    PROCESS_UNKNOWN = 0;
+
+    // This Feed message is one shard of a complete feed. Anything previously
+    // supplied by this partner will be deleted; the contents of this feed
+    // represent the entire state of the world.
+    PROCESS_AS_COMPLETE = 1;
+
+    // This Feed message is one shard of an incremental feed. Existing entities
+    // will be left untouched except as modified in this feed.
+    PROCESS_AS_INCREMENTAL = 2;
+  }
+
+  // Instructs us how to process the feed: either as a shard of a complete feed,
+  // or as a shard of an incremental update.
+  ProcessingInstruction processing_instruction = 1;
+
+  // The current shard and total number of shards for this feed.
+  //
+  // Shard number is assumed to be zero-based.
+  //
+  // There does not need to be any relationship to the file name.
+  //
+  // Shards do not need to be transferred in order, and they may not be
+  // processed in order.
+  int32 shard_number = 2;
+  int32 total_shards = 3;
+
+  // An identifier that must be consistent across all shards in a feed.
+  // This value must be globally unique across each feed type.
+  //
+  // This value ensures that complete feeds spanning multiple shards are
+  // processed together correctly.
+  //
+  // Clients only need to set this value when the processing_instruction is set
+  // to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by
+  // total_shards).
+  //
+  // Feeds that span multiple shards must set this nonce to the same value.
+  uint64 nonce = 5;
+
+  // The timestamp at which this feed shard was generated.
+  //
+  // In Unix time format (seconds since the epoch).
+  int64 generation_timestamp = 4;
+}
+
+message AvailabilityFeed {
+  FeedMetadata metadata = 1;
+  repeated ServiceAvailability service_availability = 2;
+}
+
+message ServiceAvailability {
+  // If provided, we will consider the Availability entities provided to be a
+  // complete snapshot from [start_timestamp_restrict, end_timestamp_restrict).
+  // That is, all existing availability will be deleted if the following
+  // condition holds true:
+  //
+  //    start_timestamp_restrict <= Availability.start_sec &&
+  //    Availability.start_sec < end_timestamp_restrict
+  //
+  // If a resource_restrict message is set, the condition is further restricted:
+  //
+  //    Availability.resource.staff_id == resource_restrict.staff_id &&
+  //    Availability.resource.room_id == resource_restrict.room_id
+  //
+  // These fields are typically used to provide a complete update of
+  // availability in a given time range.
+  //
+  // Setting start_timestamp_restrict while leaving end_timestamp_restrict unset
+  // is interpreted to mean all time beginning at start_timestamp_restrict.
+  //
+  // Setting end_timestamp_restrict while leaving start_timestamp_restrict unset
+  // is interpreted to mean all time up to the end_timestamp_restrict.
+  //
+  // In Unix time format (seconds since the epoch).
+  int64 start_timestamp_restrict = 1;
+  int64 end_timestamp_restrict = 2;
+
+  // If provided, the timestamp restricts will be applied only to the given
+  // merchant or service.
+  //
+  // These fields are typically used to provide complete snapshot of
+  // availability in a given range (defined above) for a specific merchant or
+  // service.
+  //
+  // Leaving these fields unset, or setting these to the empty string or null,
+  // is interpreted to mean that no restrict is intended.
+  string merchant_id_restrict = 3;
+  string service_id_restrict = 4;
+
+  // Setting resources_restrict further restricts the scope of the update to
+  // just this set of resources. All id fields of the resources must match
+  // exactly.
+  Resources resources_restrict = 6;
+
+  repeated Availability availability = 5;
+}
+
+// An availability of the merchant's service, indicating time and number
+// of spots.
+// The availability feed should be a list of this message.
+// Please note that it's up to the partner to call out all the possible
+// availabilities.
+// If a massage therapist is available 9am-12pm, and they provide
+// one-hour massage sessions, the aggregator should provide the feed as
+//   availability {start_sec: 9am, duration: 60 minutes, ...}
+//   availability {start_sec: 10am, duration: 60 minutes, ...}
+//   availability {start_sec: 11am, duration: 60 minutes, ...}
+// instead of
+//   availability {start_sec: 9am, duration: 180 minutes, ...}
+//
+message Availability {
+  // An opaque string from an aggregator to identify a merchant.
+  string merchant_id = 1;
+  // An opaque string from aggregator to identify a service of the
+  // merchant.
+  string service_id = 2;
+  // Start time of this availability, using epoch time in seconds.
+  int64 start_sec = 3;
+  // Duration of the service in seconds, e.g. 30 minutes for a chair massage.
+  int64 duration_sec = 4;
+  // Number of total spots and open spots of this availability.
+  // E.g. a Yoga class of 10 spots with 3 booked.
+  //   availability {spots_total: 10, spots_open: 7 ...}
+  // E.g. a chair massage session which was already booked.
+  //   availability {spots_total: 1, spots_open: 0 ...}
+  //
+  // Note: If sending requests using the availability compression format defined
+  //       below, these two fields will be inferred. A Recurrence
+  //       implies spots_total=1 and spots_open=1. A ScheduleException implies
+  //       spots_total=1 and spots_open=0.
+  int64 spots_total = 5;
+  int64 spots_open = 6;
+  // An optional opaque string to identify this availability slot. If set, it
+  // will be included in the requests that book/update/cancel appointments.
+  string availability_tag = 7;
+
+  // Optional resources used to disambiguate this availability slot from
+  // others when different staff, room, or party_size values are part
+  // of the service.
+  //
+  // E.g. the same Yoga class with two 2 instructors.
+  //  availability { resources { staff_id: "1" staff_name: "Amy" }
+  //                 spots_total: 10 spots_open: 7 }
+  //  availability { resources { staff_id: "2" staff_name: "John" }
+  //                 spots_total: 5 spots_open: 2 }
+  Resources resources = 8;
+
+  // A list of ids referencing the payment options which can be used to pay
+  // for this slot. The actual payment options are defined at the Merchant
+  // level, and can also be shared among multiple Merchants.
+  //
+  // This field overrides any payment_option_ids specified in the service
+  // message. Similarly payment_option_ids specified here do NOT have to be
+  // present in the service message, though must be defined at the
+  // Merchant level.
+  repeated string payment_option_id = 9;
+
+  // Recurrence messages are optional, but allow for a more compact
+  // representation of consistently repeating availability slots. They typically
+  // represent a day's working schedule.
+  // ScheduleException messages are then used to represent booked/unavailable
+  // time ranges within the work day.
+  //
+  // Requirements:
+  //   1. The expansion of availability slots or recurrences must NOT create
+  //      identical slots. If the ids, start_sec, duration_sec, and resources
+  //      match, slots are considered identical.
+  //   2. Do NOT mix the standard availability format and recurrence within the
+  //      slots of a single service. Recurrence benefits merchants/services that
+  //      offer appointments. The standard format is geared towards
+  //      merchants/services with regularly scheduled classes.
+  message Recurrence {
+    // The inclusive maximum UTC timestamp the availability repeats until.
+    int64 repeat_until_sec = 1;
+    // Defines the time between successive availability slots.
+    //
+    // E.g. An availability with a duration of 20 min, a repeat_every_sec of
+    // 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will
+    // yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am,
+    // 11-11:20am.
+    int32 repeat_every_sec = 2;
+  }
+  // The recurrence information for the availability, representing more than one
+  // start time. A recurrence should contain appointments for one working day.
+  Recurrence recurrence = 10;
+
+  // ScheduleException messages are used to represent booked/unavailable time
+  // ranges within the work day. As time slots are booked, the list of
+  // exceptions should grow to reflect the newly unavailable time ranges.
+  // The recurrence itself shouldn't be modified.
+  message ScheduleException {
+    // The time range of the exception.
+    TimeRange time_range = 1;
+  }
+  // When this service cannot be scheduled. To limit the number of
+  // schedule_exception messages consider joining adjacent exceptions.
+  repeated ScheduleException schedule_exception = 11;
+}
+
+// A resource is used to disambiguate availability slots from one another when
+// different staff, room or party_size values are part of the service.
+// Multiple slots for the same service and time interval can co-exist when they
+// have different resources.
+message Resources {
+  // Optional id for a staff member providing the service. This field identifies
+  // the staff member across all merchants, services, and availability records.
+  // It also needs to be stable over time to allow correlation with past
+  // bookings.
+  // This field must be present if staff_name is present.
+  string staff_id = 1;
+
+  // Optional name of a staff member providing the service. This field will be
+  // displayed to users making a booking, and should be human readable, as
+  // opposed to an opaque identifier.
+  // This field must be present if staff_id is present.
+  string staff_name = 2;
+
+  // An optional id for the room the service is located in. This field
+  // identifies the room across all merchants, services, and availability
+  // records. It also needs to be stable over time to allow correlation with
+  // past bookings.
+  // This field must be present if room_name is present.
+  string room_id = 3;
+
+  // An optional name for the room the service is located in. This
+  // field will be displayed to users making a booking, and should be human
+  // readable, as opposed to an opaque identifier.
+  // This field must be present if room_id is present.
+  string room_name = 4;
+
+  // Applicable only for Dining: The party size which can be accommodated
+  // during this time slot. A restaurant can be associated with multiple Slots
+  // for the same time, each specifying a different party_size, if for instance
+  // 2, 3, or 4 people can be seated with a reservation.
+  int32 party_size = 5;
+}
+
+message TimeRange {
+  int64 begin_sec = 1;
+  int64 end_sec = 2;
+}
+
+message ServiceFeed {
+  FeedMetadata metadata = 1;
+  repeated Service service = 2;
+}
+
+// The definition of a service provided by a merchant.
+message Service {
+  // An opaque string from an aggregator partner which uniquely identifies a
+  // merchant. (required)
+  string merchant_id = 1;
+  // An opaque string from an aggregator partner which uniquely identifies the
+  // service. (required)
+  string service_id = 2;
+  // The name of the service, suitable for display to users, e.g. "Men's
+  // haircut". (required)
+  string name = 3;
+  // The description of the service, suitable for display to users.
+  //
+  // This field now supports both plain text and HTML-like formatting rules to
+  // display structural contents to end-users. Unlike plain text sections,
+  // customized layouts can be created here using headings, paragraphs, lists
+  // and some phrase tags. Please read the following instructions and notes
+  // carefully to create better user-experience.
+  //
+  // Supported HTML-like formatting tags:
+  //
+  // Heading tags: <h1>, <h2>, <h3>, <h4>, <h5>, <h6>
+  //   Heading tags can be used to display titles and sub-titles. For example,
+  //   <h1>Itinerary</h1> will display the inline text as the most important
+  //   heading of the section. Note that any inner HTML tags, styles or
+  //   attributes will be ignored. For example, <h1 style=".."> will be treated
+  //   the same as <h1>. Only pure text wil be preserved.
+  //
+  // Paragraph tag: <p>:
+  //   Paragraph tag can be used to wrap detailed introduction or contents. Any
+  //   inner tags, styles or attributes will be ignored with a few exceptions:
+  //   <br>, <strong> and <em>. Please see the phrase tag section below for more
+  //   details.
+  //
+  // List tags: <ul>, <ol>, <li>
+  //   <ul> tag can be used with <li> tag to display unordered lists, and <ol>
+  //   tag can be used with <li> to display ordered lists. This is a good tool
+  //   to display checklists, highlights or any other lists that fit your
+  //   use-cases.
+  // Example: To show a list of features of a cruise trip:
+  //  <ol>
+  //    <li>Wonderful ocean view and chances to play with wildlife.</li>
+  //    <li>Carefully designed travel arrangements and services.</li>
+  //    <li>Gauranteed lowest price.</li>
+  // </ol>
+  // Note that only <li> children under <ul> or <ol> tags will be converted. All
+  // other children will be dropped. Also any inner tags, attributes and styles
+  // will be ignored, only pure text contents are preserved.
+  //
+  // Division tag: <div>
+  //   All supported inner tags of the <div> tag will be parsed with the rules
+  //   stated above. But <div> tag itself does not mean any grouping or
+  //   indenting here. Also any inner attributes and styles will be ignored.
+  //
+  // Phrase tags: <br>, <strong>, <em>:
+  //   Only the three tags mentioned above are supported. <br> can be used to
+  //   break lines in paragraphs, and <strong>/<em> can be used to highlight
+  //   important text. Any other phrase tags will be ignored.
+  //
+  // Unsupported tags:
+  //   * <html>, <header> and <body> tags are not allowed.
+  //   * Any URLs, anchors, links will be striped, and will never be displayed
+  //   to end-users. If you want to use photos to create a rich user experience,
+  //   please use the "related_media" field below to send your photo urls
+  //   * Any other tags not mentioned above are not supported (for example
+  //   <table>, <td> ...).
+  //
+  // Important notes:
+  //   * Try not to use other tags except for the supported ones mentioned
+  //   above, because contents within unsupported tags will be excluded from UI
+  //   rendering and may lead to undesired 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.
+  //   * If the currently supported layouts are not enough for your use cases,
+  //   please contact Google with your requests and we will find the best
+  //   approach for you.
+  //
+  // (required)
+  string description = 4;
+  // The price of the service. (optional, overridden when payment options or
+  // ticket types present)
+  Price price = 5;
+  // Rules to book/cancel an appointment. (optional)
+  SchedulingRules rules = 6;
+  // Intake forms to customize the service. (optional)
+  repeated ServiceIntakeForm form = 7;
+
+  // Enum to indicate the prepayment type.
+  enum PrepaymentType {
+    // By default we will assume that the prepayment is NOT_SUPPORTED.
+    PREPAYMENT_TYPE_UNSPECIFIED = 0;
+    // The user has to pay this service at the booking time.
+    REQUIRED = 1;
+    // The user can choose to pre-pay this service at the booking time or later,
+    // but it is not required in order to book.
+    OPTIONAL = 2;
+    // The prepayment is not supported for this service.
+    NOT_SUPPORTED = 3;
+  }
+  // Whether a prepayment is required, optional or not supported. (optional)
+  PrepaymentType prepayment_type = 8;
+
+  // The service's tax rate. If present this field overrides any tax_rate set at
+  // the merchant level. An empty message (i.e. tax_rate { }) will reset the
+  // applied tax rate to zero. (optional)
+  TaxRate tax_rate = 9;
+
+  // A list of ids referencing the payment options which can be used to pay
+  // for this service. The actual payment options are defined at the Merchant
+  // level, and can also be shared among multiple Merchants. (optional)
+  repeated string payment_option_id = 10;
+
+  // Defines how a deposit may be charged to the user. Can be overridden at the
+  // availability level. (optional)
+  Deposit deposit = 11;
+
+  // Defines a no show fee that may be charged to the user. Can be overridden
+  // at the availability level. (optional)
+  NoShowFee no_show_fee = 12;
+
+  // Indicates whether the user must provide a credit card in order to book this
+  // service.
+  // This value can be overridden at the availability level. (optional)
+  RequireCreditCard require_credit_card = 13;
+
+  // An action link related to this service.
+  repeated ActionLink action_link = 14;
+
+  enum ServiceType {
+    SERVICE_TYPE_UNSPECIFIED = 0;
+    SERVICE_TYPE_DINING_RESERVATION = 1;
+    SERVICE_TYPE_FOOD_ORDERING = 2;
+    SERVICE_TYPE_EVENT_TICKET = 3;
+    SERVICE_TYPE_TRIP_TOUR = 4;
+  }
+
+  // The predefined type of this service. Currently, only used with action_link.
+  ServiceType type = 15;
+
+  // Types of tickets that can be booked/purchased for this service, if tickets
+  // are supported. (optional)
+  repeated TicketType ticket_type = 16;
+
+  // Photos related to this service. Google will crawl these media to
+  // ensure that they are displayed to end-users in the most efficient way.
+  // (optional)
+  repeated RelatedMedia related_media = 17;
+
+  // Service attribute values that apply to this service (optional).
+  // Each Service may have zero or more values for each service attribute
+  // defined in the corresponding Merchant.
+  repeated ServiceAttributeValueId service_attribute_value_id = 18;
+}
+
+// The price of a service or a fee.
+message Price {
+  // The price in micro-units of the currency.
+  // Fractions of smallest currency unit will be rounded using nearest even
+  // rounding. (e.g. For USD 2.5 cents rounded to 2 cents, 3.5 cents rounded to
+  // 4 cents, 0.5 cents rounded to 0 cents, 2.51 cents rounded to 3 cents).
+  // (required)
+  int64 price_micros = 1;
+  // The currency of the price that is defined in ISO 4217. (required)
+  string currency_code = 2;
+  // An optional and opaque string that identifies the pricing option that is
+  // associated with the extended price. (optional)
+  string pricing_option_tag = 3;
+}
+// The scheduling rules for a service.
+message SchedulingRules {
+  // The minimum advance notice in seconds required to book an appointment.
+  // (optional)
+  int64 min_advance_booking = 1;
+
+  // The minimum advance notice in seconds required to cancel a booked
+  // appointment online. (optional)
+  int64 min_advance_online_canceling = 2;
+
+  // The fee for canceling within the minimum advance notice period.
+  Price late_cancellation_fee = 3 [deprecated = true];
+
+  // The fee for no-show without canceling.
+  Price noshow_fee = 4 [deprecated = true];
+}
+
+// A tax rate applied when charging the user for a service, and which can be set
+// on either a per merchant, or per service basis.
+message TaxRate {
+  // A tax rate in millionths of one percent, effectively giving 6 decimals of
+  // precision. For example, if the tax rate is 7.253%, this field should be set
+  // to 7253000.
+  //
+  // If this field is left unset or set to 0, the total price charged to a user
+  // for any service provided by this merchant is the exact price specified by
+  // Service.price. The service price is assumed to be exempt from or already
+  // inclusive of applicable taxes. Taxes will not be shown to the user as a
+  // separate line item.
+  //
+  // If this field is set to any nonzero value, the total price charged to a
+  // user for any service provided by this merchant will include the service
+  // price plus the tax assessed using the tax rate provided here. Fractions of
+  // the smallest currency unit (for example, fractions of one cent) will be
+  // rounded using nearest even rounding. Taxes will be shown to the user as a
+  // separate line item. (required)
+  int32 micro_percent = 1;
+}
+
+// Defines a field that is included in a ServiceIntakeForm.
+message ServiceIntakeFormField {
+  // Enum to indicate the type of field.
+  enum FieldType {
+    // Fields of unspecified or unknown type will be ignored.
+    FIELD_TYPE_UNSPECIFIED = 0;
+    // A one-line input field for text.
+    SHORT_ANSWER = 1;
+    // A multi-line input field for text.
+    PARAGRAPH = 2;
+    // A set of radio buttons that requires one choice from many options.
+    MULTIPLE_CHOICE = 3;
+    // One or more enumerated items with checkboxes.
+    CHECKBOXES = 4;
+    // A selection from a dropdown.
+    DROPDOWN = 5;
+    // A yes/no button.
+    BOOLEAN = 6;
+  }
+
+  // The type of this field. (required)
+  FieldType type = 1;
+
+  // The text shown to the user for this field. (required)
+  string label = 2;
+
+  // For MULTIPLE_CHOICE, CHECKBOXES, or DROPDOWN, the values to enumerate.
+  // (optional)
+  repeated string value = 3;
+
+  // Indicates whether an answer to this field is required by a user. (optional)
+  bool is_required = 4;
+}
+
+// Defines an intake form that customizes the service provided by a merchant.
+message ServiceIntakeForm {
+  // Fields that will be displayed to the user. (required)
+  repeated ServiceIntakeFormField field = 1;
+
+  // If true, this form will be shown to first time customers.
+  // (one of first_time_customers or returning_customers required)
+  bool first_time_customers = 2;
+
+  // If true, this form will be shown to repeat customers.
+  // (one of first_time_customers or returning_customers required)
+  bool returning_customers = 3;
+}
+
+// A deposit that the user may be charged or have a hold on their credit card
+// for.
+message Deposit {
+  // Deposit amount.
+  Price deposit = 1;
+
+  // Minimum advance cancellation for the deposit.
+  int64 min_advance_cancellation_sec = 2;
+
+  // Defines how the deposit is determined from the availability.
+  PriceType deposit_type = 3;
+}
+
+// A fee that a user may be charged if they have made a booking but do not
+// show up.
+message NoShowFee {
+  // The amount the user may be charged if they do not show up for their
+  // reservation.
+  Price fee = 1;
+
+  // Defines how the fee is determined from the availability.
+  PriceType fee_type = 3;
+}
+
+// Defines how a total price is determined from an availability.
+enum PriceType {
+  // The price is for a fixed amount. This is the default value if the field is
+  // not set.
+  FIXED_RATE_DEFAULT = 0;
+  // The price specified is per person, and the total price is calculated
+  // according to the party size specified in Resources as
+  // price_micros * party_size. A PER_PERSON price must be accompanied by a
+  // party size in the availability resources. If it is not, a party size of one
+  // is used.
+  PER_PERSON = 1;
+}
+
+// Defines whether a credit card is required in order to book an appointment.
+enum RequireCreditCard {
+  // The credit card requirement is not explicitly specified and the
+  // behaviour is identical to the one specified for CONDITIONAL.
+  REQUIRE_CREDIT_CARD_UNSPECIFIED = 0;
+
+  // Google will require a credit card for the booking if any of the following
+  // conditions are met:
+  // * the availability has a price and the prepayment_type is REQUIRED
+  // * the no_show_fee is set
+  // * the deposit field is set.
+  REQUIRE_CREDIT_CARD_CONDITIONAL = 1;
+
+  // A credit card is always required in order to book this availability
+  // regardless of other field values.
+  REQUIRE_CREDIT_CARD_ALWAYS = 2;
+}
+
+// An action URL with associated language, list of countries restricted to, and
+// optional platform that indicates which platform this action should be
+// performed on.
+message ActionLink {
+  // The entry point URL for this action link.
+  string url = 1;
+
+  // The BCP-47 language tag identifying the language in which the content
+  // from this URI is available.
+  string language = 2;
+
+  // ISO 3166-1 alpha-2 country code. Leave empty for unrestricted visibility.
+  repeated string restricted_country = 3;
+
+  // The platform that this action should be performed on. If this field is
+  // unset, ACTION_PLATFORM_WEB_APPLICATION will be used as fallback.
+  ActionPlatform platform = 4;
+}
+
+// The platform that the action is performed on. Web application is the general
+// fallback. It is recommended to have at least one ActionLink with
+// ACTION_PLATFORM_WEB_APPLICATION. Links with Android and iOS as platform are
+// only used on the respective system.
+enum ActionPlatform {
+  // The platform is unspecified.
+  ACTION_PLATFORM_UNSPECIFIED = 0;
+
+  // The action platform is web in general.
+  ACTION_PLATFORM_WEB_APPLICATION = 1;
+
+  // The action platform is web on mobile devices.
+  ACTION_PLATFORM_MOBILE_WEB = 2;
+
+  // The action platform is Android OS.
+  ACTION_PLATFORM_ANDROID = 3;
+
+  // The action platform is iOS.
+  ACTION_PLATFORM_IOS = 4;
+}
+
+// TicketType is used to differentiate among tickets (where a ticket can be a
+// spot on a raft trip, an admission to a museum, etc.) with different prices
+// and/or availabilities due to different user types or different service
+// attributes.
+message TicketType {
+  // The ticket id is used to differentiate among different ticket types of the
+  // same service, and is only expected to be unique within a service.
+  string ticket_type_id = 1;
+
+  // This can be user visible, e.g., “adult”, "child", “veteran”, “Row J”, etc.
+  string short_description = 2;
+
+  // The price of a single ticket of this type, exclusive of any taxes. The tax
+  // rate of Service is applied to its tickets.
+  Price price = 3;
+}
+
+// Defines a media source. This field only contains the URL of this media source
+// (only photos are supported for now). Google will crawl the media data to
+// ensure that they are displayed to end-users in the most efficient way.
+message RelatedMedia {
+  // URL of this media source.
+  string url = 1;
+
+  // Type of this media source.
+  MediaType type = 2;
+
+  // Enum to indicate the type of this media source. Only photos are supported.
+  // Please reach out to Google if supporting animations or videos is
+  // important to your feature and we will find the best solution for you.
+  enum MediaType {
+    // Unused.
+    TYPE_UNSPECIFIED = 0;
+    // Indicates the media provided by the url is a photo.
+    PHOTO = 1;
+  }
+
+  // Caption of the media, only plain text is supported. (optional)
+  string caption = 3;
+}
+
+// Identifies a particular value of a service attribute to be applied to a
+// Service.
+message ServiceAttributeValueId {
+  // ID of an attribute as defined in Merchant.service_attribute, e.g.
+  // "service-type".
+  string attribute_id = 1;
+
+  // ID of the value for this attribute, e.g. "haircut". Must match a value_id
+  // in the service attribute definition.
+  string value_id = 2;
+}
diff --git a/testclient/main.go b/testclient/bookingBasedTestClient.go
similarity index 100%
rename from testclient/main.go
rename to testclient/bookingBasedTestClient.go
diff --git a/testclient/orderBasedTestClient.go b/testclient/orderBasedTestClient.go
new file mode 100644
index 0000000..02125eb
--- /dev/null
+++ b/testclient/orderBasedTestClient.go
@@ -0,0 +1,215 @@
+/*
+Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/maps-booking-v3/api"
+	"github.com/maps-booking-v3/utils"
+
+	mpb "github.com/maps-booking-v3/v3"
+)
+
+const logFile = "http_test_client_log_"
+
+var (
+	serverAddr       = flag.String("server_addr", "example.com:80", "Your http server's address in the format of host:port")
+	credentialsFile  = flag.String("credentials_file", "", "File containing credentials for your server. Leave blank to bypass authentication. File should have exactly one line of the form 'username:password'.")
+	testSlots        = flag.Int("num_test_slots", 10, "Maximum number of slots to test from availability_feed. Slots will be selected randomly")
+	allFlows         = flag.Bool("all_tests", false, "Whether to test all endpoints syncronously, including ListOrders flow.")
+	healthFlow       = flag.Bool("health_check_test", false, "Whether to test the Health endpoint.")
+	checkFlow        = flag.Bool("check_order_test", false, "Whether to test the CheckOrderFulfillability endpoint.")
+	orderFlow        = flag.Bool("create_order_test", false, "Whether to test the CreateOrder endpoint.")
+	serviceFeed      = flag.String("service_feed", "", "Absolute path to service feed required for all tests except health. Feeds can be in either json or pb3 format")
+	availabilityFeed = flag.String("availability_feed", "", "Absolute path to availability feed required for all tests except health. Feeds can be in either json or pb3 format")
+	outputDir        = flag.String("output_dir", "", "Absolute path of dir to dump log file.")
+	caFile           = flag.String("ca_file", "", "Absolute path to your server's Certificate Authority root cert. Downloading all roots currently recommended by the Google Internet Authority is a suitable alternative https://pki.google.com/roots.pem. Leave blank to connect using http rather than https.")
+	fullServerName   = flag.String("full_server_name", "", "Fully qualified domain name. Same name used to sign CN. Only necessary if ca_file is specified and the base URL differs from the server address.")
+)
+
+type counters struct {
+	TotalSlotsProcessed             int
+	HealthCheckSuccess              bool
+	CheckOrderFulfillabilitySuccess int
+	CheckOrderFulfillabilityErrors  int
+	CreateOrderSuccess              int
+	CreateOrderErrors               int
+	ListOrdersSuccess               bool
+}
+
+func createLogFile() (*os.File, error) {
+	var err error
+	outPath := *outputDir
+	if outPath == "" {
+		outPath, err = os.Getwd()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	now := time.Now().UTC()
+	nowString := fmt.Sprintf("%d-%02d-%02d_%02d-%02d-%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
+	outFile := filepath.Join(outPath, fmt.Sprintf("%s%s", logFile, nowString))
+
+	return os.Create(outFile)
+}
+
+func logStats(stats counters) {
+	log.Println("\n************* Begin Stats *************\n")
+	var totalErrors int
+	if *healthFlow || *allFlows {
+		if stats.HealthCheckSuccess {
+			log.Println("HealthCheck Succeeded")
+		} else {
+			totalErrors++
+			log.Println("HealthCheck Failed")
+		}
+	}
+	if *checkFlow || *allFlows {
+		totalErrors += stats.CheckOrderFulfillabilityErrors
+		log.Printf("CheckOrderFulfillability Errors: %d/%d", stats.CheckOrderFulfillabilityErrors, stats.CheckOrderFulfillabilityErrors+stats.CheckOrderFulfillabilitySuccess)
+	}
+	if *orderFlow || *allFlows {
+		totalErrors += stats.CreateOrderErrors
+		log.Printf("CreateOrder Errors: %d/%d", stats.CreateOrderErrors, stats.CreateOrderErrors+stats.CreateOrderSuccess)
+	}
+	if *allFlows {
+		if stats.ListOrdersSuccess {
+			log.Println("ListOrders Succeeded")
+		} else {
+			totalErrors++
+			log.Println("ListOrders Failed")
+		}
+	}
+	if *checkFlow || *orderFlow || *allFlows {
+		log.Printf("Total Slots Processed: %d", stats.TotalSlotsProcessed)
+	}
+
+	log.Println("\n\n\n")
+	if totalErrors == 0 {
+		log.Println("All Tests Pass!")
+	} else {
+		log.Printf("Found %d Errors", totalErrors)
+	}
+
+	log.Println("\n************* End Stats *************\n")
+	os.Exit(0)
+}
+
+func main() {
+	flag.Parse()
+	var stats counters
+
+	// Set up logging before continuing with flows
+	f, err := createLogFile()
+	if err != nil {
+		log.Fatalf("Failed to create log file %v", err)
+	}
+	defer f.Close()
+	log.SetOutput(f)
+
+	conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName)
+	if err != nil {
+		log.Fatalf("Failed to init http connection %v", err)
+	}
+
+	// HealthCheck Flow
+	if *healthFlow || *allFlows {
+		stats.HealthCheckSuccess = true
+		if err := api.HealthCheck(conn); err != nil {
+			stats.HealthCheckSuccess = false
+			log.Println(err.Error())
+		}
+		if !*allFlows && !*checkFlow && !*orderFlow {
+			logStats(stats)
+		}
+	}
+
+	if *availabilityFeed == "" || *serviceFeed == "" {
+		log.Fatal("please set both availability_feed and service_feed flags")
+	}
+
+	testInventory, err := utils.MerchantLineItemMapFrom(*serviceFeed, *availabilityFeed, *testSlots)
+	if err != nil {
+		log.Fatal(err.Error())
+	}
+
+	// CheckOrderFulfillability Flow
+	if *checkFlow || *allFlows {
+		utils.LogFlow("CheckOrderFulfillability", "Start")
+		for _, value := range testInventory {
+			stats.TotalSlotsProcessed += len(value)
+		}
+
+		i := 0
+		for merchantID, lineItems := range testInventory {
+			if err = api.CheckOrderFulfillability(merchantID, lineItems, conn); err != nil {
+				log.Printf("%s. skipping slots %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.TotalSlotsProcessed)
+				stats.CheckOrderFulfillabilityErrors += len(lineItems)
+				delete(testInventory, merchantID)
+				i += len(lineItems)
+				continue
+			}
+			stats.CheckOrderFulfillabilitySuccess += len(lineItems)
+			i += len(lineItems)
+		}
+		utils.LogFlow("CheckOrderFulfillability", "End")
+	}
+	// CreateOrder Flow.
+	var orders []*mpb.Order
+	if *orderFlow || *allFlows {
+		utils.LogFlow("CreateOrder", "Start")
+		if stats.TotalSlotsProcessed == 0 {
+			for _, value := range testInventory {
+				stats.TotalSlotsProcessed += len(value)
+			}
+		}
+
+		i := 0
+		for merchantID, lineItems := range testInventory {
+			order, err := api.CreateOrder(merchantID, lineItems, conn)
+			if err != nil {
+				log.Printf("%s. skipping slot %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.TotalSlotsProcessed)
+				stats.CreateOrderErrors += len(lineItems)
+				delete(testInventory, merchantID)
+				i += len(lineItems)
+				continue
+			}
+			orders = append(orders, order)
+			stats.CreateOrderSuccess += len(lineItems)
+			i += len(lineItems)
+		}
+		utils.LogFlow("CreateOrder", "End")
+	}
+	// ListOrders Flow
+	if *allFlows {
+		utils.LogFlow("ListOrders", "Start")
+		stats.ListOrdersSuccess = true
+		if err = api.ListOrders(orders, conn); err != nil {
+			stats.ListOrdersSuccess = false
+			log.Println(err.Error())
+		}
+		utils.LogFlow("ListOrders", "End")
+	}
+
+	logStats(stats)
+}
diff --git a/utils/utils.go b/utils/utils.go
index ac9f064..7e2bf7c 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -18,12 +18,14 @@
 package utils
 
 import (
+	"crypto/md5"
 	"errors"
 	"fmt"
 	"io/ioutil"
 	"log"
 	"math/rand"
 	"path"
+	"sort"
 	"strings"
 
 	"github.com/golang/protobuf/jsonpb"
@@ -47,6 +49,116 @@
 	log.Println(strings.Join([]string{"\n##########\n", status, f, "Flow", "\n##########"}, " "))
 }
 
+func parseServiceFeed(serviceFeed string) ([]*fpb.Service, error) {
+	var feed fpb.ServiceFeed
+	content, err := ioutil.ReadFile(serviceFeed)
+	if err != nil {
+		return nil, fmt.Errorf("unable to read input file: %v", err)
+	}
+	if path.Ext(serviceFeed) == ".json" {
+		if err := jsonpb.UnmarshalString(string(content), &feed); err != nil {
+			return nil, fmt.Errorf("unable to parse feed as json: %v", err)
+		}
+	}
+	if path.Ext(serviceFeed) == ".pb3" {
+		if err := proto.Unmarshal(content, &feed); err != nil {
+			return nil, fmt.Errorf("unable to parse feed as pb3: %v", err)
+		}
+	}
+
+	if services := feed.GetService(); len(services) != 0 {
+		return services, nil
+	}
+	return nil, errors.New("service feed is empty. At least one service must be present in feed")
+}
+
+func merchantService(merchantID, serviceID string) string {
+	return strings.Join([]string{merchantID, serviceID}, "||")
+}
+
+func buildLineItem(availability *fpb.Availability, tickets []*fpb.TicketType) *mpb.LineItem {
+	lineItem := &mpb.LineItem{
+		ServiceId:   availability.GetServiceId(),
+		StartSec:    availability.GetStartSec(),
+		DurationSec: availability.GetDurationSec(),
+		Price:       &mpb.Price{},
+	}
+
+	// If not ticket types return nil as there is nothing to build.
+	if len(tickets) == 0 {
+		return nil
+	}
+
+	for i := 0; i < int(availability.GetSpotsOpen()); i++ {
+		// This deterministic which is fine given that we just want to get a mix of ticket types.
+		ticketTypeIndex := rand.Intn(len(tickets))
+		// Calculate price of line item.
+		if lineItem.GetPrice().GetCurrencyCode() == "" && tickets[ticketTypeIndex].GetPrice().GetCurrencyCode() != "" {
+			lineItem.Price.CurrencyCode = tickets[ticketTypeIndex].GetPrice().GetCurrencyCode()
+		}
+		lineItem.Price.PriceMicros += tickets[ticketTypeIndex].GetPrice().GetPriceMicros()
+
+		// Add ticket to line item.
+		lineItem.Tickets = append(lineItem.Tickets, &mpb.LineItem_OrderedTickets{
+			TicketId: tickets[ticketTypeIndex].GetTicketTypeId(),
+			Count:    1,
+		})
+	}
+
+	return lineItem
+}
+
+// MerchantLineItemMapFrom attempts to build a collection of LineItems from a service and availability feed.
+func MerchantLineItemMapFrom(serviceFeed, availabilityFeed string, testSlots int) (map[string][]*mpb.LineItem, error) {
+	services, err := parseServiceFeed(serviceFeed)
+	if err != nil {
+		return nil, err
+	}
+
+	feedHasTicketType := false
+	serviceTicketTypeMap := make(map[string][]*fpb.TicketType)
+	for _, service := range services {
+		merchantServiceID := merchantService(service.GetMerchantId(), service.GetServiceId())
+		for _, ticket := range service.GetTicketType() {
+			// TicketType can't have an empty price message or ticket_type_id. If it does it's excluded from map.
+			diff := cmp.Diff(&fpb.Price{}, ticket.GetPrice(), cmp.Comparer(proto.Equal))
+			if len(ticket.GetTicketTypeId()) == 0 || diff == "" {
+				continue
+			}
+
+			if _, ok := serviceTicketTypeMap[merchantServiceID]; !ok {
+				serviceTicketTypeMap[merchantServiceID] = []*fpb.TicketType{}
+			}
+			feedHasTicketType = true
+			serviceTicketTypeMap[merchantServiceID] = append(serviceTicketTypeMap[merchantServiceID], ticket)
+		}
+	}
+
+	if !feedHasTicketType {
+		return nil, errors.New("no valid ticket types found in service feed, please update service feed and retry")
+	}
+
+	availabilities, err := AvailabilityFrom(availabilityFeed, testSlots)
+	if err != nil {
+		return nil, err
+	}
+
+	merchantLineItemMap := make(map[string][]*mpb.LineItem)
+	for _, availability := range availabilities {
+		merchantServiceID := merchantService(availability.GetMerchantId(), availability.GetServiceId())
+		if tickets, ok := serviceTicketTypeMap[merchantServiceID]; ok {
+			lineItem := buildLineItem(availability, tickets)
+			// If lineItem can't be built, don't include in map
+			if lineItem == nil {
+				continue
+			}
+			merchantLineItemMap[availability.GetMerchantId()] = append(merchantLineItemMap[availability.GetMerchantId()], lineItem)
+		}
+	}
+
+	return merchantLineItemMap, nil
+}
+
 // AvailabilityFrom parses the file specified in availabilityFeed, returning a random permutation of availability data, maximum entries specified in testSlots
 func AvailabilityFrom(availabilityFeed string, testSlots int) ([]*fpb.Availability, error) {
 	LogFlow("Parse Input Feed", "Start")
@@ -89,7 +201,7 @@
 }
 
 // ValidateBooking performs granular comparisons between all got and want Bookings.
-func ValidateBooking(got *mpb.Booking, want *mpb.Booking) error {
+func ValidateBooking(got, want *mpb.Booking) error {
 	if got.GetBookingId() == "" {
 		return errors.New("booking_id is empty")
 	}
@@ -114,6 +226,136 @@
 	return nil
 }
 
+// ValidateLineItems performs granular comparisons between got and want LineItem arrays.
+func ValidateLineItems(got, want []*mpb.LineItem, confirmStatus bool) error {
+	if len(got) != len(want) {
+		return fmt.Errorf("number of LineItems differ got %d want %d", len(got), len(want))
+	}
+
+	for _, lineItem := range got {
+		orderTickets := OrderedTickets(lineItem.GetTickets())
+		sort.Sort(orderTickets)
+		lineItem.Tickets = orderTickets
+	}
+	for _, lineItem := range want {
+		orderTickets := OrderedTickets(lineItem.GetTickets())
+		sort.Sort(orderTickets)
+		lineItem.Tickets = orderTickets
+
+		if confirmStatus {
+			lineItem.Status = mpb.BookingStatus_CONFIRMED
+		}
+	}
+	sort.Sort(LineItems(got))
+	sort.Sort(LineItems(want))
+
+	if diff := cmp.Diff(got, want, cmp.Comparer(proto.Equal)); diff != "" {
+		return fmt.Errorf("LineItems differ (-got +want)\n%s", diff)
+	}
+
+	return nil
+}
+
+// ValidateOrder performs granular comparisons between got and want Order messages.
+// Params are purposely copied.
+func ValidateOrder(got, want mpb.Order) error {
+	if got.GetOrderId() == "" {
+		return fmt.Errorf("no order id provided for Order %v", got)
+	}
+	want.OrderId = got.GetOrderId()
+
+	if err := ValidateLineItems(got.GetItem(), want.GetItem(), true); err != nil {
+		return err
+	}
+
+	// LineItems okay. Remove, free memory, and compare rest of proto.
+	want.Item = nil
+	got.Item = nil
+	if diff := cmp.Diff(got, want, cmp.Comparer(proto.Equal)); diff != "" {
+		return fmt.Errorf("order protos differ. LineItems excluded, already validated. (-got +want)\n%s", diff)
+	}
+
+	return nil
+}
+
+// ValidateOrders performs simple comparisons and set up before forwarding orders
+// individually to ValidateOrder.
+func ValidateOrders(got, want Orders) error {
+	if len(got) != len(want) {
+		return fmt.Errorf("number of Orders differ got %d want %d", len(got), len(want))
+	}
+	sort.Sort(got)
+	sort.Sort(want)
+
+	var errorStrings []string
+	for i := 0; i < len(got); i++ {
+		if err := ValidateOrder(*got[i], *want[i]); err != nil {
+			errorStrings = append(errorStrings, err.Error())
+		}
+	}
+
+	if len(errorStrings) != 0 {
+		return errors.New(strings.Join(errorStrings, "\n"))
+	}
+	return nil
+}
+
+func hashLineItemByTicketIds(l *mpb.LineItem) string {
+	var uID []string
+	for _, ticket := range l.GetTickets() {
+		uID = append(uID, ticket.GetTicketId())
+	}
+	return fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(uID, `|`))))
+}
+
+// Orders is a convenience type for an Orders array
+type Orders []*mpb.Order
+
+func (o Orders) Len() int {
+	return len(o)
+}
+
+func (o Orders) Less(i, j int) bool {
+	return o[i].GetOrderId() < o[j].GetOrderId()
+}
+
+func (o Orders) Swap(i, j int) {
+	o[i], o[j] = o[j], o[i]
+}
+
+// OrderedTickets is a convenience type for an OrderedTickets array
+type OrderedTickets []*mpb.LineItem_OrderedTickets
+
+func (ot OrderedTickets) Len() int {
+	return len(ot)
+}
+
+func (ot OrderedTickets) Less(i, j int) bool {
+	return ot[i].GetTicketId() < ot[j].GetTicketId()
+}
+
+func (ot OrderedTickets) Swap(i, j int) {
+	ot[i], ot[j] = ot[j], ot[i]
+}
+
+// LineItems is a convenience type for a LineItem array
+// This sort interface defined below should be used iff
+// OrderedTickets have already been sorted AND
+// ticket ids are UIDs
+type LineItems []*mpb.LineItem
+
+func (l LineItems) Len() int {
+	return len(l)
+}
+
+func (l LineItems) Less(i, j int) bool {
+	return hashLineItemByTicketIds(l[i]) < hashLineItemByTicketIds(l[j])
+}
+
+func (l LineItems) Swap(i, j int) {
+	l[i], l[j] = l[j], l[i]
+}
+
 // BuildSlotFrom creates a bookingservice slot from an feed availability record.
 func BuildSlotFrom(a *fpb.Availability) (*mpb.Slot, error) {
 	r := a.GetResources()