repull 0.2.0 → 0.2.3

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 (282) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/ai_api.rb +1 -1
  3. data/lib/repull/api/airbnb_api.rb +271 -16
  4. data/lib/repull/api/atlas_api.rb +1 -1
  5. data/lib/repull/api/availability_api.rb +1 -1
  6. data/lib/repull/api/billing_api.rb +1 -1
  7. data/lib/repull/api/booking_com_api.rb +133 -1
  8. data/lib/repull/api/connect_api.rb +3 -3
  9. data/lib/repull/api/conversations_api.rb +27 -5
  10. data/lib/repull/api/guests_api.rb +14 -3
  11. data/lib/repull/api/kv_api.rb +361 -0
  12. data/lib/repull/api/listings_api.rb +26 -9
  13. data/lib/repull/api/markets_api.rb +14 -3
  14. data/lib/repull/api/plumguide_api.rb +1 -1
  15. data/lib/repull/api/pricing_api.rb +14 -3
  16. data/lib/repull/api/properties_api.rb +32 -8
  17. data/lib/repull/api/reservations_api.rb +34 -5
  18. data/lib/repull/api/reviews_api.rb +14 -3
  19. data/lib/repull/api/schema_api.rb +1 -1
  20. data/lib/repull/api/studio_api.rb +1094 -0
  21. data/lib/repull/api/system_api.rb +1 -1
  22. data/lib/repull/api/vrbo_api.rb +31 -3
  23. data/lib/repull/api/webhooks_api.rb +16 -5
  24. data/lib/repull/api_client.rb +1 -1
  25. data/lib/repull/api_error.rb +1 -1
  26. data/lib/repull/api_model_base.rb +1 -1
  27. data/lib/repull/configuration.rb +1 -1
  28. data/lib/repull/models/account_created_event.rb +217 -0
  29. data/lib/repull/models/account_created_payload.rb +185 -0
  30. data/lib/repull/models/account_disconnected_event.rb +217 -0
  31. data/lib/repull/models/account_disconnected_payload.rb +196 -0
  32. data/lib/repull/models/ai_operation.rb +1 -1
  33. data/lib/repull/models/ai_operation_completed_event.rb +217 -0
  34. data/lib/repull/models/ai_operation_completed_payload.rb +197 -0
  35. data/lib/repull/models/ai_operation_failed_event.rb +217 -0
  36. data/lib/repull/models/ai_operation_failed_payload.rb +175 -0
  37. data/lib/repull/models/ai_operation_failed_payload_error.rb +156 -0
  38. data/lib/repull/models/airbnb_connection.rb +242 -0
  39. data/lib/repull/models/airbnb_connection_accessibility_amenities_inner.rb +167 -0
  40. data/lib/repull/models/airbnb_connection_amenities_inner.rb +168 -0
  41. data/lib/repull/models/airbnb_connection_host.rb +244 -0
  42. data/lib/repull/models/airbnb_connection_response.rb +164 -0
  43. data/lib/repull/models/airbnb_connection_summary.rb +231 -0
  44. data/lib/repull/models/airbnb_data_freshness.rb +201 -0
  45. data/lib/repull/models/airbnb_listing.rb +25 -74
  46. data/lib/repull/models/airbnb_listing_list_response.rb +65 -5
  47. data/lib/repull/models/airbnb_reservation.rb +1 -1
  48. data/lib/repull/models/airbnb_reservation_list_response.rb +2 -1
  49. data/lib/repull/models/airbnb_review.rb +1 -1
  50. data/lib/repull/models/airbnb_review_list_response.rb +1 -1
  51. data/lib/repull/models/airbnb_thread.rb +1 -1
  52. data/lib/repull/models/airbnb_thread_list_response.rb +1 -1
  53. data/lib/repull/models/booking_connect_listing_option.rb +1 -1
  54. data/lib/repull/models/booking_connect_room.rb +1 -1
  55. data/lib/repull/models/booking_connect_rooms_response.rb +1 -1
  56. data/lib/repull/models/booking_conversation.rb +1 -1
  57. data/lib/repull/models/booking_conversation_list_response.rb +1 -1
  58. data/lib/repull/models/booking_pricing_rate_update.rb +1 -1
  59. data/lib/repull/models/booking_pricing_rate_update_date_range.rb +1 -1
  60. data/lib/repull/models/booking_pricing_rate_update_restrictions.rb +1 -1
  61. data/lib/repull/models/booking_pricing_response.rb +1 -1
  62. data/lib/repull/models/booking_pricing_update_request.rb +1 -1
  63. data/lib/repull/models/booking_pricing_update_response.rb +1 -1
  64. data/lib/repull/models/booking_property.rb +1 -1
  65. data/lib/repull/models/booking_property_list_response.rb +1 -1
  66. data/lib/repull/models/booking_room_mapping.rb +1 -1
  67. data/lib/repull/models/booking_verify_hotel_request.rb +1 -1
  68. data/lib/repull/models/booking_verify_hotel_response.rb +1 -1
  69. data/lib/repull/models/bulk_pricing_failure.rb +1 -1
  70. data/lib/repull/models/bulk_pricing_item.rb +1 -1
  71. data/lib/repull/models/bulk_pricing_request.rb +1 -1
  72. data/lib/repull/models/bulk_pricing_response.rb +1 -1
  73. data/lib/repull/models/calendar_day.rb +1 -1
  74. data/lib/repull/models/calendar_response.rb +1 -1
  75. data/lib/repull/models/calendar_updated_event.rb +217 -0
  76. data/lib/repull/models/calendar_updated_payload.rb +184 -0
  77. data/lib/repull/models/calendar_updated_payload_range.rb +156 -0
  78. data/lib/repull/models/clear_kv200_response.rb +147 -0
  79. data/lib/repull/models/connect_host.rb +1 -1
  80. data/lib/repull/models/connect_provider.rb +1 -1
  81. data/lib/repull/models/connect_provider_list_response.rb +1 -1
  82. data/lib/repull/models/connect_session.rb +1 -1
  83. data/lib/repull/models/connect_status.rb +1 -1
  84. data/lib/repull/models/connection.rb +1 -1
  85. data/lib/repull/models/connection_list_response.rb +1 -1
  86. data/lib/repull/models/conversation.rb +1 -1
  87. data/lib/repull/models/conversation_detail.rb +1 -1
  88. data/lib/repull/models/conversation_guest.rb +1 -1
  89. data/lib/repull/models/conversation_guest_contact.rb +1 -1
  90. data/lib/repull/models/conversation_host.rb +1 -1
  91. data/lib/repull/models/conversation_list_response.rb +1 -1
  92. data/lib/repull/models/conversation_message_attachment.rb +1 -1
  93. data/lib/repull/models/create_ai_operation200_response.rb +1 -1
  94. data/lib/repull/models/create_billing_checkout_request.rb +1 -1
  95. data/lib/repull/models/create_connect_session_request.rb +1 -1
  96. data/lib/repull/models/create_connection_request.rb +1 -1
  97. data/lib/repull/models/create_reservation_request.rb +1 -1
  98. data/lib/repull/models/create_studio_deployment201_response.rb +147 -0
  99. data/lib/repull/models/create_studio_deployment201_response_data.rb +199 -0
  100. data/lib/repull/models/create_studio_deployment_request.rb +165 -0
  101. data/lib/repull/models/create_studio_project201_response.rb +147 -0
  102. data/lib/repull/models/create_studio_project201_response_data.rb +199 -0
  103. data/lib/repull/models/create_studio_project_generation201_response.rb +147 -0
  104. data/lib/repull/models/create_studio_project_generation201_response_data.rb +165 -0
  105. data/lib/repull/models/create_studio_project_generation_request.rb +165 -0
  106. data/lib/repull/models/create_studio_project_request.rb +203 -0
  107. data/lib/repull/models/create_webhook_request.rb +2 -2
  108. data/lib/repull/models/custom_schema.rb +1 -1
  109. data/lib/repull/models/custom_schema_create.rb +1 -1
  110. data/lib/repull/models/custom_schema_create_response.rb +1 -1
  111. data/lib/repull/models/custom_schema_delete_response.rb +1 -1
  112. data/lib/repull/models/custom_schema_list_response.rb +1 -1
  113. data/lib/repull/models/custom_schema_summary.rb +1 -1
  114. data/lib/repull/models/custom_schema_update.rb +1 -1
  115. data/lib/repull/models/delete_kv200_response.rb +147 -0
  116. data/lib/repull/models/delete_studio_deployment200_response.rb +147 -0
  117. data/lib/repull/models/delete_studio_deployment200_response_data.rb +156 -0
  118. data/lib/repull/models/delete_studio_project200_response.rb +147 -0
  119. data/lib/repull/models/delete_studio_project200_response_data.rb +156 -0
  120. data/lib/repull/models/delete_studio_project_file200_response.rb +147 -0
  121. data/lib/repull/models/delete_studio_project_file200_response_data.rb +156 -0
  122. data/lib/repull/models/error.rb +1 -1
  123. data/lib/repull/models/error_error.rb +1 -1
  124. data/lib/repull/models/error_error_support.rb +1 -1
  125. data/lib/repull/models/generate_studio_completion200_response.rb +147 -0
  126. data/lib/repull/models/generate_studio_completion200_response_data.rb +224 -0
  127. data/lib/repull/models/generate_studio_completion_request.rb +305 -0
  128. data/lib/repull/models/generate_studio_completion_request_project_id.rb +105 -0
  129. data/lib/repull/models/get_health200_response.rb +1 -1
  130. data/lib/repull/models/get_studio_deployment200_response.rb +147 -0
  131. data/lib/repull/models/get_studio_project200_response.rb +147 -0
  132. data/lib/repull/models/guest.rb +1 -1
  133. data/lib/repull/models/guest_contact.rb +1 -1
  134. data/lib/repull/models/guest_flag.rb +1 -1
  135. data/lib/repull/models/guest_list_response.rb +1 -1
  136. data/lib/repull/models/guest_note.rb +1 -1
  137. data/lib/repull/models/guest_profile.rb +1 -1
  138. data/lib/repull/models/guest_reservations_summary.rb +1 -1
  139. data/lib/repull/models/list_kv200_response.rb +158 -0
  140. data/lib/repull/models/list_kv200_response_data_inner.rb +176 -0
  141. data/lib/repull/models/list_kv200_response_pagination.rb +156 -0
  142. data/lib/repull/models/list_studio_deployments200_response.rb +158 -0
  143. data/lib/repull/models/list_studio_project_files200_response.rb +149 -0
  144. data/lib/repull/models/list_studio_projects200_response.rb +149 -0
  145. data/lib/repull/models/listing.rb +36 -2
  146. data/lib/repull/models/listing_address.rb +1 -1
  147. data/lib/repull/models/listing_amenity.rb +215 -0
  148. data/lib/repull/models/listing_channel.rb +1 -1
  149. data/lib/repull/models/listing_comp.rb +1 -1
  150. data/lib/repull/models/listing_comp_nightly.rb +1 -1
  151. data/lib/repull/models/listing_comp_ratings.rb +1 -1
  152. data/lib/repull/models/listing_comps_response.rb +1 -1
  153. data/lib/repull/models/listing_content.rb +54 -13
  154. data/lib/repull/models/listing_create_request.rb +1 -1
  155. data/lib/repull/models/listing_create_response.rb +1 -1
  156. data/lib/repull/models/listing_created_event.rb +217 -0
  157. data/lib/repull/models/listing_created_payload.rb +202 -0
  158. data/lib/repull/models/listing_created_payload_address.rb +165 -0
  159. data/lib/repull/models/listing_deleted_event.rb +217 -0
  160. data/lib/repull/models/listing_deleted_payload.rb +167 -0
  161. data/lib/repull/models/listing_details.rb +374 -0
  162. data/lib/repull/models/listing_generate_content_request.rb +2 -2
  163. data/lib/repull/models/listing_generate_content_response.rb +1 -1
  164. data/lib/repull/models/listing_list_response.rb +1 -1
  165. data/lib/repull/models/listing_pricing_apply_request.rb +1 -1
  166. data/lib/repull/models/listing_pricing_apply_response.rb +1 -1
  167. data/lib/repull/models/listing_pricing_history_entry.rb +1 -1
  168. data/lib/repull/models/listing_pricing_history_response.rb +1 -1
  169. data/lib/repull/models/listing_pricing_recommendation.rb +1 -1
  170. data/lib/repull/models/listing_pricing_response.rb +1 -1
  171. data/lib/repull/models/listing_pricing_response_comp_summary.rb +1 -1
  172. data/lib/repull/models/listing_pricing_response_date_range.rb +1 -1
  173. data/lib/repull/models/listing_pricing_response_listing.rb +1 -1
  174. data/lib/repull/models/listing_pricing_strategy.rb +1 -1
  175. data/lib/repull/models/listing_pricing_strategy_input.rb +1 -1
  176. data/lib/repull/models/listing_publish_airbnb_request.rb +1 -1
  177. data/lib/repull/models/listing_publish_response.rb +1 -1
  178. data/lib/repull/models/listing_publish_status_channel.rb +1 -1
  179. data/lib/repull/models/listing_publish_status_connection.rb +180 -0
  180. data/lib/repull/models/listing_publish_status_response.rb +18 -5
  181. data/lib/repull/models/listing_quality_tier.rb +1 -1
  182. data/lib/repull/models/listing_segment.rb +1 -1
  183. data/lib/repull/models/listing_segment_recommendation.rb +1 -1
  184. data/lib/repull/models/listing_segments_response.rb +1 -1
  185. data/lib/repull/models/listing_segments_response_scope.rb +1 -1
  186. data/lib/repull/models/listing_updated_event.rb +217 -0
  187. data/lib/repull/models/listing_updated_payload.rb +169 -0
  188. data/lib/repull/models/map_connect_booking_rooms_request.rb +1 -1
  189. data/lib/repull/models/map_connect_booking_rooms_response.rb +1 -1
  190. data/lib/repull/models/market_browse_category.rb +1 -1
  191. data/lib/repull/models/market_browse_entry.rb +1 -1
  192. data/lib/repull/models/market_browse_featured.rb +1 -1
  193. data/lib/repull/models/market_browse_response.rb +1 -1
  194. data/lib/repull/models/market_calendar_day.rb +1 -1
  195. data/lib/repull/models/market_calendar_day_events_inner.rb +1 -1
  196. data/lib/repull/models/market_calendar_response.rb +1 -1
  197. data/lib/repull/models/market_detail_response.rb +1 -1
  198. data/lib/repull/models/market_detail_response_price_distribution_inner.rb +1 -1
  199. data/lib/repull/models/market_detail_response_property_type_mix_inner.rb +1 -1
  200. data/lib/repull/models/market_detail_response_supply_trend_inner.rb +1 -1
  201. data/lib/repull/models/market_detail_response_top_comps.rb +1 -1
  202. data/lib/repull/models/market_event.rb +1 -1
  203. data/lib/repull/models/market_my_listing.rb +1 -1
  204. data/lib/repull/models/market_summary.rb +1 -1
  205. data/lib/repull/models/market_top_comp.rb +1 -1
  206. data/lib/repull/models/markets_overview_response.rb +2 -2
  207. data/lib/repull/models/markets_overview_response_browse.rb +1 -1
  208. data/lib/repull/models/markets_overview_response_subscriptions.rb +1 -1
  209. data/lib/repull/models/markets_overview_response_totals.rb +1 -1
  210. data/lib/repull/models/message.rb +1 -1
  211. data/lib/repull/models/message_list_response.rb +1 -1
  212. data/lib/repull/models/pagination.rb +1 -1
  213. data/lib/repull/models/payment_completed_event.rb +217 -0
  214. data/lib/repull/models/payment_completed_payload.rb +193 -0
  215. data/lib/repull/models/payment_refunded_event.rb +217 -0
  216. data/lib/repull/models/payment_refunded_payload.rb +193 -0
  217. data/lib/repull/models/plumguide_listing.rb +1 -1
  218. data/lib/repull/models/plumguide_listing_list_response.rb +1 -1
  219. data/lib/repull/models/property.rb +17 -5
  220. data/lib/repull/models/property_list_response.rb +1 -1
  221. data/lib/repull/models/reply_booking_review200_response.rb +147 -0
  222. data/lib/repull/models/reply_booking_review_request.rb +219 -0
  223. data/lib/repull/models/repull_ping_event.rb +217 -0
  224. data/lib/repull/models/repull_ping_payload.rb +148 -0
  225. data/lib/repull/models/reservation.rb +74 -89
  226. data/lib/repull/models/reservation_cancelled_event.rb +217 -0
  227. data/lib/repull/models/reservation_cancelled_payload.rb +196 -0
  228. data/lib/repull/models/reservation_created_event.rb +218 -0
  229. data/lib/repull/models/reservation_created_payload.rb +165 -0
  230. data/lib/repull/models/reservation_financials.rb +172 -0
  231. data/lib/repull/models/reservation_list_response.rb +1 -1
  232. data/lib/repull/models/reservation_message_received_event.rb +217 -0
  233. data/lib/repull/models/reservation_message_received_payload.rb +184 -0
  234. data/lib/repull/models/reservation_message_received_payload_from.rb +157 -0
  235. data/lib/repull/models/reservation_occupancy.rb +190 -0
  236. data/lib/repull/models/reservation_primary_guest.rb +202 -0
  237. data/lib/repull/models/reservation_updated_event.rb +217 -0
  238. data/lib/repull/models/reservation_updated_payload.rb +177 -0
  239. data/lib/repull/models/reservation_webhook_object.rb +355 -0
  240. data/lib/repull/models/respond_airbnb_review_request.rb +174 -0
  241. data/lib/repull/models/review.rb +1 -1
  242. data/lib/repull/models/review_category.rb +1 -1
  243. data/lib/repull/models/review_list_response.rb +1 -1
  244. data/lib/repull/models/review_response.rb +1 -1
  245. data/lib/repull/models/rotate_webhook_secret200_response.rb +1 -1
  246. data/lib/repull/models/select_connect_provider_request.rb +1 -1
  247. data/lib/repull/models/select_provider_response.rb +1 -1
  248. data/lib/repull/models/set_kv_request.rb +180 -0
  249. data/lib/repull/models/studio_deployment.rb +207 -0
  250. data/lib/repull/models/studio_error.rb +148 -0
  251. data/lib/repull/models/studio_error_error.rb +212 -0
  252. data/lib/repull/models/studio_file.rb +188 -0
  253. data/lib/repull/models/studio_generation.rb +215 -0
  254. data/lib/repull/models/studio_project.rb +241 -0
  255. data/lib/repull/models/test_webhook_request.rb +24 -2
  256. data/lib/repull/models/update_availability_request.rb +1 -1
  257. data/lib/repull/models/update_listing_pricing_strategy200_response.rb +1 -1
  258. data/lib/repull/models/update_reservation_request.rb +1 -1
  259. data/lib/repull/models/update_studio_project_request.rb +190 -0
  260. data/lib/repull/models/update_webhook_request.rb +2 -2
  261. data/lib/repull/models/upsert_studio_project_file200_response.rb +147 -0
  262. data/lib/repull/models/upsert_studio_project_file200_response_data.rb +147 -0
  263. data/lib/repull/models/upsert_studio_project_file_request.rb +165 -0
  264. data/lib/repull/models/vrbo_listing.rb +1 -1
  265. data/lib/repull/models/vrbo_listing_list_response.rb +1 -1
  266. data/lib/repull/models/vrbo_reservation.rb +1 -1
  267. data/lib/repull/models/vrbo_reservation_list_response.rb +1 -1
  268. data/lib/repull/models/webhook_delivery.rb +25 -2
  269. data/lib/repull/models/webhook_delivery_detail.rb +26 -3
  270. data/lib/repull/models/webhook_delivery_list_response.rb +1 -1
  271. data/lib/repull/models/webhook_event.rb +82 -0
  272. data/lib/repull/models/webhook_event_catalog.rb +18 -5
  273. data/lib/repull/models/webhook_event_catalog_domains_inner.rb +2 -2
  274. data/lib/repull/models/{webhook_event_catalog_domains_inner_events_inner.rb → webhook_event_catalog_entry.rb} +28 -5
  275. data/lib/repull/models/webhook_event_type.rb +53 -0
  276. data/lib/repull/models/webhook_list_response.rb +1 -1
  277. data/lib/repull/models/webhook_subscription.rb +2 -2
  278. data/lib/repull/version.rb +2 -2
  279. data/lib/repull.rb +97 -2
  280. data/openapi/v1.json +8717 -4724
  281. data/scripts/regen.sh +1 -1
  282. metadata +99 -4
@@ -0,0 +1,361 @@
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.
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 'cgi'
14
+
15
+ module Repull
16
+ class KVApi
17
+ attr_accessor :api_client
18
+
19
+ def initialize(api_client = ApiClient.default)
20
+ @api_client = api_client
21
+ end
22
+ # Clear KV entries by prefix
23
+ # Bulk-deletes every key in the project whose name starts with `prefix`. The `prefix` parameter is required — there is no \"delete every key in this project\" shortcut; pass an empty `prefix` is rejected with 422 to prevent accidental wipes. Returns the number of rows removed.
24
+ # @param prefix [String] Required. Keys starting with this string are deleted.
25
+ # @param [Hash] opts the optional parameters
26
+ # @option opts [String] :project_id (default to 'default')
27
+ # @return [ClearKv200Response]
28
+ def clear_kv(prefix, opts = {})
29
+ data, _status_code, _headers = clear_kv_with_http_info(prefix, opts)
30
+ data
31
+ end
32
+
33
+ # Clear KV entries by prefix
34
+ # Bulk-deletes every key in the project whose name starts with `prefix`. The `prefix` parameter is required — there is no \"delete every key in this project\" shortcut; pass an empty `prefix` is rejected with 422 to prevent accidental wipes. Returns the number of rows removed.
35
+ # @param prefix [String] Required. Keys starting with this string are deleted.
36
+ # @param [Hash] opts the optional parameters
37
+ # @option opts [String] :project_id (default to 'default')
38
+ # @return [Array<(ClearKv200Response, Integer, Hash)>] ClearKv200Response data, response status code and response headers
39
+ def clear_kv_with_http_info(prefix, opts = {})
40
+ if @api_client.config.debugging
41
+ @api_client.config.logger.debug 'Calling API: KVApi.clear_kv ...'
42
+ end
43
+ # verify the required parameter 'prefix' is set
44
+ if @api_client.config.client_side_validation && prefix.nil?
45
+ fail ArgumentError, "Missing the required parameter 'prefix' when calling KVApi.clear_kv"
46
+ end
47
+ # resource path
48
+ local_var_path = '/v1/kv'
49
+
50
+ # query parameters
51
+ query_params = opts[:query_params] || {}
52
+ query_params[:'prefix'] = prefix
53
+ query_params[:'project_id'] = opts[:'project_id'] if !opts[:'project_id'].nil?
54
+
55
+ # header parameters
56
+ header_params = opts[:header_params] || {}
57
+ # HTTP header 'Accept' (if needed)
58
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
59
+
60
+ # form parameters
61
+ form_params = opts[:form_params] || {}
62
+
63
+ # http body (model)
64
+ post_body = opts[:debug_body]
65
+
66
+ # return_type
67
+ return_type = opts[:debug_return_type] || 'ClearKv200Response'
68
+
69
+ # auth_names
70
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
71
+
72
+ new_options = opts.merge(
73
+ :operation => :"KVApi.clear_kv",
74
+ :header_params => header_params,
75
+ :query_params => query_params,
76
+ :form_params => form_params,
77
+ :body => post_body,
78
+ :auth_names => auth_names,
79
+ :return_type => return_type
80
+ )
81
+
82
+ data, status_code, headers = @api_client.call_api(:DELETE, local_var_path, new_options)
83
+ if @api_client.config.debugging
84
+ @api_client.config.logger.debug "API called: KVApi#clear_kv\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
85
+ end
86
+ return data, status_code, headers
87
+ end
88
+
89
+ # Delete a KV entry
90
+ # Removes a single key. Returns `{ deleted: true }` if the row was present, `{ deleted: false }` if it was already absent — both are 200 (idempotent).
91
+ # @param key [String]
92
+ # @param [Hash] opts the optional parameters
93
+ # @option opts [String] :project_id (default to 'default')
94
+ # @return [DeleteKv200Response]
95
+ def delete_kv(key, opts = {})
96
+ data, _status_code, _headers = delete_kv_with_http_info(key, opts)
97
+ data
98
+ end
99
+
100
+ # Delete a KV entry
101
+ # Removes a single key. Returns &#x60;{ deleted: true }&#x60; if the row was present, &#x60;{ deleted: false }&#x60; if it was already absent — both are 200 (idempotent).
102
+ # @param key [String]
103
+ # @param [Hash] opts the optional parameters
104
+ # @option opts [String] :project_id (default to 'default')
105
+ # @return [Array<(DeleteKv200Response, Integer, Hash)>] DeleteKv200Response data, response status code and response headers
106
+ def delete_kv_with_http_info(key, opts = {})
107
+ if @api_client.config.debugging
108
+ @api_client.config.logger.debug 'Calling API: KVApi.delete_kv ...'
109
+ end
110
+ # verify the required parameter 'key' is set
111
+ if @api_client.config.client_side_validation && key.nil?
112
+ fail ArgumentError, "Missing the required parameter 'key' when calling KVApi.delete_kv"
113
+ end
114
+ # resource path
115
+ local_var_path = '/v1/kv/{key}'.sub('{key}', CGI.escape(key.to_s))
116
+
117
+ # query parameters
118
+ query_params = opts[:query_params] || {}
119
+ query_params[:'project_id'] = opts[:'project_id'] if !opts[:'project_id'].nil?
120
+
121
+ # header parameters
122
+ header_params = opts[:header_params] || {}
123
+ # HTTP header 'Accept' (if needed)
124
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
125
+
126
+ # form parameters
127
+ form_params = opts[:form_params] || {}
128
+
129
+ # http body (model)
130
+ post_body = opts[:debug_body]
131
+
132
+ # return_type
133
+ return_type = opts[:debug_return_type] || 'DeleteKv200Response'
134
+
135
+ # auth_names
136
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
137
+
138
+ new_options = opts.merge(
139
+ :operation => :"KVApi.delete_kv",
140
+ :header_params => header_params,
141
+ :query_params => query_params,
142
+ :form_params => form_params,
143
+ :body => post_body,
144
+ :auth_names => auth_names,
145
+ :return_type => return_type
146
+ )
147
+
148
+ data, status_code, headers = @api_client.call_api(:DELETE, local_var_path, new_options)
149
+ if @api_client.config.debugging
150
+ @api_client.config.logger.debug "API called: KVApi#delete_kv\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
151
+ end
152
+ return data, status_code, headers
153
+ end
154
+
155
+ # Get a KV entry
156
+ # Fetches a single key. Returns 404 when the key does not exist OR has expired (rows past `ttl_at` are filtered from reads). Cross-tenant lookups also return 404 — the API never reveals existence of another customer's keys.
157
+ # @param key [String] KV key. URL-encode &#x60;/&#x60;, &#x60;:&#x60;, etc. so they survive routing.
158
+ # @param [Hash] opts the optional parameters
159
+ # @option opts [String] :project_id (default to 'default')
160
+ # @return [ListKv200ResponseDataInner]
161
+ def get_kv(key, opts = {})
162
+ data, _status_code, _headers = get_kv_with_http_info(key, opts)
163
+ data
164
+ end
165
+
166
+ # Get a KV entry
167
+ # Fetches a single key. Returns 404 when the key does not exist OR has expired (rows past &#x60;ttl_at&#x60; are filtered from reads). Cross-tenant lookups also return 404 — the API never reveals existence of another customer&#39;s keys.
168
+ # @param key [String] KV key. URL-encode &#x60;/&#x60;, &#x60;:&#x60;, etc. so they survive routing.
169
+ # @param [Hash] opts the optional parameters
170
+ # @option opts [String] :project_id (default to 'default')
171
+ # @return [Array<(ListKv200ResponseDataInner, Integer, Hash)>] ListKv200ResponseDataInner data, response status code and response headers
172
+ def get_kv_with_http_info(key, opts = {})
173
+ if @api_client.config.debugging
174
+ @api_client.config.logger.debug 'Calling API: KVApi.get_kv ...'
175
+ end
176
+ # verify the required parameter 'key' is set
177
+ if @api_client.config.client_side_validation && key.nil?
178
+ fail ArgumentError, "Missing the required parameter 'key' when calling KVApi.get_kv"
179
+ end
180
+ # resource path
181
+ local_var_path = '/v1/kv/{key}'.sub('{key}', CGI.escape(key.to_s))
182
+
183
+ # query parameters
184
+ query_params = opts[:query_params] || {}
185
+ query_params[:'project_id'] = opts[:'project_id'] if !opts[:'project_id'].nil?
186
+
187
+ # header parameters
188
+ header_params = opts[:header_params] || {}
189
+ # HTTP header 'Accept' (if needed)
190
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
191
+
192
+ # form parameters
193
+ form_params = opts[:form_params] || {}
194
+
195
+ # http body (model)
196
+ post_body = opts[:debug_body]
197
+
198
+ # return_type
199
+ return_type = opts[:debug_return_type] || 'ListKv200ResponseDataInner'
200
+
201
+ # auth_names
202
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
203
+
204
+ new_options = opts.merge(
205
+ :operation => :"KVApi.get_kv",
206
+ :header_params => header_params,
207
+ :query_params => query_params,
208
+ :form_params => form_params,
209
+ :body => post_body,
210
+ :auth_names => auth_names,
211
+ :return_type => return_type
212
+ )
213
+
214
+ data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options)
215
+ if @api_client.config.debugging
216
+ @api_client.config.logger.debug "API called: KVApi#get_kv\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
217
+ end
218
+ return data, status_code, headers
219
+ end
220
+
221
+ # List KV entries
222
+ # Returns every non-expired key-value row in the given project, sorted ascending by key. Use `prefix` to scope to a key namespace (e.g. `prefix=user:42:` to fetch all entries for one user). Hard cap of 1,000 rows per response — for projects approaching that, paginate by walking prefix buckets.
223
+ # @param [Hash] opts the optional parameters
224
+ # @option opts [String] :project_id Project namespace. Defaults to &#x60;default&#x60;. Free-form string the customer chooses (typically the Studio project id). (default to 'default')
225
+ # @option opts [String] :prefix Restrict to keys starting with this string. &#x60;LIKE&#x60; wildcards (&#x60;%&#x60;, &#x60;_&#x60;) are escaped — pass them literally.
226
+ # @return [ListKv200Response]
227
+ def list_kv(opts = {})
228
+ data, _status_code, _headers = list_kv_with_http_info(opts)
229
+ data
230
+ end
231
+
232
+ # List KV entries
233
+ # Returns every non-expired key-value row in the given project, sorted ascending by key. Use &#x60;prefix&#x60; to scope to a key namespace (e.g. &#x60;prefix&#x3D;user:42:&#x60; to fetch all entries for one user). Hard cap of 1,000 rows per response — for projects approaching that, paginate by walking prefix buckets.
234
+ # @param [Hash] opts the optional parameters
235
+ # @option opts [String] :project_id Project namespace. Defaults to &#x60;default&#x60;. Free-form string the customer chooses (typically the Studio project id). (default to 'default')
236
+ # @option opts [String] :prefix Restrict to keys starting with this string. &#x60;LIKE&#x60; wildcards (&#x60;%&#x60;, &#x60;_&#x60;) are escaped — pass them literally.
237
+ # @return [Array<(ListKv200Response, Integer, Hash)>] ListKv200Response data, response status code and response headers
238
+ def list_kv_with_http_info(opts = {})
239
+ if @api_client.config.debugging
240
+ @api_client.config.logger.debug 'Calling API: KVApi.list_kv ...'
241
+ end
242
+ # resource path
243
+ local_var_path = '/v1/kv'
244
+
245
+ # query parameters
246
+ query_params = opts[:query_params] || {}
247
+ query_params[:'project_id'] = opts[:'project_id'] if !opts[:'project_id'].nil?
248
+ query_params[:'prefix'] = opts[:'prefix'] if !opts[:'prefix'].nil?
249
+
250
+ # header parameters
251
+ header_params = opts[:header_params] || {}
252
+ # HTTP header 'Accept' (if needed)
253
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
254
+
255
+ # form parameters
256
+ form_params = opts[:form_params] || {}
257
+
258
+ # http body (model)
259
+ post_body = opts[:debug_body]
260
+
261
+ # return_type
262
+ return_type = opts[:debug_return_type] || 'ListKv200Response'
263
+
264
+ # auth_names
265
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
266
+
267
+ new_options = opts.merge(
268
+ :operation => :"KVApi.list_kv",
269
+ :header_params => header_params,
270
+ :query_params => query_params,
271
+ :form_params => form_params,
272
+ :body => post_body,
273
+ :auth_names => auth_names,
274
+ :return_type => return_type
275
+ )
276
+
277
+ data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options)
278
+ if @api_client.config.debugging
279
+ @api_client.config.logger.debug "API called: KVApi#list_kv\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
280
+ end
281
+ return data, status_code, headers
282
+ end
283
+
284
+ # Set a KV entry
285
+ # Upserts a key. The full row is replaced — there is no partial update. Pass `ttl_seconds` (positive integer) to auto-expire the row; omit for no expiry. **Caps:** 64 KiB per row (key bytes + value JSON bytes), 1 MiB per customer (sum across ALL projects/keys). Over either cap returns 413.
286
+ # @param key [String]
287
+ # @param set_kv_request [SetKvRequest]
288
+ # @param [Hash] opts the optional parameters
289
+ # @option opts [String] :project_id (default to 'default')
290
+ # @return [ListKv200ResponseDataInner]
291
+ def set_kv(key, set_kv_request, opts = {})
292
+ data, _status_code, _headers = set_kv_with_http_info(key, set_kv_request, opts)
293
+ data
294
+ end
295
+
296
+ # Set a KV entry
297
+ # Upserts a key. The full row is replaced — there is no partial update. Pass &#x60;ttl_seconds&#x60; (positive integer) to auto-expire the row; omit for no expiry. **Caps:** 64 KiB per row (key bytes + value JSON bytes), 1 MiB per customer (sum across ALL projects/keys). Over either cap returns 413.
298
+ # @param key [String]
299
+ # @param set_kv_request [SetKvRequest]
300
+ # @param [Hash] opts the optional parameters
301
+ # @option opts [String] :project_id (default to 'default')
302
+ # @return [Array<(ListKv200ResponseDataInner, Integer, Hash)>] ListKv200ResponseDataInner data, response status code and response headers
303
+ def set_kv_with_http_info(key, set_kv_request, opts = {})
304
+ if @api_client.config.debugging
305
+ @api_client.config.logger.debug 'Calling API: KVApi.set_kv ...'
306
+ end
307
+ # verify the required parameter 'key' is set
308
+ if @api_client.config.client_side_validation && key.nil?
309
+ fail ArgumentError, "Missing the required parameter 'key' when calling KVApi.set_kv"
310
+ end
311
+ # verify the required parameter 'set_kv_request' is set
312
+ if @api_client.config.client_side_validation && set_kv_request.nil?
313
+ fail ArgumentError, "Missing the required parameter 'set_kv_request' when calling KVApi.set_kv"
314
+ end
315
+ # resource path
316
+ local_var_path = '/v1/kv/{key}'.sub('{key}', CGI.escape(key.to_s))
317
+
318
+ # query parameters
319
+ query_params = opts[:query_params] || {}
320
+ query_params[:'project_id'] = opts[:'project_id'] if !opts[:'project_id'].nil?
321
+
322
+ # header parameters
323
+ header_params = opts[:header_params] || {}
324
+ # HTTP header 'Accept' (if needed)
325
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
326
+ # HTTP header 'Content-Type'
327
+ content_type = @api_client.select_header_content_type(['application/json'])
328
+ if !content_type.nil?
329
+ header_params['Content-Type'] = content_type
330
+ end
331
+
332
+ # form parameters
333
+ form_params = opts[:form_params] || {}
334
+
335
+ # http body (model)
336
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(set_kv_request)
337
+
338
+ # return_type
339
+ return_type = opts[:debug_return_type] || 'ListKv200ResponseDataInner'
340
+
341
+ # auth_names
342
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
343
+
344
+ new_options = opts.merge(
345
+ :operation => :"KVApi.set_kv",
346
+ :header_params => header_params,
347
+ :query_params => query_params,
348
+ :form_params => form_params,
349
+ :body => post_body,
350
+ :auth_names => auth_names,
351
+ :return_type => return_type
352
+ )
353
+
354
+ data, status_code, headers = @api_client.call_api(:PUT, local_var_path, new_options)
355
+ if @api_client.config.debugging
356
+ @api_client.config.logger.debug "API called: KVApi#set_kv\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
357
+ end
358
+ return data, status_code, headers
359
+ end
360
+ end
361
+ end
@@ -1,7 +1,7 @@
1
1
  =begin
2
2
  #Repull API
3
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`, `pro`, `enterprise`) are enforced separately and also surface as 429s; they include `tier`, `scope`, and `resets_at` fields.
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.
5
5
 
6
6
  The version of the OpenAPI document: 1.0.0
7
7
  Contact: ivan@vanio.ai
@@ -88,7 +88,7 @@ module Repull
88
88
  end
89
89
 
90
90
  # AI-generate listing content
91
- # Generate guest-facing copy (title, summary, description, amenities, etc.) for a listing using Kimi K2. When `photos` are provided the vision model is used for photo-grounded copy. Persists into the listing by default.
91
+ # Generate guest-facing copy (title, summary, description, amenities, etc.) for a listing using Repull AI. When `photos` are provided the vision model is used for photo-grounded copy. Persists into the listing by default.
92
92
  # @param id [Integer]
93
93
  # @param [Hash] opts the optional parameters
94
94
  # @option opts [ListingGenerateContentRequest] :listing_generate_content_request
@@ -99,7 +99,7 @@ module Repull
99
99
  end
100
100
 
101
101
  # AI-generate listing content
102
- # Generate guest-facing copy (title, summary, description, amenities, etc.) for a listing using Kimi K2. When &#x60;photos&#x60; are provided the vision model is used for photo-grounded copy. Persists into the listing by default.
102
+ # Generate guest-facing copy (title, summary, description, amenities, etc.) for a listing using Repull AI. When &#x60;photos&#x60; are provided the vision model is used for photo-grounded copy. Persists into the listing by default.
103
103
  # @param id [Integer]
104
104
  # @param [Hash] opts the optional parameters
105
105
  # @option opts [ListingGenerateContentRequest] :listing_generate_content_request
@@ -158,10 +158,11 @@ module Repull
158
158
  end
159
159
 
160
160
  # Get a listing
161
- # Fetch a single listing by id. Returns the same shape as one element of the `GET /v1/listings` response, so you can bind the result to the same model. Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403, never reveals the listing's existence.
161
+ # Fetch a single listing by id. Returns the same shape as one element of the `GET /v1/listings` response, so you can bind the result to the same model. Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403, never reveals the listing's existence. **Optional expansions:** Pass `?include=amenities` to enrich the response with the listing's amenity rows (`[]` when the listing has none). Pass `?include=content` for the rich content slab (summary, description, space, house rules, etc. — sourced from `listings_descriptions` for the `en` locale; `null` when no row is stored). Pass `?include=details` for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.; `null` when no row is stored). Combine comma-separated, e.g. `?include=amenities,content,details`. The default response stays lean; consumers must opt in.
162
162
  # @param id [Integer] Repull listing id
163
163
  # @param [Hash] opts the optional parameters
164
164
  # @option opts [String] :x_schema Apply a custom or built-in schema to transform the response. Built-in: &#x60;native&#x60; (default), &#x60;calry&#x60;, &#x60;calry-v1&#x60;. Custom: any schema name created via &#x60;POST /v1/schema/custom&#x60;. Unknown / inactive schema names fall back to &#x60;native&#x60;.
165
+ # @option opts [String] :include Comma-separated optional expansions. Currently supported: &#x60;amenities&#x60;, &#x60;content&#x60;, &#x60;details&#x60;. Unknown values return 422 with a &#x60;valid_values&#x60; envelope.
165
166
  # @return [Listing]
166
167
  def get_listing(id, opts = {})
167
168
  data, _status_code, _headers = get_listing_with_http_info(id, opts)
@@ -169,10 +170,11 @@ module Repull
169
170
  end
170
171
 
171
172
  # Get a listing
172
- # Fetch a single listing by id. Returns the same shape as one element of the &#x60;GET /v1/listings&#x60; response, so you can bind the result to the same model. Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403, never reveals the listing&#39;s existence.
173
+ # Fetch a single listing by id. Returns the same shape as one element of the &#x60;GET /v1/listings&#x60; response, so you can bind the result to the same model. Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403, never reveals the listing&#39;s existence. **Optional expansions:** Pass &#x60;?include&#x3D;amenities&#x60; to enrich the response with the listing&#39;s amenity rows (&#x60;[]&#x60; when the listing has none). Pass &#x60;?include&#x3D;content&#x60; for the rich content slab (summary, description, space, house rules, etc. — sourced from &#x60;listings_descriptions&#x60; for the &#x60;en&#x60; locale; &#x60;null&#x60; when no row is stored). Pass &#x60;?include&#x3D;details&#x60; for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.; &#x60;null&#x60; when no row is stored). Combine comma-separated, e.g. &#x60;?include&#x3D;amenities,content,details&#x60;. The default response stays lean; consumers must opt in.
173
174
  # @param id [Integer] Repull listing id
174
175
  # @param [Hash] opts the optional parameters
175
176
  # @option opts [String] :x_schema Apply a custom or built-in schema to transform the response. Built-in: &#x60;native&#x60; (default), &#x60;calry&#x60;, &#x60;calry-v1&#x60;. Custom: any schema name created via &#x60;POST /v1/schema/custom&#x60;. Unknown / inactive schema names fall back to &#x60;native&#x60;.
177
+ # @option opts [String] :include Comma-separated optional expansions. Currently supported: &#x60;amenities&#x60;, &#x60;content&#x60;, &#x60;details&#x60;. Unknown values return 422 with a &#x60;valid_values&#x60; envelope.
176
178
  # @return [Array<(Listing, Integer, Hash)>] Listing data, response status code and response headers
177
179
  def get_listing_with_http_info(id, opts = {})
178
180
  if @api_client.config.debugging
@@ -187,6 +189,7 @@ module Repull
187
189
 
188
190
  # query parameters
189
191
  query_params = opts[:query_params] || {}
192
+ query_params[:'include'] = opts[:'include'] if !opts[:'include'].nil?
190
193
 
191
194
  # header parameters
192
195
  header_params = opts[:header_params] || {}
@@ -224,7 +227,7 @@ module Repull
224
227
  end
225
228
 
226
229
  # Per-channel publish status
227
- # Returns one row per platform the listing has been pushed/pulled to, with last push timestamp and any dirty fields not yet synced.
230
+ # Returns connection state and sync activity per channel. `channels` is sync activity (empty until first push). `connections` is connection state (populated as soon as a channel is linked). Recommended polling cadence: at most once per 30s per listing — for bulk views, prefer `GET /v1/listings` and filter client-side.
228
231
  # @param id [Integer]
229
232
  # @param [Hash] opts the optional parameters
230
233
  # @return [ListingPublishStatusResponse]
@@ -234,7 +237,7 @@ module Repull
234
237
  end
235
238
 
236
239
  # Per-channel publish status
237
- # Returns one row per platform the listing has been pushed/pulled to, with last push timestamp and any dirty fields not yet synced.
240
+ # Returns connection state and sync activity per channel. &#x60;channels&#x60; is sync activity (empty until first push). &#x60;connections&#x60; is connection state (populated as soon as a channel is linked). Recommended polling cadence: at most once per 30s per listing — for bulk views, prefer &#x60;GET /v1/listings&#x60; and filter client-side.
238
241
  # @param id [Integer]
239
242
  # @param [Hash] opts the optional parameters
240
243
  # @return [Array<(ListingPublishStatusResponse, Integer, Hash)>] ListingPublishStatusResponse data, response status code and response headers
@@ -287,14 +290,16 @@ module Repull
287
290
  end
288
291
 
289
292
  # List listings
290
- # Cursor-paginated list of listings owned by the authenticated workspace. Use `pagination.nextCursor` from one response as the `cursor` query param of the next request to walk the full set. Filters: `q` (substring on name/street/city), `status`, `channel`.
293
+ # Cursor-paginated list of listings owned by the authenticated workspace. Use `pagination.nextCursor` from one response as the `cursor` query param of the next request to walk the full set. `?offset=` is also accepted as a first-class alias for shallow paging (0..10000) — see the `offset` parameter below. Mutually exclusive with `cursor`. Filters: `q` (substring on name/street/city), `status`, `channel`. **Optional expansions:** Pass `?include=content` to enrich each row with the rich content slab (summary, description, space, house rules, etc. — sourced from `listings_descriptions` for the `en` locale). Pass `?include=details` for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.). Both default to `null` per row when the underlying `listings_descriptions` / `listings_details` row is missing — distinct from the field being absent (which signals the expansion was not requested). Combine comma-separated, e.g. `?include=content,details`. The default response stays lean; consumers must opt in.
291
294
  # @param [Hash] opts the optional parameters
292
295
  # @option opts [String] :x_schema Apply a custom or built-in schema to transform the response. Built-in: &#x60;native&#x60; (default), &#x60;calry&#x60;, &#x60;calry-v1&#x60;. Custom: any schema name created via &#x60;POST /v1/schema/custom&#x60;. Unknown / inactive schema names fall back to &#x60;native&#x60;.
293
296
  # @option opts [String] :cursor Opaque cursor returned in the previous response&#39;s &#x60;pagination.nextCursor&#x60;. Omit to fetch the first page.
297
+ # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with &#x60;cursor&#x60; — passing both returns 422. Accepts integers in &#x60;[0, 10000]&#x60;; deeper walks must use &#x60;cursor&#x60; (constant per-page cost). The response always includes &#x60;pagination.next_cursor&#x60; so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
294
298
  # @option opts [Integer] :limit Max items per page. Hard cap is 100. (default to 20)
295
299
  # @option opts [String] :q Case-insensitive substring search on name, street, or city.
296
300
  # @option opts [String] :status Filter by listing status.
297
301
  # @option opts [String] :channel Restrict to listings published on the given channel (&#x60;airbnb&#x60;, &#x60;booking&#x60;, &#x60;vrbo&#x60;, etc.). Joins through &#x60;listing_platform_links&#x60; and matches active links only.
302
+ # @option opts [String] :include Comma-separated optional expansions. Currently supported: &#x60;content&#x60;, &#x60;details&#x60;. Unknown values return 422 with a &#x60;valid_values&#x60; envelope. (Note: &#x60;amenities&#x60; is not yet supported on the list endpoint — use the detail endpoint to fetch amenity rows for a single listing.)
298
303
  # @return [ListingListResponse]
299
304
  def list_listings(opts = {})
300
305
  data, _status_code, _headers = list_listings_with_http_info(opts)
@@ -302,19 +307,29 @@ module Repull
302
307
  end
303
308
 
304
309
  # List listings
305
- # Cursor-paginated list of listings owned by the authenticated workspace. Use &#x60;pagination.nextCursor&#x60; from one response as the &#x60;cursor&#x60; query param of the next request to walk the full set. Filters: &#x60;q&#x60; (substring on name/street/city), &#x60;status&#x60;, &#x60;channel&#x60;.
310
+ # Cursor-paginated list of listings owned by the authenticated workspace. Use &#x60;pagination.nextCursor&#x60; from one response as the &#x60;cursor&#x60; query param of the next request to walk the full set. &#x60;?offset&#x3D;&#x60; is also accepted as a first-class alias for shallow paging (0..10000) — see the &#x60;offset&#x60; parameter below. Mutually exclusive with &#x60;cursor&#x60;. Filters: &#x60;q&#x60; (substring on name/street/city), &#x60;status&#x60;, &#x60;channel&#x60;. **Optional expansions:** Pass &#x60;?include&#x3D;content&#x60; to enrich each row with the rich content slab (summary, description, space, house rules, etc. — sourced from &#x60;listings_descriptions&#x60; for the &#x60;en&#x60; locale). Pass &#x60;?include&#x3D;details&#x60; for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.). Both default to &#x60;null&#x60; per row when the underlying &#x60;listings_descriptions&#x60; / &#x60;listings_details&#x60; row is missing — distinct from the field being absent (which signals the expansion was not requested). Combine comma-separated, e.g. &#x60;?include&#x3D;content,details&#x60;. The default response stays lean; consumers must opt in.
306
311
  # @param [Hash] opts the optional parameters
307
312
  # @option opts [String] :x_schema Apply a custom or built-in schema to transform the response. Built-in: &#x60;native&#x60; (default), &#x60;calry&#x60;, &#x60;calry-v1&#x60;. Custom: any schema name created via &#x60;POST /v1/schema/custom&#x60;. Unknown / inactive schema names fall back to &#x60;native&#x60;.
308
313
  # @option opts [String] :cursor Opaque cursor returned in the previous response&#39;s &#x60;pagination.nextCursor&#x60;. Omit to fetch the first page.
314
+ # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with &#x60;cursor&#x60; — passing both returns 422. Accepts integers in &#x60;[0, 10000]&#x60;; deeper walks must use &#x60;cursor&#x60; (constant per-page cost). The response always includes &#x60;pagination.next_cursor&#x60; so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
309
315
  # @option opts [Integer] :limit Max items per page. Hard cap is 100. (default to 20)
310
316
  # @option opts [String] :q Case-insensitive substring search on name, street, or city.
311
317
  # @option opts [String] :status Filter by listing status.
312
318
  # @option opts [String] :channel Restrict to listings published on the given channel (&#x60;airbnb&#x60;, &#x60;booking&#x60;, &#x60;vrbo&#x60;, etc.). Joins through &#x60;listing_platform_links&#x60; and matches active links only.
319
+ # @option opts [String] :include Comma-separated optional expansions. Currently supported: &#x60;content&#x60;, &#x60;details&#x60;. Unknown values return 422 with a &#x60;valid_values&#x60; envelope. (Note: &#x60;amenities&#x60; is not yet supported on the list endpoint — use the detail endpoint to fetch amenity rows for a single listing.)
313
320
  # @return [Array<(ListingListResponse, Integer, Hash)>] ListingListResponse data, response status code and response headers
314
321
  def list_listings_with_http_info(opts = {})
315
322
  if @api_client.config.debugging
316
323
  @api_client.config.logger.debug 'Calling API: ListingsApi.list_listings ...'
317
324
  end
325
+ if @api_client.config.client_side_validation && !opts[:'offset'].nil? && opts[:'offset'] > 10000
326
+ fail ArgumentError, 'invalid value for "opts[:"offset"]" when calling ListingsApi.list_listings, must be smaller than or equal to 10000.'
327
+ end
328
+
329
+ if @api_client.config.client_side_validation && !opts[:'offset'].nil? && opts[:'offset'] < 0
330
+ fail ArgumentError, 'invalid value for "opts[:"offset"]" when calling ListingsApi.list_listings, must be greater than or equal to 0.'
331
+ end
332
+
318
333
  if @api_client.config.client_side_validation && !opts[:'limit'].nil? && opts[:'limit'] > 100
319
334
  fail ArgumentError, 'invalid value for "opts[:"limit"]" when calling ListingsApi.list_listings, must be smaller than or equal to 100.'
320
335
  end
@@ -333,10 +348,12 @@ module Repull
333
348
  # query parameters
334
349
  query_params = opts[:query_params] || {}
335
350
  query_params[:'cursor'] = opts[:'cursor'] if !opts[:'cursor'].nil?
351
+ query_params[:'offset'] = opts[:'offset'] if !opts[:'offset'].nil?
336
352
  query_params[:'limit'] = opts[:'limit'] if !opts[:'limit'].nil?
337
353
  query_params[:'q'] = opts[:'q'] if !opts[:'q'].nil?
338
354
  query_params[:'status'] = opts[:'status'] if !opts[:'status'].nil?
339
355
  query_params[:'channel'] = opts[:'channel'] if !opts[:'channel'].nil?
356
+ query_params[:'include'] = opts[:'include'] if !opts[:'include'].nil?
340
357
 
341
358
  # header parameters
342
359
  header_params = opts[:header_params] || {}
@@ -1,7 +1,7 @@
1
1
  =begin
2
2
  #Repull API
3
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`, `pro`, `enterprise`) are enforced separately and also surface as 429s; they include `tier`, `scope`, and `resets_at` fields.
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.
5
5
 
6
6
  The version of the OpenAPI document: 1.0.0
7
7
  Contact: ivan@vanio.ai
@@ -162,12 +162,13 @@ module Repull
162
162
  end
163
163
 
164
164
  # Paginated discovery catalog
165
- # Cursor-paginated, search-filterable catalog of every Atlas-tracked market the customer could expand into. Backed by the precomputed `market_summaries` table (>=5 active comps per city). Supports fuzzy `q` substring search (trigram-indexed), `country` (ISO 3166-1 alpha-2) filter, and `sort` (`listings_desc` | `name_asc`). Use the `nextCursor` from `pagination` to walk pages — the cursor is an opaque base64 token; do not parse it. `pagination.total` is the count of markets matching the current `q`/`country`/`min_listings` filter (across all pages). Renamed from the upstream's legacy `total_in_filter` so SDK consumers see the same `pagination.total` field as on every other list endpoint.
165
+ # Cursor-paginated, search-filterable catalog of every Atlas-tracked market the customer could expand into. Backed by the precomputed `market_summaries` table (>=5 active comps per city). Supports fuzzy `q` substring search (trigram-indexed), `country` (ISO 3166-1 alpha-2) filter, and `sort` (`listings_desc` | `name_asc`). Use the `nextCursor` from `pagination` to walk pages — the cursor is an opaque base64 token; do not parse it. `?offset=` is also accepted as a first-class alias for shallow paging (0..10000) — see the `offset` parameter below. Mutually exclusive with `cursor`. `pagination.total` is the count of markets matching the current `q`/`country`/`min_listings` filter (across all pages) same shape as every other list endpoint.
166
166
  # @param [Hash] opts the optional parameters
167
167
  # @option opts [String] :q Substring match on city name (case-insensitive).
168
168
  # @option opts [String] :country ISO 3166-1 alpha-2 (e.g. &#x60;US&#x60;, &#x60;ES&#x60;).
169
169
  # @option opts [Integer] :min_listings Minimum comp-set size — cities with fewer active comps are excluded. (default to 5)
170
170
  # @option opts [String] :cursor Opaque cursor returned by the previous page&#39;s &#x60;pagination.nextCursor&#x60;.
171
+ # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with &#x60;cursor&#x60; — passing both returns 422. Accepts integers in &#x60;[0, 10000]&#x60;; deeper walks must use &#x60;cursor&#x60; (constant per-page cost). The response always includes &#x60;pagination.next_cursor&#x60; so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
171
172
  # @option opts [Integer] :limit (default to 30)
172
173
  # @option opts [String] :sort (default to 'listings_desc')
173
174
  # @return [MarketBrowseResponse]
@@ -177,12 +178,13 @@ module Repull
177
178
  end
178
179
 
179
180
  # Paginated discovery catalog
180
- # Cursor-paginated, search-filterable catalog of every Atlas-tracked market the customer could expand into. Backed by the precomputed &#x60;market_summaries&#x60; table (&gt;&#x3D;5 active comps per city). Supports fuzzy &#x60;q&#x60; substring search (trigram-indexed), &#x60;country&#x60; (ISO 3166-1 alpha-2) filter, and &#x60;sort&#x60; (&#x60;listings_desc&#x60; | &#x60;name_asc&#x60;). Use the &#x60;nextCursor&#x60; from &#x60;pagination&#x60; to walk pages — the cursor is an opaque base64 token; do not parse it. &#x60;pagination.total&#x60; is the count of markets matching the current &#x60;q&#x60;/&#x60;country&#x60;/&#x60;min_listings&#x60; filter (across all pages). Renamed from the upstream&#39;s legacy &#x60;total_in_filter&#x60; so SDK consumers see the same &#x60;pagination.total&#x60; field as on every other list endpoint.
181
+ # Cursor-paginated, search-filterable catalog of every Atlas-tracked market the customer could expand into. Backed by the precomputed &#x60;market_summaries&#x60; table (&gt;&#x3D;5 active comps per city). Supports fuzzy &#x60;q&#x60; substring search (trigram-indexed), &#x60;country&#x60; (ISO 3166-1 alpha-2) filter, and &#x60;sort&#x60; (&#x60;listings_desc&#x60; | &#x60;name_asc&#x60;). Use the &#x60;nextCursor&#x60; from &#x60;pagination&#x60; to walk pages — the cursor is an opaque base64 token; do not parse it. &#x60;?offset&#x3D;&#x60; is also accepted as a first-class alias for shallow paging (0..10000) — see the &#x60;offset&#x60; parameter below. Mutually exclusive with &#x60;cursor&#x60;. &#x60;pagination.total&#x60; is the count of markets matching the current &#x60;q&#x60;/&#x60;country&#x60;/&#x60;min_listings&#x60; filter (across all pages) same shape as every other list endpoint.
181
182
  # @param [Hash] opts the optional parameters
182
183
  # @option opts [String] :q Substring match on city name (case-insensitive).
183
184
  # @option opts [String] :country ISO 3166-1 alpha-2 (e.g. &#x60;US&#x60;, &#x60;ES&#x60;).
184
185
  # @option opts [Integer] :min_listings Minimum comp-set size — cities with fewer active comps are excluded. (default to 5)
185
186
  # @option opts [String] :cursor Opaque cursor returned by the previous page&#39;s &#x60;pagination.nextCursor&#x60;.
187
+ # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with &#x60;cursor&#x60; — passing both returns 422. Accepts integers in &#x60;[0, 10000]&#x60;; deeper walks must use &#x60;cursor&#x60; (constant per-page cost). The response always includes &#x60;pagination.next_cursor&#x60; so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
186
188
  # @option opts [Integer] :limit (default to 30)
187
189
  # @option opts [String] :sort (default to 'listings_desc')
188
190
  # @return [Array<(MarketBrowseResponse, Integer, Hash)>] MarketBrowseResponse data, response status code and response headers
@@ -202,6 +204,14 @@ module Repull
202
204
  fail ArgumentError, 'invalid value for "opts[:"min_listings"]" when calling MarketsApi.list_market_browse, must be greater than or equal to 0.'
203
205
  end
204
206
 
207
+ if @api_client.config.client_side_validation && !opts[:'offset'].nil? && opts[:'offset'] > 10000
208
+ fail ArgumentError, 'invalid value for "opts[:"offset"]" when calling MarketsApi.list_market_browse, must be smaller than or equal to 10000.'
209
+ end
210
+
211
+ if @api_client.config.client_side_validation && !opts[:'offset'].nil? && opts[:'offset'] < 0
212
+ fail ArgumentError, 'invalid value for "opts[:"offset"]" when calling MarketsApi.list_market_browse, must be greater than or equal to 0.'
213
+ end
214
+
205
215
  if @api_client.config.client_side_validation && !opts[:'limit'].nil? && opts[:'limit'] > 100
206
216
  fail ArgumentError, 'invalid value for "opts[:"limit"]" when calling MarketsApi.list_market_browse, must be smaller than or equal to 100.'
207
217
  end
@@ -223,6 +233,7 @@ module Repull
223
233
  query_params[:'country'] = opts[:'country'] if !opts[:'country'].nil?
224
234
  query_params[:'min_listings'] = opts[:'min_listings'] if !opts[:'min_listings'].nil?
225
235
  query_params[:'cursor'] = opts[:'cursor'] if !opts[:'cursor'].nil?
236
+ query_params[:'offset'] = opts[:'offset'] if !opts[:'offset'].nil?
226
237
  query_params[:'limit'] = opts[:'limit'] if !opts[:'limit'].nil?
227
238
  query_params[:'sort'] = opts[:'sort'] if !opts[:'sort'].nil?
228
239
 
@@ -1,7 +1,7 @@
1
1
  =begin
2
2
  #Repull API
3
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`, `pro`, `enterprise`) are enforced separately and also surface as 429s; they include `tier`, `scope`, and `resets_at` fields.
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.
5
5
 
6
6
  The version of the OpenAPI document: 1.0.0
7
7
  Contact: ivan@vanio.ai