repull 0.2.14 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/airbnb_api.rb +1265 -125
  3. data/lib/repull/api/availability_api.rb +2 -2
  4. data/lib/repull/api/connect_api.rb +4 -4
  5. data/lib/repull/api/listings_api.rb +97 -13
  6. data/lib/repull/models/airbnb_account_freshness.rb +239 -0
  7. data/lib/repull/models/airbnb_alteration.rb +46 -2
  8. data/lib/repull/models/airbnb_alteration_create_request.rb +264 -0
  9. data/lib/repull/models/airbnb_connection.rb +69 -2
  10. data/lib/repull/models/airbnb_connection_host.rb +1 -1
  11. data/lib/repull/models/airbnb_content_write_response.rb +275 -0
  12. data/lib/repull/models/airbnb_data_freshness.rb +22 -10
  13. data/lib/repull/models/{create_airbnb_alteration_request.rb → airbnb_description_write_request.rb} +44 -47
  14. data/lib/repull/models/airbnb_description_write_request_description.rb +365 -0
  15. data/lib/repull/models/airbnb_listing.rb +12 -1
  16. data/lib/repull/models/airbnb_listing_action200_response.rb +105 -0
  17. data/lib/repull/models/airbnb_listing_action200_response_one_of.rb +157 -0
  18. data/lib/repull/models/airbnb_listing_action200_response_one_of1.rb +175 -0
  19. data/lib/repull/models/airbnb_listing_action_request.rb +2 -2
  20. data/lib/repull/models/airbnb_listing_details_response.rb +284 -0
  21. data/lib/repull/models/airbnb_listing_details_write_request.rb +209 -0
  22. data/lib/repull/models/airbnb_listing_details_write_request_check_in_option.rb +214 -0
  23. data/lib/repull/models/airbnb_listing_details_write_request_quiet_hours_inner.rb +190 -0
  24. data/lib/repull/models/airbnb_listing_lifecycle_response.rb +196 -0
  25. data/lib/repull/models/airbnb_permits_response.rb +163 -0
  26. data/lib/repull/models/airbnb_permits_response_cached_inner.rb +200 -0
  27. data/lib/repull/models/airbnb_permits_write_request.rb +176 -0
  28. data/lib/repull/models/airbnb_permits_write_request_permits_inner.rb +228 -0
  29. data/lib/repull/models/airbnb_permits_write_request_permits_inner_answers_inner.rb +198 -0
  30. data/lib/repull/models/airbnb_photo_position.rb +159 -0
  31. data/lib/repull/models/airbnb_publish_result.rb +263 -0
  32. data/lib/repull/models/airbnb_reservation.rb +23 -1
  33. data/lib/repull/models/airbnb_reservation_list_response.rb +13 -4
  34. data/lib/repull/models/airbnb_review.rb +23 -1
  35. data/lib/repull/models/airbnb_review_list_response.rb +13 -4
  36. data/lib/repull/models/airbnb_safety_disclosure.rb +202 -0
  37. data/lib/repull/models/airbnb_safety_disclosures_response.rb +162 -0
  38. data/lib/repull/models/airbnb_safety_disclosures_write_request.rb +176 -0
  39. data/lib/repull/models/airbnb_thread.rb +23 -1
  40. data/lib/repull/models/airbnb_thread_list_response.rb +13 -4
  41. data/lib/repull/models/airbnb_transaction.rb +23 -1
  42. data/lib/repull/models/create_airbnb_listing_room_request.rb +315 -0
  43. data/lib/repull/models/delete_airbnb_listing_photo200_response.rb +14 -4
  44. data/lib/repull/models/delete_airbnb_listing_room200_response.rb +157 -0
  45. data/lib/repull/models/error_error.rb +31 -1
  46. data/lib/repull/models/get_airbnb_booking_settings200_response.rb +190 -0
  47. data/lib/repull/models/get_airbnb_booking_settings200_response_data.rb +246 -0
  48. data/lib/repull/models/get_airbnb_booking_settings200_response_data_advance_notice.rb +172 -0
  49. data/lib/repull/models/get_airbnb_booking_settings200_response_data_booking_window.rb +160 -0
  50. data/lib/repull/models/get_airbnb_booking_settings200_response_data_cancellation.rb +203 -0
  51. data/lib/repull/models/get_airbnb_booking_settings200_response_data_cancellation_non_refundable.rb +218 -0
  52. data/lib/repull/models/get_airbnb_booking_settings200_response_data_check_in.rb +159 -0
  53. data/lib/repull/models/get_airbnb_booking_settings200_response_data_check_in_start.rb +104 -0
  54. data/lib/repull/models/get_airbnb_booking_settings200_response_data_check_out.rb +172 -0
  55. data/lib/repull/models/get_airbnb_booking_settings200_response_data_instant_book.rb +206 -0
  56. data/lib/repull/models/get_airbnb_booking_settings200_response_data_preparation_time.rb +173 -0
  57. data/lib/repull/models/get_airbnb_listing_details200_response.rb +192 -0
  58. data/lib/repull/models/list_airbnb_listing_permits200_response.rb +190 -0
  59. data/lib/repull/models/list_airbnb_listing_safety_disclosures200_response.rb +190 -0
  60. data/lib/repull/models/listing.rb +2 -1
  61. data/lib/repull/models/listing_content.rb +13 -2
  62. data/lib/repull/models/listing_content_update_request.rb +21 -2
  63. data/lib/repull/models/listing_content_update_request_details.rb +206 -0
  64. data/lib/repull/models/listing_content_update_request_policies.rb +95 -5
  65. data/lib/repull/models/listing_content_update_response.rb +1 -1
  66. data/lib/repull/models/listing_publish_airbnb_response.rb +165 -0
  67. data/lib/repull/models/listing_pull_airbnb_request.rb +149 -0
  68. data/lib/repull/models/listing_pull_response.rb +231 -0
  69. data/lib/repull/models/publish_section_error.rb +232 -0
  70. data/lib/repull/models/reorder_airbnb_listing_photos200_response.rb +191 -0
  71. data/lib/repull/models/reorder_airbnb_listing_photos200_response_data.rb +172 -0
  72. data/lib/repull/models/reorder_airbnb_listing_photos_request.rb +185 -0
  73. data/lib/repull/models/reservation.rb +23 -1
  74. data/lib/repull/models/reservation_webhook_object.rb +38 -5
  75. data/lib/repull/models/set_airbnb_listing_cover_photo200_response.rb +191 -0
  76. data/lib/repull/models/set_airbnb_listing_cover_photo200_response_data.rb +171 -0
  77. data/lib/repull/models/set_airbnb_listing_cover_photo_request.rb +165 -0
  78. data/lib/repull/models/update_airbnb_booking_settings200_response.rb +164 -0
  79. data/lib/repull/models/update_airbnb_booking_settings200_response_data.rb +205 -0
  80. data/lib/repull/models/update_airbnb_booking_settings_request.rb +202 -0
  81. data/lib/repull/models/update_airbnb_booking_settings_request_advance_notice.rb +183 -0
  82. data/lib/repull/models/update_airbnb_booking_settings_request_booking_window.rb +183 -0
  83. data/lib/repull/models/update_airbnb_booking_settings_request_cancellation.rb +201 -0
  84. data/lib/repull/models/update_airbnb_booking_settings_request_cancellation_non_refundable.rb +183 -0
  85. data/lib/repull/models/update_airbnb_booking_settings_request_check_in.rb +159 -0
  86. data/lib/repull/models/update_airbnb_booking_settings_request_check_out.rb +182 -0
  87. data/lib/repull/models/update_airbnb_booking_settings_request_instant_book.rb +203 -0
  88. data/lib/repull/models/update_airbnb_booking_settings_request_preparation_time.rb +182 -0
  89. data/lib/repull/models/update_airbnb_listing_amenities200_response.rb +191 -0
  90. data/lib/repull/models/update_airbnb_listing_amenities200_response_data.rb +158 -0
  91. data/lib/repull/models/update_airbnb_listing_amenities_request.rb +199 -0
  92. data/lib/repull/models/update_airbnb_listing_amenities_request_accessibility_amenities_inner.rb +246 -0
  93. data/lib/repull/models/update_airbnb_listing_amenities_request_amenities_inner.rb +218 -0
  94. data/lib/repull/models/update_airbnb_listing_permits200_response.rb +168 -0
  95. data/lib/repull/models/update_airbnb_listing_photo200_response.rb +194 -0
  96. data/lib/repull/models/update_airbnb_listing_photo_request.rb +242 -0
  97. data/lib/repull/models/update_airbnb_listing_room200_response.rb +194 -0
  98. data/lib/repull/models/update_airbnb_listing_room_request.rb +293 -0
  99. data/lib/repull/models/update_airbnb_listing_room_request_beds_inner.rb +209 -0
  100. data/lib/repull/models/update_airbnb_listing_room_request_room_amenities_inner.rb +182 -0
  101. data/lib/repull/models/update_airbnb_listing_room_request_room_amenities_inner_value.rb +105 -0
  102. data/lib/repull/models/update_airbnb_listing_safety_disclosures200_response.rb +180 -0
  103. data/lib/repull/models/upload_airbnb_listing_photos_request.rb +184 -0
  104. data/lib/repull/models/upload_airbnb_listing_photos_request_photos_inner.rb +305 -0
  105. data/lib/repull/models/upload_airbnb_listing_photos_request_photos_inner_listing_id.rb +105 -0
  106. data/lib/repull/version.rb +1 -1
  107. data/lib/repull.rb +78 -1
  108. data/openapi/v1.json +4523 -1282
  109. data/scripts/regen.sh +1 -1
  110. metadata +80 -3
@@ -88,7 +88,7 @@ module Repull
88
88
  end
89
89
 
90
90
  # Get property availability
91
- # Channel-agnostic day-by-day availability calendar for a property over a date window. Returns a thin per-date shape — `{ date, available, price, minNights }` — projected from the property calendar. The `from` and `to` query params are **required** (ISO `YYYY-MM-DD`, inclusive) — omitting or malforming either returns 422. The window is capped at 366 days; longer ranges are truncated to the first 366 days. **`days` contains only the dates we actually hold calendar data for.** Requested dates with no calendar row are listed in `coverage.missingDates` — their availability is unknown. Never treat a missing date as bookable: this endpoint deliberately does not synthesise availability, because a fabricated open date can be double-booked. A property with no calendar still returns a real 200 (`days: []`, every date in `coverage.missingDates`), never a 404 — 404 means the property id does not exist or belongs to a different workspace. This endpoint is read-only, and the projected per-date shape carries **availability, price, and min-nights only** — it does NOT expose max-stay, closed-to-arrival (CTA), closed-to-departure (CTD), or the dedicated stop-sell flag. To read or write that full restriction set on Booking.com use the channel routes: `GET`/`PUT /v1/channels/booking/availability` (with the room + rate ids from `GET /v1/channels/booking/properties/{id}/rooms`). Availability **writes** always stay per-channel: `PUT /v1/channels/airbnb/listings/{id}/availability` (Airbnb) or `PUT /v1/channels/booking/availability` (Booking.com). Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
91
+ # Channel-agnostic day-by-day availability calendar for a property over a date window. Returns a thin per-date shape — `{ date, available, price, minNights }` — projected from the property calendar. The `from` and `to` query params are **required** (ISO `YYYY-MM-DD`, inclusive) — omitting or malforming either returns 422. The window is capped at 366 days; longer ranges are truncated to the first 366 days. **`days` contains only the dates we actually hold calendar data for.** Requested dates with no calendar row are listed in `coverage.missingDates` — their availability is unknown. Never treat a missing date as bookable: this endpoint deliberately does not synthesise availability, because a fabricated open date can be double-booked. A property with no calendar still returns a real 200 (`days: []`, every date in `coverage.missingDates`), never a 404 — 404 means the property id does not exist or belongs to a different workspace. This endpoint is read-only, and the projected per-date shape carries **availability, price, and min-nights only** — it does NOT expose max-stay, closed-to-arrival (CTA), closed-to-departure (CTD), or the dedicated stop-sell flag. To read or write that full restriction set on Booking.com use the channel routes: `GET`/`PUT /v1/channels/booking/availability` (with the room + rate ids from `GET /v1/channels/booking/properties/{id}/rooms`). To **write** calendar values use `PUT /v1/availability/{propertyId}` (or `PATCH /v1/availability/batch`), which updates the property calendar and pushes to its connected channels; channel-only settings stay on the channel routes. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
92
92
  # @param property_id [Integer] Repull property id (equal to `listings.id`; the same integer used as `propertyId` on availability and `listingId` on reservations).
93
93
  # @param from [Date] Start of the window (inclusive), ISO `YYYY-MM-DD`. Required — missing/malformed returns 422. `startDate` is accepted as an alias.
94
94
  # @param to [Date] End of the window (inclusive), ISO `YYYY-MM-DD`. Required — missing/malformed returns 422. `endDate` is accepted as an alias.
@@ -100,7 +100,7 @@ module Repull
100
100
  end
101
101
 
102
102
  # Get property availability
103
- # Channel-agnostic day-by-day availability calendar for a property over a date window. Returns a thin per-date shape — `{ date, available, price, minNights }` — projected from the property calendar. The `from` and `to` query params are **required** (ISO `YYYY-MM-DD`, inclusive) — omitting or malforming either returns 422. The window is capped at 366 days; longer ranges are truncated to the first 366 days. **`days` contains only the dates we actually hold calendar data for.** Requested dates with no calendar row are listed in `coverage.missingDates` — their availability is unknown. Never treat a missing date as bookable: this endpoint deliberately does not synthesise availability, because a fabricated open date can be double-booked. A property with no calendar still returns a real 200 (`days: []`, every date in `coverage.missingDates`), never a 404 — 404 means the property id does not exist or belongs to a different workspace. This endpoint is read-only, and the projected per-date shape carries **availability, price, and min-nights only** — it does NOT expose max-stay, closed-to-arrival (CTA), closed-to-departure (CTD), or the dedicated stop-sell flag. To read or write that full restriction set on Booking.com use the channel routes: `GET`/`PUT /v1/channels/booking/availability` (with the room + rate ids from `GET /v1/channels/booking/properties/{id}/rooms`). Availability **writes** always stay per-channel: `PUT /v1/channels/airbnb/listings/{id}/availability` (Airbnb) or `PUT /v1/channels/booking/availability` (Booking.com). Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
103
+ # Channel-agnostic day-by-day availability calendar for a property over a date window. Returns a thin per-date shape — `{ date, available, price, minNights }` — projected from the property calendar. The `from` and `to` query params are **required** (ISO `YYYY-MM-DD`, inclusive) — omitting or malforming either returns 422. The window is capped at 366 days; longer ranges are truncated to the first 366 days. **`days` contains only the dates we actually hold calendar data for.** Requested dates with no calendar row are listed in `coverage.missingDates` — their availability is unknown. Never treat a missing date as bookable: this endpoint deliberately does not synthesise availability, because a fabricated open date can be double-booked. A property with no calendar still returns a real 200 (`days: []`, every date in `coverage.missingDates`), never a 404 — 404 means the property id does not exist or belongs to a different workspace. This endpoint is read-only, and the projected per-date shape carries **availability, price, and min-nights only** — it does NOT expose max-stay, closed-to-arrival (CTA), closed-to-departure (CTD), or the dedicated stop-sell flag. To read or write that full restriction set on Booking.com use the channel routes: `GET`/`PUT /v1/channels/booking/availability` (with the room + rate ids from `GET /v1/channels/booking/properties/{id}/rooms`). To **write** calendar values use `PUT /v1/availability/{propertyId}` (or `PATCH /v1/availability/batch`), which updates the property calendar and pushes to its connected channels; channel-only settings stay on the channel routes. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
104
104
  # @param property_id [Integer] Repull property id (equal to `listings.id`; the same integer used as `propertyId` on availability and `listingId` on reservations).
105
105
  # @param from [Date] Start of the window (inclusive), ISO `YYYY-MM-DD`. Required — missing/malformed returns 422. `startDate` is accepted as an alias.
106
106
  # @param to [Date] End of the window (inclusive), ISO `YYYY-MM-DD`. Required — missing/malformed returns 422. `endDate` is accepted as an alias.
@@ -226,7 +226,7 @@ module Repull
226
226
  end
227
227
 
228
228
  # Disconnect provider
229
- # Disconnect ONE connected account of a provider from this workspace. Supported for `airbnb` and `booking`. **Which account.** Pass `accountId` — for Airbnb the host id (`accounts[].externalAccountId` from `GET /v1/connect/airbnb`), for Booking.com the hotel id. It is optional only when the workspace has exactly one account for the provider. With several and no `accountId`, the call returns `422` with the account ids in `valid_values` instead of guessing. An `accountId` that is not connected to this workspace returns `404`. Disconnecting one account leaves the others connected. **What happens.** The account's stored authorization is removed and it stops syncing. Its listings are **deactivated**, not deleted: they stop counting toward your plan's listing limit, their data is kept, and they are returned in `listingsDeactivated`. A listing that is still connected through another account or channel stays active. Reconnect the account, then activate the listings with `POST /v1/listings/status`. The change is all or nothing. For Airbnb, the host can also revoke access on Airbnb's side (Account → Privacy & sharing → Connected apps); that alone does not update this workspace, so call this endpoint as well. Other providers return `501 not_implemented` with instructions for disconnecting on the provider's side.
229
+ # Disconnect ONE connected account of a provider from this workspace. Supported for `airbnb` and `booking`. **Which account.** Pass `accountId` — for Airbnb the host id (`accounts[].externalAccountId` from `GET /v1/connect/airbnb`), for Booking.com the hotel id. It is optional only when the workspace has exactly one account for the provider. With several and no `accountId`, the call returns `422` with the account ids in `valid_values` instead of guessing. An `accountId` that is not connected to this workspace returns `404`. Disconnecting one account leaves the others connected. **What happens.** The account's stored authorization is removed and it stops syncing. Its listings are **deactivated**, not deleted: they stop counting toward your plan's listing limit, their data is kept, and they are returned in `listingsDeactivated`. A listing that is still connected through another account or channel stays active. Reconnect the account, then activate the listings with `POST /v1/listings/status`. The change is all or nothing. For Airbnb, the host can also revoke access on Airbnb's side (Account → Privacy & sharing → Connected apps); that alone does not update this workspace, so call this endpoint as well. Other providers return `501 not_implemented` with instructions for disconnecting on the provider's side. That answer depends only on the provider, not on your workspace: an unsupported provider returns `501` whether or not you have a connection to it. `404 not_found` on a supported provider means this workspace has no connection to it (or, with `accountId`, that the account is not connected here).
230
230
  # @param provider [String] PMS provider slug (e.g., hostaway, guesty, ownerrez)
231
231
  # @param [Hash] opts the optional parameters
232
232
  # @option opts [String] :account_id The account to disconnect: the Airbnb host id (`accounts[].externalAccountId` on `GET /v1/connect/airbnb`) or the Booking.com hotel id. Required when the workspace has more than one connected account for the provider. Not the same as the `X-Account-Id` header.
@@ -237,7 +237,7 @@ module Repull
237
237
  end
238
238
 
239
239
  # Disconnect provider
240
- # Disconnect ONE connected account of a provider from this workspace. Supported for `airbnb` and `booking`. **Which account.** Pass `accountId` — for Airbnb the host id (`accounts[].externalAccountId` from `GET /v1/connect/airbnb`), for Booking.com the hotel id. It is optional only when the workspace has exactly one account for the provider. With several and no `accountId`, the call returns `422` with the account ids in `valid_values` instead of guessing. An `accountId` that is not connected to this workspace returns `404`. Disconnecting one account leaves the others connected. **What happens.** The account's stored authorization is removed and it stops syncing. Its listings are **deactivated**, not deleted: they stop counting toward your plan's listing limit, their data is kept, and they are returned in `listingsDeactivated`. A listing that is still connected through another account or channel stays active. Reconnect the account, then activate the listings with `POST /v1/listings/status`. The change is all or nothing. For Airbnb, the host can also revoke access on Airbnb's side (Account → Privacy & sharing → Connected apps); that alone does not update this workspace, so call this endpoint as well. Other providers return `501 not_implemented` with instructions for disconnecting on the provider's side.
240
+ # Disconnect ONE connected account of a provider from this workspace. Supported for `airbnb` and `booking`. **Which account.** Pass `accountId` — for Airbnb the host id (`accounts[].externalAccountId` from `GET /v1/connect/airbnb`), for Booking.com the hotel id. It is optional only when the workspace has exactly one account for the provider. With several and no `accountId`, the call returns `422` with the account ids in `valid_values` instead of guessing. An `accountId` that is not connected to this workspace returns `404`. Disconnecting one account leaves the others connected. **What happens.** The account's stored authorization is removed and it stops syncing. Its listings are **deactivated**, not deleted: they stop counting toward your plan's listing limit, their data is kept, and they are returned in `listingsDeactivated`. A listing that is still connected through another account or channel stays active. Reconnect the account, then activate the listings with `POST /v1/listings/status`. The change is all or nothing. For Airbnb, the host can also revoke access on Airbnb's side (Account → Privacy & sharing → Connected apps); that alone does not update this workspace, so call this endpoint as well. Other providers return `501 not_implemented` with instructions for disconnecting on the provider's side. That answer depends only on the provider, not on your workspace: an unsupported provider returns `501` whether or not you have a connection to it. `404 not_found` on a supported provider means this workspace has no connection to it (or, with `accountId`, that the account is not connected here).
241
241
  # @param provider [String] PMS provider slug (e.g., hostaway, guesty, ownerrez)
242
242
  # @param [Hash] opts the optional parameters
243
243
  # @option opts [String] :account_id The account to disconnect: the Airbnb host id (`accounts[].externalAccountId` on `GET /v1/connect/airbnb`) or the Booking.com hotel id. Required when the workspace has more than one connected account for the provider. Not the same as the `X-Account-Id` header.
@@ -601,7 +601,7 @@ module Repull
601
601
  end
602
602
 
603
603
  # Bind a picker session to a provider
604
- # Called by the hosted picker page once the user clicks a channel card. Validates the provider exists and is permitted by the session's `allowed_providers` whitelist (if any), then returns the next-step URL the picker should navigate to. No API key required — the session ID is the capability token. The session must still be pending and unexpired.
604
+ # Called by the hosted picker page once the user clicks a channel card. Validates the provider exists and is permitted by the session's `allowedProviders` whitelist (if any), then returns the next-step URL the picker should navigate to. No API key required — the session ID is the capability token. The session must still be pending and unexpired.
605
605
  # @param session_id [String] The picker session ID returned by `createConnectSession`.
606
606
  # @param select_connect_provider_request [SelectConnectProviderRequest]
607
607
  # @param [Hash] opts the optional parameters
@@ -612,7 +612,7 @@ module Repull
612
612
  end
613
613
 
614
614
  # Bind a picker session to a provider
615
- # Called by the hosted picker page once the user clicks a channel card. Validates the provider exists and is permitted by the session's `allowed_providers` whitelist (if any), then returns the next-step URL the picker should navigate to. No API key required — the session ID is the capability token. The session must still be pending and unexpired.
615
+ # Called by the hosted picker page once the user clicks a channel card. Validates the provider exists and is permitted by the session's `allowedProviders` whitelist (if any), then returns the next-step URL the picker should navigate to. No API key required — the session ID is the capability token. The session must still be pending and unexpired.
616
616
  # @param session_id [String] The picker session ID returned by `createConnectSession`.
617
617
  # @param select_connect_provider_request [SelectConnectProviderRequest]
618
618
  # @param [Hash] opts the optional parameters
@@ -564,16 +564,16 @@ module Repull
564
564
  end
565
565
 
566
566
  # List listings
567
- # 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. **Inactive listings:** by default only active listings are returned. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated, so when `status` asks for inactive ones they carry only `id`, `name`, `status` and `channels` — enough to choose what to activate with `PATCH /v1/listings/{id}`. `?include=` expansions are not applied to them.
567
+ # 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). Pass `?include=thumbnail` to guarantee `thumbnailUrl` on every returned row — including the reduced inactive ones. Combine comma-separated, e.g. `?include=content,thumbnail`. The default response stays lean; consumers must opt in. **Inactive listings:** by default only active listings are returned. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated, so when `status` asks for inactive ones they carry only `id`, `name`, `status` and `channels` — enough to choose what to activate with `PATCH /v1/listings/{id}`. The `content` and `details` expansions are not applied to them. `?include=thumbnail` is the one exception: it adds `thumbnailUrl` to an inactive row so a single request can render an active/inactive selection screen with pictures, instead of one follow-up call per listing (which an inactive listing would answer with `403 listing_inactive` anyway).
568
568
  # @param [Hash] opts the optional parameters
569
569
  # @option opts [String] :x_schema Apply a custom or built-in schema to transform the response. Built-in: `native` (default), `calry`, `calry-v1`. Custom: any schema name created via `POST /v1/schema/custom`. Unknown / inactive schema names fall back to `native`.
570
570
  # @option opts [String] :cursor Opaque cursor returned in the previous response's `pagination.nextCursor`. Omit to fetch the first page.
571
571
  # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with `cursor` — passing both returns 422. Accepts integers in `[0, 10000]`; deeper walks must use `cursor` (constant per-page cost). The response always includes `pagination.nextCursor` so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
572
572
  # @option opts [Integer] :limit Max items per page. Hard cap is 100. (default to 20)
573
573
  # @option opts [String] :q Case-insensitive substring search on name, street, or city.
574
- # @option opts [String] :status Filter by listing status. Defaults to `active`. Pass `inactive` to list the listings you can activate, `archived` for archived ones, or `all` for every status. Inactive listings are returned with identity fields only — `id`, `name`, `status` and `channels` — and never with `address`, `thumbnailUrl`, `content` or `details`; activate one to see the rest. (default to 'active')
574
+ # @option opts [String] :status Filter by listing status. Defaults to `active`. Pass `inactive` to list the listings you can activate, `archived` for archived ones, or `all` for every status. Inactive listings are returned with identity fields only — `id`, `name`, `status` and `channels` — and never with `address`, `content` or `details`; activate one to see the rest. The only field you can add to an inactive row is `thumbnailUrl`, via `?include=thumbnail`. (default to 'active')
575
575
  # @option opts [String] :channel Restrict to listings published on the given channel (`airbnb`, `booking`, `vrbo`, etc.). Joins through `listing_platform_links` and matches active links only.
576
- # @option opts [String] :include Comma-separated optional expansions. Currently supported: `content`, `details`. Unknown values return 422 with a `valid_values` envelope. (Note: `amenities` is not yet supported on the list endpoint — use the detail endpoint to fetch amenity rows for a single listing.)
576
+ # @option opts [String] :include Comma-separated optional expansions. Currently supported: `content`, `details`, `thumbnail`. `thumbnail` guarantees `thumbnailUrl` on every row and is the only expansion that applies to inactive listings. Unknown values return 422 with a `valid_values` envelope. (Note: `amenities` is not yet supported on the list endpoint — use the detail endpoint to fetch amenity rows for a single listing.)
577
577
  # @return [ListingListResponse]
578
578
  def list_listings(opts = {})
579
579
  data, _status_code, _headers = list_listings_with_http_info(opts)
@@ -581,16 +581,16 @@ module Repull
581
581
  end
582
582
 
583
583
  # List listings
584
- # 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. **Inactive listings:** by default only active listings are returned. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated, so when `status` asks for inactive ones they carry only `id`, `name`, `status` and `channels` — enough to choose what to activate with `PATCH /v1/listings/{id}`. `?include=` expansions are not applied to them.
584
+ # 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). Pass `?include=thumbnail` to guarantee `thumbnailUrl` on every returned row — including the reduced inactive ones. Combine comma-separated, e.g. `?include=content,thumbnail`. The default response stays lean; consumers must opt in. **Inactive listings:** by default only active listings are returned. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated, so when `status` asks for inactive ones they carry only `id`, `name`, `status` and `channels` — enough to choose what to activate with `PATCH /v1/listings/{id}`. The `content` and `details` expansions are not applied to them. `?include=thumbnail` is the one exception: it adds `thumbnailUrl` to an inactive row so a single request can render an active/inactive selection screen with pictures, instead of one follow-up call per listing (which an inactive listing would answer with `403 listing_inactive` anyway).
585
585
  # @param [Hash] opts the optional parameters
586
586
  # @option opts [String] :x_schema Apply a custom or built-in schema to transform the response. Built-in: `native` (default), `calry`, `calry-v1`. Custom: any schema name created via `POST /v1/schema/custom`. Unknown / inactive schema names fall back to `native`.
587
587
  # @option opts [String] :cursor Opaque cursor returned in the previous response's `pagination.nextCursor`. Omit to fetch the first page.
588
588
  # @option opts [Integer] :offset First-class alias for cursor-based pagination. Mutually exclusive with `cursor` — passing both returns 422. Accepts integers in `[0, 10000]`; deeper walks must use `cursor` (constant per-page cost). The response always includes `pagination.nextCursor` so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying. (default to 0)
589
589
  # @option opts [Integer] :limit Max items per page. Hard cap is 100. (default to 20)
590
590
  # @option opts [String] :q Case-insensitive substring search on name, street, or city.
591
- # @option opts [String] :status Filter by listing status. Defaults to `active`. Pass `inactive` to list the listings you can activate, `archived` for archived ones, or `all` for every status. Inactive listings are returned with identity fields only — `id`, `name`, `status` and `channels` — and never with `address`, `thumbnailUrl`, `content` or `details`; activate one to see the rest. (default to 'active')
591
+ # @option opts [String] :status Filter by listing status. Defaults to `active`. Pass `inactive` to list the listings you can activate, `archived` for archived ones, or `all` for every status. Inactive listings are returned with identity fields only — `id`, `name`, `status` and `channels` — and never with `address`, `content` or `details`; activate one to see the rest. The only field you can add to an inactive row is `thumbnailUrl`, via `?include=thumbnail`. (default to 'active')
592
592
  # @option opts [String] :channel Restrict to listings published on the given channel (`airbnb`, `booking`, `vrbo`, etc.). Joins through `listing_platform_links` and matches active links only.
593
- # @option opts [String] :include Comma-separated optional expansions. Currently supported: `content`, `details`. Unknown values return 422 with a `valid_values` envelope. (Note: `amenities` is not yet supported on the list endpoint — use the detail endpoint to fetch amenity rows for a single listing.)
593
+ # @option opts [String] :include Comma-separated optional expansions. Currently supported: `content`, `details`, `thumbnail`. `thumbnail` guarantees `thumbnailUrl` on every row and is the only expansion that applies to inactive listings. Unknown values return 422 with a `valid_values` envelope. (Note: `amenities` is not yet supported on the list endpoint — use the detail endpoint to fetch amenity rows for a single listing.)
594
594
  # @return [Array<(ListingListResponse, Integer, Hash)>] ListingListResponse data, response status code and response headers
595
595
  def list_listings_with_http_info(opts = {})
596
596
  if @api_client.config.debugging
@@ -665,22 +665,24 @@ module Repull
665
665
  end
666
666
 
667
667
  # Publish a listing to Airbnb
668
- # Push a Repull listing to Airbnb. Pass `airbnbConnectionId` to update an already-mapped Airbnb listing, or `hostId` to create a brand-new Airbnb listing under that host. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
668
+ # Push a Repull listing's canonical content to Airbnb. Pass `airbnbConnectionId` to update an already-mapped Airbnb listing, or `hostId` to create a brand-new Airbnb listing under that host. **A publish is not one call to Airbnb.** It is up to eight independent ones — details, description, amenities, rooms, policies, photos, pricing, checkout_tasks — and each can fail on its own. `result.published` is true only when every attempted section landed; `result.sections` lists the ones that did and `result.errors[]` carries Airbnb's own reason, per section, for the ones that did not. **A partial publish is normal and is not rolled back**: what succeeded stays applied. Publish again once you have fixed the failing sections — a re-publish of an unchanged section is harmless. `result.lockedFields` names the fields Airbnb will not let this listing change at all. They are not retryable by anyone: Airbnb answers 200 and applies nothing. `GET /v1/channels/airbnb/listings/{id}` reports the same list up front. **Which fields this pushes** — title, description sections and house rules (English/primary locale), amenities, rooms and beds, photos, nightly price and fees, cancellation policy and guest controls, check-in/out times, quiet hours, property and room type, checkout tasks. **Not pushed by this endpoint:** non-primary locales (`PUT /v1/channels/airbnb/listings/{id}/descriptions`), guest-safety disclosures (`PUT …/safety-disclosures`), check-in method (`PUT …/details`), permits (`PUT …/permits`), and the calendar (`PUT …/availability`). `force: true` re-pushes every section, ignoring dirty-field tracking. Without it only the sections changed since the last successful publish are sent. Send `Idempotency-Key` to make a retry safe: a timeout on a publish otherwise leaves you unable to tell whether it ran. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
669
669
  # @param id [Integer]
670
670
  # @param [Hash] opts the optional parameters
671
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
671
672
  # @option opts [ListingPublishAirbnbRequest] :listing_publish_airbnb_request
672
- # @return [ListingPublishResponse]
673
+ # @return [ListingPublishAirbnbResponse]
673
674
  def publish_listing_to_airbnb(id, opts = {})
674
675
  data, _status_code, _headers = publish_listing_to_airbnb_with_http_info(id, opts)
675
676
  data
676
677
  end
677
678
 
678
679
  # Publish a listing to Airbnb
679
- # Push a Repull listing to Airbnb. Pass &#x60;airbnbConnectionId&#x60; to update an already-mapped Airbnb listing, or &#x60;hostId&#x60; to create a brand-new Airbnb listing under that host. Returns &#x60;403 listing_inactive&#x60; when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
680
+ # Push a Repull listing&#39;s canonical content to Airbnb. Pass &#x60;airbnbConnectionId&#x60; to update an already-mapped Airbnb listing, or &#x60;hostId&#x60; to create a brand-new Airbnb listing under that host. **A publish is not one call to Airbnb.** It is up to eight independent ones — details, description, amenities, rooms, policies, photos, pricing, checkout_tasks — and each can fail on its own. &#x60;result.published&#x60; is true only when every attempted section landed; &#x60;result.sections&#x60; lists the ones that did and &#x60;result.errors[]&#x60; carries Airbnb&#39;s own reason, per section, for the ones that did not. **A partial publish is normal and is not rolled back**: what succeeded stays applied. Publish again once you have fixed the failing sections — a re-publish of an unchanged section is harmless. &#x60;result.lockedFields&#x60; names the fields Airbnb will not let this listing change at all. They are not retryable by anyone: Airbnb answers 200 and applies nothing. &#x60;GET /v1/channels/airbnb/listings/{id}&#x60; reports the same list up front. **Which fields this pushes** — title, description sections and house rules (English/primary locale), amenities, rooms and beds, photos, nightly price and fees, cancellation policy and guest controls, check-in/out times, quiet hours, property and room type, checkout tasks. **Not pushed by this endpoint:** non-primary locales (&#x60;PUT /v1/channels/airbnb/listings/{id}/descriptions&#x60;), guest-safety disclosures (&#x60;PUT …/safety-disclosures&#x60;), check-in method (&#x60;PUT …/details&#x60;), permits (&#x60;PUT …/permits&#x60;), and the calendar (&#x60;PUT …/availability&#x60;). &#x60;force: true&#x60; re-pushes every section, ignoring dirty-field tracking. Without it only the sections changed since the last successful publish are sent. Send &#x60;Idempotency-Key&#x60; to make a retry safe: a timeout on a publish otherwise leaves you unable to tell whether it ran. Returns &#x60;403 listing_inactive&#x60; when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
680
681
  # @param id [Integer]
681
682
  # @param [Hash] opts the optional parameters
683
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
682
684
  # @option opts [ListingPublishAirbnbRequest] :listing_publish_airbnb_request
683
- # @return [Array<(ListingPublishResponse, Integer, Hash)>] ListingPublishResponse data, response status code and response headers
685
+ # @return [Array<(ListingPublishAirbnbResponse, Integer, Hash)>] ListingPublishAirbnbResponse data, response status code and response headers
684
686
  def publish_listing_to_airbnb_with_http_info(id, opts = {})
685
687
  if @api_client.config.debugging
686
688
  @api_client.config.logger.debug 'Calling API: ListingsApi.publish_listing_to_airbnb ...'
@@ -689,6 +691,10 @@ module Repull
689
691
  if @api_client.config.client_side_validation && id.nil?
690
692
  fail ArgumentError, "Missing the required parameter 'id' when calling ListingsApi.publish_listing_to_airbnb"
691
693
  end
694
+ if @api_client.config.client_side_validation && !opts[:'idempotency_key'].nil? && opts[:'idempotency_key'].to_s.length > 255
695
+ fail ArgumentError, 'invalid value for "opts[:"idempotency_key"]" when calling ListingsApi.publish_listing_to_airbnb, the character length must be smaller than or equal to 255.'
696
+ end
697
+
692
698
  # resource path
693
699
  local_var_path = '/v1/listings/{id}/publish/airbnb'.sub('{id}', CGI.escape(id.to_s))
694
700
 
@@ -704,6 +710,7 @@ module Repull
704
710
  if !content_type.nil?
705
711
  header_params['Content-Type'] = content_type
706
712
  end
713
+ header_params[:'Idempotency-Key'] = opts[:'idempotency_key'] if !opts[:'idempotency_key'].nil?
707
714
 
708
715
  # form parameters
709
716
  form_params = opts[:form_params] || {}
@@ -712,7 +719,7 @@ module Repull
712
719
  post_body = opts[:debug_body] || @api_client.object_to_http_body(opts[:'listing_publish_airbnb_request'])
713
720
 
714
721
  # return_type
715
- return_type = opts[:debug_return_type] || 'ListingPublishResponse'
722
+ return_type = opts[:debug_return_type] || 'ListingPublishAirbnbResponse'
716
723
 
717
724
  # auth_names
718
725
  auth_names = opts[:debug_auth_names] || ['bearerAuth']
@@ -797,6 +804,76 @@ module Repull
797
804
  return data, status_code, headers
798
805
  end
799
806
 
807
+ # Refresh a listing from Airbnb
808
+ # Re-read this listing from Airbnb and update your stored copy, then report what was refreshed and when. The mirror image of `POST /v1/listings/{id}/publish/airbnb`. Every other Airbnb read on this API is served from our database. This endpoint is the one that goes and asks Airbnb — use it after a push, to see the values Airbnb actually kept, or when a host has changed something in the Airbnb app. **What it refreshes:** basic listing facts (property type, bedrooms, beds, bathrooms, capacity), descriptions, photos, rooms and beds, amenities, booking settings (check-in/check-out windows, guest controls, cancellation policy), stay rules (min/max nights, advance-booking window, turnover buffer), pricing settings and standard fees, permits, checkout tasks and the check-in guide. After it returns, those values are what `GET /v1/listings/{id}?include=content,details` and the `/v1/channels/airbnb/**` routes serve. **What it does NOT refresh:** the calendar (nightly rates and availability — see `GET /v1/channels/airbnb/listings/{id}/availability`), reservations, messages, reviews or payouts. Those arrive continuously through the channel's own sync and never need a manual pull. **Runs synchronously** — the response is the result, not a job id. Expect several seconds. **One pull per listing per 15 minutes.** A pull is roughly a dozen Airbnb calls; a second call inside the window returns `429 rate_limit_exceeded` with `Retry-After` and `nextPullAvailableAt`, and makes no Airbnb calls. Two simultaneous calls cannot both run. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
809
+ # @param id [Integer]
810
+ # @param [Hash] opts the optional parameters
811
+ # @option opts [ListingPullAirbnbRequest] :listing_pull_airbnb_request
812
+ # @return [ListingPullResponse]
813
+ def pull_listing_from_airbnb(id, opts = {})
814
+ data, _status_code, _headers = pull_listing_from_airbnb_with_http_info(id, opts)
815
+ data
816
+ end
817
+
818
+ # Refresh a listing from Airbnb
819
+ # Re-read this listing from Airbnb and update your stored copy, then report what was refreshed and when. The mirror image of &#x60;POST /v1/listings/{id}/publish/airbnb&#x60;. Every other Airbnb read on this API is served from our database. This endpoint is the one that goes and asks Airbnb — use it after a push, to see the values Airbnb actually kept, or when a host has changed something in the Airbnb app. **What it refreshes:** basic listing facts (property type, bedrooms, beds, bathrooms, capacity), descriptions, photos, rooms and beds, amenities, booking settings (check-in/check-out windows, guest controls, cancellation policy), stay rules (min/max nights, advance-booking window, turnover buffer), pricing settings and standard fees, permits, checkout tasks and the check-in guide. After it returns, those values are what &#x60;GET /v1/listings/{id}?include&#x3D;content,details&#x60; and the &#x60;/v1/channels/airbnb/**&#x60; routes serve. **What it does NOT refresh:** the calendar (nightly rates and availability — see &#x60;GET /v1/channels/airbnb/listings/{id}/availability&#x60;), reservations, messages, reviews or payouts. Those arrive continuously through the channel&#39;s own sync and never need a manual pull. **Runs synchronously** — the response is the result, not a job id. Expect several seconds. **One pull per listing per 15 minutes.** A pull is roughly a dozen Airbnb calls; a second call inside the window returns &#x60;429 rate_limit_exceeded&#x60; with &#x60;Retry-After&#x60; and &#x60;nextPullAvailableAt&#x60;, and makes no Airbnb calls. Two simultaneous calls cannot both run. Returns &#x60;403 listing_inactive&#x60; when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
820
+ # @param id [Integer]
821
+ # @param [Hash] opts the optional parameters
822
+ # @option opts [ListingPullAirbnbRequest] :listing_pull_airbnb_request
823
+ # @return [Array<(ListingPullResponse, Integer, Hash)>] ListingPullResponse data, response status code and response headers
824
+ def pull_listing_from_airbnb_with_http_info(id, opts = {})
825
+ if @api_client.config.debugging
826
+ @api_client.config.logger.debug 'Calling API: ListingsApi.pull_listing_from_airbnb ...'
827
+ end
828
+ # verify the required parameter 'id' is set
829
+ if @api_client.config.client_side_validation && id.nil?
830
+ fail ArgumentError, "Missing the required parameter 'id' when calling ListingsApi.pull_listing_from_airbnb"
831
+ end
832
+ # resource path
833
+ local_var_path = '/v1/listings/{id}/pull/airbnb'.sub('{id}', CGI.escape(id.to_s))
834
+
835
+ # query parameters
836
+ query_params = opts[:query_params] || {}
837
+
838
+ # header parameters
839
+ header_params = opts[:header_params] || {}
840
+ # HTTP header 'Accept' (if needed)
841
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
842
+ # HTTP header 'Content-Type'
843
+ content_type = @api_client.select_header_content_type(['application/json'])
844
+ if !content_type.nil?
845
+ header_params['Content-Type'] = content_type
846
+ end
847
+
848
+ # form parameters
849
+ form_params = opts[:form_params] || {}
850
+
851
+ # http body (model)
852
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(opts[:'listing_pull_airbnb_request'])
853
+
854
+ # return_type
855
+ return_type = opts[:debug_return_type] || 'ListingPullResponse'
856
+
857
+ # auth_names
858
+ auth_names = opts[:debug_auth_names] || ['bearerAuth']
859
+
860
+ new_options = opts.merge(
861
+ :operation => :"ListingsApi.pull_listing_from_airbnb",
862
+ :header_params => header_params,
863
+ :query_params => query_params,
864
+ :form_params => form_params,
865
+ :body => post_body,
866
+ :auth_names => auth_names,
867
+ :return_type => return_type
868
+ )
869
+
870
+ data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options)
871
+ if @api_client.config.debugging
872
+ @api_client.config.logger.debug "API called: ListingsApi#pull_listing_from_airbnb\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
873
+ end
874
+ return data, status_code, headers
875
+ end
876
+
800
877
  # Activate or deactivate listings in bulk
801
878
  # Sets up to 500 listings active or inactive in one call. Send `{ \"listingIds\": [\"4118\", \"4119\"], \"active\": false }` to deactivate them, or `\"active\": true` to activate them. An inactive listing is not counted toward your plan's listing limit or billed. It is NOT deleted and the upstream channel (Airbnb / Booking.com / your PMS) is never touched — its data keeps syncing, so it is complete the moment you activate it again. Until then it cannot be read, changed, or receive webhooks. **All or nothing.** Nothing changes unless the whole request can be applied: - If any id is not one of your listings, the call returns `404` naming those ids. - If activating would take you over your plan's listing limit, the call returns `402 listings_limit_exceeded`. Only listings that are currently inactive count toward the new total, so re-sending ids that are already active never trips the limit. Deactivating is always allowed, including when your account is already over its limit — it is how you get back under it. **Idempotent.** Ids already in the requested state are returned in `unchanged`; ids this call changed are returned in `updated`. For a single listing, `PATCH /v1/listings/{id}` does the same.
802
879
  # @param listing_status_batch_request [ListingStatusBatchRequest]
@@ -940,10 +1017,11 @@ module Repull
940
1017
  end
941
1018
 
942
1019
  # Update canonical listing content
943
- # Write your PMS's canonical listing content — title, description, amenities, address, occupancy, and policies — into a Repull listing, making it the source of truth. This is the flagship \"the PMS owns listing content, Repull distributes it\" enabler. **Partial update:** every field is optional. Only the fields you send are written; absent fields are left untouched. `amenities` is a FULL replacement of the amenity set (omit to leave untouched, send `[]` to clear). **Local write only — NOT a channel publish.** This mutates Repull's own copy of the content. It does NOT push to Airbnb / Booking.com; it marks the channels dirty so a later publish knows what changed. Distribution stays a separate explicit step. **Photos are deferred:** a provided `photos` array is echoed back in the `deferred` field and NOT persisted (media ingestion is a follow-up). Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403. This endpoint is served even when the account is over the plan-listings cap, since editing content on a listing you already own never grows the portfolio. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
1020
+ # Write your PMS's canonical listing content — title, description, amenities, address, occupancy, and policies — into a Repull listing, making it the source of truth. This is the flagship \"the PMS owns listing content, Repull distributes it\" enabler. **Partial update:** every field is optional. Only the fields you send are written; absent fields are left untouched. `amenities` is a FULL replacement of the amenity set (omit to leave untouched, send `[]` to clear). **Multilingual:** send `locale` to say which language this copy is in (`it`, `pt-BR`, …). Canonical content is stored per locale, so each language keeps its own row instead of overwriting the English one. Omit it for English. **Local write only — NOT a channel publish.** This mutates Repull's own copy of the content. It does NOT push to Airbnb / Booking.com; it marks the channels dirty so a later publish knows what changed. Distribution stays a separate explicit step. **Photos are deferred:** a provided `photos` array is echoed back in the `deferred` field and NOT persisted (media ingestion is a follow-up). Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403. This endpoint is served even when the account is over the plan-listings cap, since editing content on a listing you already own never grows the portfolio. Returns `403 listing_inactive` when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
944
1021
  # @param id [Integer] Repull listing id
945
1022
  # @param listing_content_update_request [ListingContentUpdateRequest]
946
1023
  # @param [Hash] opts the optional parameters
1024
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
947
1025
  # @return [ListingContentUpdateResponse]
948
1026
  def update_listing_content(id, listing_content_update_request, opts = {})
949
1027
  data, _status_code, _headers = update_listing_content_with_http_info(id, listing_content_update_request, opts)
@@ -951,10 +1029,11 @@ module Repull
951
1029
  end
952
1030
 
953
1031
  # Update canonical listing content
954
- # Write your PMS&#39;s canonical listing content — title, description, amenities, address, occupancy, and policies — into a Repull listing, making it the source of truth. This is the flagship \&quot;the PMS owns listing content, Repull distributes it\&quot; enabler. **Partial update:** every field is optional. Only the fields you send are written; absent fields are left untouched. &#x60;amenities&#x60; is a FULL replacement of the amenity set (omit to leave untouched, send &#x60;[]&#x60; to clear). **Local write only — NOT a channel publish.** This mutates Repull&#39;s own copy of the content. It does NOT push to Airbnb / Booking.com; it marks the channels dirty so a later publish knows what changed. Distribution stays a separate explicit step. **Photos are deferred:** a provided &#x60;photos&#x60; array is echoed back in the &#x60;deferred&#x60; field and NOT persisted (media ingestion is a follow-up). Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403. This endpoint is served even when the account is over the plan-listings cap, since editing content on a listing you already own never grows the portfolio. Returns &#x60;403 listing_inactive&#x60; when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
1032
+ # Write your PMS&#39;s canonical listing content — title, description, amenities, address, occupancy, and policies — into a Repull listing, making it the source of truth. This is the flagship \&quot;the PMS owns listing content, Repull distributes it\&quot; enabler. **Partial update:** every field is optional. Only the fields you send are written; absent fields are left untouched. &#x60;amenities&#x60; is a FULL replacement of the amenity set (omit to leave untouched, send &#x60;[]&#x60; to clear). **Multilingual:** send &#x60;locale&#x60; to say which language this copy is in (&#x60;it&#x60;, &#x60;pt-BR&#x60;, …). Canonical content is stored per locale, so each language keeps its own row instead of overwriting the English one. Omit it for English. **Local write only — NOT a channel publish.** This mutates Repull&#39;s own copy of the content. It does NOT push to Airbnb / Booking.com; it marks the channels dirty so a later publish knows what changed. Distribution stays a separate explicit step. **Photos are deferred:** a provided &#x60;photos&#x60; array is echoed back in the &#x60;deferred&#x60; field and NOT persisted (media ingestion is a follow-up). Cross-tenant access (a listing that belongs to a different workspace) returns 404 — never 403. This endpoint is served even when the account is over the plan-listings cap, since editing content on a listing you already own never grows the portfolio. Returns &#x60;403 listing_inactive&#x60; when the listing is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.
955
1033
  # @param id [Integer] Repull listing id
956
1034
  # @param listing_content_update_request [ListingContentUpdateRequest]
957
1035
  # @param [Hash] opts the optional parameters
1036
+ # @option opts [String] :idempotency_key Makes a retry of this request safe. Send a unique string (a UUID generated at the point you build the request) and the response is stored for 24 hours: a repeat with the SAME key replays that stored response — tagged &#x60;Idempotency-Status: cached&#x60; — without running the operation again, so no duplicate reservation, guest or guest message is created. - Same key while the first request is still in flight → &#x60;409 idempotency_key_in_use&#x60;. - Same key with a DIFFERENT payload → &#x60;422 idempotency_key_reused&#x60;. Generate a new key per distinct request; reuse one only when retrying that exact request. - Responses with status &gt;&#x3D; 500 are deliberately not stored, so a server error stays retryable.
958
1037
  # @return [Array<(ListingContentUpdateResponse, Integer, Hash)>] ListingContentUpdateResponse data, response status code and response headers
959
1038
  def update_listing_content_with_http_info(id, listing_content_update_request, opts = {})
960
1039
  if @api_client.config.debugging
@@ -968,6 +1047,10 @@ module Repull
968
1047
  if @api_client.config.client_side_validation && listing_content_update_request.nil?
969
1048
  fail ArgumentError, "Missing the required parameter 'listing_content_update_request' when calling ListingsApi.update_listing_content"
970
1049
  end
1050
+ if @api_client.config.client_side_validation && !opts[:'idempotency_key'].nil? && opts[:'idempotency_key'].to_s.length > 255
1051
+ fail ArgumentError, 'invalid value for "opts[:"idempotency_key"]" when calling ListingsApi.update_listing_content, the character length must be smaller than or equal to 255.'
1052
+ end
1053
+
971
1054
  # resource path
972
1055
  local_var_path = '/v1/listings/{id}/content'.sub('{id}', CGI.escape(id.to_s))
973
1056
 
@@ -983,6 +1066,7 @@ module Repull
983
1066
  if !content_type.nil?
984
1067
  header_params['Content-Type'] = content_type
985
1068
  end
1069
+ header_params[:'Idempotency-Key'] = opts[:'idempotency_key'] if !opts[:'idempotency_key'].nil?
986
1070
 
987
1071
  # form parameters
988
1072
  form_params = opts[:form_params] || {}
@@ -0,0 +1,239 @@
1
+ =begin
2
+ #Repull API
3
+
4
+ #The unified API for vacation rental tech. Connect to 50+ PMS platforms and 4 OTA channels through one REST API. Built-in AI operations for guest communication, pricing, and listing optimization. ## Designed for AI agents Every error response on this API includes machine-parseable fields so an LLM (Claude in MCP, Cursor, Cline, GPT, etc.) can self-recover without escalating to a human: - `error.code` — stable string identifier (e.g. `invalid_params`, `rate_limit_exceeded`) - `error.message` — human-readable cause - `error.fix` — exact recovery steps (e.g. \"Pass `check_in_after` as ISO 8601: `?check_in_after=2026-01-15`\") - `error.docs_url` — link to the canonical write-up at `https://repull.dev/docs/errors/{code}` - `error.request_id` — id to correlate with server-side logs - `error.field` / `error.value_received` / `error.valid_values` / `error.did_you_mean` — when the error is parameter-specific - `error.retry_after` — seconds to wait before retrying (rate-limit + transient upstream) `Access-Control-Expose-Headers` lists `x-request-id` and the `X-RateLimit-*` family so browsers can read them on cross-origin responses. ## Quick Start 1. Get an API key at https://repull.dev/dashboard 2. Connect a PMS: `POST /v1/connect/{provider}` 3. List properties: `GET /v1/properties` 4. Get reservations: `GET /v1/reservations` ## Authentication All requests require a Bearer token: ``` Authorization: Bearer sk_live_YOUR_API_KEY ``` ## Request Correlation (X-Request-ID) Every response carries an `X-Request-ID` header, e.g. `X-Request-ID: req_01HXY...`. Include this id in support tickets and bug reports — we can trace the full request lifecycle (auth, rate limit, handler, downstream calls, log row) from a single id. You may set the header on the inbound request to forward your own trace id; we will echo it back instead of generating a new one. Accepted format: `^[\\\\w.-]{1,128}$`. The id is also embedded in error envelopes as `request_id` so server-side log diffs work even when the response headers are stripped by an intermediate proxy. ## Rate Limits The public API enforces a per-API-key sliding-window rate limit on top of the per-tier monthly + daily-AI quotas. **Default policy:** 600 requests per 60 seconds, per API key. Sliding window — there is no fixed-minute boundary you can burst across. Every response includes: | Header | Meaning | |---|---| | `X-RateLimit-Limit` | Requests permitted in the current window. | | `X-RateLimit-Remaining` | Requests left in the current window after this call. | | `X-RateLimit-Reset` | Unix epoch (seconds) when the next slot opens. | | `X-RateLimit-Policy` | Machine-readable policy descriptor, e.g. `600;w=60`. | | `Retry-After` | Seconds to wait before retrying. **Only present on 429 responses.** | **On 429 (rate_limit_exceeded):** the response body matches the standard error envelope with `code: \"rate_limit_exceeded\"`, plus `limit`, `window_seconds`, `retry_after`, and `request_id` fields. SDKs MUST honor `Retry-After` and use exponential backoff with jitter on subsequent retries — never a tight loop. Recommended backoff: ``` sleep_ms = (Retry-After * 1000) + random(0..250) ``` Monthly + daily-AI tier quotas (`free`, `starter`, `custom`) are enforced separately and also surface as 429s; they include `tier`, `scope`, and `resetsAt` fields. ## Plan Limits (402 — `listings_limit_exceeded`) The Repull API also enforces a per-tier cap on **active listings**: | Tier | Active listings cap | |---|---| | `free` | 3 | | `starter` | 50 | | `custom` | unlimited | When a customer's active-listing count is above their tier cap, the API returns **`402 Payment Required`** with `error.code = \"listings_limit_exceeded\"` on every route EXCEPT: - `/v1/health` — uptime probes are never gated. - `/v1/usage/*` — so dashboards can render the over-cap state. - Any `DELETE` — so the customer can trim listings to get back under the cap without paying. Unlike 429, 402 is NOT a \"wait and retry\" condition — `Retry-After` is not set. The only paths back to 200 are: 1. `DELETE` enough listings to come back under the cap, or 2. Upgrade at `https://repull.dev/dashboard/billing`. The server-side usage cache is 60s, so the first 200 after an upgrade may take up to a minute. The envelope mirrors `rate_limit_exceeded` for SDK ergonomics: `tier`, `limit`, `active_listings`, `upgrade_url`, plus the standard `code` / `message` / `fix` / `docs_url` / `request_id`.
5
+
6
+ The version of the OpenAPI document: 1.0.0
7
+ Contact: ivan@vanio.ai
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.22.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module Repull
17
+ # Freshness of ONE connected Airbnb account. Freshness is a property of an account, not of a workspace: one host's token expiring says nothing about another host's data.
18
+ class AirbnbAccountFreshness < ApiModelBase
19
+ # Airbnb host id, as a string (they exceed 2^53). The same value `?account_id=` accepts and `GET /v1/connect/airbnb` returns as `accounts[].externalAccountId`.
20
+ attr_accessor :account_id
21
+
22
+ # Display name of the connected account.
23
+ attr_accessor :account_name
24
+
25
+ # When this account last COMPLETED an Airbnb import. `null` when it never has. A run that failed or was rate-limited does not move it.
26
+ attr_accessor :last_synced_at
27
+
28
+ # `true` when this account is disconnected, has never synced, or has not refreshed in 24h+.
29
+ attr_accessor :stale
30
+
31
+ # Why THIS account is stale. Omitted when it is fresh.
32
+ attr_accessor :reason
33
+
34
+ # Where to reconnect this account. Omitted when it is fresh.
35
+ attr_accessor :fix_url
36
+
37
+ # Attribute mapping from ruby-style variable name to JSON key.
38
+ def self.attribute_map
39
+ {
40
+ :'account_id' => :'accountId',
41
+ :'account_name' => :'accountName',
42
+ :'last_synced_at' => :'lastSyncedAt',
43
+ :'stale' => :'stale',
44
+ :'reason' => :'reason',
45
+ :'fix_url' => :'fixUrl'
46
+ }
47
+ end
48
+
49
+ # Returns attribute mapping this model knows about
50
+ def self.acceptable_attribute_map
51
+ attribute_map
52
+ end
53
+
54
+ # Returns all the JSON keys this model knows about
55
+ def self.acceptable_attributes
56
+ acceptable_attribute_map.values
57
+ end
58
+
59
+ # Attribute type mapping.
60
+ def self.openapi_types
61
+ {
62
+ :'account_id' => :'String',
63
+ :'account_name' => :'String',
64
+ :'last_synced_at' => :'Time',
65
+ :'stale' => :'Boolean',
66
+ :'reason' => :'String',
67
+ :'fix_url' => :'String'
68
+ }
69
+ end
70
+
71
+ # List of attributes with nullable: true
72
+ def self.openapi_nullable
73
+ Set.new([
74
+ :'account_name',
75
+ :'last_synced_at',
76
+ :'reason',
77
+ :'fix_url'
78
+ ])
79
+ end
80
+
81
+ # Initializes the object
82
+ # @param [Hash] attributes Model attributes in the form of hash
83
+ def initialize(attributes = {})
84
+ if (!attributes.is_a?(Hash))
85
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::AirbnbAccountFreshness` initialize method"
86
+ end
87
+
88
+ # check to see if the attribute exists and convert string to symbol for hash key
89
+ acceptable_attribute_map = self.class.acceptable_attribute_map
90
+ attributes = attributes.each_with_object({}) { |(k, v), h|
91
+ if (!acceptable_attribute_map.key?(k.to_sym))
92
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::AirbnbAccountFreshness`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
93
+ end
94
+ h[k.to_sym] = v
95
+ }
96
+
97
+ if attributes.key?(:'account_id')
98
+ self.account_id = attributes[:'account_id']
99
+ else
100
+ self.account_id = nil
101
+ end
102
+
103
+ if attributes.key?(:'account_name')
104
+ self.account_name = attributes[:'account_name']
105
+ end
106
+
107
+ if attributes.key?(:'last_synced_at')
108
+ self.last_synced_at = attributes[:'last_synced_at']
109
+ else
110
+ self.last_synced_at = nil
111
+ end
112
+
113
+ if attributes.key?(:'stale')
114
+ self.stale = attributes[:'stale']
115
+ else
116
+ self.stale = nil
117
+ end
118
+
119
+ if attributes.key?(:'reason')
120
+ self.reason = attributes[:'reason']
121
+ end
122
+
123
+ if attributes.key?(:'fix_url')
124
+ self.fix_url = attributes[:'fix_url']
125
+ end
126
+ end
127
+
128
+ # Show invalid properties with the reasons. Usually used together with valid?
129
+ # @return Array for valid properties with the reasons
130
+ def list_invalid_properties
131
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
132
+ invalid_properties = Array.new
133
+ if @account_id.nil?
134
+ invalid_properties.push('invalid value for "account_id", account_id cannot be nil.')
135
+ end
136
+
137
+ if @stale.nil?
138
+ invalid_properties.push('invalid value for "stale", stale cannot be nil.')
139
+ end
140
+
141
+ invalid_properties
142
+ end
143
+
144
+ # Check to see if the all the properties in the model are valid
145
+ # @return true if the model is valid
146
+ def valid?
147
+ warn '[DEPRECATED] the `valid?` method is obsolete'
148
+ return false if @account_id.nil?
149
+ return false if @stale.nil?
150
+ true
151
+ end
152
+
153
+ # Custom attribute writer method with validation
154
+ # @param [Object] account_id Value to be assigned
155
+ def account_id=(account_id)
156
+ if account_id.nil?
157
+ fail ArgumentError, 'account_id cannot be nil'
158
+ end
159
+
160
+ @account_id = account_id
161
+ end
162
+
163
+ # Custom attribute writer method with validation
164
+ # @param [Object] stale Value to be assigned
165
+ def stale=(stale)
166
+ if stale.nil?
167
+ fail ArgumentError, 'stale cannot be nil'
168
+ end
169
+
170
+ @stale = stale
171
+ end
172
+
173
+ # Checks equality by comparing each attribute.
174
+ # @param [Object] Object to be compared
175
+ def ==(o)
176
+ return true if self.equal?(o)
177
+ self.class == o.class &&
178
+ account_id == o.account_id &&
179
+ account_name == o.account_name &&
180
+ last_synced_at == o.last_synced_at &&
181
+ stale == o.stale &&
182
+ reason == o.reason &&
183
+ fix_url == o.fix_url
184
+ end
185
+
186
+ # @see the `==` method
187
+ # @param [Object] Object to be compared
188
+ def eql?(o)
189
+ self == o
190
+ end
191
+
192
+ # Calculates hash code according to all attributes.
193
+ # @return [Integer] Hash code
194
+ def hash
195
+ [account_id, account_name, last_synced_at, stale, reason, fix_url].hash
196
+ end
197
+
198
+ # Builds the object from hash
199
+ # @param [Hash] attributes Model attributes in the form of hash
200
+ # @return [Object] Returns the model itself
201
+ def self.build_from_hash(attributes)
202
+ return nil unless attributes.is_a?(Hash)
203
+ attributes = attributes.transform_keys(&:to_sym)
204
+ transformed_hash = {}
205
+ openapi_types.each_pair do |key, type|
206
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
207
+ transformed_hash["#{key}"] = nil
208
+ elsif type =~ /\AArray<(.*)>/i
209
+ # check to ensure the input is an array given that the attribute
210
+ # is documented as an array but the input is not
211
+ if attributes[attribute_map[key]].is_a?(Array)
212
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
213
+ end
214
+ elsif !attributes[attribute_map[key]].nil?
215
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
216
+ end
217
+ end
218
+ new(transformed_hash)
219
+ end
220
+
221
+ # Returns the object in the form of hash
222
+ # @return [Hash] Returns the object in the form of hash
223
+ def to_hash
224
+ hash = {}
225
+ self.class.attribute_map.each_pair do |attr, param|
226
+ value = self.send(attr)
227
+ if value.nil?
228
+ is_nullable = self.class.openapi_nullable.include?(attr)
229
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
230
+ end
231
+
232
+ hash[param] = _to_hash(value)
233
+ end
234
+ hash
235
+ end
236
+
237
+ end
238
+
239
+ end