repull 0.2.14 → 0.2.15

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/airbnb_api.rb +1265 -125
  3. data/lib/repull/api/availability_api.rb +2 -2
  4. data/lib/repull/api/connect_api.rb +4 -4
  5. data/lib/repull/api/listings_api.rb +97 -13
  6. data/lib/repull/models/airbnb_account_freshness.rb +239 -0
  7. data/lib/repull/models/airbnb_alteration.rb +46 -2
  8. data/lib/repull/models/airbnb_alteration_create_request.rb +264 -0
  9. data/lib/repull/models/airbnb_connection.rb +69 -2
  10. data/lib/repull/models/airbnb_connection_host.rb +1 -1
  11. data/lib/repull/models/airbnb_content_write_response.rb +275 -0
  12. data/lib/repull/models/airbnb_data_freshness.rb +22 -10
  13. data/lib/repull/models/{create_airbnb_alteration_request.rb → airbnb_description_write_request.rb} +44 -47
  14. data/lib/repull/models/airbnb_description_write_request_description.rb +365 -0
  15. data/lib/repull/models/airbnb_listing.rb +12 -1
  16. data/lib/repull/models/airbnb_listing_action200_response.rb +105 -0
  17. data/lib/repull/models/airbnb_listing_action200_response_one_of.rb +157 -0
  18. data/lib/repull/models/airbnb_listing_action200_response_one_of1.rb +175 -0
  19. data/lib/repull/models/airbnb_listing_action_request.rb +2 -2
  20. data/lib/repull/models/airbnb_listing_details_response.rb +284 -0
  21. data/lib/repull/models/airbnb_listing_details_write_request.rb +209 -0
  22. data/lib/repull/models/airbnb_listing_details_write_request_check_in_option.rb +214 -0
  23. data/lib/repull/models/airbnb_listing_details_write_request_quiet_hours_inner.rb +190 -0
  24. data/lib/repull/models/airbnb_listing_lifecycle_response.rb +196 -0
  25. data/lib/repull/models/airbnb_permits_response.rb +163 -0
  26. data/lib/repull/models/airbnb_permits_response_cached_inner.rb +200 -0
  27. data/lib/repull/models/airbnb_permits_write_request.rb +176 -0
  28. data/lib/repull/models/airbnb_permits_write_request_permits_inner.rb +228 -0
  29. data/lib/repull/models/airbnb_permits_write_request_permits_inner_answers_inner.rb +198 -0
  30. data/lib/repull/models/airbnb_photo_position.rb +159 -0
  31. data/lib/repull/models/airbnb_publish_result.rb +263 -0
  32. data/lib/repull/models/airbnb_reservation.rb +23 -1
  33. data/lib/repull/models/airbnb_reservation_list_response.rb +13 -4
  34. data/lib/repull/models/airbnb_review.rb +23 -1
  35. data/lib/repull/models/airbnb_review_list_response.rb +13 -4
  36. data/lib/repull/models/airbnb_safety_disclosure.rb +202 -0
  37. data/lib/repull/models/airbnb_safety_disclosures_response.rb +162 -0
  38. data/lib/repull/models/airbnb_safety_disclosures_write_request.rb +176 -0
  39. data/lib/repull/models/airbnb_thread.rb +23 -1
  40. data/lib/repull/models/airbnb_thread_list_response.rb +13 -4
  41. data/lib/repull/models/airbnb_transaction.rb +23 -1
  42. data/lib/repull/models/create_airbnb_listing_room_request.rb +315 -0
  43. data/lib/repull/models/delete_airbnb_listing_photo200_response.rb +14 -4
  44. data/lib/repull/models/delete_airbnb_listing_room200_response.rb +157 -0
  45. data/lib/repull/models/error_error.rb +31 -1
  46. data/lib/repull/models/get_airbnb_booking_settings200_response.rb +190 -0
  47. data/lib/repull/models/get_airbnb_booking_settings200_response_data.rb +246 -0
  48. data/lib/repull/models/get_airbnb_booking_settings200_response_data_advance_notice.rb +172 -0
  49. data/lib/repull/models/get_airbnb_booking_settings200_response_data_booking_window.rb +160 -0
  50. data/lib/repull/models/get_airbnb_booking_settings200_response_data_cancellation.rb +203 -0
  51. data/lib/repull/models/get_airbnb_booking_settings200_response_data_cancellation_non_refundable.rb +218 -0
  52. data/lib/repull/models/get_airbnb_booking_settings200_response_data_check_in.rb +159 -0
  53. data/lib/repull/models/get_airbnb_booking_settings200_response_data_check_in_start.rb +104 -0
  54. data/lib/repull/models/get_airbnb_booking_settings200_response_data_check_out.rb +172 -0
  55. data/lib/repull/models/get_airbnb_booking_settings200_response_data_instant_book.rb +206 -0
  56. data/lib/repull/models/get_airbnb_booking_settings200_response_data_preparation_time.rb +173 -0
  57. data/lib/repull/models/get_airbnb_listing_details200_response.rb +192 -0
  58. data/lib/repull/models/list_airbnb_listing_permits200_response.rb +190 -0
  59. data/lib/repull/models/list_airbnb_listing_safety_disclosures200_response.rb +190 -0
  60. data/lib/repull/models/listing.rb +2 -1
  61. data/lib/repull/models/listing_content.rb +13 -2
  62. data/lib/repull/models/listing_content_update_request.rb +21 -2
  63. data/lib/repull/models/listing_content_update_request_details.rb +206 -0
  64. data/lib/repull/models/listing_content_update_request_policies.rb +95 -5
  65. data/lib/repull/models/listing_content_update_response.rb +1 -1
  66. data/lib/repull/models/listing_publish_airbnb_response.rb +165 -0
  67. data/lib/repull/models/listing_pull_airbnb_request.rb +149 -0
  68. data/lib/repull/models/listing_pull_response.rb +231 -0
  69. data/lib/repull/models/publish_section_error.rb +232 -0
  70. data/lib/repull/models/reorder_airbnb_listing_photos200_response.rb +191 -0
  71. data/lib/repull/models/reorder_airbnb_listing_photos200_response_data.rb +172 -0
  72. data/lib/repull/models/reorder_airbnb_listing_photos_request.rb +185 -0
  73. data/lib/repull/models/reservation.rb +23 -1
  74. data/lib/repull/models/reservation_webhook_object.rb +38 -5
  75. data/lib/repull/models/set_airbnb_listing_cover_photo200_response.rb +191 -0
  76. data/lib/repull/models/set_airbnb_listing_cover_photo200_response_data.rb +171 -0
  77. data/lib/repull/models/set_airbnb_listing_cover_photo_request.rb +165 -0
  78. data/lib/repull/models/update_airbnb_booking_settings200_response.rb +164 -0
  79. data/lib/repull/models/update_airbnb_booking_settings200_response_data.rb +205 -0
  80. data/lib/repull/models/update_airbnb_booking_settings_request.rb +202 -0
  81. data/lib/repull/models/update_airbnb_booking_settings_request_advance_notice.rb +183 -0
  82. data/lib/repull/models/update_airbnb_booking_settings_request_booking_window.rb +183 -0
  83. data/lib/repull/models/update_airbnb_booking_settings_request_cancellation.rb +201 -0
  84. data/lib/repull/models/update_airbnb_booking_settings_request_cancellation_non_refundable.rb +183 -0
  85. data/lib/repull/models/update_airbnb_booking_settings_request_check_in.rb +159 -0
  86. data/lib/repull/models/update_airbnb_booking_settings_request_check_out.rb +182 -0
  87. data/lib/repull/models/update_airbnb_booking_settings_request_instant_book.rb +203 -0
  88. data/lib/repull/models/update_airbnb_booking_settings_request_preparation_time.rb +182 -0
  89. data/lib/repull/models/update_airbnb_listing_amenities200_response.rb +191 -0
  90. data/lib/repull/models/update_airbnb_listing_amenities200_response_data.rb +158 -0
  91. data/lib/repull/models/update_airbnb_listing_amenities_request.rb +199 -0
  92. data/lib/repull/models/update_airbnb_listing_amenities_request_accessibility_amenities_inner.rb +246 -0
  93. data/lib/repull/models/update_airbnb_listing_amenities_request_amenities_inner.rb +218 -0
  94. data/lib/repull/models/update_airbnb_listing_permits200_response.rb +168 -0
  95. data/lib/repull/models/update_airbnb_listing_photo200_response.rb +194 -0
  96. data/lib/repull/models/update_airbnb_listing_photo_request.rb +242 -0
  97. data/lib/repull/models/update_airbnb_listing_room200_response.rb +194 -0
  98. data/lib/repull/models/update_airbnb_listing_room_request.rb +293 -0
  99. data/lib/repull/models/update_airbnb_listing_room_request_beds_inner.rb +209 -0
  100. data/lib/repull/models/update_airbnb_listing_room_request_room_amenities_inner.rb +182 -0
  101. data/lib/repull/models/update_airbnb_listing_room_request_room_amenities_inner_value.rb +105 -0
  102. data/lib/repull/models/update_airbnb_listing_safety_disclosures200_response.rb +180 -0
  103. data/lib/repull/models/upload_airbnb_listing_photos_request.rb +184 -0
  104. data/lib/repull/models/upload_airbnb_listing_photos_request_photos_inner.rb +305 -0
  105. data/lib/repull/models/upload_airbnb_listing_photos_request_photos_inner_listing_id.rb +105 -0
  106. data/lib/repull/version.rb +1 -1
  107. data/lib/repull.rb +78 -1
  108. data/openapi/v1.json +4523 -1282
  109. data/scripts/regen.sh +1 -1
  110. metadata +80 -3
@@ -0,0 +1,203 @@
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
+ # Send `enabled`, `guestCategory`, or `enabled` + `requiresGoodTrackRecord`. `requiresGoodTrackRecord` alone is refused — it selects WHICH guests may Instant Book, so it needs `enabled: true` (or an explicit category) with it. Contradictory combinations are refused.
18
+ class UpdateAirbnbBookingSettingsRequestInstantBook < ApiModelBase
19
+ attr_accessor :enabled
20
+
21
+ attr_accessor :guest_category
22
+
23
+ attr_accessor :requires_good_track_record
24
+
25
+ class EnumAttributeValidator
26
+ attr_reader :datatype
27
+ attr_reader :allowable_values
28
+
29
+ def initialize(datatype, allowable_values)
30
+ @allowable_values = allowable_values.map do |value|
31
+ case datatype.to_s
32
+ when /Integer/i
33
+ value.to_i
34
+ when /Float/i
35
+ value.to_f
36
+ else
37
+ value
38
+ end
39
+ end
40
+ end
41
+
42
+ def valid?(value)
43
+ !value || allowable_values.include?(value)
44
+ end
45
+ end
46
+
47
+ # Attribute mapping from ruby-style variable name to JSON key.
48
+ def self.attribute_map
49
+ {
50
+ :'enabled' => :'enabled',
51
+ :'guest_category' => :'guestCategory',
52
+ :'requires_good_track_record' => :'requiresGoodTrackRecord'
53
+ }
54
+ end
55
+
56
+ # Returns attribute mapping this model knows about
57
+ def self.acceptable_attribute_map
58
+ attribute_map
59
+ end
60
+
61
+ # Returns all the JSON keys this model knows about
62
+ def self.acceptable_attributes
63
+ acceptable_attribute_map.values
64
+ end
65
+
66
+ # Attribute type mapping.
67
+ def self.openapi_types
68
+ {
69
+ :'enabled' => :'Boolean',
70
+ :'guest_category' => :'String',
71
+ :'requires_good_track_record' => :'Boolean'
72
+ }
73
+ end
74
+
75
+ # List of attributes with nullable: true
76
+ def self.openapi_nullable
77
+ Set.new([
78
+ :'enabled',
79
+ :'guest_category',
80
+ :'requires_good_track_record'
81
+ ])
82
+ end
83
+
84
+ # Initializes the object
85
+ # @param [Hash] attributes Model attributes in the form of hash
86
+ def initialize(attributes = {})
87
+ if (!attributes.is_a?(Hash))
88
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::UpdateAirbnbBookingSettingsRequestInstantBook` initialize method"
89
+ end
90
+
91
+ # check to see if the attribute exists and convert string to symbol for hash key
92
+ acceptable_attribute_map = self.class.acceptable_attribute_map
93
+ attributes = attributes.each_with_object({}) { |(k, v), h|
94
+ if (!acceptable_attribute_map.key?(k.to_sym))
95
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::UpdateAirbnbBookingSettingsRequestInstantBook`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
96
+ end
97
+ h[k.to_sym] = v
98
+ }
99
+
100
+ if attributes.key?(:'enabled')
101
+ self.enabled = attributes[:'enabled']
102
+ end
103
+
104
+ if attributes.key?(:'guest_category')
105
+ self.guest_category = attributes[:'guest_category']
106
+ end
107
+
108
+ if attributes.key?(:'requires_good_track_record')
109
+ self.requires_good_track_record = attributes[:'requires_good_track_record']
110
+ end
111
+ end
112
+
113
+ # Show invalid properties with the reasons. Usually used together with valid?
114
+ # @return Array for valid properties with the reasons
115
+ def list_invalid_properties
116
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
117
+ invalid_properties = Array.new
118
+ invalid_properties
119
+ end
120
+
121
+ # Check to see if the all the properties in the model are valid
122
+ # @return true if the model is valid
123
+ def valid?
124
+ warn '[DEPRECATED] the `valid?` method is obsolete'
125
+ guest_category_validator = EnumAttributeValidator.new('String', ["everyone", "experienced_guests_only", "recommended_guests_only", "off"])
126
+ return false unless guest_category_validator.valid?(@guest_category)
127
+ true
128
+ end
129
+
130
+ # Custom attribute writer method checking allowed values (enum).
131
+ # @param [Object] guest_category Object to be assigned
132
+ def guest_category=(guest_category)
133
+ validator = EnumAttributeValidator.new('String', ["everyone", "experienced_guests_only", "recommended_guests_only", "off"])
134
+ unless validator.valid?(guest_category)
135
+ fail ArgumentError, "invalid value for \"guest_category\", must be one of #{validator.allowable_values}."
136
+ end
137
+ @guest_category = guest_category
138
+ end
139
+
140
+ # Checks equality by comparing each attribute.
141
+ # @param [Object] Object to be compared
142
+ def ==(o)
143
+ return true if self.equal?(o)
144
+ self.class == o.class &&
145
+ enabled == o.enabled &&
146
+ guest_category == o.guest_category &&
147
+ requires_good_track_record == o.requires_good_track_record
148
+ end
149
+
150
+ # @see the `==` method
151
+ # @param [Object] Object to be compared
152
+ def eql?(o)
153
+ self == o
154
+ end
155
+
156
+ # Calculates hash code according to all attributes.
157
+ # @return [Integer] Hash code
158
+ def hash
159
+ [enabled, guest_category, requires_good_track_record].hash
160
+ end
161
+
162
+ # Builds the object from hash
163
+ # @param [Hash] attributes Model attributes in the form of hash
164
+ # @return [Object] Returns the model itself
165
+ def self.build_from_hash(attributes)
166
+ return nil unless attributes.is_a?(Hash)
167
+ attributes = attributes.transform_keys(&:to_sym)
168
+ transformed_hash = {}
169
+ openapi_types.each_pair do |key, type|
170
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
171
+ transformed_hash["#{key}"] = nil
172
+ elsif type =~ /\AArray<(.*)>/i
173
+ # check to ensure the input is an array given that the attribute
174
+ # is documented as an array but the input is not
175
+ if attributes[attribute_map[key]].is_a?(Array)
176
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
177
+ end
178
+ elsif !attributes[attribute_map[key]].nil?
179
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
180
+ end
181
+ end
182
+ new(transformed_hash)
183
+ end
184
+
185
+ # Returns the object in the form of hash
186
+ # @return [Hash] Returns the object in the form of hash
187
+ def to_hash
188
+ hash = {}
189
+ self.class.attribute_map.each_pair do |attr, param|
190
+ value = self.send(attr)
191
+ if value.nil?
192
+ is_nullable = self.class.openapi_nullable.include?(attr)
193
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
194
+ end
195
+
196
+ hash[param] = _to_hash(value)
197
+ end
198
+ hash
199
+ end
200
+
201
+ end
202
+
203
+ end
@@ -0,0 +1,182 @@
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 UpdateAirbnbBookingSettingsRequestPreparationTime < ApiModelBase
18
+ attr_accessor :nights
19
+
20
+ # Attribute mapping from ruby-style variable name to JSON key.
21
+ def self.attribute_map
22
+ {
23
+ :'nights' => :'nights'
24
+ }
25
+ end
26
+
27
+ # Returns attribute mapping this model knows about
28
+ def self.acceptable_attribute_map
29
+ attribute_map
30
+ end
31
+
32
+ # Returns all the JSON keys this model knows about
33
+ def self.acceptable_attributes
34
+ acceptable_attribute_map.values
35
+ end
36
+
37
+ # Attribute type mapping.
38
+ def self.openapi_types
39
+ {
40
+ :'nights' => :'Integer'
41
+ }
42
+ end
43
+
44
+ # List of attributes with nullable: true
45
+ def self.openapi_nullable
46
+ Set.new([
47
+ ])
48
+ end
49
+
50
+ # Initializes the object
51
+ # @param [Hash] attributes Model attributes in the form of hash
52
+ def initialize(attributes = {})
53
+ if (!attributes.is_a?(Hash))
54
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::UpdateAirbnbBookingSettingsRequestPreparationTime` initialize method"
55
+ end
56
+
57
+ # check to see if the attribute exists and convert string to symbol for hash key
58
+ acceptable_attribute_map = self.class.acceptable_attribute_map
59
+ attributes = attributes.each_with_object({}) { |(k, v), h|
60
+ if (!acceptable_attribute_map.key?(k.to_sym))
61
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::UpdateAirbnbBookingSettingsRequestPreparationTime`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
62
+ end
63
+ h[k.to_sym] = v
64
+ }
65
+
66
+ if attributes.key?(:'nights')
67
+ self.nights = attributes[:'nights']
68
+ else
69
+ self.nights = nil
70
+ end
71
+ end
72
+
73
+ # Show invalid properties with the reasons. Usually used together with valid?
74
+ # @return Array for valid properties with the reasons
75
+ def list_invalid_properties
76
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
77
+ invalid_properties = Array.new
78
+ if @nights.nil?
79
+ invalid_properties.push('invalid value for "nights", nights cannot be nil.')
80
+ end
81
+
82
+ if @nights > 2
83
+ invalid_properties.push('invalid value for "nights", must be smaller than or equal to 2.')
84
+ end
85
+
86
+ if @nights < 0
87
+ invalid_properties.push('invalid value for "nights", must be greater than or equal to 0.')
88
+ end
89
+
90
+ invalid_properties
91
+ end
92
+
93
+ # Check to see if the all the properties in the model are valid
94
+ # @return true if the model is valid
95
+ def valid?
96
+ warn '[DEPRECATED] the `valid?` method is obsolete'
97
+ return false if @nights.nil?
98
+ return false if @nights > 2
99
+ return false if @nights < 0
100
+ true
101
+ end
102
+
103
+ # Custom attribute writer method with validation
104
+ # @param [Object] nights Value to be assigned
105
+ def nights=(nights)
106
+ if nights.nil?
107
+ fail ArgumentError, 'nights cannot be nil'
108
+ end
109
+
110
+ if nights > 2
111
+ fail ArgumentError, 'invalid value for "nights", must be smaller than or equal to 2.'
112
+ end
113
+
114
+ if nights < 0
115
+ fail ArgumentError, 'invalid value for "nights", must be greater than or equal to 0.'
116
+ end
117
+
118
+ @nights = nights
119
+ end
120
+
121
+ # Checks equality by comparing each attribute.
122
+ # @param [Object] Object to be compared
123
+ def ==(o)
124
+ return true if self.equal?(o)
125
+ self.class == o.class &&
126
+ nights == o.nights
127
+ end
128
+
129
+ # @see the `==` method
130
+ # @param [Object] Object to be compared
131
+ def eql?(o)
132
+ self == o
133
+ end
134
+
135
+ # Calculates hash code according to all attributes.
136
+ # @return [Integer] Hash code
137
+ def hash
138
+ [nights].hash
139
+ end
140
+
141
+ # Builds the object from hash
142
+ # @param [Hash] attributes Model attributes in the form of hash
143
+ # @return [Object] Returns the model itself
144
+ def self.build_from_hash(attributes)
145
+ return nil unless attributes.is_a?(Hash)
146
+ attributes = attributes.transform_keys(&:to_sym)
147
+ transformed_hash = {}
148
+ openapi_types.each_pair do |key, type|
149
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
150
+ transformed_hash["#{key}"] = nil
151
+ elsif type =~ /\AArray<(.*)>/i
152
+ # check to ensure the input is an array given that the attribute
153
+ # is documented as an array but the input is not
154
+ if attributes[attribute_map[key]].is_a?(Array)
155
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
156
+ end
157
+ elsif !attributes[attribute_map[key]].nil?
158
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
159
+ end
160
+ end
161
+ new(transformed_hash)
162
+ end
163
+
164
+ # Returns the object in the form of hash
165
+ # @return [Hash] Returns the object in the form of hash
166
+ def to_hash
167
+ hash = {}
168
+ self.class.attribute_map.each_pair do |attr, param|
169
+ value = self.send(attr)
170
+ if value.nil?
171
+ is_nullable = self.class.openapi_nullable.include?(attr)
172
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
173
+ end
174
+
175
+ hash[param] = _to_hash(value)
176
+ end
177
+ hash
178
+ end
179
+
180
+ end
181
+
182
+ end
@@ -0,0 +1,191 @@
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 UpdateAirbnbListingAmenities200Response < ApiModelBase
18
+ attr_accessor :data
19
+
20
+ # Whether our own copy was brought in line with the change.
21
+ attr_accessor :stored
22
+
23
+ # Attribute mapping from ruby-style variable name to JSON key.
24
+ def self.attribute_map
25
+ {
26
+ :'data' => :'data',
27
+ :'stored' => :'stored'
28
+ }
29
+ end
30
+
31
+ # Returns attribute mapping this model knows about
32
+ def self.acceptable_attribute_map
33
+ attribute_map
34
+ end
35
+
36
+ # Returns all the JSON keys this model knows about
37
+ def self.acceptable_attributes
38
+ acceptable_attribute_map.values
39
+ end
40
+
41
+ # Attribute type mapping.
42
+ def self.openapi_types
43
+ {
44
+ :'data' => :'UpdateAirbnbListingAmenities200ResponseData',
45
+ :'stored' => :'Boolean'
46
+ }
47
+ end
48
+
49
+ # List of attributes with nullable: true
50
+ def self.openapi_nullable
51
+ Set.new([
52
+ ])
53
+ end
54
+
55
+ # Initializes the object
56
+ # @param [Hash] attributes Model attributes in the form of hash
57
+ def initialize(attributes = {})
58
+ if (!attributes.is_a?(Hash))
59
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::UpdateAirbnbListingAmenities200Response` initialize method"
60
+ end
61
+
62
+ # check to see if the attribute exists and convert string to symbol for hash key
63
+ acceptable_attribute_map = self.class.acceptable_attribute_map
64
+ attributes = attributes.each_with_object({}) { |(k, v), h|
65
+ if (!acceptable_attribute_map.key?(k.to_sym))
66
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::UpdateAirbnbListingAmenities200Response`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
67
+ end
68
+ h[k.to_sym] = v
69
+ }
70
+
71
+ if attributes.key?(:'data')
72
+ self.data = attributes[:'data']
73
+ else
74
+ self.data = nil
75
+ end
76
+
77
+ if attributes.key?(:'stored')
78
+ self.stored = attributes[:'stored']
79
+ else
80
+ self.stored = nil
81
+ end
82
+ end
83
+
84
+ # Show invalid properties with the reasons. Usually used together with valid?
85
+ # @return Array for valid properties with the reasons
86
+ def list_invalid_properties
87
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
88
+ invalid_properties = Array.new
89
+ if @data.nil?
90
+ invalid_properties.push('invalid value for "data", data cannot be nil.')
91
+ end
92
+
93
+ if @stored.nil?
94
+ invalid_properties.push('invalid value for "stored", stored cannot be nil.')
95
+ end
96
+
97
+ invalid_properties
98
+ end
99
+
100
+ # Check to see if the all the properties in the model are valid
101
+ # @return true if the model is valid
102
+ def valid?
103
+ warn '[DEPRECATED] the `valid?` method is obsolete'
104
+ return false if @data.nil?
105
+ return false if @stored.nil?
106
+ true
107
+ end
108
+
109
+ # Custom attribute writer method with validation
110
+ # @param [Object] data Value to be assigned
111
+ def data=(data)
112
+ if data.nil?
113
+ fail ArgumentError, 'data cannot be nil'
114
+ end
115
+
116
+ @data = data
117
+ end
118
+
119
+ # Custom attribute writer method with validation
120
+ # @param [Object] stored Value to be assigned
121
+ def stored=(stored)
122
+ if stored.nil?
123
+ fail ArgumentError, 'stored cannot be nil'
124
+ end
125
+
126
+ @stored = stored
127
+ end
128
+
129
+ # Checks equality by comparing each attribute.
130
+ # @param [Object] Object to be compared
131
+ def ==(o)
132
+ return true if self.equal?(o)
133
+ self.class == o.class &&
134
+ data == o.data &&
135
+ stored == o.stored
136
+ end
137
+
138
+ # @see the `==` method
139
+ # @param [Object] Object to be compared
140
+ def eql?(o)
141
+ self == o
142
+ end
143
+
144
+ # Calculates hash code according to all attributes.
145
+ # @return [Integer] Hash code
146
+ def hash
147
+ [data, stored].hash
148
+ end
149
+
150
+ # Builds the object from hash
151
+ # @param [Hash] attributes Model attributes in the form of hash
152
+ # @return [Object] Returns the model itself
153
+ def self.build_from_hash(attributes)
154
+ return nil unless attributes.is_a?(Hash)
155
+ attributes = attributes.transform_keys(&:to_sym)
156
+ transformed_hash = {}
157
+ openapi_types.each_pair do |key, type|
158
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
159
+ transformed_hash["#{key}"] = nil
160
+ elsif type =~ /\AArray<(.*)>/i
161
+ # check to ensure the input is an array given that the attribute
162
+ # is documented as an array but the input is not
163
+ if attributes[attribute_map[key]].is_a?(Array)
164
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
165
+ end
166
+ elsif !attributes[attribute_map[key]].nil?
167
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
168
+ end
169
+ end
170
+ new(transformed_hash)
171
+ end
172
+
173
+ # Returns the object in the form of hash
174
+ # @return [Hash] Returns the object in the form of hash
175
+ def to_hash
176
+ hash = {}
177
+ self.class.attribute_map.each_pair do |attr, param|
178
+ value = self.send(attr)
179
+ if value.nil?
180
+ is_nullable = self.class.openapi_nullable.include?(attr)
181
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
182
+ end
183
+
184
+ hash[param] = _to_hash(value)
185
+ end
186
+ hash
187
+ end
188
+
189
+ end
190
+
191
+ end