repull 0.2.15 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/airbnb_api.rb +147 -52
  3. data/lib/repull/api/billing_api.rb +4 -4
  4. data/lib/repull/api/booking_com_api.rb +36 -27
  5. data/lib/repull/api/conversations_api.rb +398 -4
  6. data/lib/repull/api/guests_api.rb +2 -2
  7. data/lib/repull/api/listings_api.rb +4 -4
  8. data/lib/repull/api/reservations_api.rb +157 -6
  9. data/lib/repull/api/webhooks_api.rb +10 -3
  10. data/lib/repull/models/accept_reservation_request200_response.rb +321 -0
  11. data/lib/repull/models/account_created_event.rb +95 -31
  12. data/lib/repull/models/account_disconnected_event.rb +95 -31
  13. data/lib/repull/models/ai_operation_completed_event.rb +95 -31
  14. data/lib/repull/models/ai_operation_failed_event.rb +95 -31
  15. data/lib/repull/models/airbnb_reservation_action200_response.rb +157 -0
  16. data/lib/repull/models/airbnb_reservation_action_request.rb +248 -0
  17. data/lib/repull/models/booking_availability_update.rb +4 -20
  18. data/lib/repull/models/booking_availability_update_date_range.rb +193 -0
  19. data/lib/repull/models/booking_availability_update_request.rb +14 -3
  20. data/lib/repull/models/booking_pricing_rate_update.rb +32 -4
  21. data/lib/repull/models/booking_pricing_rate_update_date_range.rb +3 -0
  22. data/lib/repull/models/booking_pricing_rate_update_restrictions.rb +10 -10
  23. data/lib/repull/models/booking_pricing_response.rb +16 -4
  24. data/lib/repull/models/booking_pricing_update_response.rb +82 -18
  25. data/lib/repull/models/booking_property.rb +107 -36
  26. data/lib/repull/models/booking_property_listings_inner.rb +244 -0
  27. data/lib/repull/models/booking_rate_write_occupancy.rb +187 -0
  28. data/lib/repull/models/booking_rate_write_price_half.rb +188 -0
  29. data/lib/repull/models/booking_rate_write_restriction_half.rb +212 -0
  30. data/lib/repull/models/booking_rate_write_verification.rb +200 -0
  31. data/lib/repull/models/booking_rate_write_verification_row.rb +196 -0
  32. data/lib/repull/models/booking_restriction_request_row.rb +199 -0
  33. data/lib/repull/models/booking_restriction_verification.rb +208 -0
  34. data/lib/repull/models/booking_restriction_verification_row.rb +203 -0
  35. data/lib/repull/models/booking_restriction_verification_row_booking_value.rb +105 -0
  36. data/lib/repull/models/booking_restriction_verification_row_expected.rb +105 -0
  37. data/lib/repull/models/booking_rooms_rates_response.rb +41 -7
  38. data/lib/repull/models/booking_rooms_rates_response_rooms_inner.rb +12 -1
  39. data/lib/repull/models/booking_rooms_rates_response_rooms_inner_rates_inner.rb +1 -0
  40. data/lib/repull/models/booking_upstream_failure.rb +204 -0
  41. data/lib/repull/models/calendar_updated_event.rb +95 -31
  42. data/lib/repull/models/conversation_message_attachment.rb +28 -1
  43. data/lib/repull/models/create_airbnb_offer_request.rb +93 -8
  44. data/lib/repull/models/create_airbnb_offer_request_guest_details.rb +279 -0
  45. data/lib/repull/models/create_conversation_special_offer201_response.rb +293 -0
  46. data/lib/repull/models/create_conversation_special_offer201_response_guests.rb +189 -0
  47. data/lib/repull/models/create_conversation_special_offer_request.rb +263 -0
  48. data/lib/repull/models/create_conversation_special_offer_request_guests.rb +263 -0
  49. data/lib/repull/models/decline_reservation_request_request.rb +234 -0
  50. data/lib/repull/models/get_airbnb_offer200_response.rb +289 -0
  51. data/lib/repull/models/get_usage_summary200_response.rb +10 -1
  52. data/lib/repull/models/get_usage_summary200_response_limits.rb +11 -1
  53. data/lib/repull/models/get_usage_summary200_response_remaining.rb +11 -1
  54. data/lib/repull/models/get_usage_summary200_response_used.rb +10 -1
  55. data/lib/repull/models/get_usage_tier200_response.rb +10 -1
  56. data/lib/repull/models/get_usage_tier200_response_limits.rb +11 -1
  57. data/lib/repull/models/get_usage_tier200_response_remaining.rb +11 -1
  58. data/lib/repull/models/get_usage_tier200_response_used.rb +10 -1
  59. data/lib/repull/models/inquiry_created_event.rb +281 -0
  60. data/lib/repull/models/inquiry_created_payload.rb +185 -0
  61. data/lib/repull/models/inquiry_updated_event.rb +281 -0
  62. data/lib/repull/models/inquiry_updated_payload.rb +213 -0
  63. data/lib/repull/models/inquiry_webhook_object.rb +323 -0
  64. data/lib/repull/models/inquiry_webhook_object_expected_payout.rb +158 -0
  65. data/lib/repull/models/inquiry_webhook_object_guests.rb +188 -0
  66. data/lib/repull/models/list_airbnb_thread_messages200_response.rb +170 -0
  67. data/lib/repull/models/list_airbnb_thread_messages200_response_data_inner.rb +274 -0
  68. data/lib/repull/models/list_airbnb_thread_messages200_response_pagination.rb +158 -0
  69. data/lib/repull/models/list_inquiries200_response.rb +192 -0
  70. data/lib/repull/models/list_inquiries200_response_data_inner.rb +418 -0
  71. data/lib/repull/models/list_inquiries200_response_data_inner_expected_payout.rb +159 -0
  72. data/lib/repull/models/list_inquiries200_response_data_inner_guests.rb +188 -0
  73. data/lib/repull/models/listing_created_event.rb +95 -31
  74. data/lib/repull/models/listing_deleted_event.rb +95 -31
  75. data/lib/repull/models/listing_deleted_payload.rb +55 -21
  76. data/lib/repull/models/listing_reactivated_event.rb +281 -0
  77. data/lib/repull/models/listing_suspended_event.rb +281 -0
  78. data/lib/repull/models/listing_suspension_payload.rb +202 -0
  79. data/lib/repull/models/listing_updated_event.rb +95 -31
  80. data/lib/repull/models/listing_updated_payload.rb +50 -22
  81. data/lib/repull/models/listing_webhook_object.rb +291 -0
  82. data/lib/repull/models/listing_webhook_object_address.rb +168 -0
  83. data/lib/repull/models/listing_webhook_object_channels_inner.rb +184 -0
  84. data/lib/repull/models/message.rb +1 -0
  85. data/lib/repull/models/payment_completed_event.rb +95 -31
  86. data/lib/repull/models/payment_completed_payload.rb +46 -44
  87. data/lib/repull/models/payment_refunded_event.rb +95 -31
  88. data/lib/repull/models/payment_refunded_payload.rb +46 -44
  89. data/lib/repull/models/payment_webhook_object.rb +351 -0
  90. data/lib/repull/models/preapprove_conversation201_response.rb +253 -0
  91. data/lib/repull/models/preapprove_conversation_request.rb +150 -0
  92. data/lib/repull/models/replay_webhook_delivery_request.rb +150 -0
  93. data/lib/repull/models/repull_ping_event.rb +95 -31
  94. data/lib/repull/models/reservation.rb +22 -2
  95. data/lib/repull/models/reservation_alteration_created_event.rb +95 -31
  96. data/lib/repull/models/reservation_alteration_responded_event.rb +95 -31
  97. data/lib/repull/models/reservation_cancelled_event.rb +95 -31
  98. data/lib/repull/models/reservation_created_event.rb +95 -32
  99. data/lib/repull/models/reservation_message_received_event.rb +95 -31
  100. data/lib/repull/models/reservation_message_received_payload.rb +17 -4
  101. data/lib/repull/models/reservation_request_created_event.rb +281 -0
  102. data/lib/repull/models/reservation_request_created_payload.rb +224 -0
  103. data/lib/repull/models/reservation_request_updated_event.rb +281 -0
  104. data/lib/repull/models/reservation_request_updated_payload.rb +251 -0
  105. data/lib/repull/models/reservation_updated_event.rb +95 -31
  106. data/lib/repull/models/review_created_event.rb +95 -31
  107. data/lib/repull/models/review_responded_event.rb +95 -31
  108. data/lib/repull/models/send_airbnb_message201_response.rb +104 -0
  109. data/lib/repull/models/send_airbnb_message_request.rb +4 -20
  110. data/lib/repull/models/send_message_attachment.rb +225 -0
  111. data/lib/repull/models/send_message_part.rb +201 -0
  112. data/lib/repull/models/send_message_request.rb +44 -19
  113. data/lib/repull/models/send_message_response.rb +29 -5
  114. data/lib/repull/models/sent_attachment.rb +196 -0
  115. data/lib/repull/models/usage_quota_warning_event.rb +281 -0
  116. data/lib/repull/models/usage_quota_warning_payload.rb +224 -0
  117. data/lib/repull/models/usage_quota_warning_payload_top_operation.rb +167 -0
  118. data/lib/repull/models/webhook_event.rb +17 -3
  119. data/lib/repull/models/webhook_event_account.rb +171 -0
  120. data/lib/repull/models/webhook_event_type.rb +8 -1
  121. data/lib/repull/models/withdraw_conversation_special_offer200_response.rb +240 -0
  122. data/lib/repull/version.rb +1 -1
  123. data/lib/repull.rb +60 -0
  124. data/openapi/v1.json +19295 -13620
  125. data/scripts/regen.sh +1 -1
  126. metadata +62 -2
@@ -19,11 +19,81 @@ module Repull
19
19
  def initialize(api_client = ApiClient.default)
20
20
  @api_client = api_client
21
21
  end
22
+ # Accept a booking request
23
+ # Accept a pending Airbnb booking request — a reservation with status `pending`, made on a listing without Instant Book. Find them with `GET /v1/reservations?status=pending`. Airbnb expires a request the host has not answered within 24 hours. Runs the same action as the Vanio dashboard’s Accept button. Airbnb confirms asynchronously: the reservation’s status moves to confirmed, and a `reservation.updated` webhook fires, when Airbnb’s notification lands (usually within seconds). The response reports what Airbnb was asked to do. **Airbnb only**, and only for listings connected to Airbnb directly: other channels have no request step (`422 channel_not_supported`). A reservation that is not pending is refused before Airbnb is contacted (`409 reservation_not_pending`); one Airbnb says already moved on is `409 request_no_longer_pending`. Neither is worth retrying. Takes no body. Send `Idempotency-Key`: a repeat with the same key replays the first response instead of acting twice (a `409 idempotency_key_in_use` while the first is still running). A 5xx, a `429 airbnb_rate_limited` or a `403 connection_reauth_required` is not stored — nothing was done — so retrying with the same key reaches Airbnb again.
24
+ # @param id [Integer] Repull reservation id (from `GET /v1/reservations?status=pending`) — not the Airbnb confirmation code.
25
+ # @param [Hash] opts the optional parameters
26
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged `Idempotency-Status: cached` — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → `409 idempotency_key_in_use`. - Same key with a DIFFERENT payload → `422 idempotency_key_reused`. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status >= 500, `408`, `425` and `429`, and the refusals that happen before anything is done and tell you to fix something outside the request first — `connection_reauth_required`, `listing_inactive`, and the rate/daily limits. Every other answer, including a final refusal such as `422 airbnb_rejected`, is stored and replayed.
27
+ # @return [AcceptReservationRequest200Response]
28
+ def accept_reservation_request(id, opts = {})
29
+ data, _status_code, _headers = accept_reservation_request_with_http_info(id, opts)
30
+ data
31
+ end
32
+
33
+ # Accept a booking request
34
+ # Accept a pending Airbnb booking request — a reservation with status `pending`, made on a listing without Instant Book. Find them with `GET /v1/reservations?status=pending`. Airbnb expires a request the host has not answered within 24 hours. Runs the same action as the Vanio dashboard’s Accept button. Airbnb confirms asynchronously: the reservation’s status moves to confirmed, and a `reservation.updated` webhook fires, when Airbnb’s notification lands (usually within seconds). The response reports what Airbnb was asked to do. **Airbnb only**, and only for listings connected to Airbnb directly: other channels have no request step (`422 channel_not_supported`). A reservation that is not pending is refused before Airbnb is contacted (`409 reservation_not_pending`); one Airbnb says already moved on is `409 request_no_longer_pending`. Neither is worth retrying. Takes no body. Send `Idempotency-Key`: a repeat with the same key replays the first response instead of acting twice (a `409 idempotency_key_in_use` while the first is still running). A 5xx, a `429 airbnb_rate_limited` or a `403 connection_reauth_required` is not stored — nothing was done — so retrying with the same key reaches Airbnb again.
35
+ # @param id [Integer] Repull reservation id (from `GET /v1/reservations?status=pending`) — not the Airbnb confirmation code.
36
+ # @param [Hash] opts the optional parameters
37
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged `Idempotency-Status: cached` — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → `409 idempotency_key_in_use`. - Same key with a DIFFERENT payload → `422 idempotency_key_reused`. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status >= 500, `408`, `425` and `429`, and the refusals that happen before anything is done and tell you to fix something outside the request first — `connection_reauth_required`, `listing_inactive`, and the rate/daily limits. Every other answer, including a final refusal such as `422 airbnb_rejected`, is stored and replayed.
38
+ # @return [Array<(AcceptReservationRequest200Response, Integer, Hash)>] AcceptReservationRequest200Response data, response status code and response headers
39
+ def accept_reservation_request_with_http_info(id, opts = {})
40
+ if @api_client.config.debugging
41
+ @api_client.config.logger.debug 'Calling API: ReservationsApi.accept_reservation_request ...'
42
+ end
43
+ # verify the required parameter 'id' is set
44
+ if @api_client.config.client_side_validation && id.nil?
45
+ fail ArgumentError, "Missing the required parameter 'id' when calling ReservationsApi.accept_reservation_request"
46
+ end
47
+ if @api_client.config.client_side_validation && !opts[:'idempotency_key'].nil? && opts[:'idempotency_key'].to_s.length > 255
48
+ fail ArgumentError, 'invalid value for "opts[:"idempotency_key"]" when calling ReservationsApi.accept_reservation_request, the character length must be smaller than or equal to 255.'
49
+ end
50
+
51
+ # resource path
52
+ local_var_path = '/v1/reservations/{id}/accept'.sub('{id}', CGI.escape(id.to_s))
53
+
54
+ # query parameters
55
+ query_params = opts[:query_params] || {}
56
+
57
+ # header parameters
58
+ header_params = opts[:header_params] || {}
59
+ # HTTP header 'Accept' (if needed)
60
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
61
+ header_params[:'Idempotency-Key'] = opts[:'idempotency_key'] if !opts[:'idempotency_key'].nil?
62
+
63
+ # form parameters
64
+ form_params = opts[:form_params] || {}
65
+
66
+ # http body (model)
67
+ post_body = opts[:debug_body]
68
+
69
+ # return_type
70
+ return_type = opts[:debug_return_type] || 'AcceptReservationRequest200Response'
71
+
72
+ # auth_names
73
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
74
+
75
+ new_options = opts.merge(
76
+ :operation => :"ReservationsApi.accept_reservation_request",
77
+ :header_params => header_params,
78
+ :query_params => query_params,
79
+ :form_params => form_params,
80
+ :body => post_body,
81
+ :auth_names => auth_names,
82
+ :return_type => return_type
83
+ )
84
+
85
+ data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options)
86
+ if @api_client.config.debugging
87
+ @api_client.config.logger.debug "API called: ReservationsApi#accept_reservation_request\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
88
+ end
89
+ return data, status_code, headers
90
+ end
91
+
22
92
  # Create a reservation
23
93
  # Creates a reservation and everything that hangs off one: the guest, the conversation thread, the dashboard item, the calendar block, and the `reservation.created` fan-out that issues the door code and starts the messaging automations. **Platform is restricted to `direct`, `website` and `owner`.** Reservations on Airbnb, Booking.com and Vrbo are owned by the channel and arrive through sync — creating one here would mint a local booking the channel has never heard of, which then fights the next sync. Create those on the channel. **Dates are validated** (`YYYY-MM-DD`, and `checkOut` must be after `checkIn`), and an unrecognised field is rejected by name rather than silently ignored. **This endpoint does not set the price.** There is no `totalPrice` field: the reservation pipeline derives the price breakdown from the property's own rates and overwrites anything supplied, so accepting a total would be taking a value and discarding it. A reservation created here is priced by that engine (`0` when the property has no rates for the range). `currency` IS honoured. Quote a stay with `GET /v1/quotes` before booking if you need the figure up front. **Availability is NOT checked.** This creates the reservation you asked for even if the dates overlap an existing booking. Call `GET /v1/availability/{propertyId}` first if that matters. Send `Idempotency-Key` — a network timeout here is exactly the case it exists for: without it, a retry books the guest twice. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
24
94
  # @param reservation_create_request [ReservationCreateRequest]
25
95
  # @param [Hash] opts the optional parameters
26
- # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
96
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status &gt;&#x3D; 500, &#x60;408&#x60;, &#x60;425&#x60; and &#x60;429&#x60;, and the refusals that happen before anything is done and tell you to fix something outside the request first — &#x60;connection_reauth_required&#x60;, &#x60;listing_inactive&#x60;, and the rate/daily limits. Every other answer, including a final refusal such as &#x60;422 airbnb_rejected&#x60;, is stored and replayed.
27
97
  # @return [ReservationCreateResponse]
28
98
  def create_reservation(reservation_create_request, opts = {})
29
99
  data, _status_code, _headers = create_reservation_with_http_info(reservation_create_request, opts)
@@ -34,7 +104,7 @@ module Repull
34
104
  # Creates a reservation and everything that hangs off one: the guest, the conversation thread, the dashboard item, the calendar block, and the &#x60;reservation.created&#x60; fan-out that issues the door code and starts the messaging automations. **Platform is restricted to &#x60;direct&#x60;, &#x60;website&#x60; and &#x60;owner&#x60;.** Reservations on Airbnb, Booking.com and Vrbo are owned by the channel and arrive through sync — creating one here would mint a local booking the channel has never heard of, which then fights the next sync. Create those on the channel. **Dates are validated** (&#x60;YYYY-MM-DD&#x60;, and &#x60;checkOut&#x60; must be after &#x60;checkIn&#x60;), and an unrecognised field is rejected by name rather than silently ignored. **This endpoint does not set the price.** There is no &#x60;totalPrice&#x60; field: the reservation pipeline derives the price breakdown from the property&#39;s own rates and overwrites anything supplied, so accepting a total would be taking a value and discarding it. A reservation created here is priced by that engine (&#x60;0&#x60; when the property has no rates for the range). &#x60;currency&#x60; IS honoured. Quote a stay with &#x60;GET /v1/quotes&#x60; before booking if you need the figure up front. **Availability is NOT checked.** This creates the reservation you asked for even if the dates overlap an existing booking. Call &#x60;GET /v1/availability/{propertyId}&#x60; first if that matters. Send &#x60;Idempotency-Key&#x60; — a network timeout here is exactly the case it exists for: without it, a retry books the guest twice. Returns &#x60;403 listing_inactive&#x60; when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
35
105
  # @param reservation_create_request [ReservationCreateRequest]
36
106
  # @param [Hash] opts the optional parameters
37
- # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
107
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status &gt;&#x3D; 500, &#x60;408&#x60;, &#x60;425&#x60; and &#x60;429&#x60;, and the refusals that happen before anything is done and tell you to fix something outside the request first — &#x60;connection_reauth_required&#x60;, &#x60;listing_inactive&#x60;, and the rate/daily limits. Every other answer, including a final refusal such as &#x60;422 airbnb_rejected&#x60;, is stored and replayed.
38
108
  # @return [Array<(ReservationCreateResponse, Integer, Hash)>] ReservationCreateResponse data, response status code and response headers
39
109
  def create_reservation_with_http_info(reservation_create_request, opts = {})
40
110
  if @api_client.config.debugging
@@ -94,6 +164,87 @@ module Repull
94
164
  return data, status_code, headers
95
165
  end
96
166
 
167
+ # Decline a booking request
168
+ # Decline a pending Airbnb booking request (a reservation with status `pending`; find them with `GET /v1/reservations?status=pending`). `reason` must be one of Airbnb’s own decline reasons. `message` is required: Airbnb sends it to the guest with the decline (at most 500 characters). It is not defaulted — a canned message would put words in your mouth. Runs the same action as the Vanio dashboard’s Decline button. Airbnb confirms asynchronously; the reservation’s status moves, and `reservation.updated` fires, when its notification lands. Same channel and status rules as `POST /v1/reservations/{id}/accept`. Send `Idempotency-Key`: a repeat with the same key replays the first response instead of acting twice (a `409 idempotency_key_in_use` while the first is still running). A 5xx, a `429 airbnb_rate_limited` or a `403 connection_reauth_required` is not stored — nothing was done — so retrying with the same key reaches Airbnb again.
169
+ # @param id [Integer] Repull reservation id (from &#x60;GET /v1/reservations?status&#x3D;pending&#x60;) — not the Airbnb confirmation code.
170
+ # @param decline_reservation_request_request [DeclineReservationRequestRequest]
171
+ # @param [Hash] opts the optional parameters
172
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status &gt;&#x3D; 500, &#x60;408&#x60;, &#x60;425&#x60; and &#x60;429&#x60;, and the refusals that happen before anything is done and tell you to fix something outside the request first — &#x60;connection_reauth_required&#x60;, &#x60;listing_inactive&#x60;, and the rate/daily limits. Every other answer, including a final refusal such as &#x60;422 airbnb_rejected&#x60;, is stored and replayed.
173
+ # @return [AcceptReservationRequest200Response]
174
+ def decline_reservation_request(id, decline_reservation_request_request, opts = {})
175
+ data, _status_code, _headers = decline_reservation_request_with_http_info(id, decline_reservation_request_request, opts)
176
+ data
177
+ end
178
+
179
+ # Decline a booking request
180
+ # Decline a pending Airbnb booking request (a reservation with status &#x60;pending&#x60;; find them with &#x60;GET /v1/reservations?status&#x3D;pending&#x60;). &#x60;reason&#x60; must be one of Airbnb’s own decline reasons. &#x60;message&#x60; is required: Airbnb sends it to the guest with the decline (at most 500 characters). It is not defaulted — a canned message would put words in your mouth. Runs the same action as the Vanio dashboard’s Decline button. Airbnb confirms asynchronously; the reservation’s status moves, and &#x60;reservation.updated&#x60; fires, when its notification lands. Same channel and status rules as &#x60;POST /v1/reservations/{id}/accept&#x60;. Send &#x60;Idempotency-Key&#x60;: a repeat with the same key replays the first response instead of acting twice (a &#x60;409 idempotency_key_in_use&#x60; while the first is still running). A 5xx, a &#x60;429 airbnb_rate_limited&#x60; or a &#x60;403 connection_reauth_required&#x60; is not stored — nothing was done — so retrying with the same key reaches Airbnb again.
181
+ # @param id [Integer] Repull reservation id (from &#x60;GET /v1/reservations?status&#x3D;pending&#x60;) — not the Airbnb confirmation code.
182
+ # @param decline_reservation_request_request [DeclineReservationRequestRequest]
183
+ # @param [Hash] opts the optional parameters
184
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status &gt;&#x3D; 500, &#x60;408&#x60;, &#x60;425&#x60; and &#x60;429&#x60;, and the refusals that happen before anything is done and tell you to fix something outside the request first — &#x60;connection_reauth_required&#x60;, &#x60;listing_inactive&#x60;, and the rate/daily limits. Every other answer, including a final refusal such as &#x60;422 airbnb_rejected&#x60;, is stored and replayed.
185
+ # @return [Array<(AcceptReservationRequest200Response, Integer, Hash)>] AcceptReservationRequest200Response data, response status code and response headers
186
+ def decline_reservation_request_with_http_info(id, decline_reservation_request_request, opts = {})
187
+ if @api_client.config.debugging
188
+ @api_client.config.logger.debug 'Calling API: ReservationsApi.decline_reservation_request ...'
189
+ end
190
+ # verify the required parameter 'id' is set
191
+ if @api_client.config.client_side_validation && id.nil?
192
+ fail ArgumentError, "Missing the required parameter 'id' when calling ReservationsApi.decline_reservation_request"
193
+ end
194
+ # verify the required parameter 'decline_reservation_request_request' is set
195
+ if @api_client.config.client_side_validation && decline_reservation_request_request.nil?
196
+ fail ArgumentError, "Missing the required parameter 'decline_reservation_request_request' when calling ReservationsApi.decline_reservation_request"
197
+ end
198
+ if @api_client.config.client_side_validation && !opts[:'idempotency_key'].nil? && opts[:'idempotency_key'].to_s.length > 255
199
+ fail ArgumentError, 'invalid value for "opts[:"idempotency_key"]" when calling ReservationsApi.decline_reservation_request, the character length must be smaller than or equal to 255.'
200
+ end
201
+
202
+ # resource path
203
+ local_var_path = '/v1/reservations/{id}/decline'.sub('{id}', CGI.escape(id.to_s))
204
+
205
+ # query parameters
206
+ query_params = opts[:query_params] || {}
207
+
208
+ # header parameters
209
+ header_params = opts[:header_params] || {}
210
+ # HTTP header 'Accept' (if needed)
211
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
212
+ # HTTP header 'Content-Type'
213
+ content_type = @api_client.select_header_content_type(['application/json'])
214
+ if !content_type.nil?
215
+ header_params['Content-Type'] = content_type
216
+ end
217
+ header_params[:'Idempotency-Key'] = opts[:'idempotency_key'] if !opts[:'idempotency_key'].nil?
218
+
219
+ # form parameters
220
+ form_params = opts[:form_params] || {}
221
+
222
+ # http body (model)
223
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(decline_reservation_request_request)
224
+
225
+ # return_type
226
+ return_type = opts[:debug_return_type] || 'AcceptReservationRequest200Response'
227
+
228
+ # auth_names
229
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
230
+
231
+ new_options = opts.merge(
232
+ :operation => :"ReservationsApi.decline_reservation_request",
233
+ :header_params => header_params,
234
+ :query_params => query_params,
235
+ :form_params => form_params,
236
+ :body => post_body,
237
+ :auth_names => auth_names,
238
+ :return_type => return_type
239
+ )
240
+
241
+ data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options)
242
+ if @api_client.config.debugging
243
+ @api_client.config.logger.debug "API called: ReservationsApi#decline_reservation_request\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
244
+ end
245
+ return data, status_code, headers
246
+ end
247
+
97
248
  # Get reservation details
98
249
  # Returns the full record for a single reservation, scoped to the authenticated workspace. Response shape is identical to a single row in `GET /v1/reservations` so SDK consumers can use the same type for both. Returns **404** if the id does not exist OR belongs to a different workspace — the API never differentiates the two so caller can't enumerate other workspaces' ids. Returns `403 listing_inactive` when the listing this resolves to is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
99
250
  # @param id [Integer] Internal Repull reservation ID.
@@ -168,7 +319,7 @@ module Repull
168
319
  # @option opts [String] :cursor Opaque cursor returned in the previous response&#39;s &#x60;pagination.nextCursor&#x60;. Omit to fetch the first page.
169
320
  # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with &#x60;cursor&#x60; — passing both returns 422. Accepts integers in &#x60;[0, 10000]&#x60;; deeper walks must use &#x60;cursor&#x60; (constant per-page cost). The response always includes &#x60;pagination.nextCursor&#x60; so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
170
321
  # @option opts [String] :platform Filter by booking platform
171
- # @option opts [String] :status Filter by lifecycle status. **Case-insensitive** — &#x60;confirmed&#x60;, &#x60;Confirmed&#x60;, and &#x60;CONFIRMED&#x60; all match. Each public value expands to the full set of internal sub-states server-side: &#x60;confirmed&#x60; matches &#x60;accept&#x60;/&#x60;confirmed&#x60;/&#x60;modified&#x60;, &#x60;cancelled&#x60; matches every cancellation sub-state (&#x60;cancelled_by_host&#x60;, &#x60;declined&#x60;, &#x60;expired&#x60;, etc.), &#x60;pending&#x60; includes &#x60;inquiry&#x60;/&#x60;awaiting_payment&#x60;. &#x60;completed&#x60; is a derived state — combine &#x60;status&#x3D;confirmed&#x60; with &#x60;check_out_before&#x3D;&lt;today&gt;&#x60; to filter for past stays.
322
+ # @option opts [String] :status Filter by lifecycle status. **Case-insensitive** — &#x60;confirmed&#x60;, &#x60;Confirmed&#x60;, and &#x60;CONFIRMED&#x60; all match. Each public value expands to the full set of internal sub-states server-side: &#x60;confirmed&#x60; matches &#x60;accept&#x60;/&#x60;confirmed&#x60;/&#x60;modified&#x60;, &#x60;cancelled&#x60; matches every cancellation sub-state (&#x60;cancelled_by_host&#x60;, &#x60;declined&#x60;, &#x60;expired&#x60;, etc.), &#x60;pending&#x60; includes &#x60;inquiry&#x60;/&#x60;awaiting_payment&#x60;. &#x60;completed&#x60; is a derived state — combine &#x60;status&#x3D;confirmed&#x60; with &#x60;check_out_before&#x3D;&lt;today&gt;&#x60; to filter for past stays. &#x60;pending&#x60; is how an Airbnb booking **request** awaiting the host appears — answer it with &#x60;POST /v1/reservations/{id}/accept&#x60; or &#x60;/decline&#x60; before its &#x60;respondBy&#x60;. &#x60;pending&#x60; lists only requests that can still be answered: one the channel already let lapse (Airbnb expires a request 24 hours after the guest asks; no request survives its check-in date) is left out and appears under &#x60;cancelled&#x60; with &#x60;statusDetail: \&quot;request_expired\&quot;&#x60; instead. The stored record is not changed — this is derived when you read it. Airbnb **inquiries** (questions before booking) are not reservations: list them with &#x60;GET /v1/inquiries&#x60;.
172
323
  # @option opts [Integer] :listing_id Filter to a single listing
173
324
  # @option opts [Date] :check_in_after Check-in date &gt;&#x3D; this value
174
325
  # @option opts [Date] :check_in_before Check-in date &lt;&#x3D; this value
@@ -196,7 +347,7 @@ module Repull
196
347
  # @option opts [String] :cursor Opaque cursor returned in the previous response&#39;s &#x60;pagination.nextCursor&#x60;. Omit to fetch the first page.
197
348
  # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with &#x60;cursor&#x60; — passing both returns 422. Accepts integers in &#x60;[0, 10000]&#x60;; deeper walks must use &#x60;cursor&#x60; (constant per-page cost). The response always includes &#x60;pagination.nextCursor&#x60; so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
198
349
  # @option opts [String] :platform Filter by booking platform
199
- # @option opts [String] :status Filter by lifecycle status. **Case-insensitive** — &#x60;confirmed&#x60;, &#x60;Confirmed&#x60;, and &#x60;CONFIRMED&#x60; all match. Each public value expands to the full set of internal sub-states server-side: &#x60;confirmed&#x60; matches &#x60;accept&#x60;/&#x60;confirmed&#x60;/&#x60;modified&#x60;, &#x60;cancelled&#x60; matches every cancellation sub-state (&#x60;cancelled_by_host&#x60;, &#x60;declined&#x60;, &#x60;expired&#x60;, etc.), &#x60;pending&#x60; includes &#x60;inquiry&#x60;/&#x60;awaiting_payment&#x60;. &#x60;completed&#x60; is a derived state — combine &#x60;status&#x3D;confirmed&#x60; with &#x60;check_out_before&#x3D;&lt;today&gt;&#x60; to filter for past stays.
350
+ # @option opts [String] :status Filter by lifecycle status. **Case-insensitive** — &#x60;confirmed&#x60;, &#x60;Confirmed&#x60;, and &#x60;CONFIRMED&#x60; all match. Each public value expands to the full set of internal sub-states server-side: &#x60;confirmed&#x60; matches &#x60;accept&#x60;/&#x60;confirmed&#x60;/&#x60;modified&#x60;, &#x60;cancelled&#x60; matches every cancellation sub-state (&#x60;cancelled_by_host&#x60;, &#x60;declined&#x60;, &#x60;expired&#x60;, etc.), &#x60;pending&#x60; includes &#x60;inquiry&#x60;/&#x60;awaiting_payment&#x60;. &#x60;completed&#x60; is a derived state — combine &#x60;status&#x3D;confirmed&#x60; with &#x60;check_out_before&#x3D;&lt;today&gt;&#x60; to filter for past stays. &#x60;pending&#x60; is how an Airbnb booking **request** awaiting the host appears — answer it with &#x60;POST /v1/reservations/{id}/accept&#x60; or &#x60;/decline&#x60; before its &#x60;respondBy&#x60;. &#x60;pending&#x60; lists only requests that can still be answered: one the channel already let lapse (Airbnb expires a request 24 hours after the guest asks; no request survives its check-in date) is left out and appears under &#x60;cancelled&#x60; with &#x60;statusDetail: \&quot;request_expired\&quot;&#x60; instead. The stored record is not changed — this is derived when you read it. Airbnb **inquiries** (questions before booking) are not reservations: list them with &#x60;GET /v1/inquiries&#x60;.
200
351
  # @option opts [Integer] :listing_id Filter to a single listing
201
352
  # @option opts [Date] :check_in_after Check-in date &gt;&#x3D; this value
202
353
  # @option opts [Date] :check_in_before Check-in date &lt;&#x3D; this value
@@ -299,7 +450,7 @@ module Repull
299
450
  # @param id [Integer] Internal Repull reservation ID.
300
451
  # @param reservation_update_request [ReservationUpdateRequest]
301
452
  # @param [Hash] opts the optional parameters
302
- # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
453
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status &gt;&#x3D; 500, &#x60;408&#x60;, &#x60;425&#x60; and &#x60;429&#x60;, and the refusals that happen before anything is done and tell you to fix something outside the request first — &#x60;connection_reauth_required&#x60;, &#x60;listing_inactive&#x60;, and the rate/daily limits. Every other answer, including a final refusal such as &#x60;422 airbnb_rejected&#x60;, is stored and replayed.
303
454
  # @return [ReservationUpdateResponse]
304
455
  def update_reservation(id, reservation_update_request, opts = {})
305
456
  data, _status_code, _headers = update_reservation_with_http_info(id, reservation_update_request, opts)
@@ -311,7 +462,7 @@ module Repull
311
462
  # @param id [Integer] Internal Repull reservation ID.
312
463
  # @param reservation_update_request [ReservationUpdateRequest]
313
464
  # @param [Hash] opts the optional parameters
314
- # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
465
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Retryable outcomes are deliberately not stored, so a retry with the same key runs for real: any status &gt;&#x3D; 500, &#x60;408&#x60;, &#x60;425&#x60; and &#x60;429&#x60;, and the refusals that happen before anything is done and tell you to fix something outside the request first — &#x60;connection_reauth_required&#x60;, &#x60;listing_inactive&#x60;, and the rate/daily limits. Every other answer, including a final refusal such as &#x60;422 airbnb_rejected&#x60;, is stored and replayed.
315
466
  # @return [Array<(ReservationUpdateResponse, Integer, Hash)>] ReservationUpdateResponse data, response status code and response headers
316
467
  def update_reservation_with_http_info(id, reservation_update_request, opts = {})
317
468
  if @api_client.config.debugging
@@ -547,10 +547,11 @@ module Repull
547
547
  end
548
548
 
549
549
  # Replay webhook delivery
550
- # Re-sends the original payload (same eventId, fresh deliveryId, attempt + 1). A delivery about a listing that is inactive now is not re-sent and answers `403 listing_inactive`; activate the listing first.
550
+ # Re-sends the original payload (same eventId, fresh deliveryId, attempt + 1). A delivery may be replayed at most **3 times per rolling 60 minutes**; the 4th inside that window answers `409 replay_limit_reached` and names the time the next one is allowed. The limit is charged to the original delivery, so replaying the delivery a replay produced draws on the same budget. It is not a lifetime cap — a delivery that has not been replayed for an hour starts fresh. A delivery your endpoint already accepted is not re-sent (it would be a duplicate) and answers `409 delivery_already_succeeded`; send `{\"force\": true}` to replay it anyway, which still counts against the limit. A delivery about a listing that is inactive now is not re-sent and answers `403 listing_inactive`; activate the listing first.
551
551
  # @param id [String]
552
552
  # @param delivery_id [String]
553
553
  # @param [Hash] opts the optional parameters
554
+ # @option opts [ReplayWebhookDeliveryRequest] :replay_webhook_delivery_request
554
555
  # @return [nil]
555
556
  def replay_webhook_delivery(id, delivery_id, opts = {})
556
557
  replay_webhook_delivery_with_http_info(id, delivery_id, opts)
@@ -558,10 +559,11 @@ module Repull
558
559
  end
559
560
 
560
561
  # Replay webhook delivery
561
- # Re-sends the original payload (same eventId, fresh deliveryId, attempt + 1). A delivery about a listing that is inactive now is not re-sent and answers &#x60;403 listing_inactive&#x60;; activate the listing first.
562
+ # Re-sends the original payload (same eventId, fresh deliveryId, attempt + 1). A delivery may be replayed at most **3 times per rolling 60 minutes**; the 4th inside that window answers &#x60;409 replay_limit_reached&#x60; and names the time the next one is allowed. The limit is charged to the original delivery, so replaying the delivery a replay produced draws on the same budget. It is not a lifetime cap — a delivery that has not been replayed for an hour starts fresh. A delivery your endpoint already accepted is not re-sent (it would be a duplicate) and answers &#x60;409 delivery_already_succeeded&#x60;; send &#x60;{\&quot;force\&quot;: true}&#x60; to replay it anyway, which still counts against the limit. A delivery about a listing that is inactive now is not re-sent and answers &#x60;403 listing_inactive&#x60;; activate the listing first.
562
563
  # @param id [String]
563
564
  # @param delivery_id [String]
564
565
  # @param [Hash] opts the optional parameters
566
+ # @option opts [ReplayWebhookDeliveryRequest] :replay_webhook_delivery_request
565
567
  # @return [Array<(nil, Integer, Hash)>] nil, response status code and response headers
566
568
  def replay_webhook_delivery_with_http_info(id, delivery_id, opts = {})
567
569
  if @api_client.config.debugging
@@ -585,12 +587,17 @@ module Repull
585
587
  header_params = opts[:header_params] || {}
586
588
  # HTTP header 'Accept' (if needed)
587
589
  header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
590
+ # HTTP header 'Content-Type'
591
+ content_type = @api_client.select_header_content_type(['application/json'])
592
+ if !content_type.nil?
593
+ header_params['Content-Type'] = content_type
594
+ end
588
595
 
589
596
  # form parameters
590
597
  form_params = opts[:form_params] || {}
591
598
 
592
599
  # http body (model)
593
- post_body = opts[:debug_body]
600
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(opts[:'replay_webhook_delivery_request'])
594
601
 
595
602
  # return_type
596
603
  return_type = opts[:debug_return_type]
@@ -0,0 +1,321 @@
1
+ =begin
2
+ #Repull API
3
+
4
+ #The unified API for vacation rental tech. Connect to 50+ PMS platforms and 4 OTA channels through one REST API. Built-in AI operations for guest communication, pricing, and listing optimization. ## Designed for AI agents Every error response on this API includes machine-parseable fields so an LLM (Claude in MCP, Cursor, Cline, GPT, etc.) can self-recover without escalating to a human: - `error.code` — stable string identifier (e.g. `invalid_params`, `rate_limit_exceeded`) - `error.message` — human-readable cause - `error.fix` — exact recovery steps (e.g. \"Pass `check_in_after` as ISO 8601: `?check_in_after=2026-01-15`\") - `error.docs_url` — link to the canonical write-up at `https://repull.dev/docs/errors/{code}` - `error.request_id` — id to correlate with server-side logs - `error.field` / `error.value_received` / `error.valid_values` / `error.did_you_mean` — when the error is parameter-specific - `error.retry_after` — seconds to wait before retrying (rate-limit + transient upstream) `Access-Control-Expose-Headers` lists `x-request-id` and the `X-RateLimit-*` family so browsers can read them on cross-origin responses. ## Quick Start 1. Get an API key at https://repull.dev/dashboard 2. Connect a PMS: `POST /v1/connect/{provider}` 3. List properties: `GET /v1/properties` 4. Get reservations: `GET /v1/reservations` ## Authentication All requests require a Bearer token: ``` Authorization: Bearer sk_live_YOUR_API_KEY ``` ## Request Correlation (X-Request-ID) Every response carries an `X-Request-ID` header, e.g. `X-Request-ID: req_01HXY...`. Include this id in support tickets and bug reports — we can trace the full request lifecycle (auth, rate limit, handler, downstream calls, log row) from a single id. You may set the header on the inbound request to forward your own trace id; we will echo it back instead of generating a new one. Accepted format: `^[\\\\w.-]{1,128}$`. The id is also embedded in error envelopes as `request_id` so server-side log diffs work even when the response headers are stripped by an intermediate proxy. ## Rate Limits The public API enforces a per-API-key sliding-window rate limit on top of the per-tier monthly + daily-AI quotas. **Default policy:** 600 requests per 60 seconds, per API key. Sliding window — there is no fixed-minute boundary you can burst across. Every response includes: | Header | Meaning | |---|---| | `X-RateLimit-Limit` | Requests permitted in the current window. | | `X-RateLimit-Remaining` | Requests left in the current window after this call. | | `X-RateLimit-Reset` | Unix epoch (seconds) when the next slot opens. | | `X-RateLimit-Policy` | Machine-readable policy descriptor, e.g. `600;w=60`. | | `Retry-After` | Seconds to wait before retrying. **Only present on 429 responses.** | **On 429 (rate_limit_exceeded):** the response body matches the standard error envelope with `code: \"rate_limit_exceeded\"`, plus `limit`, `window_seconds`, `retry_after`, and `request_id` fields. SDKs MUST honor `Retry-After` and use exponential backoff with jitter on subsequent retries — never a tight loop. Recommended backoff: ``` sleep_ms = (Retry-After * 1000) + random(0..250) ``` Monthly + daily-AI tier quotas (`free`, `starter`, `custom`) are enforced separately and also surface as 429s; they include `tier`, `scope`, and `resetsAt` fields. ## Plan Limits (402 — `listings_limit_exceeded`) The Repull API also enforces a per-tier cap on **active listings**: | Tier | Active listings cap | |---|---| | `free` | 3 | | `starter` | 50 | | `custom` | unlimited | When a customer's active-listing count is above their tier cap, the API returns **`402 Payment Required`** with `error.code = \"listings_limit_exceeded\"` on every route EXCEPT: - `/v1/health` — uptime probes are never gated. - `/v1/usage/*` — so dashboards can render the over-cap state. - Any `DELETE` — so the customer can trim listings to get back under the cap without paying. Unlike 429, 402 is NOT a \"wait and retry\" condition — `Retry-After` is not set. The only paths back to 200 are: 1. `DELETE` enough listings to come back under the cap, or 2. Upgrade at `https://repull.dev/dashboard/billing`. The server-side usage cache is 60s, so the first 200 after an upgrade may take up to a minute. The envelope mirrors `rate_limit_exceeded` for SDK ergonomics: `tier`, `limit`, `active_listings`, `upgrade_url`, plus the standard `code` / `message` / `fix` / `docs_url` / `request_id`.
5
+
6
+ The version of the OpenAPI document: 1.0.0
7
+ Contact: ivan@vanio.ai
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.22.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module Repull
17
+ class AcceptReservationRequest200Response < ApiModelBase
18
+ attr_accessor :reservation_id
19
+
20
+ attr_accessor :confirmation_code
21
+
22
+ attr_accessor :channel
23
+
24
+ attr_accessor :action
25
+
26
+ # What Airbnb was asked to do and did not refuse. The reservation itself moves when Airbnb’s own notification lands, usually within seconds — that is when `reservation.request.updated` fires (`requestStatus` `accepted` or `declined`), plus `reservation.created` for an accepted request.
27
+ attr_accessor :status
28
+
29
+ attr_accessor :decline_reason
30
+
31
+ class EnumAttributeValidator
32
+ attr_reader :datatype
33
+ attr_reader :allowable_values
34
+
35
+ def initialize(datatype, allowable_values)
36
+ @allowable_values = allowable_values.map do |value|
37
+ case datatype.to_s
38
+ when /Integer/i
39
+ value.to_i
40
+ when /Float/i
41
+ value.to_f
42
+ else
43
+ value
44
+ end
45
+ end
46
+ end
47
+
48
+ def valid?(value)
49
+ !value || allowable_values.include?(value)
50
+ end
51
+ end
52
+
53
+ # Attribute mapping from ruby-style variable name to JSON key.
54
+ def self.attribute_map
55
+ {
56
+ :'reservation_id' => :'reservationId',
57
+ :'confirmation_code' => :'confirmationCode',
58
+ :'channel' => :'channel',
59
+ :'action' => :'action',
60
+ :'status' => :'status',
61
+ :'decline_reason' => :'declineReason'
62
+ }
63
+ end
64
+
65
+ # Returns attribute mapping this model knows about
66
+ def self.acceptable_attribute_map
67
+ attribute_map
68
+ end
69
+
70
+ # Returns all the JSON keys this model knows about
71
+ def self.acceptable_attributes
72
+ acceptable_attribute_map.values
73
+ end
74
+
75
+ # Attribute type mapping.
76
+ def self.openapi_types
77
+ {
78
+ :'reservation_id' => :'String',
79
+ :'confirmation_code' => :'String',
80
+ :'channel' => :'String',
81
+ :'action' => :'String',
82
+ :'status' => :'String',
83
+ :'decline_reason' => :'String'
84
+ }
85
+ end
86
+
87
+ # List of attributes with nullable: true
88
+ def self.openapi_nullable
89
+ Set.new([
90
+ :'decline_reason'
91
+ ])
92
+ end
93
+
94
+ # Initializes the object
95
+ # @param [Hash] attributes Model attributes in the form of hash
96
+ def initialize(attributes = {})
97
+ if (!attributes.is_a?(Hash))
98
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::AcceptReservationRequest200Response` initialize method"
99
+ end
100
+
101
+ # check to see if the attribute exists and convert string to symbol for hash key
102
+ acceptable_attribute_map = self.class.acceptable_attribute_map
103
+ attributes = attributes.each_with_object({}) { |(k, v), h|
104
+ if (!acceptable_attribute_map.key?(k.to_sym))
105
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::AcceptReservationRequest200Response`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
106
+ end
107
+ h[k.to_sym] = v
108
+ }
109
+
110
+ if attributes.key?(:'reservation_id')
111
+ self.reservation_id = attributes[:'reservation_id']
112
+ else
113
+ self.reservation_id = nil
114
+ end
115
+
116
+ if attributes.key?(:'confirmation_code')
117
+ self.confirmation_code = attributes[:'confirmation_code']
118
+ else
119
+ self.confirmation_code = nil
120
+ end
121
+
122
+ if attributes.key?(:'channel')
123
+ self.channel = attributes[:'channel']
124
+ else
125
+ self.channel = nil
126
+ end
127
+
128
+ if attributes.key?(:'action')
129
+ self.action = attributes[:'action']
130
+ else
131
+ self.action = nil
132
+ end
133
+
134
+ if attributes.key?(:'status')
135
+ self.status = attributes[:'status']
136
+ else
137
+ self.status = nil
138
+ end
139
+
140
+ if attributes.key?(:'decline_reason')
141
+ self.decline_reason = attributes[:'decline_reason']
142
+ else
143
+ self.decline_reason = nil
144
+ end
145
+ end
146
+
147
+ # Show invalid properties with the reasons. Usually used together with valid?
148
+ # @return Array for valid properties with the reasons
149
+ def list_invalid_properties
150
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
151
+ invalid_properties = Array.new
152
+ if @reservation_id.nil?
153
+ invalid_properties.push('invalid value for "reservation_id", reservation_id cannot be nil.')
154
+ end
155
+
156
+ if @confirmation_code.nil?
157
+ invalid_properties.push('invalid value for "confirmation_code", confirmation_code cannot be nil.')
158
+ end
159
+
160
+ if @channel.nil?
161
+ invalid_properties.push('invalid value for "channel", channel cannot be nil.')
162
+ end
163
+
164
+ if @action.nil?
165
+ invalid_properties.push('invalid value for "action", action cannot be nil.')
166
+ end
167
+
168
+ if @status.nil?
169
+ invalid_properties.push('invalid value for "status", status cannot be nil.')
170
+ end
171
+
172
+ invalid_properties
173
+ end
174
+
175
+ # Check to see if the all the properties in the model are valid
176
+ # @return true if the model is valid
177
+ def valid?
178
+ warn '[DEPRECATED] the `valid?` method is obsolete'
179
+ return false if @reservation_id.nil?
180
+ return false if @confirmation_code.nil?
181
+ return false if @channel.nil?
182
+ channel_validator = EnumAttributeValidator.new('String', ["airbnb"])
183
+ return false unless channel_validator.valid?(@channel)
184
+ return false if @action.nil?
185
+ action_validator = EnumAttributeValidator.new('String', ["accept", "decline"])
186
+ return false unless action_validator.valid?(@action)
187
+ return false if @status.nil?
188
+ status_validator = EnumAttributeValidator.new('String', ["accepted", "declined"])
189
+ return false unless status_validator.valid?(@status)
190
+ decline_reason_validator = EnumAttributeValidator.new('String', ["dates_not_available", "not_comfortable", "listing_not_ready", "different_dates_needed", "spam", "other"])
191
+ return false unless decline_reason_validator.valid?(@decline_reason)
192
+ true
193
+ end
194
+
195
+ # Custom attribute writer method with validation
196
+ # @param [Object] reservation_id Value to be assigned
197
+ def reservation_id=(reservation_id)
198
+ if reservation_id.nil?
199
+ fail ArgumentError, 'reservation_id cannot be nil'
200
+ end
201
+
202
+ @reservation_id = reservation_id
203
+ end
204
+
205
+ # Custom attribute writer method with validation
206
+ # @param [Object] confirmation_code Value to be assigned
207
+ def confirmation_code=(confirmation_code)
208
+ if confirmation_code.nil?
209
+ fail ArgumentError, 'confirmation_code cannot be nil'
210
+ end
211
+
212
+ @confirmation_code = confirmation_code
213
+ end
214
+
215
+ # Custom attribute writer method checking allowed values (enum).
216
+ # @param [Object] channel Object to be assigned
217
+ def channel=(channel)
218
+ validator = EnumAttributeValidator.new('String', ["airbnb"])
219
+ unless validator.valid?(channel)
220
+ fail ArgumentError, "invalid value for \"channel\", must be one of #{validator.allowable_values}."
221
+ end
222
+ @channel = channel
223
+ end
224
+
225
+ # Custom attribute writer method checking allowed values (enum).
226
+ # @param [Object] action Object to be assigned
227
+ def action=(action)
228
+ validator = EnumAttributeValidator.new('String', ["accept", "decline"])
229
+ unless validator.valid?(action)
230
+ fail ArgumentError, "invalid value for \"action\", must be one of #{validator.allowable_values}."
231
+ end
232
+ @action = action
233
+ end
234
+
235
+ # Custom attribute writer method checking allowed values (enum).
236
+ # @param [Object] status Object to be assigned
237
+ def status=(status)
238
+ validator = EnumAttributeValidator.new('String', ["accepted", "declined"])
239
+ unless validator.valid?(status)
240
+ fail ArgumentError, "invalid value for \"status\", must be one of #{validator.allowable_values}."
241
+ end
242
+ @status = status
243
+ end
244
+
245
+ # Custom attribute writer method checking allowed values (enum).
246
+ # @param [Object] decline_reason Object to be assigned
247
+ def decline_reason=(decline_reason)
248
+ validator = EnumAttributeValidator.new('String', ["dates_not_available", "not_comfortable", "listing_not_ready", "different_dates_needed", "spam", "other"])
249
+ unless validator.valid?(decline_reason)
250
+ fail ArgumentError, "invalid value for \"decline_reason\", must be one of #{validator.allowable_values}."
251
+ end
252
+ @decline_reason = decline_reason
253
+ end
254
+
255
+ # Checks equality by comparing each attribute.
256
+ # @param [Object] Object to be compared
257
+ def ==(o)
258
+ return true if self.equal?(o)
259
+ self.class == o.class &&
260
+ reservation_id == o.reservation_id &&
261
+ confirmation_code == o.confirmation_code &&
262
+ channel == o.channel &&
263
+ action == o.action &&
264
+ status == o.status &&
265
+ decline_reason == o.decline_reason
266
+ end
267
+
268
+ # @see the `==` method
269
+ # @param [Object] Object to be compared
270
+ def eql?(o)
271
+ self == o
272
+ end
273
+
274
+ # Calculates hash code according to all attributes.
275
+ # @return [Integer] Hash code
276
+ def hash
277
+ [reservation_id, confirmation_code, channel, action, status, decline_reason].hash
278
+ end
279
+
280
+ # Builds the object from hash
281
+ # @param [Hash] attributes Model attributes in the form of hash
282
+ # @return [Object] Returns the model itself
283
+ def self.build_from_hash(attributes)
284
+ return nil unless attributes.is_a?(Hash)
285
+ attributes = attributes.transform_keys(&:to_sym)
286
+ transformed_hash = {}
287
+ openapi_types.each_pair do |key, type|
288
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
289
+ transformed_hash["#{key}"] = nil
290
+ elsif type =~ /\AArray<(.*)>/i
291
+ # check to ensure the input is an array given that the attribute
292
+ # is documented as an array but the input is not
293
+ if attributes[attribute_map[key]].is_a?(Array)
294
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
295
+ end
296
+ elsif !attributes[attribute_map[key]].nil?
297
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
298
+ end
299
+ end
300
+ new(transformed_hash)
301
+ end
302
+
303
+ # Returns the object in the form of hash
304
+ # @return [Hash] Returns the object in the form of hash
305
+ def to_hash
306
+ hash = {}
307
+ self.class.attribute_map.each_pair do |attr, param|
308
+ value = self.send(attr)
309
+ if value.nil?
310
+ is_nullable = self.class.openapi_nullable.include?(attr)
311
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
312
+ end
313
+
314
+ hash[param] = _to_hash(value)
315
+ end
316
+ hash
317
+ end
318
+
319
+ end
320
+
321
+ end