repull 0.2.16 → 0.2.17

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/airbnb_api.rb +6 -6
  3. data/lib/repull/api/booking_com_api.rb +86 -2
  4. data/lib/repull/api/conversations_api.rb +8 -8
  5. data/lib/repull/api/listings_api.rb +182 -12
  6. data/lib/repull/api/reservations_api.rb +4 -4
  7. data/lib/repull/models/airbnb_publish_result.rb +41 -2
  8. data/lib/repull/models/booking_property_action_request.rb +176 -0
  9. data/lib/repull/models/booking_property_action_response.rb +281 -0
  10. data/lib/repull/models/booking_publish_result.rb +245 -0
  11. data/lib/repull/models/booking_publish_section_error.rb +220 -0
  12. data/lib/repull/models/booking_setup_request.rb +44 -5
  13. data/lib/repull/models/booking_setup_request_legal_entity.rb +235 -0
  14. data/lib/repull/models/channel_market_state_item.rb +291 -0
  15. data/lib/repull/models/error_error.rb +11 -1
  16. data/lib/repull/models/inquiry_updated_payload.rb +1 -1
  17. data/lib/repull/models/list_inquiries200_response_data_inner.rb +1 -1
  18. data/lib/repull/models/listing_address_readiness.rb +171 -0
  19. data/lib/repull/models/listing_content_update_request_address.rb +40 -3
  20. data/lib/repull/models/listing_create_request.rb +48 -2
  21. data/lib/repull/models/listing_market_state_request.rb +149 -0
  22. data/lib/repull/models/listing_market_state_response.rb +221 -0
  23. data/lib/repull/models/listing_publish_booking_request.rb +149 -0
  24. data/lib/repull/models/{listing_publish_response.rb → listing_publish_booking_response.rb} +4 -5
  25. data/lib/repull/models/listing_publish_status_channel.rb +32 -1
  26. data/lib/repull/models/listing_publish_status_connection.rb +17 -5
  27. data/lib/repull/models/listing_publish_status_response.rb +13 -1
  28. data/lib/repull/models/reservation_request_updated_payload.rb +1 -1
  29. data/lib/repull/version.rb +1 -1
  30. data/lib/repull.rb +11 -1
  31. data/openapi/v1.json +855 -72
  32. data/scripts/regen.sh +1 -1
  33. metadata +13 -3
@@ -0,0 +1,171 @@
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
+ # Whether one channel would accept this listing's postal address, answered WITHOUT attempting a publish.
18
+ class ListingAddressReadiness < ApiModelBase
19
+ # True when the address satisfies this channel's create preflight. False means a publish would be refused for the address alone.
20
+ attr_accessor :ready
21
+
22
+ # The address parts still needed, named as the REQUEST fields you send — `street`, `city`, `state`, `postalCode` — so the value can be acted on directly. Empty when `ready` is true.
23
+ attr_accessor :missing
24
+
25
+ # The address as currently resolved, for debugging.
26
+ attr_accessor :have
27
+
28
+ # Attribute mapping from ruby-style variable name to JSON key.
29
+ def self.attribute_map
30
+ {
31
+ :'ready' => :'ready',
32
+ :'missing' => :'missing',
33
+ :'have' => :'have'
34
+ }
35
+ end
36
+
37
+ # Returns attribute mapping this model knows about
38
+ def self.acceptable_attribute_map
39
+ attribute_map
40
+ end
41
+
42
+ # Returns all the JSON keys this model knows about
43
+ def self.acceptable_attributes
44
+ acceptable_attribute_map.values
45
+ end
46
+
47
+ # Attribute type mapping.
48
+ def self.openapi_types
49
+ {
50
+ :'ready' => :'Boolean',
51
+ :'missing' => :'Array<String>',
52
+ :'have' => :'String'
53
+ }
54
+ end
55
+
56
+ # List of attributes with nullable: true
57
+ def self.openapi_nullable
58
+ Set.new([
59
+ ])
60
+ end
61
+
62
+ # Initializes the object
63
+ # @param [Hash] attributes Model attributes in the form of hash
64
+ def initialize(attributes = {})
65
+ if (!attributes.is_a?(Hash))
66
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::ListingAddressReadiness` initialize method"
67
+ end
68
+
69
+ # check to see if the attribute exists and convert string to symbol for hash key
70
+ acceptable_attribute_map = self.class.acceptable_attribute_map
71
+ attributes = attributes.each_with_object({}) { |(k, v), h|
72
+ if (!acceptable_attribute_map.key?(k.to_sym))
73
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::ListingAddressReadiness`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
74
+ end
75
+ h[k.to_sym] = v
76
+ }
77
+
78
+ if attributes.key?(:'ready')
79
+ self.ready = attributes[:'ready']
80
+ end
81
+
82
+ if attributes.key?(:'missing')
83
+ if (value = attributes[:'missing']).is_a?(Array)
84
+ self.missing = value
85
+ end
86
+ end
87
+
88
+ if attributes.key?(:'have')
89
+ self.have = attributes[:'have']
90
+ end
91
+ end
92
+
93
+ # Show invalid properties with the reasons. Usually used together with valid?
94
+ # @return Array for valid properties with the reasons
95
+ def list_invalid_properties
96
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
97
+ invalid_properties = Array.new
98
+ invalid_properties
99
+ end
100
+
101
+ # Check to see if the all the properties in the model are valid
102
+ # @return true if the model is valid
103
+ def valid?
104
+ warn '[DEPRECATED] the `valid?` method is obsolete'
105
+ true
106
+ end
107
+
108
+ # Checks equality by comparing each attribute.
109
+ # @param [Object] Object to be compared
110
+ def ==(o)
111
+ return true if self.equal?(o)
112
+ self.class == o.class &&
113
+ ready == o.ready &&
114
+ missing == o.missing &&
115
+ have == o.have
116
+ end
117
+
118
+ # @see the `==` method
119
+ # @param [Object] Object to be compared
120
+ def eql?(o)
121
+ self == o
122
+ end
123
+
124
+ # Calculates hash code according to all attributes.
125
+ # @return [Integer] Hash code
126
+ def hash
127
+ [ready, missing, have].hash
128
+ end
129
+
130
+ # Builds the object from hash
131
+ # @param [Hash] attributes Model attributes in the form of hash
132
+ # @return [Object] Returns the model itself
133
+ def self.build_from_hash(attributes)
134
+ return nil unless attributes.is_a?(Hash)
135
+ attributes = attributes.transform_keys(&:to_sym)
136
+ transformed_hash = {}
137
+ openapi_types.each_pair do |key, type|
138
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
139
+ transformed_hash["#{key}"] = nil
140
+ elsif type =~ /\AArray<(.*)>/i
141
+ # check to ensure the input is an array given that the attribute
142
+ # is documented as an array but the input is not
143
+ if attributes[attribute_map[key]].is_a?(Array)
144
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
145
+ end
146
+ elsif !attributes[attribute_map[key]].nil?
147
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
148
+ end
149
+ end
150
+ new(transformed_hash)
151
+ end
152
+
153
+ # Returns the object in the form of hash
154
+ # @return [Hash] Returns the object in the form of hash
155
+ def to_hash
156
+ hash = {}
157
+ self.class.attribute_map.each_pair do |attr, param|
158
+ value = self.send(attr)
159
+ if value.nil?
160
+ is_nullable = self.class.openapi_nullable.include?(attr)
161
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
162
+ end
163
+
164
+ hash[param] = _to_hash(value)
165
+ end
166
+ hash
167
+ end
168
+
169
+ end
170
+
171
+ end
@@ -14,17 +14,30 @@ require 'date'
14
14
  require 'time'
15
15
 
16
16
  module Repull
17
- # Partial address. Only provided sub-fields are written.
17
+ # Partial address. Only provided sub-fields are written; the ones you omit keep their current value, and an explicit `null` clears one. This is also the repair path for a listing that cannot be published: Airbnb requires `street` and `city` for every country and additionally `state` and `postalCode` for a **US** property — and a listing with no `countryCode` behaves as US. Send just the missing part, e.g. `{ \"address\": { \"state\": \"FL\" } }`. `GET /v1/listings/{id}/publish-status` names what is missing.
18
18
  class ListingContentUpdateRequestAddress < ApiModelBase
19
+ # Street address including the number. Required by Airbnb for every country.
19
20
  attr_accessor :street
20
21
 
22
+ # City / town. Required by Airbnb for every country.
21
23
  attr_accessor :city
22
24
 
23
- # ISO-3166 alpha-2 country code.
25
+ # State, province or region. **Required for a US property**, and a listing with no `countryCode` counts as US.
26
+ attr_accessor :state
27
+
28
+ # Postal code — ZIP in the US, postcode in the UK, and so on. **Required for a US property**, and a listing with no `countryCode` counts as US. Send the complete code; a partial postcode is rejected downstream. Alias: `zipcode`.
29
+ attr_accessor :postal_code
30
+
31
+ # Alias for `postalCode`, accepted because it is the field name on the Airbnb mirror. `postalCode` wins if you send both.
32
+ attr_accessor :zipcode
33
+
34
+ # ISO-3166 alpha-2 country code. **Send this for any non-US property.** Leaving it unset does not mean \"unknown\" — the publish path treats a listing with no country as US and then demands `state` and `postalCode`.
24
35
  attr_accessor :country_code
25
36
 
37
+ # Latitude. Never a substitute for the postal address — Airbnb rejects coordinates it cannot reconcile with a full address.
26
38
  attr_accessor :lat
27
39
 
40
+ # Longitude. See `lat`.
28
41
  attr_accessor :lng
29
42
 
30
43
  # Attribute mapping from ruby-style variable name to JSON key.
@@ -32,6 +45,9 @@ module Repull
32
45
  {
33
46
  :'street' => :'street',
34
47
  :'city' => :'city',
48
+ :'state' => :'state',
49
+ :'postal_code' => :'postalCode',
50
+ :'zipcode' => :'zipcode',
35
51
  :'country_code' => :'countryCode',
36
52
  :'lat' => :'lat',
37
53
  :'lng' => :'lng'
@@ -53,6 +69,9 @@ module Repull
53
69
  {
54
70
  :'street' => :'String',
55
71
  :'city' => :'String',
72
+ :'state' => :'String',
73
+ :'postal_code' => :'String',
74
+ :'zipcode' => :'String',
56
75
  :'country_code' => :'String',
57
76
  :'lat' => :'Float',
58
77
  :'lng' => :'Float'
@@ -64,6 +83,9 @@ module Repull
64
83
  Set.new([
65
84
  :'street',
66
85
  :'city',
86
+ :'state',
87
+ :'postal_code',
88
+ :'zipcode',
67
89
  :'country_code',
68
90
  :'lat',
69
91
  :'lng'
@@ -94,6 +116,18 @@ module Repull
94
116
  self.city = attributes[:'city']
95
117
  end
96
118
 
119
+ if attributes.key?(:'state')
120
+ self.state = attributes[:'state']
121
+ end
122
+
123
+ if attributes.key?(:'postal_code')
124
+ self.postal_code = attributes[:'postal_code']
125
+ end
126
+
127
+ if attributes.key?(:'zipcode')
128
+ self.zipcode = attributes[:'zipcode']
129
+ end
130
+
97
131
  if attributes.key?(:'country_code')
98
132
  self.country_code = attributes[:'country_code']
99
133
  end
@@ -129,6 +163,9 @@ module Repull
129
163
  self.class == o.class &&
130
164
  street == o.street &&
131
165
  city == o.city &&
166
+ state == o.state &&
167
+ postal_code == o.postal_code &&
168
+ zipcode == o.zipcode &&
132
169
  country_code == o.country_code &&
133
170
  lat == o.lat &&
134
171
  lng == o.lng
@@ -143,7 +180,7 @@ module Repull
143
180
  # Calculates hash code according to all attributes.
144
181
  # @return [Integer] Hash code
145
182
  def hash
146
- [street, city, country_code, lat, lng].hash
183
+ [street, city, state, postal_code, zipcode, country_code, lat, lng].hash
147
184
  end
148
185
 
149
186
  # Builds the object from hash
@@ -14,23 +14,41 @@ require 'date'
14
14
  require 'time'
15
15
 
16
16
  module Repull
17
- # Inputs for `POST /v1/listings`. Provide enough address detail (street + city + lat/lng) for downstream Airbnb publish to work.
17
+ # Inputs for `POST /v1/listings`. **Address requirements — read this before you build the payload.** Publishing to Airbnb runs a create preflight that refuses the listing outright if the address is incomplete, and the refusal only surfaces later, at publish time. Airbnb requires `street` and `city` for every country. For a **US** property it additionally requires `state` and `postalCode`. Crucially, **omitting `countryCode` makes the listing behave as US**, so a listing created without a country needs `state` and `postalCode` too. Send `countryCode` explicitly for a non-US property. `lat`/`lng` alone are not enough — Airbnb rejects coordinates that are not backed by a full postal address. Use `GET /v1/listings/{id}/publish-status` to see which parts are still missing before you attempt a publish.
18
18
  class ListingCreateRequest < ApiModelBase
19
19
  # Public guest-facing title
20
20
  attr_accessor :name
21
21
 
22
22
  attr_accessor :property_type
23
23
 
24
+ # What the guest actually gets. Airbnb refuses to activate a listing that has not stated one, answering \"Please specify a valid room type\" — which reads like a beds problem and is not. It is never defaulted: most listings are an entire home, but hundreds are a private or hotel room, and publishing one of those as an entire home is a false claim about someone's property. Settable later with `PUT /v1/listings/{id}/content` under `details`.
25
+ attr_accessor :room_type_category
26
+
27
+ # Airbnb's finer property-type category, when you know it. Optional.
28
+ attr_accessor :property_type_category
29
+
30
+ # Street address including the number. Required by Airbnb for every country — a publish is refused without it.
24
31
  attr_accessor :street
25
32
 
33
+ # City / town. Required by Airbnb for every country — a publish is refused without it.
26
34
  attr_accessor :city
27
35
 
36
+ # State, province or region. **Required for a US property**, and a listing with no `countryCode` counts as US. Optional elsewhere, but stored and used wherever the channel carries it.
28
37
  attr_accessor :state
29
38
 
39
+ # Postal code — ZIP in the US, postcode in the UK, and so on. **Required for a US property**, and a listing with no `countryCode` counts as US. Send the complete code: Booking.com rejects a partial postcode such as `SW6` where the full value is `SW6 1EP`. Alias: `zipcode`.
40
+ attr_accessor :postal_code
41
+
42
+ # Alias for `postalCode`, accepted because it is the field name on the Airbnb mirror. `postalCode` wins if you send both. Prefer `postalCode` — the field holds non-US postcodes too.
43
+ attr_accessor :zipcode
44
+
45
+ # ISO-3166 alpha-2 country code. **Send this for any non-US property.** Omitting it does not mean \"unknown\" — the publish path treats a listing with no country as US, which then requires `state` and `postalCode` and will refuse the listing when they are absent.
30
46
  attr_accessor :country_code
31
47
 
48
+ # Latitude. Useful for map search, but never a substitute for the postal address — Airbnb rejects coordinates it cannot reconcile with a full address.
32
49
  attr_accessor :lat
33
50
 
51
+ # Longitude. See `lat`.
34
52
  attr_accessor :lng
35
53
 
36
54
  attr_accessor :bedrooms
@@ -68,9 +86,13 @@ module Repull
68
86
  {
69
87
  :'name' => :'name',
70
88
  :'property_type' => :'propertyType',
89
+ :'room_type_category' => :'roomTypeCategory',
90
+ :'property_type_category' => :'propertyTypeCategory',
71
91
  :'street' => :'street',
72
92
  :'city' => :'city',
73
93
  :'state' => :'state',
94
+ :'postal_code' => :'postalCode',
95
+ :'zipcode' => :'zipcode',
74
96
  :'country_code' => :'countryCode',
75
97
  :'lat' => :'lat',
76
98
  :'lng' => :'lng',
@@ -107,9 +129,13 @@ module Repull
107
129
  {
108
130
  :'name' => :'String',
109
131
  :'property_type' => :'String',
132
+ :'room_type_category' => :'String',
133
+ :'property_type_category' => :'String',
110
134
  :'street' => :'String',
111
135
  :'city' => :'String',
112
136
  :'state' => :'String',
137
+ :'postal_code' => :'String',
138
+ :'zipcode' => :'String',
113
139
  :'country_code' => :'String',
114
140
  :'lat' => :'Float',
115
141
  :'lng' => :'Float',
@@ -163,6 +189,14 @@ module Repull
163
189
  self.property_type = attributes[:'property_type']
164
190
  end
165
191
 
192
+ if attributes.key?(:'room_type_category')
193
+ self.room_type_category = attributes[:'room_type_category']
194
+ end
195
+
196
+ if attributes.key?(:'property_type_category')
197
+ self.property_type_category = attributes[:'property_type_category']
198
+ end
199
+
166
200
  if attributes.key?(:'street')
167
201
  self.street = attributes[:'street']
168
202
  end
@@ -175,6 +209,14 @@ module Repull
175
209
  self.state = attributes[:'state']
176
210
  end
177
211
 
212
+ if attributes.key?(:'postal_code')
213
+ self.postal_code = attributes[:'postal_code']
214
+ end
215
+
216
+ if attributes.key?(:'zipcode')
217
+ self.zipcode = attributes[:'zipcode']
218
+ end
219
+
178
220
  if attributes.key?(:'country_code')
179
221
  self.country_code = attributes[:'country_code']
180
222
  end
@@ -285,9 +327,13 @@ module Repull
285
327
  self.class == o.class &&
286
328
  name == o.name &&
287
329
  property_type == o.property_type &&
330
+ room_type_category == o.room_type_category &&
331
+ property_type_category == o.property_type_category &&
288
332
  street == o.street &&
289
333
  city == o.city &&
290
334
  state == o.state &&
335
+ postal_code == o.postal_code &&
336
+ zipcode == o.zipcode &&
291
337
  country_code == o.country_code &&
292
338
  lat == o.lat &&
293
339
  lng == o.lng &&
@@ -317,7 +363,7 @@ module Repull
317
363
  # Calculates hash code according to all attributes.
318
364
  # @return [Integer] Hash code
319
365
  def hash
320
- [name, property_type, street, city, state, country_code, lat, lng, bedrooms, bathrooms, beds, person_capacity, summary, description, default_daily_price, cleaning_fee, cancellation_policy, check_in_time_start, check_out_time, allows_pets, allows_smoking, allows_children, allows_events].hash
366
+ [name, property_type, room_type_category, property_type_category, street, city, state, postal_code, zipcode, country_code, lat, lng, bedrooms, bathrooms, beds, person_capacity, summary, description, default_daily_price, cleaning_fee, cancellation_policy, check_in_time_start, check_out_time, allows_pets, allows_smoking, allows_children, allows_events].hash
321
367
  end
322
368
 
323
369
  # Builds the object from hash
@@ -0,0 +1,149 @@
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
+ # Optional. Send no body at all unless this listing is mapped to more than one Booking.com property.
18
+ class ListingMarketStateRequest < ApiModelBase
19
+ # Booking.com property to act on, for a listing mapped to more than one. Without it the Booking.com item comes back refused with `ambiguous_booking_mapping` — closing the wrong property's availability takes real inventory off sale, so it is never guessed. The Airbnb items are unaffected and still run. `GET /v1/channels/booking/properties` lists every property in the workspace with the listings mapped under it. `?hotel_id=` in the query string means the same thing; the body wins if you send both.
20
+ attr_accessor :hotel_id
21
+
22
+ # Attribute mapping from ruby-style variable name to JSON key.
23
+ def self.attribute_map
24
+ {
25
+ :'hotel_id' => :'hotelId'
26
+ }
27
+ end
28
+
29
+ # Returns attribute mapping this model knows about
30
+ def self.acceptable_attribute_map
31
+ attribute_map
32
+ end
33
+
34
+ # Returns all the JSON keys this model knows about
35
+ def self.acceptable_attributes
36
+ acceptable_attribute_map.values
37
+ end
38
+
39
+ # Attribute type mapping.
40
+ def self.openapi_types
41
+ {
42
+ :'hotel_id' => :'String'
43
+ }
44
+ end
45
+
46
+ # List of attributes with nullable: true
47
+ def self.openapi_nullable
48
+ Set.new([
49
+ ])
50
+ end
51
+
52
+ # Initializes the object
53
+ # @param [Hash] attributes Model attributes in the form of hash
54
+ def initialize(attributes = {})
55
+ if (!attributes.is_a?(Hash))
56
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::ListingMarketStateRequest` initialize method"
57
+ end
58
+
59
+ # check to see if the attribute exists and convert string to symbol for hash key
60
+ acceptable_attribute_map = self.class.acceptable_attribute_map
61
+ attributes = attributes.each_with_object({}) { |(k, v), h|
62
+ if (!acceptable_attribute_map.key?(k.to_sym))
63
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::ListingMarketStateRequest`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
64
+ end
65
+ h[k.to_sym] = v
66
+ }
67
+
68
+ if attributes.key?(:'hotel_id')
69
+ self.hotel_id = attributes[:'hotel_id']
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
+ invalid_properties
79
+ end
80
+
81
+ # Check to see if the all the properties in the model are valid
82
+ # @return true if the model is valid
83
+ def valid?
84
+ warn '[DEPRECATED] the `valid?` method is obsolete'
85
+ true
86
+ end
87
+
88
+ # Checks equality by comparing each attribute.
89
+ # @param [Object] Object to be compared
90
+ def ==(o)
91
+ return true if self.equal?(o)
92
+ self.class == o.class &&
93
+ hotel_id == o.hotel_id
94
+ end
95
+
96
+ # @see the `==` method
97
+ # @param [Object] Object to be compared
98
+ def eql?(o)
99
+ self == o
100
+ end
101
+
102
+ # Calculates hash code according to all attributes.
103
+ # @return [Integer] Hash code
104
+ def hash
105
+ [hotel_id].hash
106
+ end
107
+
108
+ # Builds the object from hash
109
+ # @param [Hash] attributes Model attributes in the form of hash
110
+ # @return [Object] Returns the model itself
111
+ def self.build_from_hash(attributes)
112
+ return nil unless attributes.is_a?(Hash)
113
+ attributes = attributes.transform_keys(&:to_sym)
114
+ transformed_hash = {}
115
+ openapi_types.each_pair do |key, type|
116
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
117
+ transformed_hash["#{key}"] = nil
118
+ elsif type =~ /\AArray<(.*)>/i
119
+ # check to ensure the input is an array given that the attribute
120
+ # is documented as an array but the input is not
121
+ if attributes[attribute_map[key]].is_a?(Array)
122
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
123
+ end
124
+ elsif !attributes[attribute_map[key]].nil?
125
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
126
+ end
127
+ end
128
+ new(transformed_hash)
129
+ end
130
+
131
+ # Returns the object in the form of hash
132
+ # @return [Hash] Returns the object in the form of hash
133
+ def to_hash
134
+ hash = {}
135
+ self.class.attribute_map.each_pair do |attr, param|
136
+ value = self.send(attr)
137
+ if value.nil?
138
+ is_nullable = self.class.openapi_nullable.include?(attr)
139
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
140
+ end
141
+
142
+ hash[param] = _to_hash(value)
143
+ end
144
+ hash
145
+ end
146
+
147
+ end
148
+
149
+ end