repull 0.2.38 → 0.2.40

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30855798e11f55eafa645072fc49983de4492a0ec1d1cb8cd5457201243114f9
4
- data.tar.gz: 4c01a334c036fb00f932dcdd704ed31e6c0bff6c6e108c95f2497f102c8efd95
3
+ metadata.gz: 788a3e7ef0f0cf6d27c2878a4d1f371512e027fe45c8f73bb02ad92cd4763a37
4
+ data.tar.gz: 9ac321f22b18e15297cefb4d33c11154cabcc201d54afc5e4414b648c059a12f
5
5
  SHA512:
6
- metadata.gz: 8be00d8e7b203b2f3d5c0d008cc66b3c7a66aff7c413fc2fab38c62321aff92e68021289f8b5c84f755c125a8b33da2b4505c39e20139ec8332df0ffe338c874
7
- data.tar.gz: b8a075cab824baf908361b16f7a9b47c6b45429a19ea9d01eb08a11d14400121a423b30c69b87ec6c43657421835517e5cecb06f23aac7b48bc08e1ec0964f73
6
+ metadata.gz: 694072b548f15e571cd46db0b9f20e6350875a75092dad2d1a23b8c6836c74cdf37ba6e602d59210d0093cc4283eb8df57a092bba6b7f645d91e004085518a81
7
+ data.tar.gz: 6cea703371296e9db874f27902f774b75c3b89df6262cb7c99da017b9805c94be5e9060836d39c2604bad59119305af69fc66a9d9d75d4a6652a803858cc6a72
@@ -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, availableUnits }` — projected from the property calendar. `availableUnits` is 1 or 0 for a single home, and the rooms of the type left for a hotel-model listing (a Mews or Cloudbeds room type). 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, units left, 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.
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, availableUnits }` — projected from the property calendar. `availableUnits` is 1 or 0 for a single home, and the units left for a listing with units (a Mews or Cloudbeds room type, a Guesty multi-unit listing). On such a listing each day also carries `units: [{ id, available }]` — which unit is free that night, with the ids of the listing's `units[]` and a reservation's `unit`. 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, units left, 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, availableUnits }` — projected from the property calendar. `availableUnits` is 1 or 0 for a single home, and the rooms of the type left for a hotel-model listing (a Mews or Cloudbeds room type). 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, units left, 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.
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, availableUnits }` — projected from the property calendar. `availableUnits` is 1 or 0 for a single home, and the units left for a listing with units (a Mews or Cloudbeds room type, a Guesty multi-unit listing). On such a listing each day also carries `units: [{ id, available }]` — which unit is free that night, with the ids of the listing's `units[]` and a reservation's `unit`. 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, units left, 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.
@@ -809,7 +809,7 @@ module Repull
809
809
  end
810
810
 
811
811
  # List PMS/OTA connections
812
- # Returns every PMS and OTA connection in the workspace, each with its `status`. **Spot connections that need attention.** A connection whose `status` is not `active` may need the host to do something before it works — most commonly a Booking.com Extranet connection where the invited user was granted only partial access (`status: \"needs_permissions\"`). A Smoobu connection still on a legacy single API key carries `action.reason: \"reauth_required\"` while its `status` is `active`: Smoobu stops accepting those keys on October 31, 2026, and `fixUrl` opens the form for a new API key + secret (the connection id stays the same). These connections carry two extra fields: - `action` — `{ required: true, reason, message }`. `reason` is a stable machine code (e.g. `needs_permissions`); `message` is a host-facing one-liner describing what to do. - `fixUrl` — a durable link that reopens the hosted Connect flow **bound to that account, on the fix screen** (e.g. \"grant full access\" + a Re-check button). It is safe to store and show in your own dashboard. **Self-serve repair:** when `action.required` is true, surface a \"Fix\" button that opens `fixUrl` in a new tab (or embed it). The host resolves the issue (e.g. grants the user full access in Booking.com) and clicks Re-check; the import finishes on its own and the connection flips back to `active` — no re-invite, no support ticket. Poll this endpoint (or read it after the host returns) to confirm `action` has cleared.
812
+ # Returns every PMS and OTA connection in the workspace, each with its `status`. **One entry per Booking.com Extranet hotel login.** Each hotel that invites the Repull user connects as its own login, with its own `externalAccountId` (handle `booking_extranet:{externalAccountId}`). A new hotel adds an entry next to the existing ones and never replaces them; the entries share the workspace's Extranet `id`, and each hotel's listings, reservations, conversations and reviews stay under its own login (filter them with `?account=booking_extranet:{externalAccountId}`). **Spot connections that need attention.** A connection whose `status` is not `active` may need the host to do something before it works — most commonly a Booking.com Extranet connection where the invited user was granted only partial access (`status: \"needs_permissions\"`). A Smoobu connection still on a legacy single API key carries `action.reason: \"reauth_required\"` while its `status` is `active`: Smoobu stops accepting those keys on October 31, 2026, and `fixUrl` opens the form for a new API key + secret (the connection id stays the same). These connections carry two extra fields: - `action` — `{ required: true, reason, message }`. `reason` is a stable machine code (e.g. `needs_permissions`); `message` is a host-facing one-liner describing what to do. - `fixUrl` — a durable link that reopens the hosted Connect flow **bound to that account, on the fix screen** (e.g. \"grant full access\" + a Re-check button). It is safe to store and show in your own dashboard. **Self-serve repair:** when `action.required` is true, surface a \"Fix\" button that opens `fixUrl` in a new tab (or embed it). The host resolves the issue (e.g. grants the user full access in Booking.com) and clicks Re-check; the import finishes on its own and the connection flips back to `active` — no re-invite, no support ticket. Poll this endpoint (or read it after the host returns) to confirm `action` has cleared.
813
813
  # @param [Hash] opts the optional parameters
814
814
  # @return [ConnectionListResponse]
815
815
  def list_connections(opts = {})
@@ -818,7 +818,7 @@ module Repull
818
818
  end
819
819
 
820
820
  # List PMS/OTA connections
821
- # Returns every PMS and OTA connection in the workspace, each with its `status`. **Spot connections that need attention.** A connection whose `status` is not `active` may need the host to do something before it works — most commonly a Booking.com Extranet connection where the invited user was granted only partial access (`status: \"needs_permissions\"`). A Smoobu connection still on a legacy single API key carries `action.reason: \"reauth_required\"` while its `status` is `active`: Smoobu stops accepting those keys on October 31, 2026, and `fixUrl` opens the form for a new API key + secret (the connection id stays the same). These connections carry two extra fields: - `action` — `{ required: true, reason, message }`. `reason` is a stable machine code (e.g. `needs_permissions`); `message` is a host-facing one-liner describing what to do. - `fixUrl` — a durable link that reopens the hosted Connect flow **bound to that account, on the fix screen** (e.g. \"grant full access\" + a Re-check button). It is safe to store and show in your own dashboard. **Self-serve repair:** when `action.required` is true, surface a \"Fix\" button that opens `fixUrl` in a new tab (or embed it). The host resolves the issue (e.g. grants the user full access in Booking.com) and clicks Re-check; the import finishes on its own and the connection flips back to `active` — no re-invite, no support ticket. Poll this endpoint (or read it after the host returns) to confirm `action` has cleared.
821
+ # Returns every PMS and OTA connection in the workspace, each with its `status`. **One entry per Booking.com Extranet hotel login.** Each hotel that invites the Repull user connects as its own login, with its own `externalAccountId` (handle `booking_extranet:{externalAccountId}`). A new hotel adds an entry next to the existing ones and never replaces them; the entries share the workspace's Extranet `id`, and each hotel's listings, reservations, conversations and reviews stay under its own login (filter them with `?account=booking_extranet:{externalAccountId}`). **Spot connections that need attention.** A connection whose `status` is not `active` may need the host to do something before it works — most commonly a Booking.com Extranet connection where the invited user was granted only partial access (`status: \"needs_permissions\"`). A Smoobu connection still on a legacy single API key carries `action.reason: \"reauth_required\"` while its `status` is `active`: Smoobu stops accepting those keys on October 31, 2026, and `fixUrl` opens the form for a new API key + secret (the connection id stays the same). These connections carry two extra fields: - `action` — `{ required: true, reason, message }`. `reason` is a stable machine code (e.g. `needs_permissions`); `message` is a host-facing one-liner describing what to do. - `fixUrl` — a durable link that reopens the hosted Connect flow **bound to that account, on the fix screen** (e.g. \"grant full access\" + a Re-check button). It is safe to store and show in your own dashboard. **Self-serve repair:** when `action.required` is true, surface a \"Fix\" button that opens `fixUrl` in a new tab (or embed it). The host resolves the issue (e.g. grants the user full access in Booking.com) and clicks Re-check; the import finishes on its own and the connection flips back to `active` — no re-invite, no support ticket. Poll this endpoint (or read it after the host returns) to confirm `action` has cleared.
822
822
  # @param [Hash] opts the optional parameters
823
823
  # @return [Array<(ConnectionListResponse, Integer, Hash)>] ConnectionListResponse data, response status code and response headers
824
824
  def list_connections_with_http_info(opts = {})
@@ -684,7 +684,7 @@ module Repull
684
684
  end
685
685
 
686
686
  # Send a message to the guest
687
- # Sends a message to the guest on this conversation and records it in the thread. Omit `channel` and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, VRBO, SMS, email or the direct-booking site) — that is the right default. Pass `channel` only to force a specific one. The message is attributed to the API: it is recorded with `aiGenerated` false so an API send is never counted as an automated reply. ### Airbnb rewrites links — check `contentRewritten` Airbnb rejects guest messages containing a link, an email address or a phone number, and names the offending text. When that happens the offending fragment is stripped and the remainder is re-sent once, which means **the guest receives a message that is not the one you wrote**. Reporting that as a plain success would be a lie, so every response carries `contentRewritten`; when it is `true`, `deliveredContent` is the text that actually reached the guest. Check it before assuming your message went out verbatim. When the text cannot be salvaged (the link is most of the message) nothing is delivered and the call returns `422 message_not_sent` with the channel's verbatim refusal in `statusReason`. Send `Idempotency-Key` — without it, retrying after a network timeout sends the guest the same message twice. ### Attachments Send files with `attachments: [{ url, contentType?, filename? }]` — public `https://` URLs, up to 5 per request, 10 MB each. `message` may be omitted when there are attachments (except on Booking.com). Repull downloads each file, reads its real type from the bytes, keeps a durable copy and delivers it through the channel's own file flow. **Every file is checked before anything is sent**: if one is unreachable, too large or of a type the channel refuses, the call returns 422 naming the file (`index`) and the guest receives nothing. | Channel | Accepted types | Text | How it arrives | |---|---|---|---| | Airbnb | JPEG, PNG, GIF, WebP (converted to JPEG), MP4, QuickTime | optional | each file as its own message, then the text as a separate message | | Booking.com | JPEG, PNG | **required** | one message carrying the text and every file | | VRBO, SMS, email, direct-booking site chat | — | — | `422 attachments_not_supported`, nothing sent | Airbnb does not allow files in pre-booking (inquiry) conversations; that refusal comes back as `422 message_not_sent`. Because Airbnb delivers files one message at a time, a later file can be refused after earlier ones arrived — that returns `422 message_partially_sent` with `parts` saying exactly which messages reached the guest; resend only the rest. The response's `attachments` lists each file's durable `url`, and `parts` lists every channel message the send produced. Read-back (`GET /v1/conversations/{id}/messages`) shows the same files in each message's `attachments`. **Inactive listings:** a conversation that belongs to an inactive listing returns `403 listing_inactive` and no message is sent. Activate the listing first.
687
+ # Sends a message to the guest on this conversation and records it in the thread. Omit `channel` and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, VRBO, SMS, email or the direct-booking site) — that is the right default. Pass `channel` only to force a specific one. The message is attributed to the API: it is recorded with `aiGenerated` false so an API send is never counted as an automated reply. ### Airbnb rewrites links — check `contentRewritten` Airbnb rejects guest messages containing a link, an email address or a phone number, and names the offending text. When that happens the offending fragment is stripped and the remainder is re-sent once, which means **the guest receives a message that is not the one you wrote**. Reporting that as a plain success would be a lie, so every response carries `contentRewritten`; when it is `true`, `deliveredContent` is the text that actually reached the guest. Check it before assuming your message went out verbatim. When the text cannot be salvaged (the link is most of the message) nothing is delivered and the call returns `422 message_not_sent` with the channel's verbatim refusal in `statusReason`. `message_not_sent` only ever means the channel refused: a fault on Repull's side (a dropped database connection, a timeout) is `503 internal_error` with `sent: false` when nothing was delivered — retry it unchanged with the same `Idempotency-Key`. Send `Idempotency-Key` — without it, retrying after a network timeout sends the guest the same message twice. ### Attachments Send files with `attachments: [{ url, contentType?, filename? }]` — public `https://` URLs, up to 5 per request, 10 MB each. `message` may be omitted when there are attachments (except on Booking.com). Repull downloads each file, reads its real type from the bytes, keeps a durable copy and delivers it through the channel's own file flow. **Every file is checked before anything is sent**: if one is unreachable, too large or of a type the channel refuses, the call returns 422 naming the file (`index`) and the guest receives nothing. | Channel | Accepted types | Text | How it arrives | |---|---|---|---| | Airbnb | JPEG, PNG, GIF, WebP (converted to JPEG), MP4, QuickTime | optional | each file as its own message, then the text as a separate message | | Booking.com | JPEG, PNG | **required** | one message carrying the text and every file | | VRBO, SMS, email, direct-booking site chat | — | — | `422 attachments_not_supported`, nothing sent | Airbnb does not allow files in pre-booking (inquiry) conversations; that refusal comes back as `422 message_not_sent`. Because Airbnb delivers files one message at a time, a later file can be refused after earlier ones arrived — that returns `422 message_partially_sent` with `parts` saying exactly which messages reached the guest; resend only the rest. The response's `attachments` lists each file's durable `url`, and `parts` lists every channel message the send produced. Read-back (`GET /v1/conversations/{id}/messages`) shows the same files in each message's `attachments`. **Inactive listings:** a conversation that belongs to an inactive listing returns `403 listing_inactive` and no message is sent. Activate the listing first.
688
688
  # @param id [Integer] Internal Repull thread id.
689
689
  # @param send_message_request [SendMessageRequest]
690
690
  # @param [Hash] opts the optional parameters
@@ -696,7 +696,7 @@ module Repull
696
696
  end
697
697
 
698
698
  # Send a message to the guest
699
- # Sends a message to the guest on this conversation and records it in the thread. Omit &#x60;channel&#x60; and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, VRBO, SMS, email or the direct-booking site) — that is the right default. Pass &#x60;channel&#x60; only to force a specific one. The message is attributed to the API: it is recorded with &#x60;aiGenerated&#x60; false so an API send is never counted as an automated reply. ### Airbnb rewrites links — check &#x60;contentRewritten&#x60; Airbnb rejects guest messages containing a link, an email address or a phone number, and names the offending text. When that happens the offending fragment is stripped and the remainder is re-sent once, which means **the guest receives a message that is not the one you wrote**. Reporting that as a plain success would be a lie, so every response carries &#x60;contentRewritten&#x60;; when it is &#x60;true&#x60;, &#x60;deliveredContent&#x60; is the text that actually reached the guest. Check it before assuming your message went out verbatim. When the text cannot be salvaged (the link is most of the message) nothing is delivered and the call returns &#x60;422 message_not_sent&#x60; with the channel&#39;s verbatim refusal in &#x60;statusReason&#x60;. Send &#x60;Idempotency-Key&#x60; — without it, retrying after a network timeout sends the guest the same message twice. ### Attachments Send files with &#x60;attachments: [{ url, contentType?, filename? }]&#x60; — public &#x60;https://&#x60; URLs, up to 5 per request, 10 MB each. &#x60;message&#x60; may be omitted when there are attachments (except on Booking.com). Repull downloads each file, reads its real type from the bytes, keeps a durable copy and delivers it through the channel&#39;s own file flow. **Every file is checked before anything is sent**: if one is unreachable, too large or of a type the channel refuses, the call returns 422 naming the file (&#x60;index&#x60;) and the guest receives nothing. | Channel | Accepted types | Text | How it arrives | |---|---|---|---| | Airbnb | JPEG, PNG, GIF, WebP (converted to JPEG), MP4, QuickTime | optional | each file as its own message, then the text as a separate message | | Booking.com | JPEG, PNG | **required** | one message carrying the text and every file | | VRBO, SMS, email, direct-booking site chat | — | — | &#x60;422 attachments_not_supported&#x60;, nothing sent | Airbnb does not allow files in pre-booking (inquiry) conversations; that refusal comes back as &#x60;422 message_not_sent&#x60;. Because Airbnb delivers files one message at a time, a later file can be refused after earlier ones arrived — that returns &#x60;422 message_partially_sent&#x60; with &#x60;parts&#x60; saying exactly which messages reached the guest; resend only the rest. The response&#39;s &#x60;attachments&#x60; lists each file&#39;s durable &#x60;url&#x60;, and &#x60;parts&#x60; lists every channel message the send produced. Read-back (&#x60;GET /v1/conversations/{id}/messages&#x60;) shows the same files in each message&#39;s &#x60;attachments&#x60;. **Inactive listings:** a conversation that belongs to an inactive listing returns &#x60;403 listing_inactive&#x60; and no message is sent. Activate the listing first.
699
+ # Sends a message to the guest on this conversation and records it in the thread. Omit &#x60;channel&#x60; and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, VRBO, SMS, email or the direct-booking site) — that is the right default. Pass &#x60;channel&#x60; only to force a specific one. The message is attributed to the API: it is recorded with &#x60;aiGenerated&#x60; false so an API send is never counted as an automated reply. ### Airbnb rewrites links — check &#x60;contentRewritten&#x60; Airbnb rejects guest messages containing a link, an email address or a phone number, and names the offending text. When that happens the offending fragment is stripped and the remainder is re-sent once, which means **the guest receives a message that is not the one you wrote**. Reporting that as a plain success would be a lie, so every response carries &#x60;contentRewritten&#x60;; when it is &#x60;true&#x60;, &#x60;deliveredContent&#x60; is the text that actually reached the guest. Check it before assuming your message went out verbatim. When the text cannot be salvaged (the link is most of the message) nothing is delivered and the call returns &#x60;422 message_not_sent&#x60; with the channel&#39;s verbatim refusal in &#x60;statusReason&#x60;. &#x60;message_not_sent&#x60; only ever means the channel refused: a fault on Repull&#39;s side (a dropped database connection, a timeout) is &#x60;503 internal_error&#x60; with &#x60;sent: false&#x60; when nothing was delivered — retry it unchanged with the same &#x60;Idempotency-Key&#x60;. Send &#x60;Idempotency-Key&#x60; — without it, retrying after a network timeout sends the guest the same message twice. ### Attachments Send files with &#x60;attachments: [{ url, contentType?, filename? }]&#x60; — public &#x60;https://&#x60; URLs, up to 5 per request, 10 MB each. &#x60;message&#x60; may be omitted when there are attachments (except on Booking.com). Repull downloads each file, reads its real type from the bytes, keeps a durable copy and delivers it through the channel&#39;s own file flow. **Every file is checked before anything is sent**: if one is unreachable, too large or of a type the channel refuses, the call returns 422 naming the file (&#x60;index&#x60;) and the guest receives nothing. | Channel | Accepted types | Text | How it arrives | |---|---|---|---| | Airbnb | JPEG, PNG, GIF, WebP (converted to JPEG), MP4, QuickTime | optional | each file as its own message, then the text as a separate message | | Booking.com | JPEG, PNG | **required** | one message carrying the text and every file | | VRBO, SMS, email, direct-booking site chat | — | — | &#x60;422 attachments_not_supported&#x60;, nothing sent | Airbnb does not allow files in pre-booking (inquiry) conversations; that refusal comes back as &#x60;422 message_not_sent&#x60;. Because Airbnb delivers files one message at a time, a later file can be refused after earlier ones arrived — that returns &#x60;422 message_partially_sent&#x60; with &#x60;parts&#x60; saying exactly which messages reached the guest; resend only the rest. The response&#39;s &#x60;attachments&#x60; lists each file&#39;s durable &#x60;url&#x60;, and &#x60;parts&#x60; lists every channel message the send produced. Read-back (&#x60;GET /v1/conversations/{id}/messages&#x60;) shows the same files in each message&#39;s &#x60;attachments&#x60;. **Inactive listings:** a conversation that belongs to an inactive listing returns &#x60;403 listing_inactive&#x60; and no message is sent. Activate the listing first.
700
700
  # @param id [Integer] Internal Repull thread id.
701
701
  # @param send_message_request [SendMessageRequest]
702
702
  # @param [Hash] opts the optional parameters
@@ -689,8 +689,8 @@ module Repull
689
689
  return data, status_code, headers
690
690
  end
691
691
 
692
- # List a listing's units (rooms)
693
- # The physical rooms under a listing. For a hotel-model PMS (Mews, Cloudbeds) a listing is a room type: prices, restrictions and availability are set on the room type, and each reservation is assigned one of these rooms (`reservation.unit.id`). A room can belong to more than one room type. Any other listing is a single home, which is its own unit, and returns an empty list.
692
+ # List a listing's units
693
+ # The units under a listing. - **Hotel-model PMS (Mews, Cloudbeds):** the listing is a room type and these are its physical rooms. Prices, restrictions and availability are set on the room type, and each reservation is assigned one of these rooms (`reservation.unit.id`). A room can belong to more than one room type. - **Guesty multi-unit listing:** the listing is the parent channels sell, and these are its units. Each unit is also a listing of its own (`listingId`), with its own calendar and stays; a stay on a unit carries `reservation.unit`. Which unit is free on which night: `units` per day on `GET /v1/availability/{propertyId}`. Any other listing is a single home, which is its own unit, and returns an empty list.
694
694
  # @param id [Integer] Listing id.
695
695
  # @param [Hash] opts the optional parameters
696
696
  # @return [ListListingUnits200Response]
@@ -699,8 +699,8 @@ module Repull
699
699
  data
700
700
  end
701
701
 
702
- # List a listing&#39;s units (rooms)
703
- # The physical rooms under a listing. For a hotel-model PMS (Mews, Cloudbeds) a listing is a room type: prices, restrictions and availability are set on the room type, and each reservation is assigned one of these rooms (&#x60;reservation.unit.id&#x60;). A room can belong to more than one room type. Any other listing is a single home, which is its own unit, and returns an empty list.
702
+ # List a listing&#39;s units
703
+ # The units under a listing. - **Hotel-model PMS (Mews, Cloudbeds):** the listing is a room type and these are its physical rooms. Prices, restrictions and availability are set on the room type, and each reservation is assigned one of these rooms (&#x60;reservation.unit.id&#x60;). A room can belong to more than one room type. - **Guesty multi-unit listing:** the listing is the parent channels sell, and these are its units. Each unit is also a listing of its own (&#x60;listingId&#x60;), with its own calendar and stays; a stay on a unit carries &#x60;reservation.unit&#x60;. Which unit is free on which night: &#x60;units&#x60; per day on &#x60;GET /v1/availability/{propertyId}&#x60;. Any other listing is a single home, which is its own unit, and returns an empty list.
704
704
  # @param id [Integer] Listing id.
705
705
  # @param [Hash] opts the optional parameters
706
706
  # @return [Array<(ListListingUnits200Response, Integer, Hash)>] ListListingUnits200Response data, response status code and response headers
@@ -16,7 +16,7 @@ require 'time'
16
16
  module Repull
17
17
  # Channel-agnostic message thread between the host workspace and a guest. Returned by `GET /v1/conversations`. The `id` is the internal Repull thread id (integer) — pass it back as the `{id}` path param on detail / messages calls.
18
18
  class Conversation < ApiModelBase
19
- # The connected account this conversation belongs to. List endpoint.
19
+ # The connected account this conversation belongs to, the same block as the message webhooks. On the list and on `GET /v1/conversations/{id}`.
20
20
  attr_accessor :account
21
21
 
22
22
  attr_accessor :id
@@ -16,7 +16,7 @@ require 'time'
16
16
  module Repull
17
17
  # Returned by `GET /v1/conversations/{id}`. Extends the list-row `Conversation` shape with expanded `host` + `guest` blocks so SDK consumers can render thread headers without an extra round-trip.
18
18
  class ConversationDetail < ApiModelBase
19
- # The connected account this conversation belongs to. List endpoint.
19
+ # The connected account this conversation belongs to, the same block as the message webhooks. On the list and on `GET /v1/conversations/{id}`.
20
20
  attr_accessor :account
21
21
 
22
22
  attr_accessor :id
@@ -15,7 +15,7 @@ require 'time'
15
15
 
16
16
  module Repull
17
17
  class ListListingUnits200ResponseDataInner < ApiModelBase
18
- # The PMS's id for the room; `reservation.unit.id` refers to it.
18
+ # The PMS's id for the unit; `reservation.unit.id` and the per-night `units[].id` on `GET /v1/availability/{propertyId}` refer to it.
19
19
  attr_accessor :id
20
20
 
21
21
  attr_accessor :name
@@ -30,8 +30,12 @@ module Repull
30
30
 
31
31
  attr_accessor :floor
32
32
 
33
+ # The PMS the unit comes from: `cloudbeds`, `mews`, `guesty`, …
33
34
  attr_accessor :source
34
35
 
36
+ # The unit's own Repull listing id when the unit is a listing too (a Guesty multi-unit listing's units, each with its own calendar and stays), else null.
37
+ attr_accessor :listing_id
38
+
35
39
  # Attribute mapping from ruby-style variable name to JSON key.
36
40
  def self.attribute_map
37
41
  {
@@ -41,7 +45,8 @@ module Repull
41
45
  :'parent_id' => :'parentId',
42
46
  :'housekeeping_status' => :'housekeepingStatus',
43
47
  :'floor' => :'floor',
44
- :'source' => :'source'
48
+ :'source' => :'source',
49
+ :'listing_id' => :'listingId'
45
50
  }
46
51
  end
47
52
 
@@ -64,7 +69,8 @@ module Repull
64
69
  :'parent_id' => :'String',
65
70
  :'housekeeping_status' => :'String',
66
71
  :'floor' => :'String',
67
- :'source' => :'String'
72
+ :'source' => :'String',
73
+ :'listing_id' => :'String'
68
74
  }
69
75
  end
70
76
 
@@ -74,6 +80,7 @@ module Repull
74
80
  :'parent_id',
75
81
  :'housekeeping_status',
76
82
  :'floor',
83
+ :'listing_id'
77
84
  ])
78
85
  end
79
86
 
@@ -120,6 +127,10 @@ module Repull
120
127
  if attributes.key?(:'source')
121
128
  self.source = attributes[:'source']
122
129
  end
130
+
131
+ if attributes.key?(:'listing_id')
132
+ self.listing_id = attributes[:'listing_id']
133
+ end
123
134
  end
124
135
 
125
136
  # Show invalid properties with the reasons. Usually used together with valid?
@@ -148,7 +159,8 @@ module Repull
148
159
  parent_id == o.parent_id &&
149
160
  housekeeping_status == o.housekeeping_status &&
150
161
  floor == o.floor &&
151
- source == o.source
162
+ source == o.source &&
163
+ listing_id == o.listing_id
152
164
  end
153
165
 
154
166
  # @see the `==` method
@@ -160,7 +172,7 @@ module Repull
160
172
  # Calculates hash code according to all attributes.
161
173
  # @return [Integer] Hash code
162
174
  def hash
163
- [id, name, active, parent_id, housekeeping_status, floor, source].hash
175
+ [id, name, active, parent_id, housekeeping_status, floor, source, listing_id].hash
164
176
  end
165
177
 
166
178
  # Builds the object from hash
@@ -18,15 +18,17 @@ module Repull
18
18
  class Listing < ApiModelBase
19
19
  attr_accessor :capabilities
20
20
 
21
- # `room_type` when the listing sells several identical units (a hotel room type: `unitCount` > 1) or is a hotel PMS room type; `home` otherwise. See the Hotels & multi-unit guide.
21
+ # `room_type` when the listing sells several units (a hotel room type or a Guesty multi-unit listing: `unitCount` > 1) or is a hotel PMS room type; `home` otherwise. See the Hotels & multi-unit guide.
22
22
  attr_accessor :kind
23
23
 
24
+ attr_accessor :parent
25
+
24
26
  attr_accessor :hotel
25
27
 
26
- # How many identical units (rooms) this listing sells: the hotel PMS rooms of the room type, the room count on Booking.com, or 1 for a home. Rooms left per night are `availableUnits` on `GET /v1/availability/{propertyId}`.
28
+ # How many units (rooms) this listing sells: the hotel PMS rooms of the room type, the units of a Guesty multi-unit listing, the room count on Booking.com, or 1 for a home. Units left per night are `availableUnits` on `GET /v1/availability/{propertyId}`, and which unit is free is `units` there.
27
29
  attr_accessor :unit_count
28
30
 
29
- # `GET /v1/listings/{id}` only. The physical rooms under a hotel-model listing (a Mews or Cloudbeds room type); empty for a single home. Same items as `GET /v1/listings/{id}/units`.
31
+ # `GET /v1/listings/{id}` only. The units under the listing: a Mews or Cloudbeds room type's rooms, or a Guesty multi-unit listing's units; empty for a single home. Same items as `GET /v1/listings/{id}/units`.
30
32
  attr_accessor :units
31
33
 
32
34
  # Repull listing id
@@ -40,6 +42,12 @@ module Repull
40
42
 
41
43
  attr_accessor :address
42
44
 
45
+ # `GET /v1/listings/{id}` only. Map pin latitude in decimal degrees, as a string; the same value as `GET /v1/properties/{id}`. Null when the listing has no coordinates.
46
+ attr_accessor :latitude
47
+
48
+ # `GET /v1/listings/{id}` only. Map pin longitude in decimal degrees, as a string; the same value as `GET /v1/properties/{id}`. Null when the listing has no coordinates.
49
+ attr_accessor :longitude
50
+
43
51
  # Cover photo URL. Always present on an active listing. On an **inactive** one it is present only when the caller passes `?include=thumbnail`; `null` means the listing has no cover photo stored, absent means the expansion was not requested.
44
52
  attr_accessor :thumbnail_url
45
53
 
@@ -87,6 +95,7 @@ module Repull
87
95
  {
88
96
  :'capabilities' => :'capabilities',
89
97
  :'kind' => :'kind',
98
+ :'parent' => :'parent',
90
99
  :'hotel' => :'hotel',
91
100
  :'unit_count' => :'unitCount',
92
101
  :'units' => :'units',
@@ -94,6 +103,8 @@ module Repull
94
103
  :'name' => :'name',
95
104
  :'public_name' => :'publicName',
96
105
  :'address' => :'address',
106
+ :'latitude' => :'latitude',
107
+ :'longitude' => :'longitude',
97
108
  :'thumbnail_url' => :'thumbnailUrl',
98
109
  :'status' => :'status',
99
110
  :'channels' => :'channels',
@@ -127,6 +138,7 @@ module Repull
127
138
  {
128
139
  :'capabilities' => :'ListingCapabilities',
129
140
  :'kind' => :'String',
141
+ :'parent' => :'ListingParent',
130
142
  :'hotel' => :'ListingHotel',
131
143
  :'unit_count' => :'Integer',
132
144
  :'units' => :'Array<ListingUnitsInner>',
@@ -134,6 +146,8 @@ module Repull
134
146
  :'name' => :'String',
135
147
  :'public_name' => :'String',
136
148
  :'address' => :'ListingAddress',
149
+ :'latitude' => :'String',
150
+ :'longitude' => :'String',
137
151
  :'thumbnail_url' => :'String',
138
152
  :'status' => :'String',
139
153
  :'channels' => :'Array<ListingChannel>',
@@ -155,8 +169,11 @@ module Repull
155
169
  # List of attributes with nullable: true
156
170
  def self.openapi_nullable
157
171
  Set.new([
172
+ :'parent',
158
173
  :'hotel',
159
174
  :'public_name',
175
+ :'latitude',
176
+ :'longitude',
160
177
  :'thumbnail_url',
161
178
  :'content',
162
179
  :'details',
@@ -190,6 +207,10 @@ module Repull
190
207
  self.kind = attributes[:'kind']
191
208
  end
192
209
 
210
+ if attributes.key?(:'parent')
211
+ self.parent = attributes[:'parent']
212
+ end
213
+
193
214
  if attributes.key?(:'hotel')
194
215
  self.hotel = attributes[:'hotel']
195
216
  end
@@ -220,6 +241,14 @@ module Repull
220
241
  self.address = attributes[:'address']
221
242
  end
222
243
 
244
+ if attributes.key?(:'latitude')
245
+ self.latitude = attributes[:'latitude']
246
+ end
247
+
248
+ if attributes.key?(:'longitude')
249
+ self.longitude = attributes[:'longitude']
250
+ end
251
+
223
252
  if attributes.key?(:'thumbnail_url')
224
253
  self.thumbnail_url = attributes[:'thumbnail_url']
225
254
  end
@@ -334,6 +363,7 @@ module Repull
334
363
  self.class == o.class &&
335
364
  capabilities == o.capabilities &&
336
365
  kind == o.kind &&
366
+ parent == o.parent &&
337
367
  hotel == o.hotel &&
338
368
  unit_count == o.unit_count &&
339
369
  units == o.units &&
@@ -341,6 +371,8 @@ module Repull
341
371
  name == o.name &&
342
372
  public_name == o.public_name &&
343
373
  address == o.address &&
374
+ latitude == o.latitude &&
375
+ longitude == o.longitude &&
344
376
  thumbnail_url == o.thumbnail_url &&
345
377
  status == o.status &&
346
378
  channels == o.channels &&
@@ -367,7 +399,7 @@ module Repull
367
399
  # Calculates hash code according to all attributes.
368
400
  # @return [Integer] Hash code
369
401
  def hash
370
- [capabilities, kind, hotel, unit_count, units, id, name, public_name, address, thumbnail_url, status, channels, amenities, content, details, rooms, policies, rules, pricing, fees, taxes, photos, created_at, updated_at].hash
402
+ [capabilities, kind, parent, hotel, unit_count, units, id, name, public_name, address, latitude, longitude, thumbnail_url, status, channels, amenities, content, details, rooms, policies, rules, pricing, fees, taxes, photos, created_at, updated_at].hash
371
403
  end
372
404
 
373
405
  # Builds the object from hash
@@ -0,0 +1,159 @@
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` | unlimited (10 included, then billed per listing) | | `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
+
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
+ # The multi-unit listing this listing is one unit of (a Guesty multi-unit listing's unit, which has its own calendar and stays). The parent lists it in `units[]` with this listing's id as `units[].listingId`. `null` otherwise.
18
+ class ListingParent < ApiModelBase
19
+ # Repull listing id of the multi-unit listing.
20
+ attr_accessor :id
21
+
22
+ attr_accessor :name
23
+
24
+ # Attribute mapping from ruby-style variable name to JSON key.
25
+ def self.attribute_map
26
+ {
27
+ :'id' => :'id',
28
+ :'name' => :'name'
29
+ }
30
+ end
31
+
32
+ # Returns attribute mapping this model knows about
33
+ def self.acceptable_attribute_map
34
+ attribute_map
35
+ end
36
+
37
+ # Returns all the JSON keys this model knows about
38
+ def self.acceptable_attributes
39
+ acceptable_attribute_map.values
40
+ end
41
+
42
+ # Attribute type mapping.
43
+ def self.openapi_types
44
+ {
45
+ :'id' => :'String',
46
+ :'name' => :'String'
47
+ }
48
+ end
49
+
50
+ # List of attributes with nullable: true
51
+ def self.openapi_nullable
52
+ Set.new([
53
+ :'name'
54
+ ])
55
+ end
56
+
57
+ # Initializes the object
58
+ # @param [Hash] attributes Model attributes in the form of hash
59
+ def initialize(attributes = {})
60
+ if (!attributes.is_a?(Hash))
61
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::ListingParent` initialize method"
62
+ end
63
+
64
+ # check to see if the attribute exists and convert string to symbol for hash key
65
+ acceptable_attribute_map = self.class.acceptable_attribute_map
66
+ attributes = attributes.each_with_object({}) { |(k, v), h|
67
+ if (!acceptable_attribute_map.key?(k.to_sym))
68
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::ListingParent`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
69
+ end
70
+ h[k.to_sym] = v
71
+ }
72
+
73
+ if attributes.key?(:'id')
74
+ self.id = attributes[:'id']
75
+ end
76
+
77
+ if attributes.key?(:'name')
78
+ self.name = attributes[:'name']
79
+ end
80
+ end
81
+
82
+ # Show invalid properties with the reasons. Usually used together with valid?
83
+ # @return Array for valid properties with the reasons
84
+ def list_invalid_properties
85
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
86
+ invalid_properties = Array.new
87
+ invalid_properties
88
+ end
89
+
90
+ # Check to see if the all the properties in the model are valid
91
+ # @return true if the model is valid
92
+ def valid?
93
+ warn '[DEPRECATED] the `valid?` method is obsolete'
94
+ true
95
+ end
96
+
97
+ # Checks equality by comparing each attribute.
98
+ # @param [Object] Object to be compared
99
+ def ==(o)
100
+ return true if self.equal?(o)
101
+ self.class == o.class &&
102
+ id == o.id &&
103
+ name == o.name
104
+ end
105
+
106
+ # @see the `==` method
107
+ # @param [Object] Object to be compared
108
+ def eql?(o)
109
+ self == o
110
+ end
111
+
112
+ # Calculates hash code according to all attributes.
113
+ # @return [Integer] Hash code
114
+ def hash
115
+ [id, name].hash
116
+ end
117
+
118
+ # Builds the object from hash
119
+ # @param [Hash] attributes Model attributes in the form of hash
120
+ # @return [Object] Returns the model itself
121
+ def self.build_from_hash(attributes)
122
+ return nil unless attributes.is_a?(Hash)
123
+ attributes = attributes.transform_keys(&:to_sym)
124
+ transformed_hash = {}
125
+ openapi_types.each_pair do |key, type|
126
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
127
+ transformed_hash["#{key}"] = nil
128
+ elsif type =~ /\AArray<(.*)>/i
129
+ # check to ensure the input is an array given that the attribute
130
+ # is documented as an array but the input is not
131
+ if attributes[attribute_map[key]].is_a?(Array)
132
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
133
+ end
134
+ elsif !attributes[attribute_map[key]].nil?
135
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
136
+ end
137
+ end
138
+ new(transformed_hash)
139
+ end
140
+
141
+ # Returns the object in the form of hash
142
+ # @return [Hash] Returns the object in the form of hash
143
+ def to_hash
144
+ hash = {}
145
+ self.class.attribute_map.each_pair do |attr, param|
146
+ value = self.send(attr)
147
+ if value.nil?
148
+ is_nullable = self.class.openapi_nullable.include?(attr)
149
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
150
+ end
151
+
152
+ hash[param] = _to_hash(value)
153
+ end
154
+ hash
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -15,6 +15,7 @@ require 'time'
15
15
 
16
16
  module Repull
17
17
  class ListingUnitsInner < ApiModelBase
18
+ # Unit id — what `reservation.unit.id` and the per-night `units[].id` on availability refer to.
18
19
  attr_accessor :id
19
20
 
20
21
  attr_accessor :name
@@ -27,8 +28,12 @@ module Repull
27
28
 
28
29
  attr_accessor :floor
29
30
 
31
+ # The PMS the unit comes from: `cloudbeds`, `mews`, `guesty`, …
30
32
  attr_accessor :source
31
33
 
34
+ # The unit's own Repull listing id when the unit is a listing too (a Guesty multi-unit listing's units, each with its own calendar), else `null`.
35
+ attr_accessor :listing_id
36
+
32
37
  # Attribute mapping from ruby-style variable name to JSON key.
33
38
  def self.attribute_map
34
39
  {
@@ -38,7 +43,8 @@ module Repull
38
43
  :'parent_id' => :'parentId',
39
44
  :'housekeeping_status' => :'housekeepingStatus',
40
45
  :'floor' => :'floor',
41
- :'source' => :'source'
46
+ :'source' => :'source',
47
+ :'listing_id' => :'listingId'
42
48
  }
43
49
  end
44
50
 
@@ -61,7 +67,8 @@ module Repull
61
67
  :'parent_id' => :'String',
62
68
  :'housekeeping_status' => :'String',
63
69
  :'floor' => :'String',
64
- :'source' => :'String'
70
+ :'source' => :'String',
71
+ :'listing_id' => :'String'
65
72
  }
66
73
  end
67
74
 
@@ -71,6 +78,7 @@ module Repull
71
78
  :'parent_id',
72
79
  :'housekeeping_status',
73
80
  :'floor',
81
+ :'listing_id'
74
82
  ])
75
83
  end
76
84
 
@@ -117,6 +125,10 @@ module Repull
117
125
  if attributes.key?(:'source')
118
126
  self.source = attributes[:'source']
119
127
  end
128
+
129
+ if attributes.key?(:'listing_id')
130
+ self.listing_id = attributes[:'listing_id']
131
+ end
120
132
  end
121
133
 
122
134
  # Show invalid properties with the reasons. Usually used together with valid?
@@ -145,7 +157,8 @@ module Repull
145
157
  parent_id == o.parent_id &&
146
158
  housekeeping_status == o.housekeeping_status &&
147
159
  floor == o.floor &&
148
- source == o.source
160
+ source == o.source &&
161
+ listing_id == o.listing_id
149
162
  end
150
163
 
151
164
  # @see the `==` method
@@ -157,7 +170,7 @@ module Repull
157
170
  # Calculates hash code according to all attributes.
158
171
  # @return [Integer] Hash code
159
172
  def hash
160
- [id, name, active, parent_id, housekeeping_status, floor, source].hash
173
+ [id, name, active, parent_id, housekeeping_status, floor, source, listing_id].hash
161
174
  end
162
175
 
163
176
  # Builds the object from hash
@@ -28,9 +28,12 @@ module Repull
28
28
  # Minimum-stay requirement for a stay starting on this date. Falls back to the listing-level default min-nights when the calendar row carries none.
29
29
  attr_accessor :min_nights
30
30
 
31
- # Units still sellable that night. 1 or 0 for a single home. For a hotel-model listing (a Mews or Cloudbeds room type) the rooms of that type left, e.g. 3 of 5 — see `GET /v1/listings/{id}/units`. `available` is false whenever this is 0.
31
+ # Units still sellable that night. 1 or 0 for a single home. For a listing with units (a Mews or Cloudbeds room type, a Guesty multi-unit listing) the units left, e.g. 3 of 5 — see `GET /v1/listings/{id}/units`. `available` is false whenever this is 0.
32
32
  attr_accessor :available_units
33
33
 
34
+ # Only on a listing with units (a Mews or Cloudbeds room type, a Guesty multi-unit listing): each active unit and whether it is free that night. A unit is taken when a stay is on it, a room block covers it, or (for a unit that is a listing of its own) its own calendar is closed. Ids are the listing's `units[].id` and a reservation's `unit.id`.
35
+ attr_accessor :units
36
+
34
37
  # Attribute mapping from ruby-style variable name to JSON key.
35
38
  def self.attribute_map
36
39
  {
@@ -38,7 +41,8 @@ module Repull
38
41
  :'available' => :'available',
39
42
  :'price' => :'price',
40
43
  :'min_nights' => :'minNights',
41
- :'available_units' => :'availableUnits'
44
+ :'available_units' => :'availableUnits',
45
+ :'units' => :'units'
42
46
  }
43
47
  end
44
48
 
@@ -59,7 +63,8 @@ module Repull
59
63
  :'available' => :'Boolean',
60
64
  :'price' => :'Float',
61
65
  :'min_nights' => :'Integer',
62
- :'available_units' => :'Integer'
66
+ :'available_units' => :'Integer',
67
+ :'units' => :'Array<PropertyAvailabilityUnit>'
63
68
  }
64
69
  end
65
70
 
@@ -114,6 +119,12 @@ module Repull
114
119
  else
115
120
  self.available_units = nil
116
121
  end
122
+
123
+ if attributes.key?(:'units')
124
+ if (value = attributes[:'units']).is_a?(Array)
125
+ self.units = value
126
+ end
127
+ end
117
128
  end
118
129
 
119
130
  # Show invalid properties with the reasons. Usually used together with valid?
@@ -224,7 +235,8 @@ module Repull
224
235
  available == o.available &&
225
236
  price == o.price &&
226
237
  min_nights == o.min_nights &&
227
- available_units == o.available_units
238
+ available_units == o.available_units &&
239
+ units == o.units
228
240
  end
229
241
 
230
242
  # @see the `==` method
@@ -236,7 +248,7 @@ module Repull
236
248
  # Calculates hash code according to all attributes.
237
249
  # @return [Integer] Hash code
238
250
  def hash
239
- [date, available, price, min_nights, available_units].hash
251
+ [date, available, price, min_nights, available_units, units].hash
240
252
  end
241
253
 
242
254
  # Builds the object from hash
@@ -0,0 +1,193 @@
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` | unlimited (10 included, then billed per listing) | | `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
+
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.22.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module Repull
17
+ # One unit on one night.
18
+ class PropertyAvailabilityUnit < ApiModelBase
19
+ # The unit id: `units[].id` on the listing, `unit.id` on a reservation.
20
+ attr_accessor :id
21
+
22
+ # Whether this unit is free that night.
23
+ attr_accessor :available
24
+
25
+ # Attribute mapping from ruby-style variable name to JSON key.
26
+ def self.attribute_map
27
+ {
28
+ :'id' => :'id',
29
+ :'available' => :'available'
30
+ }
31
+ end
32
+
33
+ # Returns attribute mapping this model knows about
34
+ def self.acceptable_attribute_map
35
+ attribute_map
36
+ end
37
+
38
+ # Returns all the JSON keys this model knows about
39
+ def self.acceptable_attributes
40
+ acceptable_attribute_map.values
41
+ end
42
+
43
+ # Attribute type mapping.
44
+ def self.openapi_types
45
+ {
46
+ :'id' => :'String',
47
+ :'available' => :'Boolean'
48
+ }
49
+ end
50
+
51
+ # List of attributes with nullable: true
52
+ def self.openapi_nullable
53
+ Set.new([
54
+ ])
55
+ end
56
+
57
+ # Initializes the object
58
+ # @param [Hash] attributes Model attributes in the form of hash
59
+ def initialize(attributes = {})
60
+ if (!attributes.is_a?(Hash))
61
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Repull::PropertyAvailabilityUnit` initialize method"
62
+ end
63
+
64
+ # check to see if the attribute exists and convert string to symbol for hash key
65
+ acceptable_attribute_map = self.class.acceptable_attribute_map
66
+ attributes = attributes.each_with_object({}) { |(k, v), h|
67
+ if (!acceptable_attribute_map.key?(k.to_sym))
68
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Repull::PropertyAvailabilityUnit`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
69
+ end
70
+ h[k.to_sym] = v
71
+ }
72
+
73
+ if attributes.key?(:'id')
74
+ self.id = attributes[:'id']
75
+ else
76
+ self.id = nil
77
+ end
78
+
79
+ if attributes.key?(:'available')
80
+ self.available = attributes[:'available']
81
+ else
82
+ self.available = nil
83
+ end
84
+ end
85
+
86
+ # Show invalid properties with the reasons. Usually used together with valid?
87
+ # @return Array for valid properties with the reasons
88
+ def list_invalid_properties
89
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
90
+ invalid_properties = Array.new
91
+ if @id.nil?
92
+ invalid_properties.push('invalid value for "id", id cannot be nil.')
93
+ end
94
+
95
+ if @available.nil?
96
+ invalid_properties.push('invalid value for "available", available cannot be nil.')
97
+ end
98
+
99
+ invalid_properties
100
+ end
101
+
102
+ # Check to see if the all the properties in the model are valid
103
+ # @return true if the model is valid
104
+ def valid?
105
+ warn '[DEPRECATED] the `valid?` method is obsolete'
106
+ return false if @id.nil?
107
+ return false if @available.nil?
108
+ true
109
+ end
110
+
111
+ # Custom attribute writer method with validation
112
+ # @param [Object] id Value to be assigned
113
+ def id=(id)
114
+ if id.nil?
115
+ fail ArgumentError, 'id cannot be nil'
116
+ end
117
+
118
+ @id = id
119
+ end
120
+
121
+ # Custom attribute writer method with validation
122
+ # @param [Object] available Value to be assigned
123
+ def available=(available)
124
+ if available.nil?
125
+ fail ArgumentError, 'available cannot be nil'
126
+ end
127
+
128
+ @available = available
129
+ end
130
+
131
+ # Checks equality by comparing each attribute.
132
+ # @param [Object] Object to be compared
133
+ def ==(o)
134
+ return true if self.equal?(o)
135
+ self.class == o.class &&
136
+ id == o.id &&
137
+ available == o.available
138
+ end
139
+
140
+ # @see the `==` method
141
+ # @param [Object] Object to be compared
142
+ def eql?(o)
143
+ self == o
144
+ end
145
+
146
+ # Calculates hash code according to all attributes.
147
+ # @return [Integer] Hash code
148
+ def hash
149
+ [id, available].hash
150
+ end
151
+
152
+ # Builds the object from hash
153
+ # @param [Hash] attributes Model attributes in the form of hash
154
+ # @return [Object] Returns the model itself
155
+ def self.build_from_hash(attributes)
156
+ return nil unless attributes.is_a?(Hash)
157
+ attributes = attributes.transform_keys(&:to_sym)
158
+ transformed_hash = {}
159
+ openapi_types.each_pair do |key, type|
160
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
161
+ transformed_hash["#{key}"] = nil
162
+ elsif type =~ /\AArray<(.*)>/i
163
+ # check to ensure the input is an array given that the attribute
164
+ # is documented as an array but the input is not
165
+ if attributes[attribute_map[key]].is_a?(Array)
166
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
167
+ end
168
+ elsif !attributes[attribute_map[key]].nil?
169
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
170
+ end
171
+ end
172
+ new(transformed_hash)
173
+ end
174
+
175
+ # Returns the object in the form of hash
176
+ # @return [Hash] Returns the object in the form of hash
177
+ def to_hash
178
+ hash = {}
179
+ self.class.attribute_map.each_pair do |attr, param|
180
+ value = self.send(attr)
181
+ if value.nil?
182
+ is_nullable = self.class.openapi_nullable.include?(attr)
183
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
184
+ end
185
+
186
+ hash[param] = _to_hash(value)
187
+ end
188
+ hash
189
+ end
190
+
191
+ end
192
+
193
+ end
@@ -14,7 +14,7 @@ require 'date'
14
14
  require 'time'
15
15
 
16
16
  module Repull
17
- # The physical room the stay was assigned, for a hotel-model PMS (Mews, Cloudbeds) where the listing is a room type. `null` when no room is assigned yet, and for every listing that is a single home.
17
+ # The unit the stay is on: the room assigned by a hotel-model PMS (Mews, Cloudbeds) where the listing is a room type, or the unit of a Guesty multi-unit listing (the stay's `listingId` is then that unit's own listing). Matches the listing's `units[].id` and the per-night `units` on availability. `null` when no unit is assigned yet, and for every listing that is a single home.
18
18
  class ReservationUnit < ApiModelBase
19
19
  # The room id, as listed by `GET /v1/listings/{id}/units`.
20
20
  attr_accessor :id
@@ -11,5 +11,5 @@ Generator version: 7.22.0
11
11
  =end
12
12
 
13
13
  module Repull
14
- VERSION = '0.2.38'
14
+ VERSION = '0.2.40'
15
15
  end
data/lib/repull.rb CHANGED
@@ -389,6 +389,7 @@ require 'repull/models/listing_hotel'
389
389
  require 'repull/models/listing_list_response'
390
390
  require 'repull/models/listing_market_state_request'
391
391
  require 'repull/models/listing_market_state_response'
392
+ require 'repull/models/listing_parent'
392
393
  require 'repull/models/listing_photo'
393
394
  require 'repull/models/listing_photo_delete_request'
394
395
  require 'repull/models/listing_photo_delete_response'
@@ -548,6 +549,7 @@ require 'repull/models/property'
548
549
  require 'repull/models/property_availability'
549
550
  require 'repull/models/property_availability_coverage'
550
551
  require 'repull/models/property_availability_day'
552
+ require 'repull/models/property_availability_unit'
551
553
  require 'repull/models/property_list_response'
552
554
  require 'repull/models/publish_section_error'
553
555
  require 'repull/models/quote'
data/openapi/v1.json CHANGED
@@ -338,8 +338,45 @@
338
338
  "availableUnits": {
339
339
  "type": "integer",
340
340
  "minimum": 0,
341
- "description": "Units still sellable that night. 1 or 0 for a single home. For a hotel-model listing (a Mews or Cloudbeds room type) the rooms of that type left, e.g. 3 of 5 \u2014 see `GET /v1/listings/{id}/units`. `available` is false whenever this is 0.",
341
+ "description": "Units still sellable that night. 1 or 0 for a single home. For a listing with units (a Mews or Cloudbeds room type, a Guesty multi-unit listing) the units left, e.g. 3 of 5 \u2014 see `GET /v1/listings/{id}/units`. `available` is false whenever this is 0.",
342
342
  "example": 1
343
+ },
344
+ "units": {
345
+ "type": "array",
346
+ "description": "Only on a listing with units (a Mews or Cloudbeds room type, a Guesty multi-unit listing): each active unit and whether it is free that night. A unit is taken when a stay is on it, a room block covers it, or (for a unit that is a listing of its own) its own calendar is closed. Ids are the listing's `units[].id` and a reservation's `unit.id`.",
347
+ "items": {
348
+ "$ref": "#/components/schemas/PropertyAvailabilityUnit"
349
+ },
350
+ "example": [
351
+ {
352
+ "id": "unit_a",
353
+ "available": true
354
+ },
355
+ {
356
+ "id": "unit_b",
357
+ "available": false
358
+ }
359
+ ]
360
+ }
361
+ }
362
+ },
363
+ "PropertyAvailabilityUnit": {
364
+ "type": "object",
365
+ "description": "One unit on one night.",
366
+ "required": [
367
+ "id",
368
+ "available"
369
+ ],
370
+ "properties": {
371
+ "id": {
372
+ "type": "string",
373
+ "description": "The unit id: `units[].id` on the listing, `unit.id` on a reservation.",
374
+ "example": "unit_b"
375
+ },
376
+ "available": {
377
+ "type": "boolean",
378
+ "description": "Whether this unit is free that night.",
379
+ "example": false
343
380
  }
344
381
  }
345
382
  },
@@ -813,7 +850,7 @@
813
850
  "unit": {
814
851
  "type": "object",
815
852
  "nullable": true,
816
- "description": "The physical room the stay was assigned, for a hotel-model PMS (Mews, Cloudbeds) where the listing is a room type. `null` when no room is assigned yet, and for every listing that is a single home.",
853
+ "description": "The unit the stay is on: the room assigned by a hotel-model PMS (Mews, Cloudbeds) where the listing is a room type, or the unit of a Guesty multi-unit listing (the stay's `listingId` is then that unit's own listing). Matches the listing's `units[].id` and the per-night `units` on availability. `null` when no unit is assigned yet, and for every listing that is a single home.",
817
854
  "properties": {
818
855
  "id": {
819
856
  "type": "string",
@@ -1196,7 +1233,7 @@
1196
1233
  "$ref": "#/components/schemas/RecordAccount"
1197
1234
  }
1198
1235
  ],
1199
- "description": "The connected account this conversation belongs to. List endpoint."
1236
+ "description": "The connected account this conversation belongs to, the same block as the message webhooks. On the list and on `GET /v1/conversations/{id}`."
1200
1237
  },
1201
1238
  "id": {
1202
1239
  "type": "string"
@@ -10298,7 +10335,22 @@
10298
10335
  },
10299
10336
  "kind": {
10300
10337
  "type": "string",
10301
- "description": "`room_type` when the listing sells several identical units (a hotel room type: `unitCount` > 1) or is a hotel PMS room type; `home` otherwise. See the Hotels & multi-unit guide."
10338
+ "description": "`room_type` when the listing sells several units (a hotel room type or a Guesty multi-unit listing: `unitCount` > 1) or is a hotel PMS room type; `home` otherwise. See the Hotels & multi-unit guide."
10339
+ },
10340
+ "parent": {
10341
+ "type": "object",
10342
+ "nullable": true,
10343
+ "description": "The multi-unit listing this listing is one unit of (a Guesty multi-unit listing's unit, which has its own calendar and stays). The parent lists it in `units[]` with this listing's id as `units[].listingId`. `null` otherwise.",
10344
+ "properties": {
10345
+ "id": {
10346
+ "type": "string",
10347
+ "description": "Repull listing id of the multi-unit listing."
10348
+ },
10349
+ "name": {
10350
+ "type": "string",
10351
+ "nullable": true
10352
+ }
10353
+ }
10302
10354
  },
10303
10355
  "hotel": {
10304
10356
  "type": "object",
@@ -10326,16 +10378,17 @@
10326
10378
  "unitCount": {
10327
10379
  "type": "integer",
10328
10380
  "minimum": 1,
10329
- "description": "How many identical units (rooms) this listing sells: the hotel PMS rooms of the room type, the room count on Booking.com, or 1 for a home. Rooms left per night are `availableUnits` on `GET /v1/availability/{propertyId}`."
10381
+ "description": "How many units (rooms) this listing sells: the hotel PMS rooms of the room type, the units of a Guesty multi-unit listing, the room count on Booking.com, or 1 for a home. Units left per night are `availableUnits` on `GET /v1/availability/{propertyId}`, and which unit is free is `units` there."
10330
10382
  },
10331
10383
  "units": {
10332
10384
  "type": "array",
10333
- "description": "`GET /v1/listings/{id}` only. The physical rooms under a hotel-model listing (a Mews or Cloudbeds room type); empty for a single home. Same items as `GET /v1/listings/{id}/units`.",
10385
+ "description": "`GET /v1/listings/{id}` only. The units under the listing: a Mews or Cloudbeds room type's rooms, or a Guesty multi-unit listing's units; empty for a single home. Same items as `GET /v1/listings/{id}/units`.",
10334
10386
  "items": {
10335
10387
  "type": "object",
10336
10388
  "properties": {
10337
10389
  "id": {
10338
- "type": "string"
10390
+ "type": "string",
10391
+ "description": "Unit id \u2014 what `reservation.unit.id` and the per-night `units[].id` on availability refer to."
10339
10392
  },
10340
10393
  "name": {
10341
10394
  "type": "string"
@@ -10356,7 +10409,13 @@
10356
10409
  "nullable": true
10357
10410
  },
10358
10411
  "source": {
10359
- "type": "string"
10412
+ "type": "string",
10413
+ "description": "The PMS the unit comes from: `cloudbeds`, `mews`, `guesty`, \u2026"
10414
+ },
10415
+ "listingId": {
10416
+ "type": "string",
10417
+ "nullable": true,
10418
+ "description": "The unit's own Repull listing id when the unit is a listing too (a Guesty multi-unit listing's units, each with its own calendar), else `null`."
10360
10419
  }
10361
10420
  }
10362
10421
  }
@@ -10423,6 +10482,18 @@
10423
10482
  }
10424
10483
  }
10425
10484
  },
10485
+ "latitude": {
10486
+ "type": "string",
10487
+ "nullable": true,
10488
+ "description": "`GET /v1/listings/{id}` only. Map pin latitude in decimal degrees, as a string; the same value as `GET /v1/properties/{id}`. Null when the listing has no coordinates.",
10489
+ "example": "25.7617"
10490
+ },
10491
+ "longitude": {
10492
+ "type": "string",
10493
+ "nullable": true,
10494
+ "description": "`GET /v1/listings/{id}` only. Map pin longitude in decimal degrees, as a string; the same value as `GET /v1/properties/{id}`. Null when the listing has no coordinates.",
10495
+ "example": "-80.1918"
10496
+ },
10426
10497
  "thumbnailUrl": {
10427
10498
  "type": "string",
10428
10499
  "format": "uri",
@@ -16829,7 +16900,7 @@
16829
16900
  "get": {
16830
16901
  "operationId": "get_availability",
16831
16902
  "summary": "Get property availability",
16832
- "description": "Channel-agnostic day-by-day availability calendar for a property over a date window. Returns a thin per-date shape \u2014 `{ date, available, price, minNights, availableUnits }` \u2014 projected from the property calendar. `availableUnits` is 1 or 0 for a single home, and the rooms of the type left for a hotel-model listing (a Mews or Cloudbeds room type).\n\nThe `from` and `to` query params are **required** (ISO `YYYY-MM-DD`, inclusive) \u2014 omitting or malforming either returns 422. The window is capped at 366 days; longer ranges are truncated to the first 366 days.\n\n**`days` contains only the dates we actually hold calendar data for.** Requested dates with no calendar row are listed in `coverage.missingDates` \u2014 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 \u2014 404 means the property id does not exist or belongs to a different workspace.\n\nThis endpoint is read-only, and the projected per-date shape carries **availability, units left, price, and min-nights only** \u2014 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.\n\nReturns `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.",
16903
+ "description": "Channel-agnostic day-by-day availability calendar for a property over a date window. Returns a thin per-date shape \u2014 `{ date, available, price, minNights, availableUnits }` \u2014 projected from the property calendar. `availableUnits` is 1 or 0 for a single home, and the units left for a listing with units (a Mews or Cloudbeds room type, a Guesty multi-unit listing). On such a listing each day also carries `units: [{ id, available }]` \u2014 which unit is free that night, with the ids of the listing's `units[]` and a reservation's `unit`.\n\nThe `from` and `to` query params are **required** (ISO `YYYY-MM-DD`, inclusive) \u2014 omitting or malforming either returns 422. The window is capped at 366 days; longer ranges are truncated to the first 366 days.\n\n**`days` contains only the dates we actually hold calendar data for.** Requested dates with no calendar row are listed in `coverage.missingDates` \u2014 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 \u2014 404 means the property id does not exist or belongs to a different workspace.\n\nThis endpoint is read-only, and the projected per-date shape carries **availability, units left, price, and min-nights only** \u2014 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.\n\nReturns `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.",
16833
16904
  "tags": [
16834
16905
  "Availability"
16835
16906
  ],
@@ -18543,7 +18614,7 @@
18543
18614
  "post": {
18544
18615
  "operationId": "send_conversation_message",
18545
18616
  "summary": "Send a message to the guest",
18546
- "description": "Sends a message to the guest on this conversation and records it in the thread.\n\nOmit `channel` and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, VRBO, SMS, email or the direct-booking site) \u2014 that is the right default. Pass `channel` only to force a specific one.\n\nThe message is attributed to the API: it is recorded with `aiGenerated` false so an API send is never counted as an automated reply.\n\n### Airbnb rewrites links \u2014 check `contentRewritten`\n\nAirbnb rejects guest messages containing a link, an email address or a phone number, and names the offending text. When that happens the offending fragment is stripped and the remainder is re-sent once, which means **the guest receives a message that is not the one you wrote**. Reporting that as a plain success would be a lie, so every response carries `contentRewritten`; when it is `true`, `deliveredContent` is the text that actually reached the guest. Check it before assuming your message went out verbatim.\n\nWhen the text cannot be salvaged (the link is most of the message) nothing is delivered and the call returns `422 message_not_sent` with the channel's verbatim refusal in `statusReason`.\n\nSend `Idempotency-Key` \u2014 without it, retrying after a network timeout sends the guest the same message twice.\n\n### Attachments\n\nSend files with `attachments: [{ url, contentType?, filename? }]` \u2014 public `https://` URLs, up to 5 per request, 10 MB each. `message` may be omitted when there are attachments (except on Booking.com). Repull downloads each file, reads its real type from the bytes, keeps a durable copy and delivers it through the channel's own file flow. **Every file is checked before anything is sent**: if one is unreachable, too large or of a type the channel refuses, the call returns 422 naming the file (`index`) and the guest receives nothing.\n\n| Channel | Accepted types | Text | How it arrives |\n|---|---|---|---|\n| Airbnb | JPEG, PNG, GIF, WebP (converted to JPEG), MP4, QuickTime | optional | each file as its own message, then the text as a separate message |\n| Booking.com | JPEG, PNG | **required** | one message carrying the text and every file |\n| VRBO, SMS, email, direct-booking site chat | \u2014 | \u2014 | `422 attachments_not_supported`, nothing sent |\n\nAirbnb does not allow files in pre-booking (inquiry) conversations; that refusal comes back as `422 message_not_sent`. Because Airbnb delivers files one message at a time, a later file can be refused after earlier ones arrived \u2014 that returns `422 message_partially_sent` with `parts` saying exactly which messages reached the guest; resend only the rest.\n\nThe response's `attachments` lists each file's durable `url`, and `parts` lists every channel message the send produced. Read-back (`GET /v1/conversations/{id}/messages`) shows the same files in each message's `attachments`.\n\n**Inactive listings:** a conversation that belongs to an inactive listing returns `403 listing_inactive` and no message is sent. Activate the listing first.",
18617
+ "description": "Sends a message to the guest on this conversation and records it in the thread.\n\nOmit `channel` and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, VRBO, SMS, email or the direct-booking site) \u2014 that is the right default. Pass `channel` only to force a specific one.\n\nThe message is attributed to the API: it is recorded with `aiGenerated` false so an API send is never counted as an automated reply.\n\n### Airbnb rewrites links \u2014 check `contentRewritten`\n\nAirbnb rejects guest messages containing a link, an email address or a phone number, and names the offending text. When that happens the offending fragment is stripped and the remainder is re-sent once, which means **the guest receives a message that is not the one you wrote**. Reporting that as a plain success would be a lie, so every response carries `contentRewritten`; when it is `true`, `deliveredContent` is the text that actually reached the guest. Check it before assuming your message went out verbatim.\n\nWhen the text cannot be salvaged (the link is most of the message) nothing is delivered and the call returns `422 message_not_sent` with the channel's verbatim refusal in `statusReason`. `message_not_sent` only ever means the channel refused: a fault on Repull's side (a dropped database connection, a timeout) is `503 internal_error` with `sent: false` when nothing was delivered \u2014 retry it unchanged with the same `Idempotency-Key`.\n\nSend `Idempotency-Key` \u2014 without it, retrying after a network timeout sends the guest the same message twice.\n\n### Attachments\n\nSend files with `attachments: [{ url, contentType?, filename? }]` \u2014 public `https://` URLs, up to 5 per request, 10 MB each. `message` may be omitted when there are attachments (except on Booking.com). Repull downloads each file, reads its real type from the bytes, keeps a durable copy and delivers it through the channel's own file flow. **Every file is checked before anything is sent**: if one is unreachable, too large or of a type the channel refuses, the call returns 422 naming the file (`index`) and the guest receives nothing.\n\n| Channel | Accepted types | Text | How it arrives |\n|---|---|---|---|\n| Airbnb | JPEG, PNG, GIF, WebP (converted to JPEG), MP4, QuickTime | optional | each file as its own message, then the text as a separate message |\n| Booking.com | JPEG, PNG | **required** | one message carrying the text and every file |\n| VRBO, SMS, email, direct-booking site chat | \u2014 | \u2014 | `422 attachments_not_supported`, nothing sent |\n\nAirbnb does not allow files in pre-booking (inquiry) conversations; that refusal comes back as `422 message_not_sent`. Because Airbnb delivers files one message at a time, a later file can be refused after earlier ones arrived \u2014 that returns `422 message_partially_sent` with `parts` saying exactly which messages reached the guest; resend only the rest.\n\nThe response's `attachments` lists each file's durable `url`, and `parts` lists every channel message the send produced. Read-back (`GET /v1/conversations/{id}/messages`) shows the same files in each message's `attachments`.\n\n**Inactive listings:** a conversation that belongs to an inactive listing returns `403 listing_inactive` and no message is sent. Activate the listing first.",
18547
18618
  "tags": [
18548
18619
  "Conversations"
18549
18620
  ],
@@ -18664,7 +18735,7 @@
18664
18735
  "$ref": "#/components/responses/Unauthorized"
18665
18736
  },
18666
18737
  "403": {
18667
- "description": "`listing_inactive` \u2014 the conversation belongs to an inactive listing, so nothing was sent. The error names the listing.",
18738
+ "description": "`listing_inactive` \u2014 the conversation belongs to an inactive listing, so nothing was sent. The error names the listing. `connection_reauth_required` \u2014 the channel account behind this conversation has no working credentials (`reconnectRequired: true`); nothing was sent and the text was not refused. Reconnect via `POST /v1/connect/{provider}`, then send again.",
18668
18739
  "content": {
18669
18740
  "application/json": {
18670
18741
  "schema": {
@@ -18701,7 +18772,7 @@
18701
18772
  }
18702
18773
  },
18703
18774
  "422": {
18704
- "description": "Nothing was sent unless the code is `message_partially_sent`. Codes: `invalid_params` (malformed body, field named), `message_not_sent` (the channel refused \u2014 `statusReason` has its words), `attachments_not_supported`, `too_many_attachments`, `attachment_requires_message`, `attachment_url_not_allowed`, `attachment_unreachable`, `attachment_too_large`, `attachment_type_not_supported` (each names the file in `index`/`field` and the conversation's `channel`), and `message_partially_sent` (some channel messages were delivered \u2014 `parts` says which).",
18775
+ "description": "Nothing was sent unless the code is `message_partially_sent`. Codes: `invalid_params` (malformed body, field named), `message_not_sent` (the channel itself refused \u2014 `statusReason` has its words verbatim; a failure on Repull's side is never `message_not_sent`, it is `503 internal_error`), `attachments_not_supported`, `too_many_attachments`, `attachment_requires_message`, `attachment_url_not_allowed`, `attachment_unreachable`, `attachment_too_large`, `attachment_type_not_supported` (each names the file in `index`/`field` and the conversation's `channel`), and `message_partially_sent` (some channel messages were delivered \u2014 `parts` says which).",
18705
18776
  "content": {
18706
18777
  "application/json": {
18707
18778
  "schema": {
@@ -18892,11 +18963,30 @@
18892
18963
  }
18893
18964
  },
18894
18965
  "503": {
18895
- "description": "`attachment_storage_failed` \u2014 a file could not be stored for delivery. Nothing was sent; retry with the same `Idempotency-Key` after `retry_after` seconds.",
18966
+ "description": "A temporary fault on Repull's side \u2014 never a verdict on the message. `internal_error`: the send failed inside Repull (a dropped database connection, a timeout) before the channel refused anything. `sent` says whether the message reached the channel: with `messageId: null` and `sent: false` nothing was delivered or recorded, and retrying the same request with the same `Idempotency-Key` after `retry_after` seconds sends it once. With a `messageId`, check that message in `GET /v1/conversations/{id}/messages` before resending. `attachment_storage_failed`: a file could not be stored for delivery; nothing was sent, retry the same way. A 503 is never stored against the `Idempotency-Key`, so the retry runs for real. Do not edit the text: it was not refused.",
18896
18967
  "content": {
18897
18968
  "application/json": {
18898
18969
  "schema": {
18899
18970
  "$ref": "#/components/schemas/Error"
18971
+ },
18972
+ "examples": {
18973
+ "internalNotSent": {
18974
+ "summary": "Repull lost its database connection before the send reached Airbnb \u2014 not sent, retry with the same key",
18975
+ "value": {
18976
+ "error": {
18977
+ "code": "internal_error",
18978
+ "message": "The message was not sent: Repull hit a temporary fault on its side before the message reached Airbnb. Nothing was delivered to the guest and nothing was recorded in the conversation.",
18979
+ "fix": "Retry the same request with the same `Idempotency-Key` after `retry_after` seconds \u2014 this failure is not stored against the key, so the retry sends the message for real, once. Do not change the text: it was not refused.",
18980
+ "docs_url": "https://repull.dev/docs/errors/internal_error",
18981
+ "request_id": "req_01J5X7Y8Z9ABCDEF12345678",
18982
+ "retry_after": 5,
18983
+ "retryable": true,
18984
+ "sent": false,
18985
+ "channel": "airbnb",
18986
+ "messageId": null
18987
+ }
18988
+ }
18989
+ }
18900
18990
  }
18901
18991
  }
18902
18992
  }
@@ -19138,7 +19228,7 @@
19138
19228
  "get": {
19139
19229
  "operationId": "list_connections",
19140
19230
  "summary": "List PMS/OTA connections",
19141
- "description": "Returns every PMS and OTA connection in the workspace, each with its `status`.\n\n**Spot connections that need attention.** A connection whose `status` is not `active` may need the host to do something before it works \u2014 most commonly a Booking.com Extranet connection where the invited user was granted only partial access (`status: \"needs_permissions\"`). A Smoobu connection still on a legacy single API key carries `action.reason: \"reauth_required\"` while its `status` is `active`: Smoobu stops accepting those keys on October 31, 2026, and `fixUrl` opens the form for a new API key + secret (the connection id stays the same). These connections carry two extra fields:\n\n- `action` \u2014 `{ required: true, reason, message }`. `reason` is a stable machine code (e.g. `needs_permissions`); `message` is a host-facing one-liner describing what to do.\n- `fixUrl` \u2014 a durable link that reopens the hosted Connect flow **bound to that account, on the fix screen** (e.g. \"grant full access\" + a Re-check button). It is safe to store and show in your own dashboard.\n\n**Self-serve repair:** when `action.required` is true, surface a \"Fix\" button that opens `fixUrl` in a new tab (or embed it). The host resolves the issue (e.g. grants the user full access in Booking.com) and clicks Re-check; the import finishes on its own and the connection flips back to `active` \u2014 no re-invite, no support ticket. Poll this endpoint (or read it after the host returns) to confirm `action` has cleared.",
19231
+ "description": "Returns every PMS and OTA connection in the workspace, each with its `status`.\n\n**One entry per Booking.com Extranet hotel login.** Each hotel that invites the Repull user connects as its own login, with its own `externalAccountId` (handle `booking_extranet:{externalAccountId}`). A new hotel adds an entry next to the existing ones and never replaces them; the entries share the workspace's Extranet `id`, and each hotel's listings, reservations, conversations and reviews stay under its own login (filter them with `?account=booking_extranet:{externalAccountId}`).\n\n**Spot connections that need attention.** A connection whose `status` is not `active` may need the host to do something before it works \u2014 most commonly a Booking.com Extranet connection where the invited user was granted only partial access (`status: \"needs_permissions\"`). A Smoobu connection still on a legacy single API key carries `action.reason: \"reauth_required\"` while its `status` is `active`: Smoobu stops accepting those keys on October 31, 2026, and `fixUrl` opens the form for a new API key + secret (the connection id stays the same). These connections carry two extra fields:\n\n- `action` \u2014 `{ required: true, reason, message }`. `reason` is a stable machine code (e.g. `needs_permissions`); `message` is a host-facing one-liner describing what to do.\n- `fixUrl` \u2014 a durable link that reopens the hosted Connect flow **bound to that account, on the fix screen** (e.g. \"grant full access\" + a Re-check button). It is safe to store and show in your own dashboard.\n\n**Self-serve repair:** when `action.required` is true, surface a \"Fix\" button that opens `fixUrl` in a new tab (or embed it). The host resolves the issue (e.g. grants the user full access in Booking.com) and clicks Re-check; the import finishes on its own and the connection flips back to `active` \u2014 no re-invite, no support ticket. Poll this endpoint (or read it after the host returns) to confirm `action` has cleared.",
19142
19232
  "tags": [
19143
19233
  "Connect"
19144
19234
  ],
@@ -22475,6 +22565,16 @@
22475
22565
  },
22476
22566
  "502": {
22477
22567
  "$ref": "#/components/responses/AirbnbUpstreamError"
22568
+ },
22569
+ "503": {
22570
+ "description": "With `mediaUrl`: `internal_error` \u2014 the send failed on Repull's side (a dropped database connection, a timeout), not at Airbnb. With `sent: false` and `messageId: null` nothing reached the guest; retry the same request with the same `Idempotency-Key` after `retry_after` seconds. Never stored against the key. `message_not_sent` is reserved for Airbnb's own refusals.",
22571
+ "content": {
22572
+ "application/json": {
22573
+ "schema": {
22574
+ "$ref": "#/components/schemas/Error"
22575
+ }
22576
+ }
22577
+ }
22478
22578
  }
22479
22579
  }
22480
22580
  }
@@ -35940,8 +36040,8 @@
35940
36040
  "/v1/listings/{id}/units": {
35941
36041
  "get": {
35942
36042
  "operationId": "listListingUnits",
35943
- "summary": "List a listing's units (rooms)",
35944
- "description": "The physical rooms under a listing. For a hotel-model PMS (Mews, Cloudbeds) a listing is a room type: prices, restrictions and availability are set on the room type, and each reservation is assigned one of these rooms (`reservation.unit.id`). A room can belong to more than one room type.\n\nAny other listing is a single home, which is its own unit, and returns an empty list.",
36043
+ "summary": "List a listing's units",
36044
+ "description": "The units under a listing.\n\n- **Hotel-model PMS (Mews, Cloudbeds):** the listing is a room type and these are its physical rooms. Prices, restrictions and availability are set on the room type, and each reservation is assigned one of these rooms (`reservation.unit.id`). A room can belong to more than one room type.\n- **Guesty multi-unit listing:** the listing is the parent channels sell, and these are its units. Each unit is also a listing of its own (`listingId`), with its own calendar and stays; a stay on a unit carries `reservation.unit`.\n\nWhich unit is free on which night: `units` per day on `GET /v1/availability/{propertyId}`. Any other listing is a single home, which is its own unit, and returns an empty list.",
35945
36045
  "tags": [
35946
36046
  "Listings"
35947
36047
  ],
@@ -35978,7 +36078,7 @@
35978
36078
  "properties": {
35979
36079
  "id": {
35980
36080
  "type": "string",
35981
- "description": "The PMS's id for the room; `reservation.unit.id` refers to it."
36081
+ "description": "The PMS's id for the unit; `reservation.unit.id` and the per-night `units[].id` on `GET /v1/availability/{propertyId}` refer to it."
35982
36082
  },
35983
36083
  "name": {
35984
36084
  "type": "string",
@@ -36003,7 +36103,13 @@
36003
36103
  },
36004
36104
  "source": {
36005
36105
  "type": "string",
36006
- "example": "mews"
36106
+ "example": "mews",
36107
+ "description": "The PMS the unit comes from: `cloudbeds`, `mews`, `guesty`, \u2026"
36108
+ },
36109
+ "listingId": {
36110
+ "type": "string",
36111
+ "nullable": true,
36112
+ "description": "The unit's own Repull listing id when the unit is a listing too (a Guesty multi-unit listing's units, each with its own calendar and stays), else null."
36007
36113
  }
36008
36114
  }
36009
36115
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: repull
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.38
4
+ version: 0.2.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nikolov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-11-03 00:00:00.000000000 Z
11
+ date: 2026-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -469,6 +469,7 @@ files:
469
469
  - lib/repull/models/listing_list_response.rb
470
470
  - lib/repull/models/listing_market_state_request.rb
471
471
  - lib/repull/models/listing_market_state_response.rb
472
+ - lib/repull/models/listing_parent.rb
472
473
  - lib/repull/models/listing_photo.rb
473
474
  - lib/repull/models/listing_photo_delete_request.rb
474
475
  - lib/repull/models/listing_photo_delete_response.rb
@@ -628,6 +629,7 @@ files:
628
629
  - lib/repull/models/property_availability.rb
629
630
  - lib/repull/models/property_availability_coverage.rb
630
631
  - lib/repull/models/property_availability_day.rb
632
+ - lib/repull/models/property_availability_unit.rb
631
633
  - lib/repull/models/property_list_response.rb
632
634
  - lib/repull/models/publish_section_error.rb
633
635
  - lib/repull/models/quote.rb