repull 0.2.35 → 0.2.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/connect_api.rb +70 -2
  3. data/lib/repull/api/conversations_api.rb +2 -2
  4. data/lib/repull/api/listings_api.rb +8 -8
  5. data/lib/repull/api/migrate_api.rb +68 -0
  6. data/lib/repull/api/reservations_api.rb +4 -4
  7. data/lib/repull/models/account_disconnected_payload.rb +16 -4
  8. data/lib/repull/models/account_disconnected_payload_listings_inner.rb +157 -0
  9. data/lib/repull/models/airbnb_calendar_operation.rb +12 -12
  10. data/lib/repull/models/booking_unacknowledged_event.rb +281 -0
  11. data/lib/repull/models/booking_unacknowledged_payload.rb +229 -0
  12. data/lib/repull/models/channel_degraded_event.rb +279 -0
  13. data/lib/repull/models/channel_degraded_payload.rb +174 -0
  14. data/lib/repull/models/channel_degraded_payload_object.rb +262 -0
  15. data/lib/repull/models/channel_sync_failed_event.rb +280 -0
  16. data/lib/repull/models/channel_sync_failure_payload.rb +214 -0
  17. data/lib/repull/models/channel_sync_failure_payload_failures_inner.rb +213 -0
  18. data/lib/repull/models/channel_sync_failure_payload_object.rb +289 -0
  19. data/lib/repull/models/charge_per.rb +42 -0
  20. data/lib/repull/models/create_connect_session_request.rb +2 -2
  21. data/lib/repull/models/create_connection_request.rb +1 -1
  22. data/lib/repull/models/import_from_channex200_response.rb +147 -0
  23. data/lib/repull/models/import_from_channex200_response_data.rb +324 -0
  24. data/lib/repull/models/import_from_channex200_response_data_channels_inner.rb +263 -0
  25. data/lib/repull/models/import_from_channex200_response_data_properties_inner.rb +222 -0
  26. data/lib/repull/models/import_from_channex200_response_data_summary.rb +201 -0
  27. data/lib/repull/models/import_from_channex200_response_data_summary_bookings.rb +165 -0
  28. data/lib/repull/models/import_from_channex200_response_data_summary_properties.rb +165 -0
  29. data/lib/repull/models/import_from_channex_request.rb +223 -0
  30. data/lib/repull/models/inquiry_webhook_object.rb +10 -1
  31. data/lib/repull/models/inquiry_webhook_object_guest.rb +159 -0
  32. data/lib/repull/models/list_inquiries200_response_data_inner.rb +27 -1
  33. data/lib/repull/models/list_inquiries200_response_data_inner_guest.rb +160 -0
  34. data/lib/repull/models/listing.rb +82 -1
  35. data/lib/repull/models/listing_channel.rb +15 -4
  36. data/lib/repull/models/listing_content_photo.rb +206 -0
  37. data/lib/repull/models/listing_fee.rb +229 -0
  38. data/lib/repull/models/listing_policies.rb +257 -0
  39. data/lib/repull/models/listing_policies_check_in.rb +179 -0
  40. data/lib/repull/models/listing_policies_check_out.rb +148 -0
  41. data/lib/repull/models/listing_policies_quiet_hours.rb +156 -0
  42. data/lib/repull/models/listing_pricing_settings.rb +233 -0
  43. data/lib/repull/models/listing_room.rb +190 -0
  44. data/lib/repull/models/listing_room_beds_inner.rb +156 -0
  45. data/lib/repull/models/listing_rules.rb +212 -0
  46. data/lib/repull/models/listing_tax.rb +241 -0
  47. data/lib/repull/models/listing_webhook_object_channels_inner.rb +16 -5
  48. data/lib/repull/models/reservation.rb +25 -1
  49. data/lib/repull/models/reservation_access_code.rb +301 -0
  50. data/lib/repull/models/reservation_access_code_locks_inner.rb +190 -0
  51. data/lib/repull/models/reservation_thread_assigned_event.rb +279 -0
  52. data/lib/repull/models/reservation_thread_assigned_payload.rb +174 -0
  53. data/lib/repull/models/reservation_thread_assigned_payload_object.rb +249 -0
  54. data/lib/repull/models/submit_ciirus_credentials200_response.rb +213 -0
  55. data/lib/repull/models/submit_ciirus_credentials200_response_account_info.rb +159 -0
  56. data/lib/repull/models/submit_ciirus_credentials_request.rb +184 -0
  57. data/lib/repull/models/submit_ciirus_credentials_request_credentials.rb +203 -0
  58. data/lib/repull/models/webhook_event.rb +8 -0
  59. data/lib/repull/models/webhook_event_type.rb +5 -1
  60. data/lib/repull/version.rb +1 -1
  61. data/lib/repull.rb +41 -0
  62. data/openapi/v1.json +1937 -32
  63. data/scripts/regen.sh +1 -1
  64. metadata +43 -4
  65. data/spec/api/plumguide_api_spec.rb +0 -85
data/openapi/v1.json CHANGED
@@ -405,6 +405,94 @@
405
405
  }
406
406
  }
407
407
  },
408
+ "ReservationAccessCode": {
409
+ "type": "object",
410
+ "description": "One door code for a stay, as the PMS reported it.",
411
+ "required": [
412
+ "code",
413
+ "type",
414
+ "status",
415
+ "validFrom",
416
+ "validUntil",
417
+ "locks",
418
+ "source",
419
+ "updatedAt"
420
+ ],
421
+ "properties": {
422
+ "code": {
423
+ "type": "string",
424
+ "description": "The code to enter on the lock.",
425
+ "example": "482913"
426
+ },
427
+ "type": {
428
+ "type": "string",
429
+ "description": "`guest`: the code to give the guest. `backup`: give it only if the guest code fails; Guesty may change a backup code read before check-in day.",
430
+ "example": "guest"
431
+ },
432
+ "status": {
433
+ "type": "string",
434
+ "nullable": true,
435
+ "description": "The PMS's status for the code, lower-cased.",
436
+ "example": "active"
437
+ },
438
+ "validFrom": {
439
+ "type": "string",
440
+ "format": "date-time",
441
+ "nullable": true,
442
+ "description": "When the code starts working. `null` when the PMS gives no window.",
443
+ "example": "2026-11-01T20:00:00.000Z"
444
+ },
445
+ "validUntil": {
446
+ "type": "string",
447
+ "format": "date-time",
448
+ "nullable": true,
449
+ "description": "When the code stops working. `null` when the PMS gives no window.",
450
+ "example": "2026-11-05T15:00:00.000Z"
451
+ },
452
+ "locks": {
453
+ "type": "array",
454
+ "description": "The locks the code opens.",
455
+ "items": {
456
+ "type": "object",
457
+ "required": [
458
+ "id",
459
+ "name",
460
+ "provider"
461
+ ],
462
+ "properties": {
463
+ "id": {
464
+ "type": "string",
465
+ "description": "The lock's id in the PMS.",
466
+ "example": "64f0c1aa2b3c4d5e6f708192"
467
+ },
468
+ "name": {
469
+ "type": "string",
470
+ "nullable": true,
471
+ "example": "Front door"
472
+ },
473
+ "provider": {
474
+ "type": "string",
475
+ "nullable": true,
476
+ "description": "The lock brand, as the PMS names it.",
477
+ "example": "schlage"
478
+ }
479
+ }
480
+ }
481
+ },
482
+ "source": {
483
+ "type": "string",
484
+ "description": "The PMS the code was read from.",
485
+ "example": "guesty"
486
+ },
487
+ "updatedAt": {
488
+ "type": "string",
489
+ "format": "date-time",
490
+ "nullable": true,
491
+ "description": "When the codes were last read from the PMS.",
492
+ "example": "2026-10-31T14:30:00.000Z"
493
+ }
494
+ }
495
+ },
408
496
  "ReservationPrimaryGuest": {
409
497
  "type": "object",
410
498
  "description": "Inline guest summary resolved by JOIN-ing the `guests` table. Populated for every reservation that has a linked guest row; OMITTED entirely (not null) for owner-blocks / pre-arrival rows / partial-sync gaps. Always optional-chain in SDK consumers.",
@@ -738,6 +826,20 @@
738
826
  }
739
827
  }
740
828
  },
829
+ "pmsReservationId": {
830
+ "type": "string",
831
+ "nullable": true,
832
+ "description": "The PMS's own id for this reservation (for example Guesty's reservation `_id`), so you can look it up in the PMS. `null` when the reservation did not come from a PMS.",
833
+ "example": "6a0374cd5eeb88f1381f9ac1"
834
+ },
835
+ "accessCodes": {
836
+ "type": "array",
837
+ "nullable": true,
838
+ "items": {
839
+ "$ref": "#/components/schemas/ReservationAccessCode"
840
+ },
841
+ "description": "Door codes for the stay from the PMS's lock manager (today: Guesty Locks Manager), refreshed about every 30 minutes for stays checking in within 14 days that have not checked out. `null` when the reservation's codes are not read from a PMS. `[]` when they are and there is none yet, and for a cancelled stay."
842
+ },
741
843
  "primaryGuest": {
742
844
  "allOf": [
743
845
  {
@@ -2749,6 +2851,10 @@
2749
2851
  "reservation.alteration.responded",
2750
2852
  "reservation.request.created",
2751
2853
  "reservation.request.updated",
2854
+ "booking.unacknowledged",
2855
+ "channel.sync.failed",
2856
+ "channel.degraded",
2857
+ "reservation.thread.assigned",
2752
2858
  "inquiry.created",
2753
2859
  "inquiry.updated",
2754
2860
  "listing.created",
@@ -3365,6 +3471,20 @@
3365
3471
  "nullable": true,
3366
3472
  "example": "2026-11-11"
3367
3473
  },
3474
+ "guest": {
3475
+ "type": "object",
3476
+ "description": "Who asked. Airbnb shares only the first name before the guest books.",
3477
+ "properties": {
3478
+ "id": {
3479
+ "type": "string",
3480
+ "nullable": true
3481
+ },
3482
+ "firstName": {
3483
+ "type": "string",
3484
+ "nullable": true
3485
+ }
3486
+ }
3487
+ },
3368
3488
  "guests": {
3369
3489
  "type": "object",
3370
3490
  "properties": {
@@ -3620,6 +3740,15 @@
3620
3740
  "type": "string",
3621
3741
  "nullable": true,
3622
3742
  "example": "sync_all"
3743
+ },
3744
+ "account": {
3745
+ "allOf": [
3746
+ {
3747
+ "$ref": "#/components/schemas/RecordAccount"
3748
+ }
3749
+ ],
3750
+ "nullable": true,
3751
+ "description": "The connected account this listing belongs to on this channel \u2014 for Airbnb the host account (`externalAccountId` is the Airbnb host id). The same account `account.disconnected` names, so a disconnect can be matched to its listings. Null when it cannot be resolved."
3623
3752
  }
3624
3753
  }
3625
3754
  }
@@ -3889,6 +4018,24 @@
3889
4018
  "type": "string",
3890
4019
  "example": "refresh_token_rejected",
3891
4020
  "description": "Why the connection was lost. `refresh_token_rejected` \u2014 upstream OAuth refresh endpoint returned a hard rejection. `manual_disconnect` \u2014 host/admin disconnected via the dashboard. `auth_expired` \u2014 credentials aged out without ever being used. `revoked_upstream` \u2014 provider notified us the user revoked access."
4021
+ },
4022
+ "listings": {
4023
+ "type": "array",
4024
+ "description": "The listings this account holds in your workspace \u2014 the ones that stop syncing until it is reconnected. Identity only; inactive listings included. Matches `channels[].account` on `GET /v1/listings`. Empty when the account has no listings here.",
4025
+ "items": {
4026
+ "type": "object",
4027
+ "properties": {
4028
+ "id": {
4029
+ "type": "integer",
4030
+ "example": 4118
4031
+ },
4032
+ "name": {
4033
+ "type": "string",
4034
+ "nullable": true,
4035
+ "example": "Ocean View Villa"
4036
+ }
4037
+ }
4038
+ }
3892
4039
  }
3893
4040
  }
3894
4041
  },
@@ -4618,7 +4765,103 @@
4618
4765
  }
4619
4766
  }
4620
4767
  },
4621
- "InquiryCreatedEvent": {
4768
+ "ChannelSyncFailurePayload": {
4769
+ "type": "object",
4770
+ "description": "Payload for `channel.sync.failed`. A push of availability, rates or content to a connected channel did not land.",
4771
+ "required": [
4772
+ "object",
4773
+ "failures"
4774
+ ],
4775
+ "properties": {
4776
+ "object": {
4777
+ "type": "object",
4778
+ "required": [
4779
+ "listingId",
4780
+ "customerId",
4781
+ "operation",
4782
+ "total"
4783
+ ],
4784
+ "properties": {
4785
+ "listingId": {
4786
+ "type": "integer"
4787
+ },
4788
+ "customerId": {
4789
+ "type": "integer"
4790
+ },
4791
+ "operation": {
4792
+ "type": "string",
4793
+ "enum": [
4794
+ "availability",
4795
+ "rate",
4796
+ "restriction",
4797
+ "content",
4798
+ "reservation"
4799
+ ],
4800
+ "description": "Which wire failed. A refused rate and a closed connection call for different responses."
4801
+ },
4802
+ "total": {
4803
+ "type": "boolean",
4804
+ "description": "true when NO mapping accepted the push. false means some did \u2014 the more dangerous case, because the listing is live somewhere and inconsistent."
4805
+ },
4806
+ "from": {
4807
+ "type": "string",
4808
+ "format": "date",
4809
+ "nullable": true,
4810
+ "description": "Start of the date range the push covered, when the operation is date-scoped."
4811
+ },
4812
+ "to": {
4813
+ "type": "string",
4814
+ "format": "date",
4815
+ "nullable": true
4816
+ }
4817
+ }
4818
+ },
4819
+ "failures": {
4820
+ "type": "array",
4821
+ "description": "One entry per channel mapping that refused the push.",
4822
+ "items": {
4823
+ "type": "object",
4824
+ "properties": {
4825
+ "channel": {
4826
+ "type": "string",
4827
+ "example": "airbnb"
4828
+ },
4829
+ "externalListingId": {
4830
+ "type": "string",
4831
+ "nullable": true,
4832
+ "description": "The channel's own id for the room or listing."
4833
+ },
4834
+ "kind": {
4835
+ "type": "string",
4836
+ "enum": [
4837
+ "rejected",
4838
+ "auth",
4839
+ "not_mapped",
4840
+ "unreachable",
4841
+ "unknown"
4842
+ ],
4843
+ "description": "`unknown` means we could not classify it \u2014 never that the channel refused."
4844
+ },
4845
+ "message": {
4846
+ "type": "string",
4847
+ "nullable": true,
4848
+ "description": "The channel's own message, verbatim."
4849
+ }
4850
+ }
4851
+ }
4852
+ },
4853
+ "reservationId": {
4854
+ "type": "integer",
4855
+ "nullable": true,
4856
+ "description": "Set when the push was blocking dates for a booking just taken. Those nights are sold on one channel and still open on another \u2014 treat as urgent."
4857
+ },
4858
+ "occurredAt": {
4859
+ "type": "string",
4860
+ "format": "date-time"
4861
+ }
4862
+ }
4863
+ },
4864
+ "ChannelSyncFailedEvent": {
4622
4865
  "type": "object",
4623
4866
  "required": [
4624
4867
  "event",
@@ -4643,18 +4886,64 @@
4643
4886
  },
4644
4887
  "timestamp": {
4645
4888
  "type": "string",
4646
- "format": "date-time",
4647
- "description": "When this delivery was built."
4889
+ "format": "date-time"
4648
4890
  },
4649
4891
  "account": {
4650
4892
  "$ref": "#/components/schemas/WebhookEventAccount"
4651
4893
  },
4652
4894
  "data": {
4653
- "$ref": "#/components/schemas/InquiryCreatedPayload"
4895
+ "$ref": "#/components/schemas/ChannelSyncFailurePayload"
4654
4896
  }
4655
4897
  }
4656
4898
  },
4657
- "InquiryUpdatedEvent": {
4899
+ "ChannelDegradedPayload": {
4900
+ "type": "object",
4901
+ "description": "Payload for `channel.degraded`. A connection is failing repeatedly and will stop working. Sent on the transition into degraded or down, not on every health check.",
4902
+ "required": [
4903
+ "object"
4904
+ ],
4905
+ "properties": {
4906
+ "object": {
4907
+ "type": "object",
4908
+ "required": [
4909
+ "customerId",
4910
+ "provider",
4911
+ "status"
4912
+ ],
4913
+ "properties": {
4914
+ "customerId": {
4915
+ "type": "integer"
4916
+ },
4917
+ "provider": {
4918
+ "type": "string",
4919
+ "example": "booking"
4920
+ },
4921
+ "externalAccountId": {
4922
+ "type": "string",
4923
+ "nullable": true,
4924
+ "description": "The provider's own account, host or hotel id."
4925
+ },
4926
+ "status": {
4927
+ "type": "string",
4928
+ "enum": [
4929
+ "degraded",
4930
+ "down"
4931
+ ]
4932
+ },
4933
+ "detail": {
4934
+ "type": "string",
4935
+ "nullable": true,
4936
+ "description": "What we observed, in the provider's words where possible. A bare \"degraded\" tells nobody what to fix."
4937
+ }
4938
+ }
4939
+ },
4940
+ "occurredAt": {
4941
+ "type": "string",
4942
+ "format": "date-time"
4943
+ }
4944
+ }
4945
+ },
4946
+ "ChannelDegradedEvent": {
4658
4947
  "type": "object",
4659
4948
  "required": [
4660
4949
  "event",
@@ -4670,8 +4959,7 @@
4670
4959
  },
4671
4960
  "eventId": {
4672
4961
  "type": "string",
4673
- "format": "uuid",
4674
- "description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
4962
+ "format": "uuid"
4675
4963
  },
4676
4964
  "apiVersion": {
4677
4965
  "type": "string",
@@ -4679,18 +4967,64 @@
4679
4967
  },
4680
4968
  "timestamp": {
4681
4969
  "type": "string",
4682
- "format": "date-time",
4683
- "description": "When this delivery was built."
4970
+ "format": "date-time"
4684
4971
  },
4685
4972
  "account": {
4686
4973
  "$ref": "#/components/schemas/WebhookEventAccount"
4687
4974
  },
4688
4975
  "data": {
4689
- "$ref": "#/components/schemas/InquiryUpdatedPayload"
4976
+ "$ref": "#/components/schemas/ChannelDegradedPayload"
4690
4977
  }
4691
4978
  }
4692
4979
  },
4693
- "ListingCreatedEvent": {
4980
+ "ReservationThreadAssignedPayload": {
4981
+ "type": "object",
4982
+ "description": "Payload for `reservation.thread.assigned`. A message thread was attached to a booking. Threads are often born orphaned \u2014 a guest messages about a booking whose import has not landed \u2014 and adopted when the reservation arrives.",
4983
+ "required": [
4984
+ "object"
4985
+ ],
4986
+ "properties": {
4987
+ "object": {
4988
+ "type": "object",
4989
+ "required": [
4990
+ "reservationId",
4991
+ "customerId",
4992
+ "threadId"
4993
+ ],
4994
+ "properties": {
4995
+ "reservationId": {
4996
+ "type": "integer"
4997
+ },
4998
+ "customerId": {
4999
+ "type": "integer"
5000
+ },
5001
+ "listingId": {
5002
+ "type": "integer",
5003
+ "nullable": true
5004
+ },
5005
+ "threadId": {
5006
+ "type": "string",
5007
+ "description": "Repull conversation id."
5008
+ },
5009
+ "externalThreadId": {
5010
+ "type": "string",
5011
+ "nullable": true,
5012
+ "description": "The channel's own conversation id."
5013
+ },
5014
+ "previousReservationId": {
5015
+ "type": "integer",
5016
+ "nullable": true,
5017
+ "description": "What the thread pointed at before. Null on the common orphan-adoption case; set when a thread moved between bookings."
5018
+ }
5019
+ }
5020
+ },
5021
+ "occurredAt": {
5022
+ "type": "string",
5023
+ "format": "date-time"
5024
+ }
5025
+ }
5026
+ },
5027
+ "ReservationThreadAssignedEvent": {
4694
5028
  "type": "object",
4695
5029
  "required": [
4696
5030
  "event",
@@ -4706,8 +5040,7 @@
4706
5040
  },
4707
5041
  "eventId": {
4708
5042
  "type": "string",
4709
- "format": "uuid",
4710
- "description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
5043
+ "format": "uuid"
4711
5044
  },
4712
5045
  "apiVersion": {
4713
5046
  "type": "string",
@@ -4715,18 +5048,189 @@
4715
5048
  },
4716
5049
  "timestamp": {
4717
5050
  "type": "string",
4718
- "format": "date-time",
4719
- "description": "When this delivery was built."
5051
+ "format": "date-time"
4720
5052
  },
4721
5053
  "account": {
4722
5054
  "$ref": "#/components/schemas/WebhookEventAccount"
4723
5055
  },
4724
5056
  "data": {
4725
- "$ref": "#/components/schemas/ListingCreatedPayload"
5057
+ "$ref": "#/components/schemas/ReservationThreadAssignedPayload"
4726
5058
  }
4727
5059
  }
4728
5060
  },
4729
- "ListingUpdatedEvent": {
5061
+ "BookingUnacknowledgedPayload": {
5062
+ "type": "object",
5063
+ "description": "Payload for `booking.unacknowledged`. A booking changed and your system has not acknowledged the change through the revision feed. Sent once per outstanding revision \u2014 not once per check \u2014 so a booking that keeps waiting does not keep alerting. A booking that changes AGAIN mints a new revision and does alert again, because that is a new thing to acknowledge. Acknowledging the revision stops it.",
5064
+ "required": [
5065
+ "object",
5066
+ "revisionId",
5067
+ "outstandingSince"
5068
+ ],
5069
+ "properties": {
5070
+ "object": {
5071
+ "$ref": "#/components/schemas/ReservationWebhookObject"
5072
+ },
5073
+ "revisionId": {
5074
+ "type": "string",
5075
+ "format": "uuid",
5076
+ "description": "The outstanding revision. Acknowledge this to stop the alert."
5077
+ },
5078
+ "outstandingSince": {
5079
+ "type": "string",
5080
+ "format": "date-time",
5081
+ "description": "When the booking actually changed \u2014 NOT when we noticed. Revisions are minted when you first poll the feed, so a mint timestamp would measure how recently you looked rather than how long the booking has waited."
5082
+ },
5083
+ "outstandingHours": {
5084
+ "type": "integer",
5085
+ "description": "Whole hours since `outstandingSince`, for convenience."
5086
+ }
5087
+ }
5088
+ },
5089
+ "BookingUnacknowledgedEvent": {
5090
+ "type": "object",
5091
+ "required": [
5092
+ "event",
5093
+ "eventId",
5094
+ "apiVersion",
5095
+ "timestamp",
5096
+ "data"
5097
+ ],
5098
+ "properties": {
5099
+ "event": {
5100
+ "type": "string",
5101
+ "description": "The event name. This field is `event`, not `type`."
5102
+ },
5103
+ "eventId": {
5104
+ "type": "string",
5105
+ "format": "uuid",
5106
+ "description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
5107
+ },
5108
+ "apiVersion": {
5109
+ "type": "string",
5110
+ "example": "2026-04"
5111
+ },
5112
+ "timestamp": {
5113
+ "type": "string",
5114
+ "format": "date-time",
5115
+ "description": "When this delivery was built."
5116
+ },
5117
+ "account": {
5118
+ "$ref": "#/components/schemas/WebhookEventAccount"
5119
+ },
5120
+ "data": {
5121
+ "$ref": "#/components/schemas/BookingUnacknowledgedPayload"
5122
+ }
5123
+ }
5124
+ },
5125
+ "InquiryCreatedEvent": {
5126
+ "type": "object",
5127
+ "required": [
5128
+ "event",
5129
+ "eventId",
5130
+ "apiVersion",
5131
+ "timestamp",
5132
+ "data"
5133
+ ],
5134
+ "properties": {
5135
+ "event": {
5136
+ "type": "string",
5137
+ "description": "The event name. This field is `event`, not `type`."
5138
+ },
5139
+ "eventId": {
5140
+ "type": "string",
5141
+ "format": "uuid",
5142
+ "description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
5143
+ },
5144
+ "apiVersion": {
5145
+ "type": "string",
5146
+ "example": "2026-04"
5147
+ },
5148
+ "timestamp": {
5149
+ "type": "string",
5150
+ "format": "date-time",
5151
+ "description": "When this delivery was built."
5152
+ },
5153
+ "account": {
5154
+ "$ref": "#/components/schemas/WebhookEventAccount"
5155
+ },
5156
+ "data": {
5157
+ "$ref": "#/components/schemas/InquiryCreatedPayload"
5158
+ }
5159
+ }
5160
+ },
5161
+ "InquiryUpdatedEvent": {
5162
+ "type": "object",
5163
+ "required": [
5164
+ "event",
5165
+ "eventId",
5166
+ "apiVersion",
5167
+ "timestamp",
5168
+ "data"
5169
+ ],
5170
+ "properties": {
5171
+ "event": {
5172
+ "type": "string",
5173
+ "description": "The event name. This field is `event`, not `type`."
5174
+ },
5175
+ "eventId": {
5176
+ "type": "string",
5177
+ "format": "uuid",
5178
+ "description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
5179
+ },
5180
+ "apiVersion": {
5181
+ "type": "string",
5182
+ "example": "2026-04"
5183
+ },
5184
+ "timestamp": {
5185
+ "type": "string",
5186
+ "format": "date-time",
5187
+ "description": "When this delivery was built."
5188
+ },
5189
+ "account": {
5190
+ "$ref": "#/components/schemas/WebhookEventAccount"
5191
+ },
5192
+ "data": {
5193
+ "$ref": "#/components/schemas/InquiryUpdatedPayload"
5194
+ }
5195
+ }
5196
+ },
5197
+ "ListingCreatedEvent": {
5198
+ "type": "object",
5199
+ "required": [
5200
+ "event",
5201
+ "eventId",
5202
+ "apiVersion",
5203
+ "timestamp",
5204
+ "data"
5205
+ ],
5206
+ "properties": {
5207
+ "event": {
5208
+ "type": "string",
5209
+ "description": "The event name. This field is `event`, not `type`."
5210
+ },
5211
+ "eventId": {
5212
+ "type": "string",
5213
+ "format": "uuid",
5214
+ "description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
5215
+ },
5216
+ "apiVersion": {
5217
+ "type": "string",
5218
+ "example": "2026-04"
5219
+ },
5220
+ "timestamp": {
5221
+ "type": "string",
5222
+ "format": "date-time",
5223
+ "description": "When this delivery was built."
5224
+ },
5225
+ "account": {
5226
+ "$ref": "#/components/schemas/WebhookEventAccount"
5227
+ },
5228
+ "data": {
5229
+ "$ref": "#/components/schemas/ListingCreatedPayload"
5230
+ }
5231
+ }
5232
+ },
5233
+ "ListingUpdatedEvent": {
4730
5234
  "type": "object",
4731
5235
  "required": [
4732
5236
  "event",
@@ -5741,6 +6245,18 @@
5741
6245
  {
5742
6246
  "$ref": "#/components/schemas/ReservationRequestUpdatedEvent"
5743
6247
  },
6248
+ {
6249
+ "$ref": "#/components/schemas/BookingUnacknowledgedEvent"
6250
+ },
6251
+ {
6252
+ "$ref": "#/components/schemas/ChannelSyncFailedEvent"
6253
+ },
6254
+ {
6255
+ "$ref": "#/components/schemas/ChannelDegradedEvent"
6256
+ },
6257
+ {
6258
+ "$ref": "#/components/schemas/ReservationThreadAssignedEvent"
6259
+ },
5744
6260
  {
5745
6261
  "$ref": "#/components/schemas/InquiryCreatedEvent"
5746
6262
  },
@@ -5827,6 +6343,10 @@
5827
6343
  "reservation.alteration.responded": "#/components/schemas/ReservationAlterationRespondedEvent",
5828
6344
  "reservation.request.created": "#/components/schemas/ReservationRequestCreatedEvent",
5829
6345
  "reservation.request.updated": "#/components/schemas/ReservationRequestUpdatedEvent",
6346
+ "booking.unacknowledged": "#/components/schemas/BookingUnacknowledgedEvent",
6347
+ "channel.sync.failed": "#/components/schemas/ChannelSyncFailedEvent",
6348
+ "channel.degraded": "#/components/schemas/ChannelDegradedEvent",
6349
+ "reservation.thread.assigned": "#/components/schemas/ReservationThreadAssignedEvent",
5830
6350
  "inquiry.created": "#/components/schemas/InquiryCreatedEvent",
5831
6351
  "inquiry.updated": "#/components/schemas/InquiryUpdatedEvent",
5832
6352
  "listing.created": "#/components/schemas/ListingCreatedEvent",
@@ -9384,6 +9904,15 @@
9384
9904
  },
9385
9905
  "syncEnabled": {
9386
9906
  "type": "boolean"
9907
+ },
9908
+ "account": {
9909
+ "allOf": [
9910
+ {
9911
+ "$ref": "#/components/schemas/RecordAccount"
9912
+ }
9913
+ ],
9914
+ "nullable": true,
9915
+ "description": "The connected account this listing belongs to on this channel \u2014 for Airbnb the host account (`externalAccountId` is the Airbnb host id). The same account `account.disconnected` names, so a disconnect can be matched to its listings. Null when it cannot be resolved."
9387
9916
  }
9388
9917
  }
9389
9918
  },
@@ -9416,6 +9945,341 @@
9416
9945
  "isPresent"
9417
9946
  ]
9418
9947
  },
9948
+ "ChargePer": {
9949
+ "type": "string",
9950
+ "enum": [
9951
+ "stay",
9952
+ "night",
9953
+ "guest",
9954
+ "guest_night"
9955
+ ],
9956
+ "description": "How often an amount is charged: once per stay, per night, per guest, or per guest per night."
9957
+ },
9958
+ "ListingRoom": {
9959
+ "type": "object",
9960
+ "properties": {
9961
+ "type": {
9962
+ "type": "string",
9963
+ "description": "Room kind, Airbnb spelling: `bedroom`, `living_room`, `full_bathroom`, `kitchen`, \u2026",
9964
+ "example": "bedroom"
9965
+ },
9966
+ "name": {
9967
+ "type": "string",
9968
+ "nullable": true
9969
+ },
9970
+ "number": {
9971
+ "type": "integer",
9972
+ "nullable": true,
9973
+ "description": "Position among rooms of the same type (bedroom 1, 2, \u2026)."
9974
+ },
9975
+ "isPrivate": {
9976
+ "type": "boolean",
9977
+ "nullable": true
9978
+ },
9979
+ "beds": {
9980
+ "type": "array",
9981
+ "items": {
9982
+ "type": "object",
9983
+ "properties": {
9984
+ "type": {
9985
+ "type": "string",
9986
+ "example": "queen_bed"
9987
+ },
9988
+ "quantity": {
9989
+ "type": "integer",
9990
+ "example": 1
9991
+ }
9992
+ }
9993
+ }
9994
+ }
9995
+ }
9996
+ },
9997
+ "ListingPolicies": {
9998
+ "type": "object",
9999
+ "properties": {
10000
+ "cancellationPolicy": {
10001
+ "type": "string",
10002
+ "nullable": true,
10003
+ "example": "flexible"
10004
+ },
10005
+ "houseRules": {
10006
+ "type": "string",
10007
+ "nullable": true
10008
+ },
10009
+ "checkIn": {
10010
+ "type": "object",
10011
+ "properties": {
10012
+ "method": {
10013
+ "type": "string",
10014
+ "nullable": true,
10015
+ "description": "`lockbox`, `smartlock`, `keypad`, `doorman`, `host_greets`, `building_staff`, `other`.",
10016
+ "example": "lockbox"
10017
+ },
10018
+ "instructions": {
10019
+ "type": "string",
10020
+ "nullable": true
10021
+ },
10022
+ "from": {
10023
+ "type": "string",
10024
+ "nullable": true,
10025
+ "example": "15:00"
10026
+ },
10027
+ "until": {
10028
+ "type": "string",
10029
+ "nullable": true,
10030
+ "example": "22:00"
10031
+ }
10032
+ }
10033
+ },
10034
+ "checkOut": {
10035
+ "type": "object",
10036
+ "properties": {
10037
+ "until": {
10038
+ "type": "string",
10039
+ "nullable": true,
10040
+ "example": "11:00"
10041
+ }
10042
+ }
10043
+ },
10044
+ "allowsPets": {
10045
+ "type": "boolean",
10046
+ "nullable": true
10047
+ },
10048
+ "allowsSmoking": {
10049
+ "type": "boolean",
10050
+ "nullable": true
10051
+ },
10052
+ "allowsEvents": {
10053
+ "type": "boolean",
10054
+ "nullable": true
10055
+ },
10056
+ "allowsChildren": {
10057
+ "type": "boolean",
10058
+ "nullable": true
10059
+ },
10060
+ "allowsInfants": {
10061
+ "type": "boolean",
10062
+ "nullable": true
10063
+ },
10064
+ "childrenMinAge": {
10065
+ "type": "integer",
10066
+ "nullable": true
10067
+ },
10068
+ "quietHours": {
10069
+ "type": "object",
10070
+ "nullable": true,
10071
+ "properties": {
10072
+ "start": {
10073
+ "type": "string",
10074
+ "example": "22:00"
10075
+ },
10076
+ "end": {
10077
+ "type": "string",
10078
+ "example": "07:00"
10079
+ }
10080
+ }
10081
+ },
10082
+ "securityDeposit": {
10083
+ "type": "number",
10084
+ "nullable": true,
10085
+ "description": "In the listing currency."
10086
+ }
10087
+ }
10088
+ },
10089
+ "ListingRules": {
10090
+ "type": "object",
10091
+ "properties": {
10092
+ "minNights": {
10093
+ "type": "integer",
10094
+ "nullable": true
10095
+ },
10096
+ "maxNights": {
10097
+ "type": "integer",
10098
+ "nullable": true
10099
+ },
10100
+ "bookingWindowDays": {
10101
+ "type": "integer",
10102
+ "nullable": true,
10103
+ "description": "How far ahead a guest can book. `null` when unlimited or not set."
10104
+ },
10105
+ "advanceNoticeHours": {
10106
+ "type": "integer",
10107
+ "nullable": true,
10108
+ "description": "Hours of notice required before check-in."
10109
+ },
10110
+ "preparationDays": {
10111
+ "type": "integer",
10112
+ "nullable": true,
10113
+ "description": "Nights blocked between stays."
10114
+ },
10115
+ "allowsBookingRequests": {
10116
+ "type": "boolean",
10117
+ "nullable": true,
10118
+ "description": "Whether guests may send a booking request inside the notice window."
10119
+ },
10120
+ "instantBook": {
10121
+ "type": "string",
10122
+ "nullable": true
10123
+ }
10124
+ }
10125
+ },
10126
+ "ListingPricingSettings": {
10127
+ "type": "object",
10128
+ "description": "The listing's standing prices. Nightly rates per date are on `GET /v1/availability/{propertyId}`.",
10129
+ "properties": {
10130
+ "currency": {
10131
+ "type": "string",
10132
+ "nullable": true,
10133
+ "example": "GBP"
10134
+ },
10135
+ "basePrice": {
10136
+ "type": "number",
10137
+ "nullable": true,
10138
+ "description": "Default nightly rate."
10139
+ },
10140
+ "weekendPrice": {
10141
+ "type": "number",
10142
+ "nullable": true
10143
+ },
10144
+ "cleaningFee": {
10145
+ "type": "number",
10146
+ "nullable": true
10147
+ },
10148
+ "extraGuestFee": {
10149
+ "type": "number",
10150
+ "nullable": true,
10151
+ "description": "Per extra guest per night, above `guestsIncluded`."
10152
+ },
10153
+ "guestsIncluded": {
10154
+ "type": "integer",
10155
+ "nullable": true
10156
+ },
10157
+ "securityDeposit": {
10158
+ "type": "number",
10159
+ "nullable": true
10160
+ },
10161
+ "weeklyDiscountPercent": {
10162
+ "type": "number",
10163
+ "nullable": true,
10164
+ "description": "Percent off for 7+ nights: `10` is 10%."
10165
+ },
10166
+ "monthlyDiscountPercent": {
10167
+ "type": "number",
10168
+ "nullable": true,
10169
+ "description": "Percent off for 28+ nights."
10170
+ }
10171
+ }
10172
+ },
10173
+ "ListingFee": {
10174
+ "type": "object",
10175
+ "description": "A fee on top of the nightly rate, the cleaning fee included. One shape for every channel and PMS.",
10176
+ "properties": {
10177
+ "type": {
10178
+ "type": "string",
10179
+ "description": "`cleaning`, `pet`, `linen`, `resort`, `management`, `community`, or the source's own kind.",
10180
+ "example": "cleaning"
10181
+ },
10182
+ "name": {
10183
+ "type": "string",
10184
+ "nullable": true,
10185
+ "example": "Damage Waiver"
10186
+ },
10187
+ "amount": {
10188
+ "type": "number",
10189
+ "description": "In the listing currency when `amountType` is `flat`; a percent when `percent`.",
10190
+ "example": 35
10191
+ },
10192
+ "amountType": {
10193
+ "type": "string"
10194
+ },
10195
+ "chargedPer": {
10196
+ "$ref": "#/components/schemas/ChargePer"
10197
+ },
10198
+ "optional": {
10199
+ "type": "boolean",
10200
+ "description": "Offered to the guest (an add-on) rather than always charged."
10201
+ },
10202
+ "source": {
10203
+ "type": "string",
10204
+ "description": "Where it was read from: `airbnb`, `guesty`, \u2026",
10205
+ "example": "guesty"
10206
+ }
10207
+ }
10208
+ },
10209
+ "ListingTax": {
10210
+ "type": "object",
10211
+ "description": "A tax charged to the guest. One shape for every channel and PMS.",
10212
+ "properties": {
10213
+ "type": {
10214
+ "type": "string",
10215
+ "description": "`vat`, `vat_gst`, `city_tax`, `transient_occupancy_tax`, \u2026",
10216
+ "example": "vat"
10217
+ },
10218
+ "name": {
10219
+ "type": "string",
10220
+ "nullable": true,
10221
+ "example": "VAT"
10222
+ },
10223
+ "amount": {
10224
+ "type": "number",
10225
+ "example": 20
10226
+ },
10227
+ "amountType": {
10228
+ "type": "string"
10229
+ },
10230
+ "chargedPer": {
10231
+ "$ref": "#/components/schemas/ChargePer"
10232
+ },
10233
+ "appliesTo": {
10234
+ "type": "array",
10235
+ "nullable": true,
10236
+ "items": {
10237
+ "type": "string"
10238
+ },
10239
+ "description": "What it is charged on (`accommodation`, `cleaning`, other fee types). `null`: the whole stay."
10240
+ },
10241
+ "included": {
10242
+ "type": "boolean",
10243
+ "description": "Already included in the price rather than added on top."
10244
+ },
10245
+ "source": {
10246
+ "type": "string",
10247
+ "description": "`airbnb` (host-remitted), `airbnb_collected` (Airbnb collects and remits), `guesty`, \u2026"
10248
+ }
10249
+ }
10250
+ },
10251
+ "ListingContentPhoto": {
10252
+ "type": "object",
10253
+ "properties": {
10254
+ "url": {
10255
+ "type": "string",
10256
+ "format": "uri"
10257
+ },
10258
+ "thumbnailUrl": {
10259
+ "type": "string",
10260
+ "nullable": true
10261
+ },
10262
+ "caption": {
10263
+ "type": "string",
10264
+ "nullable": true
10265
+ },
10266
+ "category": {
10267
+ "type": "string",
10268
+ "nullable": true
10269
+ },
10270
+ "width": {
10271
+ "type": "integer",
10272
+ "nullable": true
10273
+ },
10274
+ "height": {
10275
+ "type": "integer",
10276
+ "nullable": true
10277
+ },
10278
+ "sortOrder": {
10279
+ "type": "integer"
10280
+ }
10281
+ }
10282
+ },
9419
10283
  "Listing": {
9420
10284
  "type": "object",
9421
10285
  "description": "A vacation rental listing in your Repull workspace.\n\nAn **inactive** listing appears only in `GET /v1/listings`, and only when `?status=` asks for it. Such a row carries identity fields only \u2014 `id`, `name`, `status`, `inactiveReason`, `address.city`, `channels` \u2014 so the street, `content`, `details`, `createdAt` and `updatedAt` are absent until the listing is activated. `inactiveReason` is `plan_limit` (held back by the plan; activating needs a free slot or an upgrade), `unlisted_on_airbnb`, or `deactivated` (switched off by you). `GET /v1/listings/{id}` and every other listing endpoint answer `403 listing_inactive` for it. The one field you can add back is `thumbnailUrl`, by passing `?include=thumbnail` \u2014 enough to render an activate/deactivate picker with pictures from a single request.",
@@ -9600,6 +10464,61 @@
9600
10464
  "nullable": true,
9601
10465
  "description": "**Only present when the caller passes `?include=details`.** Sourced from `listings_details`. `null` when the listing has no details row stored (vs the field being absent \u2014 that signals the caller did not opt into the expansion)."
9602
10466
  },
10467
+ "rooms": {
10468
+ "type": "array",
10469
+ "items": {
10470
+ "$ref": "#/components/schemas/ListingRoom"
10471
+ },
10472
+ "description": "**Only present with `?include=rooms`.** Each room and its beds. `[]` when none are stored."
10473
+ },
10474
+ "policies": {
10475
+ "allOf": [
10476
+ {
10477
+ "$ref": "#/components/schemas/ListingPolicies"
10478
+ }
10479
+ ],
10480
+ "nullable": true,
10481
+ "description": "**Only present with `?include=policies`.** Cancellation, house rules, check-in and check-out, pets/smoking/events/children, quiet hours, deposit."
10482
+ },
10483
+ "rules": {
10484
+ "allOf": [
10485
+ {
10486
+ "$ref": "#/components/schemas/ListingRules"
10487
+ }
10488
+ ],
10489
+ "nullable": true,
10490
+ "description": "**Only present with `?include=rules`.** Stay rules: min/max nights, booking window, advance notice, preparation time, and whether booking requests are allowed."
10491
+ },
10492
+ "pricing": {
10493
+ "allOf": [
10494
+ {
10495
+ "$ref": "#/components/schemas/ListingPricingSettings"
10496
+ }
10497
+ ],
10498
+ "nullable": true,
10499
+ "description": "**Only present with `?include=pricing`.** Standing prices: base and weekend rate, cleaning and extra-guest fees, deposit, weekly/monthly discounts. `null` when none are stored."
10500
+ },
10501
+ "fees": {
10502
+ "type": "array",
10503
+ "items": {
10504
+ "$ref": "#/components/schemas/ListingFee"
10505
+ },
10506
+ "description": "**Only present with `?include=fees`.** Every fee, the cleaning fee included, whatever channel or PMS it came from. `[]` when none."
10507
+ },
10508
+ "taxes": {
10509
+ "type": "array",
10510
+ "items": {
10511
+ "$ref": "#/components/schemas/ListingTax"
10512
+ },
10513
+ "description": "**Only present with `?include=taxes`.** Taxes charged to the guest. `[]` when none."
10514
+ },
10515
+ "photos": {
10516
+ "type": "array",
10517
+ "items": {
10518
+ "$ref": "#/components/schemas/ListingContentPhoto"
10519
+ },
10520
+ "description": "**Only present with `?include=photos`.** The listing photos in display order."
10521
+ },
9603
10522
  "createdAt": {
9604
10523
  "type": "string",
9605
10524
  "format": "date-time"
@@ -11736,17 +12655,17 @@
11736
12655
  },
11737
12656
  "min_nights": {
11738
12657
  "type": "integer",
11739
- "minimum": 1,
12658
+ "minimum": -1,
11740
12659
  "maximum": 1125,
11741
12660
  "nullable": true,
11742
- "description": "Minimum length of stay for the date(s)."
12661
+ "description": "Minimum length of stay for the date(s), 1\u20131125. Send `-1` to drop the date's own minimum so it follows the listing default again (`default_min_nights`). `0` and `null` do not clear it."
11743
12662
  },
11744
12663
  "max_nights": {
11745
12664
  "type": "integer",
11746
- "minimum": 1,
12665
+ "minimum": -1,
11747
12666
  "maximum": 1125,
11748
12667
  "nullable": true,
11749
- "description": "Maximum length of stay for the date(s); no lower than `min_nights`."
12668
+ "description": "Maximum length of stay for the date(s), 1\u20131125 and no lower than `min_nights`. Send `-1` to drop the date's own maximum so it follows the listing default again (`default_max_nights`)."
11750
12669
  },
11751
12670
  "closed_to_arrival": {
11752
12671
  "type": "boolean",
@@ -16232,7 +17151,7 @@
16232
17151
  "post": {
16233
17152
  "operationId": "create_reservation",
16234
17153
  "summary": "Create a reservation",
16235
- "description": "Creates a reservation \u2014 in the listing's PMS when it has one, otherwise as a direct booking in Repull.\n\n### Where the booking is made\n\n- **A listing managed in a connected PMS** (Mews, Cloudbeds, Hostaway, Guesty, Beds24, BookingSync, Lodgify, Smoobu, Hospitable, iGMS, OwnerRez, Track): the booking is created **in the PMS first**, then recorded in Repull from the PMS's own record, so the next sync lands on the same confirmation code and nothing is duplicated. A booking is **never** created only in Repull for such a listing \u2014 the PMS would keep selling the dates. What the PMS cannot do is refused (`422 pms_write_unsupported`), never faked. The PMS checks availability: taken dates answer `409 pms_unavailable`.\n- **Any other listing**: a direct booking made in Repull, with everything that hangs off one \u2014 the guest, the conversation, the calendar block and the `reservation.created` fan-out that issues the door code and starts the messaging automations. Priced by the listing's own rates; **availability is NOT checked** (call `GET /v1/availability/{propertyId}` first if that matters).\n\n`GET /v1/listings/{id}` \u2192 `capabilities.reservations` says which applies to a listing and exactly what it supports (`create`, `modify`, `cancel`, `quote`, `customPrice`, plus `notes`).\n\n### Fields by listing kind\n\n| Field | PMS listing | Direct-booking listing |\n|---|---|---|\n| `listingId`, `checkIn`, `checkOut`, `guest`, `guestCount`, `adults`, `children` | \u2713 | \u2713 |\n| `status` | `confirmed` (default) or `tentative` | \u2713 |\n| `totalPrice` | \u2713 where `capabilities.reservations.customPrice`; otherwise the PMS prices the stay | `422 unsupported_field` (priced from the listing's rates) |\n| `notes`, `unitId`, `sendConfirmationEmail` | \u2713 | `422 unsupported_field` |\n| `checkInTime`, `checkOutTime`, `currency`, `guestId` | `422 unsupported_field` (the PMS's own settings apply) | \u2713 |\n| `platform` | `direct` or `website` (`owner` \u2192 `422 pms_write_unsupported`; block owner stays in the PMS) | `direct`, `website` or `owner` |\n\nA field a listing cannot take is refused by name, never silently dropped. `platform` never accepts `airbnb` / `booking` / `vrbo`: those reservations are owned by the channel and arrive through sync.\n\n### Per-PMS limits\n\n| PMS | create | change | cancel | quote | `totalPrice` | Limits |\n|---|---|---|---|---|---|---|\n| Mews | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 | \u2014 |\n| Cloudbeds | \u2713 | \u2713 | \u2713 | \u2013 | \u2013 | Books at the rate plan's price; group bookings supported. |\n| Hostaway | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Direct-channel bookings only; a specific unit is refused; a date change keeps the booked total. |\n| Guesty | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Cancels direct and Vrbo bookings; other channel bookings are cancelled on the channel. |\n| Beds24 | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs the `write:bookings` scope; a multi-room property needs `unitId`. |\n| BookingSync | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs `bookings_write`; fees and taxes are not itemized; no guest email. |\n| Lodgify | \u2713 | \u2713 | \u2713 (declines) | \u2713 | \u2713 | Cancel declines the booking; single-room bookings. |\n| Smoobu | \u2713 | \u2713 (no dates) | \u2713 | \u2713 | \u2713 | Dates cannot be changed through Smoobu's API \u2014 cancel and rebook, or change them in Smoobu. |\n| Hospitable | \u2713 | \u2713 | \u2713 | \u2713 (Direct plan) | \u2713 | Manual reservations only; needs `reservation:write`; adds no fees or taxes. |\n| iGMS | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 (required) | iGMS direct bookings only; a price is required; no tentative holds. |\n| OwnerRez | \u2713 | \u2713 | \u2013 | \u2713 | \u2013 | No cancel through OwnerRez's API; priced by the property's own rates; needs the `full` scope. |\n| Track | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Track decides Hold vs Confirmed from the property's channel mode; a set price on a Channel Key needs \"Allow Custom Pricing\"; a unit change needs `moveReasonId` on the connection; a date change re-prices at Track's current rates. |\n\nEvery PMS except Cloudbeds refuses group bookings, and every vacation-rental PMS refuses to change or cancel a booking that came from a channel (Airbnb, Booking.com, Vrbo\u2026) \u2014 that is done on the channel.\n\n**Verification.** Mews and Cloudbeds were run end to end on their vendors' sandboxes. Every other PMS is **verified against the vendor's API documentation only** \u2014 no live account has been written to yet. `capabilities.reservations.verifiedAgainst` says which.\n\n### Idempotency\n\n**Send `Idempotency-Key`.** A network timeout here is exactly the case it exists for. The key is also sent to the PMS as the booking's reference, so even a retry that reaches the PMS again finds the booking instead of making a second one (`409 pms_duplicate` with `existing`, or the existing booking returned). A completed answer is replayed with `Idempotency-Status: cached` and the PMS is not called again. `502 pms_error` (the PMS could not be reached) is NOT stored \u2014 retry with the same key. `502 reservation_created_in_pms_only` IS stored, unlike every other 5xx: the booking exists in the PMS and arrives with the next sync, so a retry replays that answer rather than booking twice.\n\n### Partial success\n\nWhen the PMS created the booking but a follow-up step did not apply (for example the notes, or a tentative state), the response is still `201`, with `pms.partial: true` and the steps in `pms.failedSections`. The booking exists \u2014 do not create it again.\n\n`X-Account-Id` restricts the listing to one connected account. Returns `403 listing_inactive` when the listing is inactive.",
17154
+ "description": "Creates a reservation \u2014 in the listing's PMS when it has one, otherwise as a direct booking in Repull.\n\n### Where the booking is made\n\n- **A listing managed in a connected PMS** (Mews, Cloudbeds, Hostaway, Guesty, Beds24, BookingSync, Lodgify, Smoobu, Hospitable, iGMS, OwnerRez, Track, CiiRUS): the booking is created **in the PMS first**, then recorded in Repull from the PMS's own record, so the next sync lands on the same confirmation code and nothing is duplicated. A booking is **never** created only in Repull for such a listing \u2014 the PMS would keep selling the dates. What the PMS cannot do is refused (`422 pms_write_unsupported`), never faked. The PMS checks availability: taken dates answer `409 pms_unavailable`.\n- **Any other listing**: a direct booking made in Repull, with everything that hangs off one \u2014 the guest, the conversation, the calendar block and the `reservation.created` fan-out that issues the door code and starts the messaging automations. Priced by the listing's own rates; **availability is NOT checked** (call `GET /v1/availability/{propertyId}` first if that matters).\n\n`GET /v1/listings/{id}` \u2192 `capabilities.reservations` says which applies to a listing and exactly what it supports (`create`, `modify`, `cancel`, `quote`, `customPrice`, plus `notes`).\n\n### Fields by listing kind\n\n| Field | PMS listing | Direct-booking listing |\n|---|---|---|\n| `listingId`, `checkIn`, `checkOut`, `guest`, `guestCount`, `adults`, `children` | \u2713 | \u2713 |\n| `status` | `confirmed` (default) or `tentative` | \u2713 |\n| `totalPrice` | \u2713 where `capabilities.reservations.customPrice`; otherwise the PMS prices the stay | `422 unsupported_field` (priced from the listing's rates) |\n| `notes`, `unitId`, `sendConfirmationEmail` | \u2713 | `422 unsupported_field` |\n| `checkInTime`, `checkOutTime`, `currency`, `guestId` | `422 unsupported_field` (the PMS's own settings apply) | \u2713 |\n| `platform` | `direct` or `website` (`owner` \u2192 `422 pms_write_unsupported`; block owner stays in the PMS) | `direct`, `website` or `owner` |\n\nA field a listing cannot take is refused by name, never silently dropped. `platform` never accepts `airbnb` / `booking` / `vrbo`: those reservations are owned by the channel and arrive through sync.\n\n### Per-PMS limits\n\n| PMS | create | change | cancel | quote | `totalPrice` | Limits |\n|---|---|---|---|---|---|---|\n| Mews | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 | \u2014 |\n| Cloudbeds | \u2713 | \u2713 | \u2713 | \u2013 | \u2013 | Books at the rate plan's price; group bookings supported. |\n| Hostaway | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Direct-channel bookings only; a specific unit is refused; a date change keeps the booked total. |\n| Guesty | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Cancels direct and Vrbo bookings; other channel bookings are cancelled on the channel. |\n| Beds24 | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs the `write:bookings` scope; a multi-room property needs `unitId`. |\n| BookingSync | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs `bookings_write`; fees and taxes are not itemized; no guest email. |\n| Lodgify | \u2713 | \u2713 | \u2713 (declines) | \u2713 | \u2713 | Cancel declines the booking; single-room bookings. |\n| Smoobu | \u2713 | \u2713 (no dates) | \u2713 | \u2713 | \u2713 | Dates cannot be changed through Smoobu's API \u2014 cancel and rebook, or change them in Smoobu. |\n| Hospitable | \u2713 | \u2713 | \u2713 | \u2713 (Direct plan) | \u2713 | Manual reservations only; needs `reservation:write`; adds no fees or taxes. |\n| iGMS | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 (required) | iGMS direct bookings only; a price is required; no tentative holds. |\n| OwnerRez | \u2713 | \u2713 | \u2013 | \u2713 | \u2013 | No cancel through OwnerRez's API; priced by the property's own rates; needs the `full` scope. |\n| Track | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Track decides Hold vs Confirmed from the property's channel mode; a set price on a Channel Key needs \"Allow Custom Pricing\"; a unit change needs `moveReasonId` on the connection; a date change re-prices at Track's current rates. |\n| CiiRUS | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | A set price is the rent, with CiiRUS adding its taxes; no tentative holds. |\n\nEvery PMS except Cloudbeds refuses group bookings, and every vacation-rental PMS refuses to change or cancel a booking that came from a channel (Airbnb, Booking.com, Vrbo\u2026) \u2014 that is done on the channel.\n\n**Verification.** Mews and Cloudbeds were run end to end on their vendors' sandboxes. Every other PMS is **verified against the vendor's API documentation only** \u2014 no live account has been written to yet. `capabilities.reservations.verifiedAgainst` says which.\n\n### Idempotency\n\n**Send `Idempotency-Key`.** A network timeout here is exactly the case it exists for. The key is also sent to the PMS as the booking's reference, so even a retry that reaches the PMS again finds the booking instead of making a second one (`409 pms_duplicate` with `existing`, or the existing booking returned). A completed answer is replayed with `Idempotency-Status: cached` and the PMS is not called again. `502 pms_error` (the PMS could not be reached) is NOT stored \u2014 retry with the same key. `502 reservation_created_in_pms_only` IS stored, unlike every other 5xx: the booking exists in the PMS and arrives with the next sync, so a retry replays that answer rather than booking twice.\n\n### Partial success\n\nWhen the PMS created the booking but a follow-up step did not apply (for example the notes, or a tentative state), the response is still `201`, with `pms.partial: true` and the steps in `pms.failedSections`. The booking exists \u2014 do not create it again.\n\n`X-Account-Id` restricts the listing to one connected account. Returns `403 listing_inactive` when the listing is inactive.",
16236
17155
  "tags": [
16237
17156
  "Reservations"
16238
17157
  ],
@@ -17439,7 +18358,7 @@
17439
18358
  "get": {
17440
18359
  "operationId": "getConversation",
17441
18360
  "summary": "Get conversation detail",
17442
- "description": "Returns one thread (the same shape as the list-row `Conversation`) plus expanded `host` (from `airbnb_hosts` for the thread's `host_id`) and `guest` (resolved via the thread's `reservation_id`, with up to 50 contacts) blocks.\n\nA conversation that belongs to an inactive listing (by the thread's listing or its reservation's listing) returns `403 listing_inactive`. Inactive listings keep syncing; activate the listing to use it here.",
18361
+ "description": "Returns one thread (the same shape as the list-row `Conversation`) plus expanded `host` (from `airbnb_hosts` for the thread's `host_id`) and `guest` (resolved via the thread's `reservation_id`, with up to 50 contacts) blocks. On an inquiry (no reservation yet) `guest` carries the first name Airbnb shares with the inquiry; contacts such as the phone number arrive once the guest books.\n\nA conversation that belongs to an inactive listing (by the thread's listing or its reservation's listing) returns `403 listing_inactive`. Inactive listings keep syncing; activate the listing to use it here.",
17443
18362
  "tags": [
17444
18363
  "Conversations"
17445
18364
  ],
@@ -18277,7 +19196,7 @@
18277
19196
  "api"
18278
19197
  ],
18279
19198
  "default": "hosted",
18280
- "description": "Where the channel's units get linked to listings. `hosted` (default): after a Vrbo or Booking.com Extranet sign-in, Connect shows a mapping step and returns to `redirectUrl` when it's confirmed. `api`: Connect skips that step and finishes right after sign-in. The connection reaches you in the `connect.session.completed` webhook (and as `connectionId` in the popup message), its handle is `vrbo:{connectionId}` or `booking_extranet:{connectionId}`; you list the units with `GET /v1/connections/{id}/units` and link them with `POST /v1/connections/{id}/mappings`. Nothing is imported until you post the mappings. Other providers ignore it."
19199
+ "description": "Where the channel's units get linked to listings. `hosted` (default): after a Vrbo or Booking.com Extranet sign-in, Connect shows a mapping step and returns to `redirectUrl` when it's confirmed. `api`: Connect skips that step and finishes right after sign-in. The connection reaches you in the `connect.session.completed` webhook as `account.externalAccountId` (its `connectionId` is null for these two), and as `connectionId` in the popup message and `accountId` on the redirect, all the same id; its handle is `vrbo:{id}` or `booking_extranet:{id}`; you list the units with `GET /v1/connections/{id}/units` and link them with `POST /v1/connections/{id}/mappings`. Nothing is imported until you post the mappings. Other providers ignore it."
18281
19200
  },
18282
19201
  "accessType": {
18283
19202
  "type": "string",
@@ -18304,7 +19223,7 @@
18304
19223
  "locale": {
18305
19224
  "type": "string",
18306
19225
  "nullable": true,
18307
- "description": "Optional UI language for the hosted Connect pages. Accepts any supported locale code (currently `en`, `fr`). When set it pins the language for the whole flow, overriding the workspace `default_language`. Unknown codes are ignored and the page falls back to the workspace default, then `Accept-Language`, then `en`. The end user can still override per-visit with a `?locale=` query param on the hosted page.",
19226
+ "description": "Optional UI language for the hosted Connect pages. Accepts any supported locale code (currently `en`, `fr`, `de`, `es`, `it`, `nl`). When set it pins the language for the whole flow, overriding the workspace `default_language`. Unknown codes are ignored and the page falls back to the workspace default, then `Accept-Language`, then `en`. The end user can still override per-visit with a `?locale=` query param on the hosted page.",
18308
19227
  "example": "fr"
18309
19228
  },
18310
19229
  "purpose": {
@@ -18562,7 +19481,7 @@
18562
19481
  "locale": {
18563
19482
  "type": "string",
18564
19483
  "nullable": true,
18565
- "description": "Airbnb only \u2014 optional UI language for the hosted Connect pages. Accepts any supported locale code (currently `en`, `fr`); unknown codes are ignored and resolution falls back to the workspace `default_language`, then `Accept-Language`, then `en`.",
19484
+ "description": "Airbnb and Booking.com \u2014 optional UI language for the hosted Connect pages. Accepts any supported locale code (currently `en`, `fr`, `de`, `es`, `it`, `nl`); unknown codes are ignored and resolution falls back to the workspace `default_language`, then `Accept-Language`, then `en`.",
18566
19485
  "example": "fr"
18567
19486
  }
18568
19487
  }
@@ -18586,7 +19505,7 @@
18586
19505
  "delete": {
18587
19506
  "operationId": "delete_connection",
18588
19507
  "summary": "Disconnect provider",
18589
- "description": "Disconnect ONE connected account of a provider from this workspace. Supported for `airbnb` and `booking`.\n\n**Which account.** Pass `accountId` \u2014 for Airbnb the host id (`accounts[].externalAccountId` from `GET /v1/connect/airbnb`), for Booking.com the hotel id. It is optional only when the workspace has exactly one account for the provider. With several and no `accountId`, the call returns `422` with the account ids in `valid_values` instead of guessing. An `accountId` that is not connected to this workspace returns `404`. Disconnecting one account leaves the others connected.\n\n**What happens.** The account's stored authorization is removed and it stops syncing. Its listings are **deactivated**, not deleted: they stop counting toward your plan's listing limit, their data is kept, and they are returned in `listingsDeactivated`. A listing that is still connected through another account or channel stays active. Reconnect the account, then activate the listings with `POST /v1/listings/status`.\n\nThe change is all or nothing. For Airbnb, the host can also revoke access on Airbnb's side (Account \u2192 Privacy & sharing \u2192 Connected apps); that alone does not update this workspace, so call this endpoint as well.\n\nOther providers return `501 not_implemented` with instructions for disconnecting on the provider's side. That answer depends only on the provider, not on your workspace: an unsupported provider returns `501` whether or not you have a connection to it. `404 not_found` on a supported provider means this workspace has no connection to it (or, with `accountId`, that the account is not connected here).",
19508
+ "description": "Disconnect ONE connected account of a provider from this workspace. Supported for `airbnb`, `booking`, `vrbo-login` and every PMS (`guesty`, `hostaway`, `hospitable`, \u2026).\n\n**Which account.** Pass `accountId` \u2014 for Airbnb the host id (`accounts[].externalAccountId` from `GET /v1/connect/airbnb`), for Booking.com the hotel id, for `vrbo-login` the sign-in's `accounts[].externalAccountId` from `GET /v1/connect/vrbo-login`. It is optional only when the workspace has exactly one account for the provider. With several and no `accountId`, the call returns `422` with the account ids in `valid_values` instead of guessing. An `accountId` that is not connected to this workspace returns `404`. Disconnecting one account leaves the others connected.\n\n**What happens.** The account's stored authorization is removed and it stops syncing. Its listings are **deactivated**, not deleted: they stop counting toward your plan's listing limit, their data is kept, and they are returned in `listingsDeactivated`. A listing that is still connected through another account or channel stays active. Reconnect the account, then activate the listings with `POST /v1/listings/status`.\n\nThe change is all or nothing. For Airbnb, the host can also revoke access on Airbnb's side (Account \u2192 Privacy & sharing \u2192 Connected apps); that alone does not update this workspace, so call this endpoint as well.\n\n**PMS.** A workspace has one connection per PMS, so no `accountId` is needed. Disconnecting stops syncing, clears the stored credentials and deactivates the connection's listings; nothing is deleted, and reservations and messages stay. The response also carries `connectionId`. The PMS's own webhook subscription is left in place, because the same PMS account can be connected to another workspace (such as a Repull Migrate workspace). Reconnect with a new Connect session.\n\nOther providers return `501 not_implemented` with instructions for disconnecting on the provider's side. That answer depends only on the provider, not on your workspace: an unsupported provider returns `501` whether or not you have a connection to it. `404 not_found` on a supported provider means this workspace has no connection to it (or, with `accountId`, that the account is not connected here).",
18590
19509
  "tags": [
18591
19510
  "Connect"
18592
19511
  ],
@@ -25143,7 +26062,7 @@
25143
26062
  "get": {
25144
26063
  "operationId": "listListings",
25145
26064
  "summary": "List listings",
25146
- "description": "Cursor-paginated list of listings owned by the authenticated workspace. Use `pagination.nextCursor` from one response as the `cursor` query param of the next request to walk the full set. `?offset=` is also accepted as a first-class alias for shallow paging (0..10000) \u2014 see the `offset` parameter below. Mutually exclusive with `cursor`. Filters: `q` (substring on name/street/city), `status`, `channel`.\n\n**Optional expansions:** Pass `?include=content` to enrich each row with the rich content slab (summary, description, space, house rules, etc. \u2014 sourced from `listings_descriptions` for the `en` locale). Pass `?include=details` for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.). Both default to `null` per row when the underlying `listings_descriptions` / `listings_details` row is missing \u2014 distinct from the field being absent (which signals the expansion was not requested). Pass `?include=thumbnail` to guarantee `thumbnailUrl` on every returned row \u2014 including the reduced inactive ones. Combine comma-separated, e.g. `?include=content,thumbnail`. The default response stays lean; consumers must opt in.\n\n**Inactive listings:** by default only active listings are returned. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated, so when `status` asks for inactive ones they carry only `id`, `name`, `status` and `channels` \u2014 enough to choose what to activate with `PATCH /v1/listings/{id}`. The `content` and `details` expansions are not applied to them. `?include=thumbnail` is the one exception: it adds `thumbnailUrl` to an inactive row so a single request can render an active/inactive selection screen with pictures, instead of one follow-up call per listing (which an inactive listing would answer with `403 listing_inactive` anyway).",
26065
+ "description": "Cursor-paginated list of listings owned by the authenticated workspace. Use `pagination.nextCursor` from one response as the `cursor` query param of the next request to walk the full set. `?offset=` is also accepted as a first-class alias for shallow paging (0..10000) \u2014 see the `offset` parameter below. Mutually exclusive with `cursor`. Filters: `q` (substring on name/street/city), `status`, `channel`.\n\n**Optional expansions:** Pass `?include=content` to enrich each row with the rich content slab (summary, description, space, house rules, etc. \u2014 sourced from `listings_descriptions` for the `en` locale). Pass `?include=details` for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.). Both default to `null` per row when the underlying `listings_descriptions` / `listings_details` row is missing \u2014 distinct from the field being absent (which signals the expansion was not requested). Pass `?include=thumbnail` to guarantee `thumbnailUrl` on every returned row \u2014 including the reduced inactive ones. Pass `rooms`, `policies`, `rules`, `pricing`, `fees`, `taxes` or `photos` for the same sections as `GET /v1/listings/{id}`, loaded for the whole page in one go. Combine comma-separated, e.g. `?include=content,thumbnail`. The default response stays lean; consumers must opt in.\n\n**Inactive listings:** by default only active listings are returned. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated, so when `status` asks for inactive ones they carry only `id`, `name`, `status` and `channels` \u2014 enough to choose what to activate with `PATCH /v1/listings/{id}`. The `content` and `details` expansions are not applied to them. `?include=thumbnail` is the one exception: it adds `thumbnailUrl` to an inactive row so a single request can render an active/inactive selection screen with pictures, instead of one follow-up call per listing (which an inactive listing would answer with `403 listing_inactive` anyway).",
25147
26066
  "tags": [
25148
26067
  "Listings"
25149
26068
  ],
@@ -25213,7 +26132,7 @@
25213
26132
  "type": "string",
25214
26133
  "example": "content,thumbnail"
25215
26134
  },
25216
- "description": "Comma-separated optional expansions. Currently supported: `content`, `details`, `thumbnail`. `thumbnail` guarantees `thumbnailUrl` on every row and is the only expansion that applies to inactive listings. Unknown values return 422 with a `valid_values` envelope. (Note: `amenities` is not yet supported on the list endpoint \u2014 use the detail endpoint to fetch amenity rows for a single listing.)"
26135
+ "description": "Comma-separated optional expansions. Currently supported: `content`, `details`, `thumbnail`, `rooms`, `policies`, `rules`, `pricing`, `fees`, `taxes`, `photos` (the last seven are described on `GET /v1/listings/{id}`; read for the whole page at once). `thumbnail` guarantees `thumbnailUrl` on every row and is the only expansion that applies to inactive listings. Unknown values return 422 with a `valid_values` envelope. (Note: `amenities` is not yet supported on the list endpoint \u2014 use the detail endpoint to fetch amenity rows for a single listing.)"
25217
26136
  }
25218
26137
  ],
25219
26138
  "responses": {
@@ -25276,7 +26195,7 @@
25276
26195
  "get": {
25277
26196
  "operationId": "getListing",
25278
26197
  "summary": "Get a listing",
25279
- "description": "Fetch a single listing by id. Returns the same shape as one element of the `GET /v1/listings` response, so you can bind the result to the same model. Cross-tenant access (a listing that belongs to a different workspace) returns 404 \u2014 never 403, never reveals the listing's existence.\n\n**Optional expansions:** Pass `?include=amenities` to enrich the response with the listing's amenity rows (`[]` when the listing has none). Pass `?include=content` for the rich content slab (summary, description, space, house rules, etc. \u2014 sourced from `listings_descriptions` for the `en` locale; `null` when no row is stored). Pass `?include=details` for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.; `null` when no row is stored). Combine comma-separated, e.g. `?include=amenities,content,details`. The default response stays lean; consumers must opt in.\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.",
26198
+ "description": "Fetch a single listing by id. Returns the same shape as one element of the `GET /v1/listings` response, so you can bind the result to the same model. Cross-tenant access (a listing that belongs to a different workspace) returns 404 \u2014 never 403, never reveals the listing's existence.\n\n**Optional expansions:** Pass `?include=amenities` to enrich the response with the listing's amenity rows (`[]` when the listing has none). Pass `?include=content` for the rich content slab (summary, description, space, house rules, etc. \u2014 sourced from `listings_descriptions` for the `en` locale; `null` when no row is stored). Pass `?include=details` for the structural slab (bedrooms, bathrooms, person capacity, check-in window, wifi, house manual, etc.; `null` when no row is stored). Pass `?include=rooms` for rooms and beds, `policies` for cancellation, house rules, check-in/out and guest rules, `rules` for stay rules (min/max nights, booking window, notice, preparation time), `pricing` for standing prices and discounts, `fees` and `taxes` for every fee and tax in one shape whatever channel or PMS the listing comes from, and `photos`. Combine comma-separated, e.g. `?include=content,details,rooms,policies,pricing,fees,taxes`. The default response stays lean; consumers must opt in.\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.",
25280
26199
  "tags": [
25281
26200
  "Listings"
25282
26201
  ],
@@ -25301,7 +26220,7 @@
25301
26220
  "type": "string",
25302
26221
  "example": "content,details"
25303
26222
  },
25304
- "description": "Comma-separated optional expansions. Currently supported: `amenities`, `content`, `details`. Unknown values return 422 with a `valid_values` envelope."
26223
+ "description": "Comma-separated optional expansions. Currently supported: `amenities`, `content`, `details`, `rooms`, `policies`, `rules`, `pricing`, `fees`, `taxes`, `photos`. Unknown values return 422 with a `valid_values` envelope."
25305
26224
  }
25306
26225
  ],
25307
26226
  "responses": {
@@ -32908,6 +33827,7 @@
32908
33827
  "status",
32909
33828
  "checkIn",
32910
33829
  "checkOut",
33830
+ "guest",
32911
33831
  "guests",
32912
33832
  "expectedPayout",
32913
33833
  "reservationId",
@@ -32963,6 +33883,23 @@
32963
33883
  "nullable": true,
32964
33884
  "example": "2026-10-11"
32965
33885
  },
33886
+ "guest": {
33887
+ "type": "object",
33888
+ "description": "Who asked. Airbnb shares only the guest\u2019s first name before they book; the last name and phone number arrive on the reservation (`GET /v1/reservations/{id}`) once they do.",
33889
+ "properties": {
33890
+ "id": {
33891
+ "type": "string",
33892
+ "nullable": true,
33893
+ "description": "Repull guest id.",
33894
+ "example": "365763"
33895
+ },
33896
+ "firstName": {
33897
+ "type": "string",
33898
+ "nullable": true,
33899
+ "example": "Ximena"
33900
+ }
33901
+ }
33902
+ },
32966
33903
  "guests": {
32967
33904
  "type": "object",
32968
33905
  "properties": {
@@ -37275,7 +38212,7 @@
37275
38212
  "post": {
37276
38213
  "operationId": "quote_reservation",
37277
38214
  "summary": "Quote a reservation in the PMS",
37278
- "description": "Prices a stay and checks its availability **in the PMS that manages the listing**, without booking anything. It is the same check `POST /v1/reservations` makes before booking when no `totalPrice` is sent, so `available: true` with a `total` is what that create would be priced at (dates can still be taken in between).\n\n`available: false` is an answer, not an error: the PMS's reasons are in `restrictions` (minimum stay, closed to arrival, taken dates\u2026).\n\n- A listing **not managed in a PMS** answers `422 pms_not_linked`. Book it directly with `POST /v1/reservations`, or price it with `GET /v1/quotes`.\n- A PMS **without a quote API** (Mews, Cloudbeds, iGMS) answers `422 pms_write_unsupported`. You can still create the booking; on iGMS a `totalPrice` is required.\n\n`GET /v1/listings/{id}` \u2192 `capabilities.reservations.quote` says whether a listing can be quoted.\n\n### Per-PMS limits\n\n| PMS | create | change | cancel | quote | `totalPrice` | Limits |\n|---|---|---|---|---|---|---|\n| Mews | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 | \u2014 |\n| Cloudbeds | \u2713 | \u2713 | \u2713 | \u2013 | \u2013 | Books at the rate plan's price; group bookings supported. |\n| Hostaway | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Direct-channel bookings only; a specific unit is refused; a date change keeps the booked total. |\n| Guesty | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Cancels direct and Vrbo bookings; other channel bookings are cancelled on the channel. |\n| Beds24 | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs the `write:bookings` scope; a multi-room property needs `unitId`. |\n| BookingSync | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs `bookings_write`; fees and taxes are not itemized; no guest email. |\n| Lodgify | \u2713 | \u2713 | \u2713 (declines) | \u2713 | \u2713 | Cancel declines the booking; single-room bookings. |\n| Smoobu | \u2713 | \u2713 (no dates) | \u2713 | \u2713 | \u2713 | Dates cannot be changed through Smoobu's API \u2014 cancel and rebook, or change them in Smoobu. |\n| Hospitable | \u2713 | \u2713 | \u2713 | \u2713 (Direct plan) | \u2713 | Manual reservations only; needs `reservation:write`; adds no fees or taxes. |\n| iGMS | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 (required) | iGMS direct bookings only; a price is required; no tentative holds. |\n| OwnerRez | \u2713 | \u2713 | \u2013 | \u2713 | \u2013 | No cancel through OwnerRez's API; priced by the property's own rates; needs the `full` scope. |\n| Track | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Track decides Hold vs Confirmed from the property's channel mode; a set price on a Channel Key needs \"Allow Custom Pricing\"; a unit change needs `moveReasonId` on the connection; a date change re-prices at Track's current rates. |\n\nEvery PMS except Cloudbeds refuses group bookings, and every vacation-rental PMS refuses to change or cancel a booking that came from a channel (Airbnb, Booking.com, Vrbo\u2026) \u2014 that is done on the channel.\n\n**Verification.** Mews and Cloudbeds were run end to end on their vendors' sandboxes. Every other PMS is **verified against the vendor's API documentation only** \u2014 no live account has been written to yet. `capabilities.reservations.verifiedAgainst` says which.\n\n`X-Account-Id` restricts the listing to one connected account. Returns `403 listing_inactive` when the listing is inactive.",
38215
+ "description": "Prices a stay and checks its availability **in the PMS that manages the listing**, without booking anything. It is the same check `POST /v1/reservations` makes before booking when no `totalPrice` is sent, so `available: true` with a `total` is what that create would be priced at (dates can still be taken in between).\n\n`available: false` is an answer, not an error: the PMS's reasons are in `restrictions` (minimum stay, closed to arrival, taken dates\u2026).\n\n- A listing **not managed in a PMS** answers `422 pms_not_linked`. Book it directly with `POST /v1/reservations`, or price it with `GET /v1/quotes`.\n- A PMS **without a quote API** (Mews, Cloudbeds, iGMS) answers `422 pms_write_unsupported`. You can still create the booking; on iGMS a `totalPrice` is required.\n\n`GET /v1/listings/{id}` \u2192 `capabilities.reservations.quote` says whether a listing can be quoted.\n\n### Per-PMS limits\n\n| PMS | create | change | cancel | quote | `totalPrice` | Limits |\n|---|---|---|---|---|---|---|\n| Mews | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 | \u2014 |\n| Cloudbeds | \u2713 | \u2713 | \u2713 | \u2013 | \u2013 | Books at the rate plan's price; group bookings supported. |\n| Hostaway | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Direct-channel bookings only; a specific unit is refused; a date change keeps the booked total. |\n| Guesty | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Cancels direct and Vrbo bookings; other channel bookings are cancelled on the channel. |\n| Beds24 | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs the `write:bookings` scope; a multi-room property needs `unitId`. |\n| BookingSync | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Needs `bookings_write`; fees and taxes are not itemized; no guest email. |\n| Lodgify | \u2713 | \u2713 | \u2713 (declines) | \u2713 | \u2713 | Cancel declines the booking; single-room bookings. |\n| Smoobu | \u2713 | \u2713 (no dates) | \u2713 | \u2713 | \u2713 | Dates cannot be changed through Smoobu's API \u2014 cancel and rebook, or change them in Smoobu. |\n| Hospitable | \u2713 | \u2713 | \u2713 | \u2713 (Direct plan) | \u2713 | Manual reservations only; needs `reservation:write`; adds no fees or taxes. |\n| iGMS | \u2713 | \u2713 | \u2713 | \u2013 | \u2713 (required) | iGMS direct bookings only; a price is required; no tentative holds. |\n| OwnerRez | \u2713 | \u2713 | \u2013 | \u2713 | \u2013 | No cancel through OwnerRez's API; priced by the property's own rates; needs the `full` scope. |\n| Track | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | Track decides Hold vs Confirmed from the property's channel mode; a set price on a Channel Key needs \"Allow Custom Pricing\"; a unit change needs `moveReasonId` on the connection; a date change re-prices at Track's current rates. |\n| CiiRUS | \u2713 | \u2713 | \u2713 | \u2713 | \u2713 | A set price is the rent, with CiiRUS adding its taxes; no tentative holds. |\n\nEvery PMS except Cloudbeds refuses group bookings, and every vacation-rental PMS refuses to change or cancel a booking that came from a channel (Airbnb, Booking.com, Vrbo\u2026) \u2014 that is done on the channel.\n\n**Verification.** Mews and Cloudbeds were run end to end on their vendors' sandboxes. Every other PMS is **verified against the vendor's API documentation only** \u2014 no live account has been written to yet. `capabilities.reservations.verifiedAgainst` says which.\n\n`X-Account-Id` restricts the listing to one connected account. Returns `403 listing_inactive` when the listing is inactive.",
37279
38216
  "tags": [
37280
38217
  "Reservations"
37281
38218
  ],
@@ -39275,6 +40212,974 @@
39275
40212
  }
39276
40213
  }
39277
40214
  }
40215
+ },
40216
+ "/v1/migrations/channex": {
40217
+ "post": {
40218
+ "operationId": "importFromChannex",
40219
+ "summary": "Import from Channex",
40220
+ "description": "Move a workspace off Channex without changing a single id on your side. Pass your Channex API key: Repull reads your Channex properties, room types, rate plans, webhooks, signing keys, channels and upcoming bookings through Channex's own API (read-only \u2014 nothing is written to Channex, and the key is not stored). Each Channex property is matched to your Repull listing or hotel (by Booking.com hotel/room and Airbnb/Vrbo listing ids, then by title and address) or created; every Channex id then resolves, and is returned, unchanged on the Channex-compatible API at `/channex/api/v1`. Webhooks are recreated inactive; activate them with `POST /channex/api/v1/migration/cutover`. Safe to re-run: a run that hits its time budget returns `complete: false` and the next run continues.",
40221
+ "tags": [
40222
+ "Migrate"
40223
+ ],
40224
+ "requestBody": {
40225
+ "required": true,
40226
+ "content": {
40227
+ "application/json": {
40228
+ "schema": {
40229
+ "type": "object",
40230
+ "required": [
40231
+ "apiKey"
40232
+ ],
40233
+ "properties": {
40234
+ "apiKey": {
40235
+ "type": "string",
40236
+ "description": "Your Channex API key (the `user-api-key` header value). Used for this request only."
40237
+ },
40238
+ "environment": {
40239
+ "type": "string",
40240
+ "enum": [
40241
+ "production",
40242
+ "staging"
40243
+ ],
40244
+ "default": "production",
40245
+ "description": "`production` reads app.channex.io, `staging` reads staging.channex.io."
40246
+ },
40247
+ "dryRun": {
40248
+ "type": "boolean",
40249
+ "default": false,
40250
+ "description": "Match and report without writing anything."
40251
+ }
40252
+ }
40253
+ }
40254
+ }
40255
+ }
40256
+ },
40257
+ "responses": {
40258
+ "200": {
40259
+ "description": "Dry run: what the import would do",
40260
+ "content": {
40261
+ "application/json": {
40262
+ "schema": {
40263
+ "type": "object",
40264
+ "properties": {
40265
+ "data": {
40266
+ "type": "object",
40267
+ "properties": {
40268
+ "id": {
40269
+ "type": "string",
40270
+ "format": "uuid"
40271
+ },
40272
+ "environment": {
40273
+ "type": "string",
40274
+ "enum": [
40275
+ "production",
40276
+ "staging"
40277
+ ]
40278
+ },
40279
+ "dryRun": {
40280
+ "type": "boolean"
40281
+ },
40282
+ "complete": {
40283
+ "type": "boolean",
40284
+ "description": "False when the time budget ran out; run the same request again to continue."
40285
+ },
40286
+ "startedAt": {
40287
+ "type": "string",
40288
+ "format": "date-time"
40289
+ },
40290
+ "finishedAt": {
40291
+ "type": "string",
40292
+ "format": "date-time"
40293
+ },
40294
+ "summary": {
40295
+ "type": "object",
40296
+ "properties": {
40297
+ "properties": {
40298
+ "type": "object",
40299
+ "properties": {
40300
+ "matched": {
40301
+ "type": "integer"
40302
+ },
40303
+ "created": {
40304
+ "type": "integer"
40305
+ },
40306
+ "needsAction": {
40307
+ "type": "integer"
40308
+ }
40309
+ }
40310
+ },
40311
+ "roomTypes": {
40312
+ "type": "object",
40313
+ "properties": {
40314
+ "matched": {
40315
+ "type": "integer"
40316
+ },
40317
+ "created": {
40318
+ "type": "integer"
40319
+ },
40320
+ "needsAction": {
40321
+ "type": "integer"
40322
+ }
40323
+ }
40324
+ },
40325
+ "ratePlans": {
40326
+ "type": "object",
40327
+ "properties": {
40328
+ "matched": {
40329
+ "type": "integer"
40330
+ },
40331
+ "created": {
40332
+ "type": "integer"
40333
+ },
40334
+ "needsAction": {
40335
+ "type": "integer"
40336
+ }
40337
+ }
40338
+ },
40339
+ "webhooks": {
40340
+ "type": "object",
40341
+ "properties": {
40342
+ "matched": {
40343
+ "type": "integer"
40344
+ },
40345
+ "created": {
40346
+ "type": "integer"
40347
+ },
40348
+ "needsAction": {
40349
+ "type": "integer"
40350
+ }
40351
+ }
40352
+ },
40353
+ "signingKeys": {
40354
+ "type": "object",
40355
+ "properties": {
40356
+ "matched": {
40357
+ "type": "integer"
40358
+ },
40359
+ "created": {
40360
+ "type": "integer"
40361
+ },
40362
+ "needsAction": {
40363
+ "type": "integer"
40364
+ }
40365
+ }
40366
+ },
40367
+ "channels": {
40368
+ "type": "object",
40369
+ "properties": {
40370
+ "matched": {
40371
+ "type": "integer"
40372
+ },
40373
+ "created": {
40374
+ "type": "integer"
40375
+ },
40376
+ "needsAction": {
40377
+ "type": "integer"
40378
+ }
40379
+ }
40380
+ },
40381
+ "bookings": {
40382
+ "type": "object",
40383
+ "properties": {
40384
+ "read": {
40385
+ "type": "integer"
40386
+ },
40387
+ "linked": {
40388
+ "type": "integer"
40389
+ },
40390
+ "waiting": {
40391
+ "type": "integer"
40392
+ }
40393
+ }
40394
+ }
40395
+ }
40396
+ },
40397
+ "properties": {
40398
+ "type": "array",
40399
+ "items": {
40400
+ "type": "object",
40401
+ "properties": {
40402
+ "channexId": {
40403
+ "type": "string",
40404
+ "format": "uuid",
40405
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40406
+ },
40407
+ "title": {
40408
+ "type": "string",
40409
+ "nullable": true
40410
+ },
40411
+ "status": {
40412
+ "type": "string",
40413
+ "enum": [
40414
+ "matched",
40415
+ "created",
40416
+ "needs_action"
40417
+ ]
40418
+ },
40419
+ "detail": {
40420
+ "type": "string",
40421
+ "nullable": true,
40422
+ "description": "How it was matched, or what to do."
40423
+ },
40424
+ "repullListingId": {
40425
+ "type": "integer",
40426
+ "nullable": true
40427
+ }
40428
+ }
40429
+ }
40430
+ },
40431
+ "roomTypes": {
40432
+ "type": "array",
40433
+ "items": {
40434
+ "type": "object",
40435
+ "properties": {
40436
+ "channexId": {
40437
+ "type": "string",
40438
+ "format": "uuid",
40439
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40440
+ },
40441
+ "title": {
40442
+ "type": "string",
40443
+ "nullable": true
40444
+ },
40445
+ "status": {
40446
+ "type": "string",
40447
+ "enum": [
40448
+ "matched",
40449
+ "created",
40450
+ "needs_action"
40451
+ ]
40452
+ },
40453
+ "detail": {
40454
+ "type": "string",
40455
+ "nullable": true,
40456
+ "description": "How it was matched, or what to do."
40457
+ },
40458
+ "repullListingId": {
40459
+ "type": "integer",
40460
+ "nullable": true
40461
+ }
40462
+ }
40463
+ }
40464
+ },
40465
+ "ratePlans": {
40466
+ "type": "array",
40467
+ "items": {
40468
+ "type": "object",
40469
+ "properties": {
40470
+ "channexId": {
40471
+ "type": "string",
40472
+ "format": "uuid",
40473
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40474
+ },
40475
+ "title": {
40476
+ "type": "string",
40477
+ "nullable": true
40478
+ },
40479
+ "status": {
40480
+ "type": "string",
40481
+ "enum": [
40482
+ "matched",
40483
+ "created",
40484
+ "needs_action"
40485
+ ]
40486
+ },
40487
+ "detail": {
40488
+ "type": "string",
40489
+ "nullable": true,
40490
+ "description": "How it was matched, or what to do."
40491
+ },
40492
+ "repullListingId": {
40493
+ "type": "integer",
40494
+ "nullable": true
40495
+ }
40496
+ }
40497
+ }
40498
+ },
40499
+ "webhooks": {
40500
+ "type": "array",
40501
+ "items": {
40502
+ "type": "object",
40503
+ "properties": {
40504
+ "channexId": {
40505
+ "type": "string",
40506
+ "format": "uuid",
40507
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40508
+ },
40509
+ "title": {
40510
+ "type": "string",
40511
+ "nullable": true
40512
+ },
40513
+ "status": {
40514
+ "type": "string",
40515
+ "enum": [
40516
+ "matched",
40517
+ "created",
40518
+ "needs_action"
40519
+ ]
40520
+ },
40521
+ "detail": {
40522
+ "type": "string",
40523
+ "nullable": true,
40524
+ "description": "How it was matched, or what to do."
40525
+ },
40526
+ "repullListingId": {
40527
+ "type": "integer",
40528
+ "nullable": true
40529
+ }
40530
+ }
40531
+ }
40532
+ },
40533
+ "signingKeys": {
40534
+ "type": "array",
40535
+ "items": {
40536
+ "type": "object",
40537
+ "properties": {
40538
+ "channexId": {
40539
+ "type": "string",
40540
+ "format": "uuid",
40541
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40542
+ },
40543
+ "title": {
40544
+ "type": "string",
40545
+ "nullable": true
40546
+ },
40547
+ "status": {
40548
+ "type": "string",
40549
+ "enum": [
40550
+ "matched",
40551
+ "created",
40552
+ "needs_action"
40553
+ ]
40554
+ },
40555
+ "detail": {
40556
+ "type": "string",
40557
+ "nullable": true,
40558
+ "description": "How it was matched, or what to do."
40559
+ },
40560
+ "repullListingId": {
40561
+ "type": "integer",
40562
+ "nullable": true
40563
+ }
40564
+ }
40565
+ }
40566
+ },
40567
+ "channels": {
40568
+ "type": "array",
40569
+ "items": {
40570
+ "type": "object",
40571
+ "properties": {
40572
+ "channexId": {
40573
+ "type": "string",
40574
+ "format": "uuid",
40575
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40576
+ },
40577
+ "title": {
40578
+ "type": "string",
40579
+ "nullable": true
40580
+ },
40581
+ "status": {
40582
+ "type": "string",
40583
+ "enum": [
40584
+ "matched",
40585
+ "created",
40586
+ "needs_action"
40587
+ ]
40588
+ },
40589
+ "detail": {
40590
+ "type": "string",
40591
+ "nullable": true,
40592
+ "description": "How it was matched, or what to do."
40593
+ },
40594
+ "repullListingId": {
40595
+ "type": "integer",
40596
+ "nullable": true
40597
+ },
40598
+ "channel": {
40599
+ "type": "string",
40600
+ "description": "Channex adapter code (BookingCom, AirBNB, VRBO\u2026)."
40601
+ },
40602
+ "providerSwitch": {
40603
+ "type": "string",
40604
+ "enum": [
40605
+ "switched",
40606
+ "required",
40607
+ "unsupported"
40608
+ ]
40609
+ },
40610
+ "instructions": {
40611
+ "type": "string",
40612
+ "nullable": true
40613
+ }
40614
+ }
40615
+ }
40616
+ },
40617
+ "warnings": {
40618
+ "type": "array",
40619
+ "items": {
40620
+ "type": "string"
40621
+ }
40622
+ },
40623
+ "nextSteps": {
40624
+ "type": "array",
40625
+ "items": {
40626
+ "type": "string"
40627
+ }
40628
+ }
40629
+ }
40630
+ }
40631
+ }
40632
+ }
40633
+ }
40634
+ }
40635
+ },
40636
+ "201": {
40637
+ "description": "The import report",
40638
+ "content": {
40639
+ "application/json": {
40640
+ "schema": {
40641
+ "type": "object",
40642
+ "properties": {
40643
+ "data": {
40644
+ "type": "object",
40645
+ "properties": {
40646
+ "id": {
40647
+ "type": "string",
40648
+ "format": "uuid"
40649
+ },
40650
+ "environment": {
40651
+ "type": "string",
40652
+ "enum": [
40653
+ "production",
40654
+ "staging"
40655
+ ]
40656
+ },
40657
+ "dryRun": {
40658
+ "type": "boolean"
40659
+ },
40660
+ "complete": {
40661
+ "type": "boolean",
40662
+ "description": "False when the time budget ran out; run the same request again to continue."
40663
+ },
40664
+ "startedAt": {
40665
+ "type": "string",
40666
+ "format": "date-time"
40667
+ },
40668
+ "finishedAt": {
40669
+ "type": "string",
40670
+ "format": "date-time"
40671
+ },
40672
+ "summary": {
40673
+ "type": "object",
40674
+ "properties": {
40675
+ "properties": {
40676
+ "type": "object",
40677
+ "properties": {
40678
+ "matched": {
40679
+ "type": "integer"
40680
+ },
40681
+ "created": {
40682
+ "type": "integer"
40683
+ },
40684
+ "needsAction": {
40685
+ "type": "integer"
40686
+ }
40687
+ }
40688
+ },
40689
+ "roomTypes": {
40690
+ "type": "object",
40691
+ "properties": {
40692
+ "matched": {
40693
+ "type": "integer"
40694
+ },
40695
+ "created": {
40696
+ "type": "integer"
40697
+ },
40698
+ "needsAction": {
40699
+ "type": "integer"
40700
+ }
40701
+ }
40702
+ },
40703
+ "ratePlans": {
40704
+ "type": "object",
40705
+ "properties": {
40706
+ "matched": {
40707
+ "type": "integer"
40708
+ },
40709
+ "created": {
40710
+ "type": "integer"
40711
+ },
40712
+ "needsAction": {
40713
+ "type": "integer"
40714
+ }
40715
+ }
40716
+ },
40717
+ "webhooks": {
40718
+ "type": "object",
40719
+ "properties": {
40720
+ "matched": {
40721
+ "type": "integer"
40722
+ },
40723
+ "created": {
40724
+ "type": "integer"
40725
+ },
40726
+ "needsAction": {
40727
+ "type": "integer"
40728
+ }
40729
+ }
40730
+ },
40731
+ "signingKeys": {
40732
+ "type": "object",
40733
+ "properties": {
40734
+ "matched": {
40735
+ "type": "integer"
40736
+ },
40737
+ "created": {
40738
+ "type": "integer"
40739
+ },
40740
+ "needsAction": {
40741
+ "type": "integer"
40742
+ }
40743
+ }
40744
+ },
40745
+ "channels": {
40746
+ "type": "object",
40747
+ "properties": {
40748
+ "matched": {
40749
+ "type": "integer"
40750
+ },
40751
+ "created": {
40752
+ "type": "integer"
40753
+ },
40754
+ "needsAction": {
40755
+ "type": "integer"
40756
+ }
40757
+ }
40758
+ },
40759
+ "bookings": {
40760
+ "type": "object",
40761
+ "properties": {
40762
+ "read": {
40763
+ "type": "integer"
40764
+ },
40765
+ "linked": {
40766
+ "type": "integer"
40767
+ },
40768
+ "waiting": {
40769
+ "type": "integer"
40770
+ }
40771
+ }
40772
+ }
40773
+ }
40774
+ },
40775
+ "properties": {
40776
+ "type": "array",
40777
+ "items": {
40778
+ "type": "object",
40779
+ "properties": {
40780
+ "channexId": {
40781
+ "type": "string",
40782
+ "format": "uuid",
40783
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40784
+ },
40785
+ "title": {
40786
+ "type": "string",
40787
+ "nullable": true
40788
+ },
40789
+ "status": {
40790
+ "type": "string",
40791
+ "enum": [
40792
+ "matched",
40793
+ "created",
40794
+ "needs_action"
40795
+ ]
40796
+ },
40797
+ "detail": {
40798
+ "type": "string",
40799
+ "nullable": true,
40800
+ "description": "How it was matched, or what to do."
40801
+ },
40802
+ "repullListingId": {
40803
+ "type": "integer",
40804
+ "nullable": true
40805
+ }
40806
+ }
40807
+ }
40808
+ },
40809
+ "roomTypes": {
40810
+ "type": "array",
40811
+ "items": {
40812
+ "type": "object",
40813
+ "properties": {
40814
+ "channexId": {
40815
+ "type": "string",
40816
+ "format": "uuid",
40817
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40818
+ },
40819
+ "title": {
40820
+ "type": "string",
40821
+ "nullable": true
40822
+ },
40823
+ "status": {
40824
+ "type": "string",
40825
+ "enum": [
40826
+ "matched",
40827
+ "created",
40828
+ "needs_action"
40829
+ ]
40830
+ },
40831
+ "detail": {
40832
+ "type": "string",
40833
+ "nullable": true,
40834
+ "description": "How it was matched, or what to do."
40835
+ },
40836
+ "repullListingId": {
40837
+ "type": "integer",
40838
+ "nullable": true
40839
+ }
40840
+ }
40841
+ }
40842
+ },
40843
+ "ratePlans": {
40844
+ "type": "array",
40845
+ "items": {
40846
+ "type": "object",
40847
+ "properties": {
40848
+ "channexId": {
40849
+ "type": "string",
40850
+ "format": "uuid",
40851
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40852
+ },
40853
+ "title": {
40854
+ "type": "string",
40855
+ "nullable": true
40856
+ },
40857
+ "status": {
40858
+ "type": "string",
40859
+ "enum": [
40860
+ "matched",
40861
+ "created",
40862
+ "needs_action"
40863
+ ]
40864
+ },
40865
+ "detail": {
40866
+ "type": "string",
40867
+ "nullable": true,
40868
+ "description": "How it was matched, or what to do."
40869
+ },
40870
+ "repullListingId": {
40871
+ "type": "integer",
40872
+ "nullable": true
40873
+ }
40874
+ }
40875
+ }
40876
+ },
40877
+ "webhooks": {
40878
+ "type": "array",
40879
+ "items": {
40880
+ "type": "object",
40881
+ "properties": {
40882
+ "channexId": {
40883
+ "type": "string",
40884
+ "format": "uuid",
40885
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40886
+ },
40887
+ "title": {
40888
+ "type": "string",
40889
+ "nullable": true
40890
+ },
40891
+ "status": {
40892
+ "type": "string",
40893
+ "enum": [
40894
+ "matched",
40895
+ "created",
40896
+ "needs_action"
40897
+ ]
40898
+ },
40899
+ "detail": {
40900
+ "type": "string",
40901
+ "nullable": true,
40902
+ "description": "How it was matched, or what to do."
40903
+ },
40904
+ "repullListingId": {
40905
+ "type": "integer",
40906
+ "nullable": true
40907
+ }
40908
+ }
40909
+ }
40910
+ },
40911
+ "signingKeys": {
40912
+ "type": "array",
40913
+ "items": {
40914
+ "type": "object",
40915
+ "properties": {
40916
+ "channexId": {
40917
+ "type": "string",
40918
+ "format": "uuid",
40919
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40920
+ },
40921
+ "title": {
40922
+ "type": "string",
40923
+ "nullable": true
40924
+ },
40925
+ "status": {
40926
+ "type": "string",
40927
+ "enum": [
40928
+ "matched",
40929
+ "created",
40930
+ "needs_action"
40931
+ ]
40932
+ },
40933
+ "detail": {
40934
+ "type": "string",
40935
+ "nullable": true,
40936
+ "description": "How it was matched, or what to do."
40937
+ },
40938
+ "repullListingId": {
40939
+ "type": "integer",
40940
+ "nullable": true
40941
+ }
40942
+ }
40943
+ }
40944
+ },
40945
+ "channels": {
40946
+ "type": "array",
40947
+ "items": {
40948
+ "type": "object",
40949
+ "properties": {
40950
+ "channexId": {
40951
+ "type": "string",
40952
+ "format": "uuid",
40953
+ "description": "The Channex id, which keeps resolving on `/channex/api/v1`."
40954
+ },
40955
+ "title": {
40956
+ "type": "string",
40957
+ "nullable": true
40958
+ },
40959
+ "status": {
40960
+ "type": "string",
40961
+ "enum": [
40962
+ "matched",
40963
+ "created",
40964
+ "needs_action"
40965
+ ]
40966
+ },
40967
+ "detail": {
40968
+ "type": "string",
40969
+ "nullable": true,
40970
+ "description": "How it was matched, or what to do."
40971
+ },
40972
+ "repullListingId": {
40973
+ "type": "integer",
40974
+ "nullable": true
40975
+ },
40976
+ "channel": {
40977
+ "type": "string",
40978
+ "description": "Channex adapter code (BookingCom, AirBNB, VRBO\u2026)."
40979
+ },
40980
+ "providerSwitch": {
40981
+ "type": "string",
40982
+ "enum": [
40983
+ "switched",
40984
+ "required",
40985
+ "unsupported"
40986
+ ]
40987
+ },
40988
+ "instructions": {
40989
+ "type": "string",
40990
+ "nullable": true
40991
+ }
40992
+ }
40993
+ }
40994
+ },
40995
+ "warnings": {
40996
+ "type": "array",
40997
+ "items": {
40998
+ "type": "string"
40999
+ }
41000
+ },
41001
+ "nextSteps": {
41002
+ "type": "array",
41003
+ "items": {
41004
+ "type": "string"
41005
+ }
41006
+ }
41007
+ }
41008
+ }
41009
+ }
41010
+ }
41011
+ }
41012
+ }
41013
+ },
41014
+ "400": {
41015
+ "$ref": "#/components/responses/BadRequest"
41016
+ },
41017
+ "401": {
41018
+ "$ref": "#/components/responses/Unauthorized"
41019
+ },
41020
+ "422": {
41021
+ "$ref": "#/components/responses/UnprocessableEntity"
41022
+ },
41023
+ "500": {
41024
+ "$ref": "#/components/responses/InternalError"
41025
+ },
41026
+ "502": {
41027
+ "$ref": "#/components/responses/InternalError"
41028
+ }
41029
+ }
41030
+ }
41031
+ },
41032
+ "/v1/connect/ciirus/credentials": {
41033
+ "post": {
41034
+ "operationId": "submitCiirusCredentials",
41035
+ "summary": "Submit CiiRUS credentials for a Connect session",
41036
+ "description": "Completes a credentials-pattern connection for CiiRUS with the property manager's CiiRUS API username and password.\n\n**What syncs.** Units become listings (with descriptions, photos, amenities, rooms and house rules); reservations (with their charge and tax breakdown and payments) and reviews are imported, then polled for changes. Bookings can be created, quoted, changed and cancelled in CiiRUS \u2014 see `capabilities.reservations` on `GET /v1/connect/ciirus`. Guest messages are not available through CiiRUS's API.\n\n**Where the credentials come from.** The CiiRUS API Explorer gives the API username and password after signing in with the CiiRUS MC username and company password; CiiRUS support can also issue them. Credentials that see several suppliers (management companies) also need `managementCompanyUserId`.\n\n**IP whitelist.** CiiRUS only accepts calls from whitelisted IP addresses, and answers a wrong password and a non-whitelisted address the same way. When this returns `invalid_credentials` with correct credentials, ask CiiRUS support to whitelist Repull for the API user.\n\nThe credentials are validated against CiiRUS before anything is stored, so invalid ones return `invalid_credentials` rather than creating a dead connection. On success only the credential fields below are stored, and the first sync of listings and reservations is queued.\n\nCiiRUS has no webhooks: after the first sync, reservation changes are picked up by polling about once a minute.\n\nReconnecting replaces the stored credentials on the workspace's existing CiiRUS connection \u2014 the `pmsConnectionId` stays the same.\n\nNo API key required when called with a `sessionId` \u2014 the session is the capability token.",
41037
+ "tags": [
41038
+ "Connect"
41039
+ ],
41040
+ "requestBody": {
41041
+ "required": true,
41042
+ "content": {
41043
+ "application/json": {
41044
+ "schema": {
41045
+ "type": "object",
41046
+ "properties": {
41047
+ "sessionId": {
41048
+ "type": "string",
41049
+ "description": "Connect session id from `POST /v1/connect/ciirus`. Omit when calling with your API key."
41050
+ },
41051
+ "writePolicy": {
41052
+ "type": "object",
41053
+ "description": "Optional: what the app may change in CiiRUS, set before the first sync. Same shape as `PATCH /v1/connect/{provider}/write-policy`; switches you leave out keep the provider default (everything on).",
41054
+ "example": {
41055
+ "reservations": {
41056
+ "api": false
41057
+ }
41058
+ }
41059
+ },
41060
+ "credentials": {
41061
+ "type": "object",
41062
+ "description": "The CiiRUS API username and password.",
41063
+ "properties": {
41064
+ "apiUsername": {
41065
+ "type": "string",
41066
+ "description": "CiiRUS API username, from the CiiRUS API Explorer or CiiRUS support."
41067
+ },
41068
+ "apiPassword": {
41069
+ "type": "string",
41070
+ "description": "CiiRUS API password."
41071
+ },
41072
+ "managementCompanyUserId": {
41073
+ "type": "integer",
41074
+ "description": "The CiiRUS supplier (management company) id, the MCUserID. Only needed when the credentials see several suppliers.",
41075
+ "example": 42330
41076
+ }
41077
+ },
41078
+ "required": [
41079
+ "apiUsername",
41080
+ "apiPassword"
41081
+ ]
41082
+ }
41083
+ },
41084
+ "required": [
41085
+ "credentials"
41086
+ ]
41087
+ },
41088
+ "example": {
41089
+ "credentials": {
41090
+ "apiUsername": "AcmeVacationsAPI",
41091
+ "apiPassword": "\u2026"
41092
+ }
41093
+ }
41094
+ }
41095
+ }
41096
+ },
41097
+ "responses": {
41098
+ "200": {
41099
+ "description": "Credentials accepted, the connection stored and its first sync queued",
41100
+ "content": {
41101
+ "application/json": {
41102
+ "schema": {
41103
+ "type": "object",
41104
+ "properties": {
41105
+ "provider": {
41106
+ "type": "string",
41107
+ "example": "ciirus"
41108
+ },
41109
+ "connected": {
41110
+ "type": "boolean"
41111
+ },
41112
+ "pmsConnectionId": {
41113
+ "type": "string",
41114
+ "description": "Id of the stored connection."
41115
+ },
41116
+ "created": {
41117
+ "type": "boolean",
41118
+ "description": "False when an existing connection was updated."
41119
+ },
41120
+ "sessionId": {
41121
+ "type": "string",
41122
+ "nullable": true
41123
+ },
41124
+ "accountInfo": {
41125
+ "type": "object",
41126
+ "properties": {
41127
+ "supplierId": {
41128
+ "type": "string",
41129
+ "description": "The CiiRUS supplier (management company) id the connection reads.",
41130
+ "example": "42330"
41131
+ },
41132
+ "accountName": {
41133
+ "type": "string",
41134
+ "nullable": true,
41135
+ "description": "The supplier's company name."
41136
+ }
41137
+ }
41138
+ },
41139
+ "firstSync": {
41140
+ "type": "object",
41141
+ "description": "Whether the first import of listings and reservations was queued. When it was not, the connection still stands and polling syncs it.",
41142
+ "properties": {
41143
+ "queued": {
41144
+ "type": "boolean"
41145
+ },
41146
+ "error": {
41147
+ "type": "string"
41148
+ }
41149
+ }
41150
+ },
41151
+ "writePolicy": {
41152
+ "$ref": "#/components/schemas/PmsWritePolicy"
41153
+ }
41154
+ }
41155
+ }
41156
+ }
41157
+ }
41158
+ },
41159
+ "400": {
41160
+ "$ref": "#/components/responses/BadRequest"
41161
+ },
41162
+ "401": {
41163
+ "$ref": "#/components/responses/Unauthorized"
41164
+ },
41165
+ "404": {
41166
+ "$ref": "#/components/responses/NotFound"
41167
+ },
41168
+ "422": {
41169
+ "$ref": "#/components/responses/UnprocessableEntity"
41170
+ },
41171
+ "502": {
41172
+ "description": "CiiRUS could not be reached (`network_error`). Nothing was stored.",
41173
+ "content": {
41174
+ "application/json": {
41175
+ "schema": {
41176
+ "$ref": "#/components/schemas/Error"
41177
+ }
41178
+ }
41179
+ }
41180
+ }
41181
+ }
41182
+ }
39278
41183
  }
39279
41184
  }
39280
41185
  }