repull 0.2.10 → 0.2.14

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/repull/api/airbnb_api.rb +332 -99
  4. data/lib/repull/api/atlas_api.rb +5 -5
  5. data/lib/repull/api/availability_api.rb +145 -3
  6. data/lib/repull/api/billing_api.rb +7 -7
  7. data/lib/repull/api/booking_com_api.rb +210 -54
  8. data/lib/repull/api/connect_api.rb +761 -8
  9. data/lib/repull/api/conversations_api.rb +92 -11
  10. data/lib/repull/api/guests_api.rb +82 -7
  11. data/lib/repull/api/health_api.rb +318 -0
  12. data/lib/repull/api/kv_api.rb +1 -1
  13. data/lib/repull/api/listings_api.rb +303 -24
  14. data/lib/repull/api/markets_api.rb +3 -3
  15. data/lib/repull/api/plumguide_api.rb +19 -11
  16. data/lib/repull/api/pricing_api.rb +15 -15
  17. data/lib/repull/api/properties_api.rb +12 -9
  18. data/lib/repull/api/quotes_api.rb +109 -0
  19. data/lib/repull/api/reservations_api.rb +166 -7
  20. data/lib/repull/api/reviews_api.rb +81 -7
  21. data/lib/repull/api/schema_api.rb +1 -1
  22. data/lib/repull/api/system_api.rb +1 -1
  23. data/lib/repull/api/vrbo_api.rb +10 -10
  24. data/lib/repull/api/webhooks_api.rb +11 -9
  25. data/lib/repull/api_client.rb +1 -1
  26. data/lib/repull/api_error.rb +1 -1
  27. data/lib/repull/api_model_base.rb +1 -1
  28. data/lib/repull/configuration.rb +1 -1
  29. data/lib/repull/models/account_created_event.rb +1 -1
  30. data/lib/repull/models/account_created_payload.rb +1 -1
  31. data/lib/repull/models/account_disconnected_event.rb +1 -1
  32. data/lib/repull/models/account_disconnected_payload.rb +1 -1
  33. data/lib/repull/models/acknowledge_booking_reservations_request.rb +1 -1
  34. data/lib/repull/models/ai_operation.rb +1 -1
  35. data/lib/repull/models/ai_operation_completed_event.rb +1 -1
  36. data/lib/repull/models/ai_operation_completed_payload.rb +1 -1
  37. data/lib/repull/models/ai_operation_failed_event.rb +1 -1
  38. data/lib/repull/models/ai_operation_failed_payload.rb +1 -1
  39. data/lib/repull/models/ai_operation_failed_payload_error.rb +1 -1
  40. data/lib/repull/models/airbnb_alteration.rb +154 -10
  41. data/lib/repull/models/airbnb_amenity.rb +1 -1
  42. data/lib/repull/models/airbnb_availability_write_request.rb +20 -1
  43. data/lib/repull/models/airbnb_calendar_operation.rb +97 -8
  44. data/lib/repull/models/airbnb_connection.rb +4 -4
  45. data/lib/repull/models/airbnb_connection_accessibility_amenities_inner.rb +1 -1
  46. data/lib/repull/models/airbnb_connection_amenities_inner.rb +1 -1
  47. data/lib/repull/models/airbnb_connection_host.rb +1 -1
  48. data/lib/repull/models/airbnb_connection_response.rb +1 -1
  49. data/lib/repull/models/airbnb_connection_summary.rb +1 -1
  50. data/lib/repull/models/airbnb_data_freshness.rb +3 -3
  51. data/lib/repull/models/airbnb_listing.rb +2 -2
  52. data/lib/repull/models/airbnb_listing_action_request.rb +1 -1
  53. data/lib/repull/models/airbnb_listing_list_response.rb +2 -2
  54. data/lib/repull/models/airbnb_pricing_write_request.rb +54 -3
  55. data/lib/repull/models/airbnb_pricing_write_request_records_inner.rb +236 -0
  56. data/lib/repull/models/airbnb_reservation.rb +1 -1
  57. data/lib/repull/models/airbnb_reservation_list_response.rb +2 -2
  58. data/lib/repull/models/airbnb_review.rb +1 -1
  59. data/lib/repull/models/airbnb_review_list_response.rb +1 -1
  60. data/lib/repull/models/airbnb_thread.rb +1 -1
  61. data/lib/repull/models/airbnb_thread_list_response.rb +1 -1
  62. data/lib/repull/models/airbnb_transaction.rb +502 -0
  63. data/lib/repull/models/airbnb_transaction_guest_breakdown.rb +171 -0
  64. data/lib/repull/models/airbnb_transaction_host_breakdown.rb +246 -0
  65. data/lib/repull/models/airbnb_transaction_payout.rb +175 -0
  66. data/lib/repull/models/{booking_conversation_list_response.rb → alteration_change.rb} +22 -19
  67. data/lib/repull/models/alteration_webhook_object.rb +296 -0
  68. data/lib/repull/models/availability_batch_write_request.rb +296 -0
  69. data/lib/repull/models/availability_write_request.rb +259 -0
  70. data/lib/repull/models/availability_write_result.rb +178 -0
  71. data/lib/repull/models/availability_write_result_synced.rb +176 -0
  72. data/lib/repull/models/availability_write_settings.rb +215 -0
  73. data/lib/repull/models/booking_availability_state_response.rb +149 -0
  74. data/lib/repull/models/booking_availability_update.rb +1 -1
  75. data/lib/repull/models/booking_availability_update_request.rb +1 -1
  76. data/lib/repull/models/booking_availability_update_request_property_id.rb +1 -1
  77. data/lib/repull/models/booking_availability_update_request_updates_inner.rb +1 -1
  78. data/lib/repull/models/booking_connect_listing_option.rb +1 -1
  79. data/lib/repull/models/booking_connect_room.rb +1 -1
  80. data/lib/repull/models/booking_connect_rooms_response.rb +39 -5
  81. data/lib/repull/models/booking_conversation.rb +1 -1
  82. data/lib/repull/models/booking_pricing_rate_update.rb +1 -1
  83. data/lib/repull/models/booking_pricing_rate_update_date_range.rb +1 -1
  84. data/lib/repull/models/booking_pricing_rate_update_restrictions.rb +1 -1
  85. data/lib/repull/models/booking_pricing_response.rb +1 -1
  86. data/lib/repull/models/booking_pricing_update_request.rb +1 -1
  87. data/lib/repull/models/booking_pricing_update_response.rb +1 -1
  88. data/lib/repull/models/booking_property.rb +1 -1
  89. data/lib/repull/models/booking_reservation.rb +277 -0
  90. data/lib/repull/models/booking_reservation_room.rb +271 -0
  91. data/lib/repull/models/booking_room_mapping.rb +18 -8
  92. data/lib/repull/models/booking_rooms_rates_response.rb +170 -0
  93. data/lib/repull/models/booking_rooms_rates_response_rooms_inner.rb +170 -0
  94. data/lib/repull/models/booking_rooms_rates_response_rooms_inner_rates_inner.rb +212 -0
  95. data/lib/repull/models/booking_setup_request.rb +1 -1
  96. data/lib/repull/models/booking_verify_hotel_request.rb +1 -1
  97. data/lib/repull/models/booking_verify_hotel_response.rb +40 -6
  98. data/lib/repull/models/bulk_pricing_failure.rb +1 -1
  99. data/lib/repull/models/bulk_pricing_item.rb +1 -1
  100. data/lib/repull/models/bulk_pricing_request.rb +1 -1
  101. data/lib/repull/models/bulk_pricing_response.rb +1 -1
  102. data/lib/repull/models/calendar_day.rb +1 -1
  103. data/lib/repull/models/calendar_response.rb +1 -1
  104. data/lib/repull/models/calendar_updated_event.rb +1 -1
  105. data/lib/repull/models/calendar_updated_payload.rb +1 -1
  106. data/lib/repull/models/calendar_updated_payload_range.rb +1 -1
  107. data/lib/repull/models/clear_kv200_response.rb +1 -1
  108. data/lib/repull/models/connect_host.rb +1 -1
  109. data/lib/repull/models/connect_provider.rb +1 -1
  110. data/lib/repull/models/connect_provider_list_response.rb +1 -1
  111. data/lib/repull/models/connect_session.rb +1 -1
  112. data/lib/repull/models/connect_status.rb +18 -6
  113. data/lib/repull/models/connect_status_accounts_inner.rb +188 -0
  114. data/lib/repull/models/connection.rb +1 -1
  115. data/lib/repull/models/connection_list_response.rb +1 -1
  116. data/lib/repull/models/conversation.rb +13 -2
  117. data/lib/repull/models/conversation_detail.rb +13 -2
  118. data/lib/repull/models/conversation_guest.rb +1 -1
  119. data/lib/repull/models/conversation_guest_contact.rb +1 -1
  120. data/lib/repull/models/conversation_host.rb +1 -1
  121. data/lib/repull/models/conversation_list_response.rb +1 -1
  122. data/lib/repull/models/conversation_message_attachment.rb +1 -1
  123. data/lib/repull/models/create_airbnb_alteration_request.rb +1 -1
  124. data/lib/repull/models/create_airbnb_offer_request.rb +1 -1
  125. data/lib/repull/models/create_billing_checkout_request.rb +1 -1
  126. data/lib/repull/models/create_booking_webhook_request.rb +1 -1
  127. data/lib/repull/models/create_connect_session_request.rb +1 -1
  128. data/lib/repull/models/create_connection_request.rb +2 -2
  129. data/lib/repull/models/create_webhook_request.rb +1 -1
  130. data/lib/repull/models/custom_schema.rb +1 -1
  131. data/lib/repull/models/custom_schema_create.rb +1 -1
  132. data/lib/repull/models/custom_schema_create_response.rb +1 -1
  133. data/lib/repull/models/custom_schema_delete_response.rb +1 -1
  134. data/lib/repull/models/custom_schema_list_response.rb +1 -1
  135. data/lib/repull/models/custom_schema_summary.rb +1 -1
  136. data/lib/repull/models/custom_schema_update.rb +1 -1
  137. data/lib/repull/models/delete_airbnb_listing_photo200_response.rb +1 -1
  138. data/lib/repull/models/delete_connection200_response.rb +234 -0
  139. data/lib/repull/models/delete_kv200_response.rb +1 -1
  140. data/lib/repull/models/error.rb +1 -1
  141. data/lib/repull/models/error_error.rb +14 -2
  142. data/lib/repull/models/error_error_support.rb +1 -1
  143. data/lib/repull/models/get_airbnb_alteration200_response.rb +2 -2
  144. data/lib/repull/models/get_airbnb_checkin_guide200_response.rb +192 -0
  145. data/lib/repull/models/get_airbnb_listing_quality200_response.rb +2 -2
  146. data/lib/repull/models/get_airbnb_listing_settings200_response.rb +2 -2
  147. data/lib/repull/models/get_airbnb_thread200_response.rb +2 -2
  148. data/lib/repull/models/get_health200_response.rb +1 -1
  149. data/lib/repull/models/get_usage_logs200_response.rb +1 -1
  150. data/lib/repull/models/get_usage_logs200_response_data_inner.rb +1 -1
  151. data/lib/repull/models/get_usage_logs200_response_pagination.rb +3 -3
  152. data/lib/repull/models/get_usage_summary200_response.rb +2 -2
  153. data/lib/repull/models/get_usage_summary200_response_breakdown_inner.rb +1 -1
  154. data/lib/repull/models/get_usage_summary200_response_limits.rb +3 -3
  155. data/lib/repull/models/get_usage_summary200_response_remaining.rb +2 -2
  156. data/lib/repull/models/get_usage_summary200_response_status_distribution.rb +1 -1
  157. data/lib/repull/models/get_usage_summary200_response_timeline_inner.rb +1 -1
  158. data/lib/repull/models/get_usage_summary200_response_totals.rb +1 -1
  159. data/lib/repull/models/get_usage_summary200_response_used.rb +2 -2
  160. data/lib/repull/models/get_usage_tier200_response.rb +2 -2
  161. data/lib/repull/models/get_usage_tier200_response_limits.rb +4 -4
  162. data/lib/repull/models/get_usage_tier200_response_remaining.rb +3 -3
  163. data/lib/repull/models/get_usage_tier200_response_used.rb +3 -3
  164. data/lib/repull/models/guest.rb +1 -1
  165. data/lib/repull/models/guest_contact.rb +1 -1
  166. data/lib/repull/models/guest_create_request.rb +250 -0
  167. data/lib/repull/models/guest_create_response.rb +227 -0
  168. data/lib/repull/models/guest_create_response_contacts_inner.rb +199 -0
  169. data/lib/repull/models/guest_flag.rb +1 -1
  170. data/lib/repull/models/guest_list_response.rb +1 -1
  171. data/lib/repull/models/guest_note.rb +1 -1
  172. data/lib/repull/models/guest_profile.rb +1 -1
  173. data/lib/repull/models/guest_reservations_summary.rb +1 -1
  174. data/lib/repull/models/list_airbnb_alterations200_response.rb +2 -2
  175. data/lib/repull/models/list_airbnb_listing_amenities200_response.rb +2 -2
  176. data/lib/repull/models/list_airbnb_listing_amenities200_response_data.rb +1 -1
  177. data/lib/repull/models/list_airbnb_transactions200_response.rb +3 -3
  178. data/lib/repull/models/list_booking_reservations200_response.rb +104 -0
  179. data/lib/repull/models/list_kv200_response.rb +1 -1
  180. data/lib/repull/models/list_kv200_response_data_inner.rb +1 -1
  181. data/lib/repull/models/list_kv200_response_pagination.rb +2 -2
  182. data/lib/repull/models/listing.rb +2 -2
  183. data/lib/repull/models/listing_active_request.rb +2 -2
  184. data/lib/repull/models/listing_active_response.rb +1 -1
  185. data/lib/repull/models/listing_address.rb +1 -1
  186. data/lib/repull/models/listing_amenity.rb +1 -1
  187. data/lib/repull/models/listing_channel.rb +1 -1
  188. data/lib/repull/models/listing_comp.rb +1 -1
  189. data/lib/repull/models/listing_comp_nightly.rb +1 -1
  190. data/lib/repull/models/listing_comp_ratings.rb +1 -1
  191. data/lib/repull/models/listing_comps_response.rb +1 -1
  192. data/lib/repull/models/listing_content.rb +1 -1
  193. data/lib/repull/models/listing_content_update_request.rb +1 -1
  194. data/lib/repull/models/listing_content_update_request_address.rb +1 -1
  195. data/lib/repull/models/listing_content_update_request_amenities.rb +1 -1
  196. data/lib/repull/models/listing_content_update_request_amenities_one_of_inner.rb +1 -1
  197. data/lib/repull/models/listing_content_update_request_occupancy.rb +1 -1
  198. data/lib/repull/models/listing_content_update_request_photos_inner.rb +1 -1
  199. data/lib/repull/models/listing_content_update_request_photos_inner_one_of.rb +1 -1
  200. data/lib/repull/models/listing_content_update_request_policies.rb +1 -1
  201. data/lib/repull/models/listing_content_update_response.rb +1 -1
  202. data/lib/repull/models/listing_create_request.rb +1 -1
  203. data/lib/repull/models/listing_create_response.rb +1 -1
  204. data/lib/repull/models/listing_created_event.rb +1 -1
  205. data/lib/repull/models/listing_created_payload.rb +1 -1
  206. data/lib/repull/models/listing_created_payload_address.rb +1 -1
  207. data/lib/repull/models/listing_deleted_event.rb +1 -1
  208. data/lib/repull/models/listing_deleted_payload.rb +1 -1
  209. data/lib/repull/models/listing_details.rb +1 -1
  210. data/lib/repull/models/listing_generate_content_request.rb +1 -1
  211. data/lib/repull/models/listing_generate_content_response.rb +1 -1
  212. data/lib/repull/models/listing_list_response.rb +1 -1
  213. data/lib/repull/models/{studio_file.rb → listing_photo.rb} +23 -34
  214. data/lib/repull/models/listing_photo_delete_request.rb +165 -0
  215. data/lib/repull/models/{studio_error.rb → listing_photo_delete_response.rb} +20 -12
  216. data/lib/repull/models/listing_photo_upload_url_request.rb +203 -0
  217. data/lib/repull/models/listing_photo_upload_url_response.rb +198 -0
  218. data/lib/repull/models/{vrbo_listing_list_response.rb → listing_photos_response.rb} +19 -19
  219. data/lib/repull/models/listing_pricing_apply_request.rb +1 -1
  220. data/lib/repull/models/listing_pricing_apply_response.rb +1 -1
  221. data/lib/repull/models/listing_pricing_history_entry.rb +2 -2
  222. data/lib/repull/models/listing_pricing_history_response.rb +1 -1
  223. data/lib/repull/models/listing_pricing_recommendation.rb +1 -1
  224. data/lib/repull/models/listing_pricing_response.rb +1 -1
  225. data/lib/repull/models/listing_pricing_response_comp_summary.rb +1 -1
  226. data/lib/repull/models/listing_pricing_response_date_range.rb +1 -1
  227. data/lib/repull/models/listing_pricing_response_listing.rb +1 -1
  228. data/lib/repull/models/listing_pricing_strategy.rb +1 -1
  229. data/lib/repull/models/listing_pricing_strategy_input.rb +1 -1
  230. data/lib/repull/models/listing_publish_airbnb_request.rb +1 -1
  231. data/lib/repull/models/listing_publish_response.rb +1 -1
  232. data/lib/repull/models/listing_publish_status_channel.rb +1 -1
  233. data/lib/repull/models/listing_publish_status_connection.rb +1 -1
  234. data/lib/repull/models/listing_publish_status_response.rb +1 -1
  235. data/lib/repull/models/listing_quality_tier.rb +1 -1
  236. data/lib/repull/models/listing_segment.rb +1 -1
  237. data/lib/repull/models/listing_segment_recommendation.rb +1 -1
  238. data/lib/repull/models/listing_segments_response.rb +1 -1
  239. data/lib/repull/models/listing_segments_response_scope.rb +1 -1
  240. data/lib/repull/models/listing_status_batch_request.rb +212 -0
  241. data/lib/repull/models/listing_status_batch_response.rb +223 -0
  242. data/lib/repull/models/listing_updated_event.rb +1 -1
  243. data/lib/repull/models/listing_updated_payload.rb +1 -1
  244. data/lib/repull/models/map_airbnb_listing_request.rb +1 -1
  245. data/lib/repull/models/map_airbnb_listing_response.rb +1 -1
  246. data/lib/repull/models/map_connect_booking_rooms_request.rb +1 -1
  247. data/lib/repull/models/map_connect_booking_rooms_response.rb +16 -5
  248. data/lib/repull/models/market_browse_category.rb +1 -1
  249. data/lib/repull/models/market_browse_entry.rb +1 -1
  250. data/lib/repull/models/market_browse_featured.rb +1 -1
  251. data/lib/repull/models/market_browse_response.rb +1 -1
  252. data/lib/repull/models/market_calendar_day.rb +1 -1
  253. data/lib/repull/models/market_calendar_day_events_inner.rb +1 -1
  254. data/lib/repull/models/market_calendar_response.rb +1 -1
  255. data/lib/repull/models/market_detail_response.rb +1 -1
  256. data/lib/repull/models/market_detail_response_price_distribution_inner.rb +1 -1
  257. data/lib/repull/models/market_detail_response_property_type_mix_inner.rb +1 -1
  258. data/lib/repull/models/market_detail_response_supply_trend_inner.rb +1 -1
  259. data/lib/repull/models/market_detail_response_top_comps.rb +1 -1
  260. data/lib/repull/models/market_event.rb +1 -1
  261. data/lib/repull/models/market_my_listing.rb +1 -1
  262. data/lib/repull/models/market_summary.rb +1 -1
  263. data/lib/repull/models/market_top_comp.rb +1 -1
  264. data/lib/repull/models/markets_overview_response.rb +1 -1
  265. data/lib/repull/models/markets_overview_response_browse.rb +1 -1
  266. data/lib/repull/models/markets_overview_response_subscriptions.rb +1 -1
  267. data/lib/repull/models/markets_overview_response_totals.rb +1 -1
  268. data/lib/repull/models/message.rb +1 -1
  269. data/lib/repull/models/message_list_response.rb +1 -1
  270. data/lib/repull/models/pagination.rb +1 -1
  271. data/lib/repull/models/payment_completed_event.rb +1 -1
  272. data/lib/repull/models/payment_completed_payload.rb +1 -1
  273. data/lib/repull/models/payment_refunded_event.rb +1 -1
  274. data/lib/repull/models/payment_refunded_payload.rb +1 -1
  275. data/lib/repull/models/plumguide_listing.rb +1 -1
  276. data/lib/repull/models/plumguide_listing_list_response.rb +1 -1
  277. data/lib/repull/models/property.rb +17 -7
  278. data/lib/repull/models/property_availability.rb +33 -7
  279. data/lib/repull/models/property_availability_coverage.rb +222 -0
  280. data/lib/repull/models/property_availability_day.rb +4 -4
  281. data/lib/repull/models/property_list_response.rb +1 -1
  282. data/lib/repull/models/quote.rb +240 -0
  283. data/lib/repull/models/quote_pricing.rb +218 -0
  284. data/lib/repull/models/reply_booking_review200_response.rb +1 -1
  285. data/lib/repull/models/reply_booking_review_request.rb +1 -1
  286. data/lib/repull/models/reply_to_review201_response.rb +165 -0
  287. data/lib/repull/models/reply_to_review_request.rb +165 -0
  288. data/lib/repull/models/repull_ping_event.rb +1 -1
  289. data/lib/repull/models/repull_ping_payload.rb +1 -1
  290. data/lib/repull/models/reservation.rb +1 -1
  291. data/lib/repull/models/{studio_deployment.rb → reservation_alteration_created_event.rb} +67 -57
  292. data/lib/repull/models/reservation_alteration_created_payload.rb +177 -0
  293. data/lib/repull/models/reservation_alteration_responded_event.rb +217 -0
  294. data/lib/repull/models/reservation_alteration_responded_payload.rb +175 -0
  295. data/lib/repull/models/reservation_cancelled_event.rb +1 -1
  296. data/lib/repull/models/reservation_cancelled_payload.rb +1 -1
  297. data/lib/repull/models/reservation_create_request.rb +403 -0
  298. data/lib/repull/models/reservation_create_response.rb +233 -0
  299. data/lib/repull/models/reservation_created_event.rb +1 -1
  300. data/lib/repull/models/reservation_created_payload.rb +1 -1
  301. data/lib/repull/models/reservation_financials.rb +36 -8
  302. data/lib/repull/models/reservation_guest_financials.rb +173 -0
  303. data/lib/repull/models/reservation_guest_input.rb +193 -0
  304. data/lib/repull/models/reservation_host_financials.rb +207 -0
  305. data/lib/repull/models/reservation_list_response.rb +1 -1
  306. data/lib/repull/models/reservation_message_received_event.rb +1 -1
  307. data/lib/repull/models/reservation_message_received_payload.rb +1 -1
  308. data/lib/repull/models/reservation_message_received_payload_from.rb +1 -1
  309. data/lib/repull/models/reservation_money_line.rb +216 -0
  310. data/lib/repull/models/reservation_occupancy.rb +1 -1
  311. data/lib/repull/models/reservation_primary_guest.rb +1 -1
  312. data/lib/repull/models/reservation_update_request.rb +255 -0
  313. data/lib/repull/models/reservation_update_response.rb +240 -0
  314. data/lib/repull/models/reservation_updated_event.rb +1 -1
  315. data/lib/repull/models/reservation_updated_payload.rb +1 -1
  316. data/lib/repull/models/reservation_webhook_object.rb +1 -1
  317. data/lib/repull/models/respond_airbnb_review_request.rb +1 -1
  318. data/lib/repull/models/review.rb +14 -3
  319. data/lib/repull/models/review_category.rb +1 -1
  320. data/lib/repull/models/{sandbox_reset_result.rb → review_created_event.rb} +59 -59
  321. data/lib/repull/models/review_created_payload.rb +165 -0
  322. data/lib/repull/models/review_list_response.rb +1 -1
  323. data/lib/repull/models/review_responded_event.rb +217 -0
  324. data/lib/repull/models/review_responded_payload.rb +175 -0
  325. data/lib/repull/models/review_response.rb +1 -1
  326. data/lib/repull/models/review_webhook_object.rb +277 -0
  327. data/lib/repull/models/rotate_webhook_secret200_response.rb +1 -1
  328. data/lib/repull/models/select_connect_provider_request.rb +1 -1
  329. data/lib/repull/models/select_provider_response.rb +1 -1
  330. data/lib/repull/models/send_airbnb_message_request.rb +187 -0
  331. data/lib/repull/models/{sandbox_reset_result_deleted.rb → send_booking_message_request.rb} +53 -50
  332. data/lib/repull/models/{studio_error_error.rb → send_message_request.rb} +31 -50
  333. data/lib/repull/models/send_message_response.rb +241 -0
  334. data/lib/repull/models/set_kv_request.rb +1 -1
  335. data/lib/repull/models/{booking_property_list_response.rb → submit_beds24_credentials200_response.rb} +19 -19
  336. data/lib/repull/models/submit_beds24_credentials_request.rb +177 -0
  337. data/lib/repull/models/submit_bookingsync_credentials_request.rb +177 -0
  338. data/lib/repull/models/submit_guesty_credentials_request.rb +177 -0
  339. data/lib/repull/models/submit_hospitable_credentials_request.rb +177 -0
  340. data/lib/repull/models/submit_hostaway_credentials_request.rb +177 -0
  341. data/lib/repull/models/submit_igms_credentials_request.rb +177 -0
  342. data/lib/repull/models/submit_lodgify_credentials_request.rb +177 -0
  343. data/lib/repull/models/submit_ownerrez_credentials_request.rb +177 -0
  344. data/lib/repull/models/submit_smoobu_credentials_request.rb +177 -0
  345. data/lib/repull/models/submit_vrbo_credentials_request.rb +177 -0
  346. data/lib/repull/models/{sandbox_fixture_ref.rb → sync_airbnb_transactions200_response.rb} +36 -38
  347. data/lib/repull/models/sync_airbnb_transactions_request.rb +201 -0
  348. data/lib/repull/models/test_webhook_request.rb +1 -1
  349. data/lib/repull/models/update_airbnb_message_request.rb +1 -1
  350. data/lib/repull/models/update_booking_charges_request.rb +1 -1
  351. data/lib/repull/models/update_listing_pricing_strategy200_response.rb +1 -1
  352. data/lib/repull/models/update_webhook_request.rb +1 -1
  353. data/lib/repull/models/vrbo_listing.rb +1 -1
  354. data/lib/repull/models/vrbo_reservation.rb +1 -1
  355. data/lib/repull/models/vrbo_reservation_list_response.rb +1 -1
  356. data/lib/repull/models/webhook_delivery.rb +1 -1
  357. data/lib/repull/models/webhook_delivery_detail.rb +1 -1
  358. data/lib/repull/models/webhook_delivery_list_response.rb +1 -1
  359. data/lib/repull/models/webhook_event.rb +12 -4
  360. data/lib/repull/models/webhook_event_catalog.rb +1 -1
  361. data/lib/repull/models/webhook_event_catalog_domains_inner.rb +1 -1
  362. data/lib/repull/models/webhook_event_catalog_entry.rb +1 -1
  363. data/lib/repull/models/webhook_event_type.rb +6 -2
  364. data/lib/repull/models/webhook_list_response.rb +1 -1
  365. data/lib/repull/models/webhook_subscription.rb +1 -1
  366. data/lib/repull/version.rb +2 -2
  367. data/lib/repull.rb +75 -15
  368. data/openapi/v1.json +6119 -2021
  369. data/scripts/check-spec-freshness.py +399 -0
  370. data/scripts/regen.sh +32 -3
  371. metadata +77 -18
  372. data/lib/repull/api/sandbox_api.rb +0 -136
  373. data/lib/repull/models/sandbox_seed_result.rb +0 -278
  374. data/lib/repull/models/studio_generation.rb +0 -215
  375. data/lib/repull/models/studio_project.rb +0 -241
  376. data/spec/api/ai_api_spec.rb +0 -47
@@ -0,0 +1,399 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SDK spec-freshness guard.
4
+
5
+ Fails CI when this SDK's view of the Repull API disagrees with the live spec at
6
+ https://api.repull.dev/openapi.json.
7
+
8
+ WHY THIS EXISTS
9
+ ---------------
10
+ Every Repull SDK is generated from a committed snapshot of the OpenAPI spec
11
+ (openapi/v1.json). Nothing used to compare that snapshot to the live API, so
12
+ drift was invisible:
13
+
14
+ * repull-sdk and repull-dotnet carried a 89-path snapshot that still declared
15
+ ten /api/studio/* paths -- a different product on a different host -- inside
16
+ the public API types, plus four endpoints that only ever returned 501/404.
17
+ * Four SDKs still shipped /v1/sandbox/seed and /v1/sandbox/reset months after
18
+ the sandbox was deleted from the API.
19
+ * Every SDK was missing between 24 and 49 real endpoints, so those operations
20
+ were unreachable from any SDK.
21
+
22
+ None of that failed a single check.
23
+
24
+ TWO MODES
25
+ ---------
26
+ snapshot (generated SDKs: python, php, go, ruby, dotnet, ts, mcp)
27
+ Compares the PATH SET of the committed snapshot against the live spec and
28
+ fails in BOTH directions:
29
+ - a path in the snapshot that the API does not have (the /api/studio/*
30
+ and /v1/sandbox/* class of bug -- SDK ships dead code)
31
+ - a path the API has that the snapshot is missing (the reason dozens of
32
+ operations were unreachable -- SDK is behind)
33
+
34
+ source (hand-written SDKs with no snapshot: repull-ai-sdk)
35
+ Scans source files for hardcoded "/v1/..." request paths and fails if any of
36
+ them is absent from the live spec. One direction only: a hand-written SDK is
37
+ not obliged to cover every endpoint, but it must never ship a call to an
38
+ endpoint that does not exist. This is exactly the bug that let repull-ai-sdk
39
+ ship six tools against /v1/market/context, /v1/analytics/revenue,
40
+ /v1/analytics/occupancy, /v1/cleaning/rota, /v1/agent/quota and
41
+ /v1/agent/usage -- all six 404.
42
+
43
+ The scheduled (cron) run matters as much as the pull_request run: the API can
44
+ grow new endpoints without anyone touching the SDK repo, and a PR-only check
45
+ would never notice.
46
+
47
+ USAGE
48
+ python3 scripts/check-spec-freshness.py # snapshot mode, openapi/v1.json
49
+ python3 scripts/check-spec-freshness.py --snapshot path.json
50
+ python3 scripts/check-spec-freshness.py --mode source --source-dir src
51
+
52
+ ENV
53
+ REPULL_OPENAPI_URL override the live spec URL (default https://api.repull.dev/openapi.json)
54
+
55
+ EXIT CODES
56
+ 0 in sync
57
+ 1 drift detected
58
+ 2 could not fetch the live spec (network/HTTP) -- deliberately distinct
59
+ from 1 so an outage is not mistaken for drift
60
+ """
61
+
62
+ from __future__ import annotations
63
+
64
+ import argparse
65
+ import json
66
+ import os
67
+ import re
68
+ import subprocess
69
+ import sys
70
+ import time
71
+ import urllib.error
72
+ import urllib.request
73
+
74
+ DEFAULT_SPEC_URL = "https://api.repull.dev/openapi.json"
75
+ FETCH_ATTEMPTS = 3
76
+ FETCH_BACKOFF_SECONDS = 2
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # live spec
81
+ # ---------------------------------------------------------------------------
82
+
83
+ def _fetch_with_curl(url: str) -> str:
84
+ """
85
+ Fetch via curl.
86
+
87
+ api.repull.dev sits behind Cloudflare, which fingerprints the TLS handshake
88
+ (JA3/JA4) -- not just headers. Python's urllib/OpenSSL handshake gets a hard
89
+ 403 on EVERY request no matter what User-Agent or Accept headers are set,
90
+ while curl on the same machine gets a 200. Setting a browser User-Agent does
91
+ NOT help, because the block is below the HTTP layer.
92
+
93
+ So curl is the primary transport. It is preinstalled on ubuntu-latest (and
94
+ every other GitHub runner image), so this costs nothing in CI.
95
+ """
96
+ proc = subprocess.run(
97
+ [
98
+ "curl", "--silent", "--show-error", "--fail",
99
+ "--location", "--max-time", "30",
100
+ "--header", "Accept: application/json",
101
+ url,
102
+ ],
103
+ capture_output=True,
104
+ text=True,
105
+ )
106
+ if proc.returncode != 0:
107
+ raise RuntimeError(f"curl exit {proc.returncode}: {proc.stderr.strip()}")
108
+ return proc.stdout
109
+
110
+
111
+ def _fetch_with_urllib(url: str) -> str:
112
+ req = urllib.request.Request(url, headers={"Accept": "application/json"})
113
+ with urllib.request.urlopen(req, timeout=30) as resp:
114
+ if resp.status != 200:
115
+ raise RuntimeError(f"HTTP {resp.status}")
116
+ return resp.read().decode("utf-8")
117
+
118
+
119
+ def fetch_live_spec(url: str) -> dict:
120
+ last_err: Exception | None = None
121
+ for attempt in range(1, FETCH_ATTEMPTS + 1):
122
+ for transport in (_fetch_with_curl, _fetch_with_urllib):
123
+ try:
124
+ return json.loads(transport(url))
125
+ except FileNotFoundError as err: # curl missing -> try urllib
126
+ last_err = err
127
+ except Exception as err: # noqa: BLE001 - retry anything transient
128
+ last_err = err
129
+ if attempt < FETCH_ATTEMPTS:
130
+ print(
131
+ f" fetch attempt {attempt}/{FETCH_ATTEMPTS} failed ({last_err}); retrying...",
132
+ file=sys.stderr,
133
+ )
134
+ time.sleep(FETCH_BACKOFF_SECONDS * attempt)
135
+
136
+ print(f"\nERROR: could not fetch the live spec at {url}: {last_err}", file=sys.stderr)
137
+ print("This is a fetch failure, not drift. Re-run the job.", file=sys.stderr)
138
+ sys.exit(2)
139
+
140
+
141
+ LIVE_SPEC: dict | None = None
142
+ LOCAL_SPEC: dict | None = None
143
+
144
+ HTTP_METHODS = ("get", "post", "put", "patch", "delete", "head", "options")
145
+
146
+
147
+ def schema_fingerprint(spec: dict) -> set[str]:
148
+ """Every property as "Schema.path:type" -- the SHAPE, not the prose.
149
+
150
+ Operation inventory alone is not enough. Nineteen schema corrections shipped
151
+ -- ten fields renamed because the serializer camelCases keys, three list
152
+ responses that are arrays rather than objects, four ids that are strings
153
+ rather than integers, and lat/lng likewise -- and every SDK snapshot stayed
154
+ stale while this check printed OK, because not one path or method had moved.
155
+
156
+ Descriptions, examples and enums are deliberately excluded: prose changes
157
+ constantly and would make this cry wolf, and repull-ruby's regen strips
158
+ enums from its snapshot before generating, so comparing them would fail
159
+ there forever for a reason that has nothing to do with drift.
160
+ """
161
+ out: set[str] = set()
162
+
163
+ def walk(node, where, depth=0):
164
+ if not isinstance(node, dict) or depth > 12:
165
+ return
166
+ props = node.get("properties")
167
+ if isinstance(props, dict):
168
+ for k, v in props.items():
169
+ t = v.get("type") if isinstance(v, dict) else None
170
+ ref = v.get("$ref") if isinstance(v, dict) else None
171
+ out.add(f"{where}.{k}:{t or ref or '?'}")
172
+ walk(v, f"{where}.{k}", depth + 1)
173
+ items = node.get("items")
174
+ if isinstance(items, dict):
175
+ out.add(f"{where}[]:{items.get('type') or items.get('$ref') or '?'}")
176
+ walk(items, f"{where}[]", depth + 1)
177
+ if isinstance(node.get("required"), list):
178
+ for r in node["required"]:
179
+ out.add(f"{where}.required:{r}")
180
+
181
+ for name, sch in (spec.get("components", {}).get("schemas", {}) or {}).items():
182
+ t = sch.get("type") if isinstance(sch, dict) else None
183
+ out.add(f"{name}:{t or '?'}")
184
+ walk(sch, name)
185
+ return out
186
+
187
+
188
+ def spec_bare_paths(spec: dict, label: str) -> set[str]:
189
+ """Just the path keys, for source mode -- a literal in code has no method."""
190
+ paths = spec.get("paths")
191
+ if not isinstance(paths, dict):
192
+ print(f"ERROR: {label} has no usable 'paths' object", file=sys.stderr)
193
+ sys.exit(2)
194
+ return set(paths)
195
+
196
+
197
+ def spec_paths(spec: dict, label: str) -> set[str]:
198
+ """Every OPERATION, as "POST /v1/reservations" -- not just the path key.
199
+
200
+ Comparing bare path keys let a whole operation disappear unnoticed. Four
201
+ write endpoints (create a reservation, update a reservation, create a
202
+ guest, send a message to a guest) were added as new METHODS on paths that
203
+ already existed for GET, so the path set was byte-identical before and
204
+ after and every SDK silently lacked them while this check printed OK.
205
+
206
+ Method + path is the unit a caller actually reaches for, so it is the unit
207
+ the guard compares.
208
+ """
209
+ paths = spec.get("paths")
210
+ if not isinstance(paths, dict):
211
+ print(f"ERROR: {label} has no usable 'paths' object", file=sys.stderr)
212
+ sys.exit(2)
213
+ ops: set[str] = set()
214
+ for path, item in paths.items():
215
+ if not isinstance(item, dict):
216
+ continue
217
+ for method in item:
218
+ if method.lower() in HTTP_METHODS:
219
+ ops.add(f"{method.upper()} {path}")
220
+ return ops
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # snapshot mode
225
+ # ---------------------------------------------------------------------------
226
+
227
+ def run_snapshot_mode(snapshot_path: str, live: set[str]) -> int:
228
+ if not os.path.isfile(snapshot_path):
229
+ print(f"ERROR: snapshot not found at {snapshot_path}", file=sys.stderr)
230
+ return 2
231
+
232
+ with open(snapshot_path, encoding="utf-8") as handle:
233
+ local = spec_paths(json.load(handle), snapshot_path)
234
+
235
+ stale = sorted(local - live) # SDK declares it, API does not have it
236
+ missing = sorted(live - local) # API has it, SDK cannot reach it
237
+
238
+ # Schema-shape comparison. `live_spec`/`local_spec` are set by main().
239
+ schema_stale: list[str] = []
240
+ schema_missing: list[str] = []
241
+ if LIVE_SPEC is not None and LOCAL_SPEC is not None:
242
+ lf = schema_fingerprint(LOCAL_SPEC)
243
+ rf = schema_fingerprint(LIVE_SPEC)
244
+ schema_stale = sorted(lf - rf)
245
+ schema_missing = sorted(rf - lf)
246
+
247
+ print(f" snapshot : {snapshot_path} ({len(local)} operations)")
248
+ print(f" live : {len(live)} operations")
249
+
250
+ if not stale and not missing and not schema_stale and not schema_missing:
251
+ print(f"\nOK: snapshot matches the live API exactly ({len(local)} operations).")
252
+ return 0
253
+
254
+ print("\nFAIL: committed OpenAPI snapshot has drifted from the live API.\n")
255
+
256
+ if stale:
257
+ print(f" {len(stale)} operation(s) in the SDK that the API DOES NOT HAVE")
258
+ print(" (the SDK ships dead code -- these calls will 404):")
259
+ for path in stale:
260
+ print(f" - {path}")
261
+ print()
262
+
263
+ if missing:
264
+ print(f" {len(missing)} operation(s) the API HAS that the SDK IS MISSING")
265
+ print(" (these operations are unreachable from this SDK):")
266
+ for path in missing:
267
+ print(f" + {path}")
268
+ print()
269
+
270
+ if schema_stale or schema_missing:
271
+ n = len(schema_stale) + len(schema_missing)
272
+ print(f" {n} schema difference(s) -- the shapes the SDK types are built")
273
+ print(" from no longer match the API:")
274
+ for d in schema_missing[:12]:
275
+ print(f" + {d}")
276
+ for d in schema_stale[:12]:
277
+ print(f" - {d}")
278
+ if n > 24:
279
+ print(f" ... and {n - 24} more")
280
+ print()
281
+
282
+ print(" FIX: re-run this repo's regen script to pull the live spec and")
283
+ print(" regenerate the client, then commit the snapshot and the")
284
+ print(" generated code together. Never hand-edit generated types.")
285
+ return 1
286
+
287
+
288
+ # ---------------------------------------------------------------------------
289
+ # source mode
290
+ # ---------------------------------------------------------------------------
291
+
292
+ SOURCE_SUFFIXES = (".ts", ".tsx", ".js", ".mjs", ".py", ".go", ".rb", ".php", ".cs")
293
+ SKIP_DIRS = {
294
+ "node_modules", "dist", "build", "vendor", ".git", ".claude",
295
+ "__pycache__", "coverage", ".next", "bin", "obj",
296
+ }
297
+
298
+ # Matches a quoted/templated request path literal beginning with /v1/.
299
+ PATH_LITERAL = re.compile(r"""['"`](/v1/[A-Za-z0-9_\-./{}$@:]*)['"`]""")
300
+ # ${id} / {id} / :id / #{id} -> a single wildcard segment
301
+ INTERPOLATION = re.compile(r"\$\{[^}]*\}|#\{[^}]*\}|\{[^}]*\}|(?<=/):[A-Za-z_][A-Za-z0-9_]*")
302
+
303
+
304
+ def normalize(path: str) -> tuple[str, ...]:
305
+ """Turn a path into comparable segments, with parameters as a wildcard."""
306
+ path = path.split("?", 1)[0].rstrip("/")
307
+ segments: list[str] = []
308
+ for segment in path.split("/"):
309
+ if not segment:
310
+ continue
311
+ collapsed = INTERPOLATION.sub("\x00", segment)
312
+ segments.append("*" if "\x00" in collapsed else segment)
313
+ return tuple(segments)
314
+
315
+
316
+ def live_shapes(live: set[str]) -> set[tuple[str, ...]]:
317
+ return {normalize(p) for p in live}
318
+
319
+
320
+ def iter_source_files(root: str):
321
+ for dirpath, dirnames, filenames in os.walk(root):
322
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not d.startswith(".")]
323
+ for name in filenames:
324
+ if name.endswith(SOURCE_SUFFIXES) and ".test." not in name and ".spec." not in name:
325
+ yield os.path.join(dirpath, name)
326
+
327
+
328
+ def run_source_mode(source_dir: str, live: set[str]) -> int:
329
+ if not os.path.isdir(source_dir):
330
+ print(f"ERROR: source dir not found at {source_dir}", file=sys.stderr)
331
+ return 2
332
+
333
+ shapes = live_shapes(live)
334
+ found: dict[str, set[str]] = {}
335
+
336
+ for file_path in iter_source_files(source_dir):
337
+ try:
338
+ text = open(file_path, encoding="utf-8", errors="replace").read()
339
+ except OSError:
340
+ continue
341
+ for raw in PATH_LITERAL.findall(text):
342
+ found.setdefault(raw, set()).add(file_path)
343
+
344
+ print(f" source dir : {source_dir}")
345
+ print(f" live : {len(live)} paths")
346
+ print(f" found : {len(found)} distinct /v1/ path literal(s) in source")
347
+
348
+ bogus = sorted(p for p in found if normalize(p) not in shapes)
349
+
350
+ if not bogus:
351
+ print("\nOK: every /v1/ path referenced in source exists in the live API.")
352
+ return 0
353
+
354
+ print(f"\nFAIL: {len(bogus)} path(s) referenced in source do NOT exist in the live API.")
355
+ print("These calls will 404 at runtime.\n")
356
+ for path in bogus:
357
+ print(f" - {path}")
358
+ for where in sorted(found[path]):
359
+ print(f" {where}")
360
+ print()
361
+ print(" FIX: delete the tool/method, or repoint it at a real endpoint.")
362
+ print(f" Full endpoint list: {os.environ.get('REPULL_OPENAPI_URL', DEFAULT_SPEC_URL)}")
363
+ return 1
364
+
365
+
366
+ # ---------------------------------------------------------------------------
367
+
368
+ def main() -> int:
369
+ parser = argparse.ArgumentParser(description="Fail when an SDK drifts from the live Repull OpenAPI spec.")
370
+ parser.add_argument("--mode", choices=("snapshot", "source"), default="snapshot")
371
+ parser.add_argument("--snapshot", default="openapi/v1.json", help="committed spec snapshot (snapshot mode)")
372
+ parser.add_argument("--source-dir", default="src", help="source root to scan (source mode)")
373
+ parser.add_argument("--spec-url", default=os.environ.get("REPULL_OPENAPI_URL", DEFAULT_SPEC_URL))
374
+ args = parser.parse_args()
375
+
376
+ print("Repull SDK spec-freshness check")
377
+ print(f" spec url : {args.spec_url}")
378
+
379
+ spec = fetch_live_spec(args.spec_url)
380
+
381
+ if args.mode == "snapshot":
382
+ global LIVE_SPEC, LOCAL_SPEC
383
+ LIVE_SPEC = spec
384
+ try:
385
+ with open(args.snapshot, encoding="utf-8") as fh:
386
+ LOCAL_SPEC = json.load(fh)
387
+ except Exception:
388
+ LOCAL_SPEC = None
389
+ # Method + path, so an operation added to an existing path is visible.
390
+ return run_snapshot_mode(args.snapshot, spec_paths(spec, args.spec_url))
391
+
392
+ # Source mode matches bare "/v1/..." literals found in code, which carry no
393
+ # method, so it compares against paths rather than operations.
394
+ return run_source_mode(args.source_dir, spec_bare_paths(spec, args.spec_url))
395
+
396
+
397
+
398
+ if __name__ == "__main__":
399
+ sys.exit(main())
data/scripts/regen.sh CHANGED
@@ -24,7 +24,7 @@ trap 'rm -rf "$GEN_OUT"' EXIT
24
24
  echo "==> snapshotting spec from $SPEC_URL"
25
25
  curl --fail --silent --show-error -o "$SPEC_PATH" "$SPEC_URL"
26
26
 
27
- echo "==> normalizing spec (drop narrow string enums)"
27
+ echo "==> normalizing spec (drop narrow string enums, patch missing path params)"
28
28
  python3 - "$SPEC_PATH" <<'PY'
29
29
  import json, sys
30
30
  path = sys.argv[1]
@@ -35,8 +35,37 @@ for name, schema in spec.get('components', {}).get('schemas', {}).items():
35
35
  if isinstance(prop, dict) and 'enum' in prop and prop.get('type') == 'string':
36
36
  prop.pop('enum')
37
37
  removed += 1
38
- json.dump(spec, open(path, 'w'), indent=2)
39
38
  print(f'dropped {removed} string enums')
39
+
40
+ # The live spec's POST /v1/reviews/{id}/reply omits the `id` path-parameter
41
+ # declaration (present in the path template, missing from `parameters`),
42
+ # which fails openapi-generator's spec validation. Patch it in-place, mirroring
43
+ # the declaration style used by sibling {id}-scoped write routes (e.g.
44
+ # /v1/channels/airbnb/alterations/{id}/accept). Remove this patch once the
45
+ # live spec declares the parameter itself.
46
+ patched = 0
47
+ for path_tpl, item in spec.get('paths', {}).items():
48
+ if '{id}' not in path_tpl:
49
+ continue
50
+ for method, op in item.items():
51
+ if method not in ('get', 'post', 'put', 'patch', 'delete') or not isinstance(op, dict):
52
+ continue
53
+ params = op.get('parameters', [])
54
+ if any(isinstance(p, dict) and p.get('in') == 'path' and p.get('name') == 'id' for p in params):
55
+ continue
56
+ params.append({
57
+ 'name': 'id',
58
+ 'in': 'path',
59
+ 'required': True,
60
+ 'schema': {'type': 'string'},
61
+ 'description': 'Resource id from the path.',
62
+ })
63
+ op['parameters'] = params
64
+ patched += 1
65
+ print(f'patched missing path param: {method.upper()} {path_tpl}')
66
+ print(f'patched {patched} missing path-parameter declarations')
67
+
68
+ json.dump(spec, open(path, 'w'), indent=2)
40
69
  PY
41
70
 
42
71
  echo "==> generating ruby client into $GEN_OUT"
@@ -44,7 +73,7 @@ npx --yes @openapitools/openapi-generator-cli@latest generate \
44
73
  -i "$SPEC_PATH" \
45
74
  -g ruby \
46
75
  -o "$GEN_OUT" \
47
- --additional-properties=gemName=repull,moduleName=Repull,gemVersion=0.2.3
76
+ --additional-properties=gemName=repull,moduleName=Repull,gemVersion=0.2.14
48
77
 
49
78
  echo "==> syncing lib/ + docs/"
50
79
  rm -rf "$ROOT/lib/repull" "$ROOT/lib/repull.rb" "$ROOT/docs"