repull 0.2.8 → 0.2.10

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/airbnb_api.rb +1443 -182
  3. data/lib/repull/api/availability_api.rb +24 -92
  4. data/lib/repull/api/billing_api.rb +186 -15
  5. data/lib/repull/api/booking_com_api.rb +532 -41
  6. data/lib/repull/api/listings_api.rb +74 -0
  7. data/lib/repull/api/plumguide_api.rb +245 -0
  8. data/lib/repull/api/reservations_api.rb +0 -197
  9. data/lib/repull/api/sandbox_api.rb +136 -0
  10. data/lib/repull/api/vrbo_api.rb +0 -126
  11. data/lib/repull/models/acknowledge_booking_reservations_request.rb +176 -0
  12. data/lib/repull/models/{create_studio_project201_response_data.rb → airbnb_alteration.rb} +44 -52
  13. data/lib/repull/models/airbnb_amenity.rb +171 -0
  14. data/lib/repull/models/{get_studio_deployment200_response.rb → airbnb_availability_write_request.rb} +52 -10
  15. data/lib/repull/models/airbnb_calendar_operation.rb +250 -0
  16. data/lib/repull/models/airbnb_listing_action_request.rb +198 -0
  17. data/lib/repull/models/{update_availability_request.rb → airbnb_pricing_write_request.rb} +100 -11
  18. data/lib/repull/models/booking_availability_update.rb +276 -0
  19. data/lib/repull/models/booking_availability_update_request.rb +230 -0
  20. data/lib/repull/models/{generate_studio_completion_request_project_id.rb → booking_availability_update_request_property_id.rb} +2 -2
  21. data/lib/repull/models/booking_availability_update_request_updates_inner.rb +104 -0
  22. data/lib/repull/models/booking_pricing_rate_update.rb +1 -0
  23. data/lib/repull/models/booking_pricing_rate_update_restrictions.rb +65 -6
  24. data/lib/repull/models/booking_setup_request.rb +220 -0
  25. data/lib/repull/models/create_airbnb_alteration_request.rb +195 -0
  26. data/lib/repull/models/{create_studio_deployment201_response_data.rb → create_airbnb_offer_request.rb} +42 -30
  27. data/lib/repull/models/{create_studio_project_request.rb → create_booking_webhook_request.rb} +36 -47
  28. data/lib/repull/models/{generate_studio_completion200_response.rb → delete_airbnb_listing_photo200_response.rb} +10 -10
  29. data/lib/repull/models/{delete_studio_project200_response.rb → get_airbnb_alteration200_response.rb} +50 -7
  30. data/lib/repull/models/{delete_studio_project_file200_response.rb → get_airbnb_listing_quality200_response.rb} +37 -7
  31. data/lib/repull/models/{create_studio_project_generation201_response.rb → get_airbnb_listing_settings200_response.rb} +37 -7
  32. data/lib/repull/models/{delete_studio_deployment200_response.rb → get_airbnb_thread200_response.rb} +50 -7
  33. data/lib/repull/models/{list_studio_deployments200_response.rb → get_usage_logs200_response.rb} +17 -8
  34. data/lib/repull/models/get_usage_logs200_response_data_inner.rb +264 -0
  35. data/lib/repull/models/{create_studio_deployment201_response.rb → get_usage_logs200_response_pagination.rb} +29 -10
  36. data/lib/repull/models/get_usage_summary200_response.rb +232 -0
  37. data/lib/repull/models/get_usage_summary200_response_breakdown_inner.rb +183 -0
  38. data/lib/repull/models/{delete_studio_deployment200_response_data.rb → get_usage_summary200_response_limits.rb} +18 -16
  39. data/lib/repull/models/{delete_studio_project200_response_data.rb → get_usage_summary200_response_remaining.rb} +18 -16
  40. data/lib/repull/models/{create_studio_project_generation201_response_data.rb → get_usage_summary200_response_status_distribution.rb} +31 -22
  41. data/lib/repull/models/{upsert_studio_project_file_request.rb → get_usage_summary200_response_timeline_inner.rb} +28 -28
  42. data/lib/repull/models/{create_studio_project_generation_request.rb → get_usage_summary200_response_totals.rb} +28 -28
  43. data/lib/repull/models/{create_studio_project201_response.rb → get_usage_summary200_response_used.rb} +19 -10
  44. data/lib/repull/models/get_usage_tier200_response.rb +183 -0
  45. data/lib/repull/models/{list_studio_projects200_response.rb → get_usage_tier200_response_limits.rb} +31 -12
  46. data/lib/repull/models/{update_reservation_request.rb → get_usage_tier200_response_remaining.rb} +25 -31
  47. data/lib/repull/models/{get_studio_project200_response.rb → get_usage_tier200_response_used.rb} +28 -10
  48. data/lib/repull/models/list_airbnb_alterations200_response.rb +192 -0
  49. data/lib/repull/models/list_airbnb_listing_amenities200_response.rb +190 -0
  50. data/lib/repull/models/{create_studio_deployment_request.rb → list_airbnb_listing_amenities200_response_data.rb} +23 -28
  51. data/lib/repull/models/{list_studio_project_files200_response.rb → list_airbnb_transactions200_response.rb} +50 -7
  52. data/lib/repull/models/listing_content_update_request.rb +243 -0
  53. data/lib/repull/models/{upsert_studio_project_file200_response.rb → listing_content_update_request_address.rb} +53 -10
  54. data/lib/repull/models/listing_content_update_request_amenities.rb +105 -0
  55. data/lib/repull/models/listing_content_update_request_amenities_one_of_inner.rb +196 -0
  56. data/lib/repull/models/listing_content_update_request_occupancy.rb +179 -0
  57. data/lib/repull/{api/ai_api.rb → models/listing_content_update_request_photos_inner.rb} +77 -59
  58. data/lib/repull/models/{generate_studio_completion200_response_data.rb → listing_content_update_request_photos_inner_one_of.rb} +81 -66
  59. data/lib/repull/models/listing_content_update_request_policies.rb +253 -0
  60. data/lib/repull/models/{create_ai_operation200_response.rb → listing_content_update_response.rb} +32 -16
  61. data/lib/repull/models/map_airbnb_listing_request.rb +215 -0
  62. data/lib/repull/models/{create_reservation_request.rb → map_airbnb_listing_response.rb} +137 -116
  63. data/lib/repull/models/property.rb +42 -69
  64. data/lib/repull/models/property_availability.rb +222 -0
  65. data/lib/repull/models/property_availability_day.rb +247 -0
  66. data/lib/repull/models/reservation.rb +28 -1
  67. data/lib/repull/models/sandbox_fixture_ref.rb +193 -0
  68. data/lib/repull/models/{delete_studio_project_file200_response_data.rb → sandbox_reset_result.rb} +72 -11
  69. data/lib/repull/models/sandbox_reset_result_deleted.rb +216 -0
  70. data/lib/repull/models/sandbox_seed_result.rb +278 -0
  71. data/lib/repull/models/{update_studio_project_request.rb → update_airbnb_message_request.rb} +43 -24
  72. data/lib/repull/models/update_booking_charges_request.rb +194 -0
  73. data/lib/repull/version.rb +1 -1
  74. data/lib/repull.rb +59 -34
  75. data/openapi/v1.json +3956 -2522
  76. metadata +61 -36
  77. data/lib/repull/api/studio_api.rb +0 -1094
  78. data/lib/repull/models/generate_studio_completion_request.rb +0 -305
  79. data/lib/repull/models/upsert_studio_project_file200_response_data.rb +0 -147
@@ -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_test_YOUR_API_KEY ``` Sandbox keys start with `sk_test_`, production with `sk_live_`. ## 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 `resets_at` 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
+ # A single Airbnb amenity from the local cache.
18
+ class AirbnbAmenity < ApiModelBase
19
+ # Airbnb amenity id (e.g. `wifi`, `bathroom_step_free_access`).
20
+ attr_accessor :id
21
+
22
+ # Whether the amenity is present on the listing.
23
+ attr_accessor :is_present
24
+
25
+ # Optional host-supplied instruction for the amenity.
26
+ attr_accessor :instruction
27
+
28
+ # Attribute mapping from ruby-style variable name to JSON key.
29
+ def self.attribute_map
30
+ {
31
+ :'id' => :'id',
32
+ :'is_present' => :'is_present',
33
+ :'instruction' => :'instruction'
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
+ :'id' => :'String',
51
+ :'is_present' => :'Boolean',
52
+ :'instruction' => :'String'
53
+ }
54
+ end
55
+
56
+ # List of attributes with nullable: true
57
+ def self.openapi_nullable
58
+ Set.new([
59
+ :'is_present',
60
+ :'instruction'
61
+ ])
62
+ end
63
+
64
+ # Initializes the object
65
+ # @param [Hash] attributes Model attributes in the form of hash
66
+ def initialize(attributes = {})
67
+ if (!attributes.is_a?(Hash))
68
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::AirbnbAmenity` initialize method"
69
+ end
70
+
71
+ # check to see if the attribute exists and convert string to symbol for hash key
72
+ acceptable_attribute_map = self.class.acceptable_attribute_map
73
+ attributes = attributes.each_with_object({}) { |(k, v), h|
74
+ if (!acceptable_attribute_map.key?(k.to_sym))
75
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::AirbnbAmenity`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
76
+ end
77
+ h[k.to_sym] = v
78
+ }
79
+
80
+ if attributes.key?(:'id')
81
+ self.id = attributes[:'id']
82
+ end
83
+
84
+ if attributes.key?(:'is_present')
85
+ self.is_present = attributes[:'is_present']
86
+ end
87
+
88
+ if attributes.key?(:'instruction')
89
+ self.instruction = attributes[:'instruction']
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
+ id == o.id &&
114
+ is_present == o.is_present &&
115
+ instruction == o.instruction
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
+ [id, is_present, instruction].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,13 +14,22 @@ require 'date'
14
14
  require 'time'
15
15
 
16
16
  module Repull
17
- class GetStudioDeployment200Response < ApiModelBase
18
- attr_accessor :data
17
+ # Body for `PUT /v1/channels/airbnb/listings/{id}/availability`. `type: \"calendar\"` carries per-date restrictions (min/max nights, closed-to-arrival/departure, stop-sell); `type: \"rules\"` carries listing-level availability rules (default min/max nights, booking lead time, turnover days).
18
+ class AirbnbAvailabilityWriteRequest < ApiModelBase
19
+ attr_accessor :type
20
+
21
+ # Required when `type: \"calendar\"`. Batch of per-date restriction operations.
22
+ attr_accessor :operations
23
+
24
+ # Required when `type: \"rules\"`. Airbnb availability-rules object — `default_min_nights`, `default_max_nights`, `booking_lead_time`, `turnover_days`, `day_of_week_min_nights`, `seasonal_min_nights`, etc.
25
+ attr_accessor :rules
19
26
 
20
27
  # Attribute mapping from ruby-style variable name to JSON key.
21
28
  def self.attribute_map
22
29
  {
23
- :'data' => :'data'
30
+ :'type' => :'type',
31
+ :'operations' => :'operations',
32
+ :'rules' => :'rules'
24
33
  }
25
34
  end
26
35
 
@@ -37,7 +46,9 @@ module Repull
37
46
  # Attribute type mapping.
38
47
  def self.openapi_types
39
48
  {
40
- :'data' => :'StudioDeployment'
49
+ :'type' => :'String',
50
+ :'operations' => :'Array<AirbnbCalendarOperation>',
51
+ :'rules' => :'Hash<String, Object>'
41
52
  }
42
53
  end
43
54
 
@@ -51,20 +62,34 @@ module Repull
51
62
  # @param [Hash] attributes Model attributes in the form of hash
52
63
  def initialize(attributes = {})
53
64
  if (!attributes.is_a?(Hash))
54
- fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::GetStudioDeployment200Response` initialize method"
65
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::AirbnbAvailabilityWriteRequest` initialize method"
55
66
  end
56
67
 
57
68
  # check to see if the attribute exists and convert string to symbol for hash key
58
69
  acceptable_attribute_map = self.class.acceptable_attribute_map
59
70
  attributes = attributes.each_with_object({}) { |(k, v), h|
60
71
  if (!acceptable_attribute_map.key?(k.to_sym))
61
- fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::GetStudioDeployment200Response`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
72
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::AirbnbAvailabilityWriteRequest`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
62
73
  end
63
74
  h[k.to_sym] = v
64
75
  }
65
76
 
66
- if attributes.key?(:'data')
67
- self.data = attributes[:'data']
77
+ if attributes.key?(:'type')
78
+ self.type = attributes[:'type']
79
+ else
80
+ self.type = nil
81
+ end
82
+
83
+ if attributes.key?(:'operations')
84
+ if (value = attributes[:'operations']).is_a?(Array)
85
+ self.operations = value
86
+ end
87
+ end
88
+
89
+ if attributes.key?(:'rules')
90
+ if (value = attributes[:'rules']).is_a?(Hash)
91
+ self.rules = value
92
+ end
68
93
  end
69
94
  end
70
95
 
@@ -73,6 +98,10 @@ module Repull
73
98
  def list_invalid_properties
74
99
  warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
75
100
  invalid_properties = Array.new
101
+ if @type.nil?
102
+ invalid_properties.push('invalid value for "type", type cannot be nil.')
103
+ end
104
+
76
105
  invalid_properties
77
106
  end
78
107
 
@@ -80,15 +109,28 @@ module Repull
80
109
  # @return true if the model is valid
81
110
  def valid?
82
111
  warn '[DEPRECATED] the `valid?` method is obsolete'
112
+ return false if @type.nil?
83
113
  true
84
114
  end
85
115
 
116
+ # Custom attribute writer method with validation
117
+ # @param [Object] type Value to be assigned
118
+ def type=(type)
119
+ if type.nil?
120
+ fail ArgumentError, 'type cannot be nil'
121
+ end
122
+
123
+ @type = type
124
+ end
125
+
86
126
  # Checks equality by comparing each attribute.
87
127
  # @param [Object] Object to be compared
88
128
  def ==(o)
89
129
  return true if self.equal?(o)
90
130
  self.class == o.class &&
91
- data == o.data
131
+ type == o.type &&
132
+ operations == o.operations &&
133
+ rules == o.rules
92
134
  end
93
135
 
94
136
  # @see the `==` method
@@ -100,7 +142,7 @@ module Repull
100
142
  # Calculates hash code according to all attributes.
101
143
  # @return [Integer] Hash code
102
144
  def hash
103
- [data].hash
145
+ [type, operations, rules].hash
104
146
  end
105
147
 
106
148
  # Builds the object from hash
@@ -0,0 +1,250 @@
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_test_YOUR_API_KEY ``` Sandbox keys start with `sk_test_`, production with `sk_live_`. ## 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 `resets_at` 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
+ # One calendar operation. Supply either `start_date` + `end_date` OR a `dates` array. Every restriction here is forwarded verbatim to Airbnb's batch calendar API.
18
+ class AirbnbCalendarOperation < ApiModelBase
19
+ # Inclusive range start (pair with `end_date`).
20
+ attr_accessor :start_date
21
+
22
+ # Inclusive range end (pair with `start_date`).
23
+ attr_accessor :end_date
24
+
25
+ # Explicit date or `start:end` range strings, as an alternative to `start_date`/`end_date`.
26
+ attr_accessor :dates
27
+
28
+ # Nightly price override.
29
+ attr_accessor :daily_price
30
+
31
+ # Stop-sell is expressed here: `unavailable` blocks the date(s); `available` re-opens; `default` reverts to rule-based availability.
32
+ attr_accessor :availability
33
+
34
+ # Minimum length of stay for the date(s).
35
+ attr_accessor :min_nights
36
+
37
+ # Maximum length of stay for the date(s).
38
+ attr_accessor :max_nights
39
+
40
+ # Closed-to-arrival — no check-ins on the affected date(s).
41
+ attr_accessor :closed_to_arrival
42
+
43
+ # Closed-to-departure — no check-outs on the affected date(s).
44
+ attr_accessor :closed_to_departure
45
+
46
+ attr_accessor :notes
47
+
48
+ # Attribute mapping from ruby-style variable name to JSON key.
49
+ def self.attribute_map
50
+ {
51
+ :'start_date' => :'start_date',
52
+ :'end_date' => :'end_date',
53
+ :'dates' => :'dates',
54
+ :'daily_price' => :'daily_price',
55
+ :'availability' => :'availability',
56
+ :'min_nights' => :'min_nights',
57
+ :'max_nights' => :'max_nights',
58
+ :'closed_to_arrival' => :'closed_to_arrival',
59
+ :'closed_to_departure' => :'closed_to_departure',
60
+ :'notes' => :'notes'
61
+ }
62
+ end
63
+
64
+ # Returns attribute mapping this model knows about
65
+ def self.acceptable_attribute_map
66
+ attribute_map
67
+ end
68
+
69
+ # Returns all the JSON keys this model knows about
70
+ def self.acceptable_attributes
71
+ acceptable_attribute_map.values
72
+ end
73
+
74
+ # Attribute type mapping.
75
+ def self.openapi_types
76
+ {
77
+ :'start_date' => :'Date',
78
+ :'end_date' => :'Date',
79
+ :'dates' => :'Array<String>',
80
+ :'daily_price' => :'Float',
81
+ :'availability' => :'String',
82
+ :'min_nights' => :'Integer',
83
+ :'max_nights' => :'Integer',
84
+ :'closed_to_arrival' => :'Boolean',
85
+ :'closed_to_departure' => :'Boolean',
86
+ :'notes' => :'String'
87
+ }
88
+ end
89
+
90
+ # List of attributes with nullable: true
91
+ def self.openapi_nullable
92
+ Set.new([
93
+ :'start_date',
94
+ :'end_date',
95
+ :'dates',
96
+ :'daily_price',
97
+ :'availability',
98
+ :'min_nights',
99
+ :'max_nights',
100
+ :'closed_to_arrival',
101
+ :'closed_to_departure',
102
+ :'notes'
103
+ ])
104
+ end
105
+
106
+ # Initializes the object
107
+ # @param [Hash] attributes Model attributes in the form of hash
108
+ def initialize(attributes = {})
109
+ if (!attributes.is_a?(Hash))
110
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::AirbnbCalendarOperation` initialize method"
111
+ end
112
+
113
+ # check to see if the attribute exists and convert string to symbol for hash key
114
+ acceptable_attribute_map = self.class.acceptable_attribute_map
115
+ attributes = attributes.each_with_object({}) { |(k, v), h|
116
+ if (!acceptable_attribute_map.key?(k.to_sym))
117
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::AirbnbCalendarOperation`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
118
+ end
119
+ h[k.to_sym] = v
120
+ }
121
+
122
+ if attributes.key?(:'start_date')
123
+ self.start_date = attributes[:'start_date']
124
+ end
125
+
126
+ if attributes.key?(:'end_date')
127
+ self.end_date = attributes[:'end_date']
128
+ end
129
+
130
+ if attributes.key?(:'dates')
131
+ if (value = attributes[:'dates']).is_a?(Array)
132
+ self.dates = value
133
+ end
134
+ end
135
+
136
+ if attributes.key?(:'daily_price')
137
+ self.daily_price = attributes[:'daily_price']
138
+ end
139
+
140
+ if attributes.key?(:'availability')
141
+ self.availability = attributes[:'availability']
142
+ end
143
+
144
+ if attributes.key?(:'min_nights')
145
+ self.min_nights = attributes[:'min_nights']
146
+ end
147
+
148
+ if attributes.key?(:'max_nights')
149
+ self.max_nights = attributes[:'max_nights']
150
+ end
151
+
152
+ if attributes.key?(:'closed_to_arrival')
153
+ self.closed_to_arrival = attributes[:'closed_to_arrival']
154
+ end
155
+
156
+ if attributes.key?(:'closed_to_departure')
157
+ self.closed_to_departure = attributes[:'closed_to_departure']
158
+ end
159
+
160
+ if attributes.key?(:'notes')
161
+ self.notes = attributes[:'notes']
162
+ end
163
+ end
164
+
165
+ # Show invalid properties with the reasons. Usually used together with valid?
166
+ # @return Array for valid properties with the reasons
167
+ def list_invalid_properties
168
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
169
+ invalid_properties = Array.new
170
+ invalid_properties
171
+ end
172
+
173
+ # Check to see if the all the properties in the model are valid
174
+ # @return true if the model is valid
175
+ def valid?
176
+ warn '[DEPRECATED] the `valid?` method is obsolete'
177
+ true
178
+ end
179
+
180
+ # Checks equality by comparing each attribute.
181
+ # @param [Object] Object to be compared
182
+ def ==(o)
183
+ return true if self.equal?(o)
184
+ self.class == o.class &&
185
+ start_date == o.start_date &&
186
+ end_date == o.end_date &&
187
+ dates == o.dates &&
188
+ daily_price == o.daily_price &&
189
+ availability == o.availability &&
190
+ min_nights == o.min_nights &&
191
+ max_nights == o.max_nights &&
192
+ closed_to_arrival == o.closed_to_arrival &&
193
+ closed_to_departure == o.closed_to_departure &&
194
+ notes == o.notes
195
+ end
196
+
197
+ # @see the `==` method
198
+ # @param [Object] Object to be compared
199
+ def eql?(o)
200
+ self == o
201
+ end
202
+
203
+ # Calculates hash code according to all attributes.
204
+ # @return [Integer] Hash code
205
+ def hash
206
+ [start_date, end_date, dates, daily_price, availability, min_nights, max_nights, closed_to_arrival, closed_to_departure, notes].hash
207
+ end
208
+
209
+ # Builds the object from hash
210
+ # @param [Hash] attributes Model attributes in the form of hash
211
+ # @return [Object] Returns the model itself
212
+ def self.build_from_hash(attributes)
213
+ return nil unless attributes.is_a?(Hash)
214
+ attributes = attributes.transform_keys(&:to_sym)
215
+ transformed_hash = {}
216
+ openapi_types.each_pair do |key, type|
217
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
218
+ transformed_hash["#{key}"] = nil
219
+ elsif type =~ /\AArray<(.*)>/i
220
+ # check to ensure the input is an array given that the attribute
221
+ # is documented as an array but the input is not
222
+ if attributes[attribute_map[key]].is_a?(Array)
223
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
224
+ end
225
+ elsif !attributes[attribute_map[key]].nil?
226
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
227
+ end
228
+ end
229
+ new(transformed_hash)
230
+ end
231
+
232
+ # Returns the object in the form of hash
233
+ # @return [Hash] Returns the object in the form of hash
234
+ def to_hash
235
+ hash = {}
236
+ self.class.attribute_map.each_pair do |attr, param|
237
+ value = self.send(attr)
238
+ if value.nil?
239
+ is_nullable = self.class.openapi_nullable.include?(attr)
240
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
241
+ end
242
+
243
+ hash[param] = _to_hash(value)
244
+ end
245
+ hash
246
+ end
247
+
248
+ end
249
+
250
+ end