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 +4 -4
- data/lib/repull/api/availability_api.rb +2 -2
- data/lib/repull/api/connect_api.rb +2 -2
- data/lib/repull/api/conversations_api.rb +2 -2
- data/lib/repull/api/listings_api.rb +4 -4
- data/lib/repull/models/conversation.rb +1 -1
- data/lib/repull/models/conversation_detail.rb +1 -1
- data/lib/repull/models/list_listing_units200_response_data_inner.rb +17 -5
- data/lib/repull/models/listing.rb +36 -4
- data/lib/repull/models/listing_parent.rb +159 -0
- data/lib/repull/models/listing_units_inner.rb +17 -4
- data/lib/repull/models/property_availability_day.rb +17 -5
- data/lib/repull/models/property_availability_unit.rb +193 -0
- data/lib/repull/models/reservation_unit.rb +1 -1
- data/lib/repull/version.rb +1 -1
- data/lib/repull.rb +2 -0
- data/openapi/v1.json +124 -18
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 788a3e7ef0f0cf6d27c2878a4d1f371512e027fe45c8f73bb02ad92cd4763a37
|
|
4
|
+
data.tar.gz: 9ac321f22b18e15297cefb4d33c11154cabcc201d54afc5e4414b648c059a12f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
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 `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.
|
|
699
|
+
# 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.
|
|
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
|
|
693
|
-
# The
|
|
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's units
|
|
703
|
-
# The
|
|
702
|
+
# List a listing'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 (`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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
data/lib/repull/version.rb
CHANGED
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
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
|
|
35944
|
-
"description": "The
|
|
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
|
|
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.
|
|
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-
|
|
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
|