repull 0.2.33 → 0.2.34

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/repull/api/promotions_api.rb +574 -0
  3. data/lib/repull/models/listing_updated_payload.rb +2 -2
  4. data/lib/repull/models/promotion.rb +344 -0
  5. data/lib/repull/models/promotion_applies_to.rb +161 -0
  6. data/lib/repull/models/promotion_bulk_request.rb +236 -0
  7. data/lib/repull/models/promotion_bulk_response.rb +158 -0
  8. data/lib/repull/models/promotion_bulk_response_data_inner.rb +174 -0
  9. data/lib/repull/models/promotion_bulk_response_data_inner_error.rb +165 -0
  10. data/lib/repull/models/promotion_bulk_response_summary.rb +156 -0
  11. data/lib/repull/models/promotion_catalog.rb +158 -0
  12. data/lib/repull/models/promotion_catalog_channels_inner.rb +222 -0
  13. data/lib/repull/models/promotion_catalog_entry.rb +265 -0
  14. data/lib/repull/models/promotion_catalog_field.rb +205 -0
  15. data/lib/repull/models/promotion_catalog_field_options_inner.rb +156 -0
  16. data/lib/repull/models/promotion_catalog_response.rb +147 -0
  17. data/lib/repull/models/promotion_create_request.rb +258 -0
  18. data/lib/repull/models/promotion_delete_response.rb +147 -0
  19. data/lib/repull/models/promotion_delete_response_data.rb +156 -0
  20. data/lib/repull/models/promotion_discount.rb +191 -0
  21. data/lib/repull/models/promotion_input.rb +241 -0
  22. data/lib/repull/models/promotion_list_response.rb +170 -0
  23. data/lib/repull/models/promotion_refresh_response.rb +170 -0
  24. data/lib/repull/models/promotion_response.rb +147 -0
  25. data/lib/repull/models/promotion_rules.rb +290 -0
  26. data/lib/repull/models/promotion_rules_blackout_dates_quota.rb +156 -0
  27. data/lib/repull/models/promotion_rules_booking_window.rb +230 -0
  28. data/lib/repull/models/promotion_rules_last_minute.rb +218 -0
  29. data/lib/repull/models/promotion_rules_stay_window.rb +211 -0
  30. data/lib/repull/models/promotion_type.rb +47 -0
  31. data/lib/repull/models/promotion_update_request.rb +175 -0
  32. data/lib/repull/models/promotion_usage.rb +193 -0
  33. data/lib/repull/models/promotion_usage_revenue.rb +156 -0
  34. data/lib/repull/models/reservation_money_line.rb +14 -4
  35. data/lib/repull/version.rb +1 -1
  36. data/lib/repull.rb +31 -0
  37. data/openapi/v1.json +1061 -2
  38. data/scripts/regen.sh +1 -1
  39. metadata +32 -1
data/openapi/v1.json CHANGED
@@ -118,6 +118,10 @@
118
118
  "name": "AI",
119
119
  "description": "AI-powered operations \u2014 guest responses, intent classification, listing generation, pricing suggestions"
120
120
  },
121
+ {
122
+ "name": "Promotions",
123
+ "description": "Airbnb and Booking.com promotions on a listing, in one model: Airbnb\u2019s discount programmes (top-rated guests, mobile-only, new listing) and Booking.com deals, mobile and country rates and seasonal campaigns. Read the catalog first \u2014 it says, per channel, which types the listing can use and which fields each supports."
124
+ },
121
125
  {
122
126
  "name": "Pricing",
123
127
  "description": "Atlas-powered pricing recommendations and per-listing strategy. Recommendations are pre-computed by the Atlas pricing model from comp data, demand signals, and event calendars; this surface lets you read them, apply or decline them, and tune the strategy that constrains the model."
@@ -448,6 +452,10 @@
448
452
  "type": "number",
449
453
  "description": "VAT charged on top of this line, when the channel splits it out separately.",
450
454
  "example": 0
455
+ },
456
+ "promotionType": {
457
+ "type": "string",
458
+ "description": "Discount lines only: the listing promotion that produced the discount, named as in `GET /v1/listings/{id}/promotions` (Airbnb reports each applied promotion as its own line). `custom` is an Airbnb Custom promotion."
451
459
  }
452
460
  }
453
461
  },
@@ -3646,7 +3654,7 @@
3646
3654
  },
3647
3655
  "ListingUpdatedPayload": {
3648
3656
  "type": "object",
3649
- "description": "Payload for `listing.updated`. Something about the listing changed on the channel \u2014 content, pricing, booking settings, house rules, availability or sync settings.",
3657
+ "description": "Payload for `listing.updated`. Something about the listing changed on the channel \u2014 content, pricing, booking settings, house rules, availability, sync settings or promotions.",
3650
3658
  "required": [
3651
3659
  "object"
3652
3660
  ],
@@ -3656,7 +3664,7 @@
3656
3664
  },
3657
3665
  "area": {
3658
3666
  "type": "string",
3659
- "description": "Which part moved. Airbnb sends one notification per area rather than a diff, so this is the signal for what to re-read.",
3667
+ "description": "Which part moved. Airbnb sends one notification per area rather than a diff, so this is the signal for what to re-read. `promotions`: the listing\u2019s Airbnb or Booking.com promotions changed \u2014 re-read `GET /v1/listings/{id}/promotions`.",
3660
3668
  "example": "content"
3661
3669
  },
3662
3670
  "previousAttributes": {
@@ -9692,6 +9700,624 @@
9692
9700
  }
9693
9701
  }
9694
9702
  },
9703
+ "Promotion": {
9704
+ "type": "object",
9705
+ "description": "One promotion on one listing. Airbnb sets the discount for its promotions (`discount.setBy: channel`); Booking.com promotions belong to the property and list the rooms they cover in `appliesTo`.",
9706
+ "properties": {
9707
+ "id": {
9708
+ "type": "string",
9709
+ "description": "Repull id of this promotion on this listing (use it in `/promotions/{promotionId}`)."
9710
+ },
9711
+ "listingId": {
9712
+ "type": "string"
9713
+ },
9714
+ "channel": {
9715
+ "type": "string"
9716
+ },
9717
+ "type": {
9718
+ "$ref": "#/components/schemas/PromotionType"
9719
+ },
9720
+ "channelType": {
9721
+ "type": "string",
9722
+ "description": "The channel\u2019s own name, e.g. `HIGH_RATED_GUEST_DISCOUNT`, `basic`, `campaign_getaway`."
9723
+ },
9724
+ "externalId": {
9725
+ "type": "string",
9726
+ "description": "The channel\u2019s promotion id. Booking.com may issue a new one when a promotion is changed; the Repull `id` stays the same."
9727
+ },
9728
+ "channelPropertyId": {
9729
+ "type": "string",
9730
+ "description": "Airbnb listing id, or the Booking.com property (hotel id) the promotion belongs to."
9731
+ },
9732
+ "name": {
9733
+ "type": "string",
9734
+ "nullable": true
9735
+ },
9736
+ "status": {
9737
+ "type": "string",
9738
+ "description": "`eligible`: the listing qualifies but is not enrolled (Airbnb new listing promotion). `inactive`: switched off; Booking.com keeps deactivated promotions."
9739
+ },
9740
+ "managedBy": {
9741
+ "type": "string",
9742
+ "description": "`channel`: created in the channel\u2019s own tools and read-only here (Airbnb Custom promotions)."
9743
+ },
9744
+ "managedKey": {
9745
+ "type": "string",
9746
+ "nullable": true,
9747
+ "description": "Set when it comes from the listing\u2019s weekly or monthly discount in its pricing settings \u2014 change those instead."
9748
+ },
9749
+ "editable": {
9750
+ "type": "boolean"
9751
+ },
9752
+ "removable": {
9753
+ "type": "boolean"
9754
+ },
9755
+ "discount": {
9756
+ "type": "object",
9757
+ "properties": {
9758
+ "percent": {
9759
+ "type": "number",
9760
+ "nullable": true
9761
+ },
9762
+ "setBy": {
9763
+ "type": "string",
9764
+ "enum": [
9765
+ "host",
9766
+ "channel"
9767
+ ]
9768
+ }
9769
+ }
9770
+ },
9771
+ "rules": {
9772
+ "$ref": "#/components/schemas/PromotionRules"
9773
+ },
9774
+ "appliesTo": {
9775
+ "type": "object",
9776
+ "nullable": true,
9777
+ "description": "Booking.com only. An empty `rooms` list means every room on the property.",
9778
+ "properties": {
9779
+ "rooms": {
9780
+ "type": "array",
9781
+ "items": {
9782
+ "type": "string"
9783
+ }
9784
+ },
9785
+ "ratePlans": {
9786
+ "type": "array",
9787
+ "items": {
9788
+ "type": "string"
9789
+ }
9790
+ }
9791
+ }
9792
+ },
9793
+ "usage": {
9794
+ "type": "object",
9795
+ "nullable": true,
9796
+ "properties": {
9797
+ "bookings": {
9798
+ "type": "integer"
9799
+ },
9800
+ "roomNights": {
9801
+ "type": "integer"
9802
+ },
9803
+ "cancellations": {
9804
+ "type": "integer"
9805
+ },
9806
+ "revenue": {
9807
+ "type": "object",
9808
+ "properties": {
9809
+ "amount": {
9810
+ "type": "number"
9811
+ },
9812
+ "currency": {
9813
+ "type": "string"
9814
+ }
9815
+ }
9816
+ },
9817
+ "lastAppliedAt": {
9818
+ "type": "string",
9819
+ "format": "date-time",
9820
+ "description": "Airbnb Custom promotion: when it was last applied to a booking."
9821
+ },
9822
+ "lastAppliedAmount": {
9823
+ "type": "object",
9824
+ "properties": {
9825
+ "amount": {
9826
+ "type": "number"
9827
+ },
9828
+ "currency": {
9829
+ "type": "string"
9830
+ }
9831
+ }
9832
+ }
9833
+ }
9834
+ },
9835
+ "expiresAt": {
9836
+ "type": "string",
9837
+ "format": "date-time",
9838
+ "nullable": true
9839
+ },
9840
+ "syncedAt": {
9841
+ "type": "string",
9842
+ "format": "date-time"
9843
+ }
9844
+ }
9845
+ },
9846
+ "PromotionType": {
9847
+ "type": "string",
9848
+ "enum": [
9849
+ "new_listing",
9850
+ "mobile",
9851
+ "top_rated_guests",
9852
+ "deal",
9853
+ "last_minute",
9854
+ "early_booker",
9855
+ "country",
9856
+ "campaign",
9857
+ "custom"
9858
+ ],
9859
+ "description": "The same idea on both channels shares a type: `mobile` is Airbnb\u2019s mobile-only discount or Booking.com\u2019s mobile rate; `new_listing` is Airbnb\u2019s new listing promotion or Booking.com\u2019s new property deal. `top_rated_guests` is Airbnb only; the deal, last-minute, early-booker, country and campaign types are Booking.com only; `custom` (Airbnb Custom promotion) is read-only."
9860
+ },
9861
+ "PromotionRules": {
9862
+ "type": "object",
9863
+ "description": "Which rules a type supports depends on the channel \u2014 see the catalog.",
9864
+ "properties": {
9865
+ "campaign": {
9866
+ "type": "string",
9867
+ "description": "Booking.com campaigns: `black_friday`, `getaway`, `late_escape`, `early_year`."
9868
+ },
9869
+ "audience": {
9870
+ "type": "string",
9871
+ "description": "`public`, `subscribers` (members only, at least 10%), `app`, `all` (app and mobile web), or a country id from the catalog (e.g. `united_kingdom_pos`)."
9872
+ },
9873
+ "stayWindow": {
9874
+ "type": "object",
9875
+ "properties": {
9876
+ "start": {
9877
+ "type": "string",
9878
+ "format": "date"
9879
+ },
9880
+ "end": {
9881
+ "type": "string",
9882
+ "format": "date"
9883
+ },
9884
+ "weekdays": {
9885
+ "type": "array",
9886
+ "items": {
9887
+ "type": "string",
9888
+ "enum": [
9889
+ "mon",
9890
+ "tue",
9891
+ "wed",
9892
+ "thu",
9893
+ "fri",
9894
+ "sat",
9895
+ "sun"
9896
+ ]
9897
+ }
9898
+ },
9899
+ "excludedDates": {
9900
+ "type": "array",
9901
+ "items": {
9902
+ "type": "string",
9903
+ "format": "date"
9904
+ }
9905
+ },
9906
+ "additionalDates": {
9907
+ "type": "array",
9908
+ "items": {
9909
+ "type": "string",
9910
+ "format": "date"
9911
+ }
9912
+ }
9913
+ }
9914
+ },
9915
+ "bookingWindow": {
9916
+ "type": "object",
9917
+ "properties": {
9918
+ "start": {
9919
+ "type": "string",
9920
+ "format": "date"
9921
+ },
9922
+ "end": {
9923
+ "type": "string",
9924
+ "format": "date"
9925
+ },
9926
+ "startHour": {
9927
+ "type": "integer",
9928
+ "minimum": 0,
9929
+ "maximum": 23
9930
+ },
9931
+ "endHour": {
9932
+ "type": "integer",
9933
+ "minimum": 0,
9934
+ "maximum": 23
9935
+ }
9936
+ }
9937
+ },
9938
+ "lastMinute": {
9939
+ "type": "object",
9940
+ "properties": {
9941
+ "unit": {
9942
+ "type": "string",
9943
+ "enum": [
9944
+ "day",
9945
+ "hour"
9946
+ ]
9947
+ },
9948
+ "value": {
9949
+ "type": "integer",
9950
+ "minimum": 1,
9951
+ "maximum": 1000
9952
+ }
9953
+ }
9954
+ },
9955
+ "earlyBookerDays": {
9956
+ "type": "integer",
9957
+ "minimum": 1,
9958
+ "maximum": 500
9959
+ },
9960
+ "minNights": {
9961
+ "type": "integer",
9962
+ "minimum": 0,
9963
+ "maximum": 30
9964
+ },
9965
+ "nonRefundable": {
9966
+ "type": "boolean"
9967
+ },
9968
+ "blackoutDates": {
9969
+ "type": "array",
9970
+ "items": {
9971
+ "type": "string",
9972
+ "format": "date"
9973
+ },
9974
+ "description": "Airbnb \u2014 not yet available."
9975
+ },
9976
+ "blackoutDatesQuota": {
9977
+ "type": "object",
9978
+ "properties": {
9979
+ "used": {
9980
+ "type": "integer"
9981
+ },
9982
+ "available": {
9983
+ "type": "integer"
9984
+ }
9985
+ }
9986
+ }
9987
+ }
9988
+ },
9989
+ "PromotionInput": {
9990
+ "type": "object",
9991
+ "required": [
9992
+ "channel",
9993
+ "type"
9994
+ ],
9995
+ "properties": {
9996
+ "channel": {
9997
+ "type": "string"
9998
+ },
9999
+ "type": {
10000
+ "$ref": "#/components/schemas/PromotionType"
10001
+ },
10002
+ "name": {
10003
+ "type": "string",
10004
+ "description": "Booking.com only; only you see it."
10005
+ },
10006
+ "discountPercent": {
10007
+ "type": "integer",
10008
+ "description": "Booking.com only \u2014 Airbnb sets its own rates. Limits per type are in the catalog."
10009
+ },
10010
+ "rules": {
10011
+ "$ref": "#/components/schemas/PromotionRules"
10012
+ }
10013
+ }
10014
+ },
10015
+ "PromotionCreateRequest": {
10016
+ "allOf": [
10017
+ {
10018
+ "$ref": "#/components/schemas/PromotionInput"
10019
+ },
10020
+ {
10021
+ "type": "object",
10022
+ "properties": {
10023
+ "hotelId": {
10024
+ "type": "string",
10025
+ "description": "Booking.com: required when the listing has a room on more than one property (see `properties` in the catalog)."
10026
+ }
10027
+ }
10028
+ }
10029
+ ]
10030
+ },
10031
+ "PromotionUpdateRequest": {
10032
+ "type": "object",
10033
+ "properties": {
10034
+ "name": {
10035
+ "type": "string"
10036
+ },
10037
+ "discountPercent": {
10038
+ "type": "integer"
10039
+ },
10040
+ "rules": {
10041
+ "$ref": "#/components/schemas/PromotionRules"
10042
+ },
10043
+ "confirmShared": {
10044
+ "type": "boolean",
10045
+ "description": "Required when the promotion also covers other listings\u2019 rooms \u2014 the change applies to them too."
10046
+ }
10047
+ }
10048
+ },
10049
+ "PromotionCatalogField": {
10050
+ "type": "object",
10051
+ "properties": {
10052
+ "supported": {
10053
+ "type": "boolean"
10054
+ },
10055
+ "comingSoon": {
10056
+ "type": "boolean",
10057
+ "description": "Shown but not yet usable on this channel."
10058
+ },
10059
+ "required": {
10060
+ "type": "boolean"
10061
+ },
10062
+ "min": {
10063
+ "type": "number"
10064
+ },
10065
+ "max": {
10066
+ "type": "number"
10067
+ },
10068
+ "fixed": {
10069
+ "type": "number",
10070
+ "description": "Value the channel sets (e.g. Airbnb\u2019s 15% top-rated-guests discount)."
10071
+ },
10072
+ "options": {
10073
+ "type": "array",
10074
+ "items": {
10075
+ "type": "object",
10076
+ "properties": {
10077
+ "id": {
10078
+ "type": "string"
10079
+ },
10080
+ "label": {
10081
+ "type": "string"
10082
+ }
10083
+ }
10084
+ }
10085
+ }
10086
+ }
10087
+ },
10088
+ "PromotionCatalogEntry": {
10089
+ "type": "object",
10090
+ "properties": {
10091
+ "channel": {
10092
+ "type": "string"
10093
+ },
10094
+ "type": {
10095
+ "$ref": "#/components/schemas/PromotionType"
10096
+ },
10097
+ "channelType": {
10098
+ "type": "string"
10099
+ },
10100
+ "campaign": {
10101
+ "type": "string"
10102
+ },
10103
+ "label": {
10104
+ "type": "string"
10105
+ },
10106
+ "description": {
10107
+ "type": "string",
10108
+ "description": "One or two sentences to show hosts."
10109
+ },
10110
+ "isNew": {
10111
+ "type": "boolean",
10112
+ "description": "Newly available \u2014 show a \u201cNew\u201d badge."
10113
+ },
10114
+ "available": {
10115
+ "type": "boolean"
10116
+ },
10117
+ "unavailableReason": {
10118
+ "type": "string"
10119
+ },
10120
+ "single": {
10121
+ "type": "boolean",
10122
+ "description": "Only one at a time (Airbnb types; Booking.com mobile and country rates per property)."
10123
+ },
10124
+ "fields": {
10125
+ "type": "object",
10126
+ "description": "Every field, supported or not, so a UI can grey out what a type does not take.",
10127
+ "additionalProperties": {
10128
+ "$ref": "#/components/schemas/PromotionCatalogField"
10129
+ }
10130
+ }
10131
+ }
10132
+ },
10133
+ "PromotionCatalog": {
10134
+ "type": "object",
10135
+ "properties": {
10136
+ "listingId": {
10137
+ "type": "string"
10138
+ },
10139
+ "channels": {
10140
+ "type": "array",
10141
+ "items": {
10142
+ "type": "object",
10143
+ "properties": {
10144
+ "channel": {
10145
+ "type": "string",
10146
+ "enum": [
10147
+ "airbnb",
10148
+ "booking"
10149
+ ]
10150
+ },
10151
+ "connected": {
10152
+ "type": "boolean"
10153
+ },
10154
+ "reason": {
10155
+ "type": "string"
10156
+ },
10157
+ "properties": {
10158
+ "type": "array",
10159
+ "items": {
10160
+ "type": "string"
10161
+ },
10162
+ "description": "Booking.com properties the listing has a room on."
10163
+ },
10164
+ "entries": {
10165
+ "type": "array",
10166
+ "items": {
10167
+ "$ref": "#/components/schemas/PromotionCatalogEntry"
10168
+ }
10169
+ }
10170
+ }
10171
+ }
10172
+ }
10173
+ }
10174
+ },
10175
+ "PromotionListResponse": {
10176
+ "type": "object",
10177
+ "properties": {
10178
+ "data": {
10179
+ "type": "array",
10180
+ "items": {
10181
+ "$ref": "#/components/schemas/Promotion"
10182
+ }
10183
+ },
10184
+ "syncedAt": {
10185
+ "type": "string",
10186
+ "format": "date-time",
10187
+ "nullable": true,
10188
+ "description": "When the channels were last read for this listing."
10189
+ },
10190
+ "syncError": {
10191
+ "type": "string",
10192
+ "nullable": true
10193
+ }
10194
+ }
10195
+ },
10196
+ "PromotionResponse": {
10197
+ "type": "object",
10198
+ "properties": {
10199
+ "data": {
10200
+ "$ref": "#/components/schemas/Promotion"
10201
+ }
10202
+ }
10203
+ },
10204
+ "PromotionDeleteResponse": {
10205
+ "type": "object",
10206
+ "properties": {
10207
+ "data": {
10208
+ "type": "object",
10209
+ "properties": {
10210
+ "removed": {
10211
+ "type": "boolean"
10212
+ },
10213
+ "promotionId": {
10214
+ "type": "string"
10215
+ }
10216
+ }
10217
+ }
10218
+ }
10219
+ },
10220
+ "PromotionCatalogResponse": {
10221
+ "type": "object",
10222
+ "properties": {
10223
+ "data": {
10224
+ "$ref": "#/components/schemas/PromotionCatalog"
10225
+ }
10226
+ }
10227
+ },
10228
+ "PromotionRefreshResponse": {
10229
+ "type": "object",
10230
+ "properties": {
10231
+ "data": {
10232
+ "type": "array",
10233
+ "items": {
10234
+ "$ref": "#/components/schemas/Promotion"
10235
+ }
10236
+ },
10237
+ "syncedAt": {
10238
+ "type": "string",
10239
+ "format": "date-time",
10240
+ "nullable": true
10241
+ },
10242
+ "errors": {
10243
+ "type": "array",
10244
+ "items": {
10245
+ "type": "string"
10246
+ }
10247
+ }
10248
+ }
10249
+ },
10250
+ "PromotionBulkRequest": {
10251
+ "type": "object",
10252
+ "required": [
10253
+ "listingIds",
10254
+ "action",
10255
+ "promotion"
10256
+ ],
10257
+ "properties": {
10258
+ "listingIds": {
10259
+ "type": "array",
10260
+ "items": {
10261
+ "type": "integer"
10262
+ },
10263
+ "minItems": 1,
10264
+ "maxItems": 100
10265
+ },
10266
+ "action": {
10267
+ "type": "string"
10268
+ },
10269
+ "promotion": {
10270
+ "$ref": "#/components/schemas/PromotionInput"
10271
+ }
10272
+ }
10273
+ },
10274
+ "PromotionBulkResponse": {
10275
+ "type": "object",
10276
+ "properties": {
10277
+ "data": {
10278
+ "type": "array",
10279
+ "items": {
10280
+ "type": "object",
10281
+ "properties": {
10282
+ "listingId": {
10283
+ "type": "string"
10284
+ },
10285
+ "ok": {
10286
+ "type": "boolean"
10287
+ },
10288
+ "promotion": {
10289
+ "$ref": "#/components/schemas/Promotion"
10290
+ },
10291
+ "error": {
10292
+ "type": "object",
10293
+ "properties": {
10294
+ "message": {
10295
+ "type": "string"
10296
+ },
10297
+ "code": {
10298
+ "type": "string"
10299
+ },
10300
+ "field": {
10301
+ "type": "string"
10302
+ }
10303
+ }
10304
+ }
10305
+ }
10306
+ }
10307
+ },
10308
+ "summary": {
10309
+ "type": "object",
10310
+ "properties": {
10311
+ "succeeded": {
10312
+ "type": "integer"
10313
+ },
10314
+ "failed": {
10315
+ "type": "integer"
10316
+ }
10317
+ }
10318
+ }
10319
+ }
10320
+ },
9695
10321
  "ListingPricingResponse": {
9696
10322
  "type": "object",
9697
10323
  "properties": {
@@ -38439,6 +39065,439 @@
38439
39065
  }
38440
39066
  }
38441
39067
  }
39068
+ },
39069
+ "/v1/listings/promotions/bulk": {
39070
+ "post": {
39071
+ "operationId": "bulk_listing_promotions",
39072
+ "summary": "Add or remove a promotion on many listings",
39073
+ "description": "Applies one promotion to up to 100 listings \u2014 `add` it, or `remove` the listing's running promotion of that channel and type. Each listing gets its own result with the channel's reason when refused, so one refusal doesn't stop the rest. Returns `403 listing_inactive` before doing anything when any listing is inactive.",
39074
+ "tags": [
39075
+ "Promotions"
39076
+ ],
39077
+ "requestBody": {
39078
+ "required": true,
39079
+ "content": {
39080
+ "application/json": {
39081
+ "schema": {
39082
+ "$ref": "#/components/schemas/PromotionBulkRequest"
39083
+ }
39084
+ }
39085
+ }
39086
+ },
39087
+ "responses": {
39088
+ "200": {
39089
+ "description": "A result per listing",
39090
+ "content": {
39091
+ "application/json": {
39092
+ "schema": {
39093
+ "$ref": "#/components/schemas/PromotionBulkResponse"
39094
+ }
39095
+ }
39096
+ }
39097
+ },
39098
+ "400": {
39099
+ "$ref": "#/components/responses/BadRequest"
39100
+ },
39101
+ "401": {
39102
+ "$ref": "#/components/responses/Unauthorized"
39103
+ },
39104
+ "403": {
39105
+ "$ref": "#/components/responses/ListingInactive"
39106
+ }
39107
+ }
39108
+ }
39109
+ },
39110
+ "/v1/listings/{id}/promotions": {
39111
+ "get": {
39112
+ "operationId": "list_listing_promotions",
39113
+ "summary": "List a listing's promotions",
39114
+ "description": "Airbnb and Booking.com promotions on the listing, in one model. Includes Airbnb promotions running or available (the new listing promotion shows as `eligible` when the listing qualifies), Airbnb Custom promotions seen on bookings (read-only \u2014 they override your prices on Airbnb), and Booking.com promotions covering the listing's room, including deactivated ones.\n\nPass `refresh=true` to re-read the channels first; otherwise the stored copy is returned (kept current by Airbnb's notifications and a sync every 30 minutes).\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.",
39115
+ "tags": [
39116
+ "Promotions"
39117
+ ],
39118
+ "parameters": [
39119
+ {
39120
+ "name": "id",
39121
+ "in": "path",
39122
+ "required": true,
39123
+ "schema": {
39124
+ "type": "integer"
39125
+ },
39126
+ "description": "Listing ID"
39127
+ },
39128
+ {
39129
+ "name": "refresh",
39130
+ "in": "query",
39131
+ "required": false,
39132
+ "schema": {
39133
+ "type": "boolean"
39134
+ },
39135
+ "description": "Re-read Airbnb and Booking.com before answering."
39136
+ }
39137
+ ],
39138
+ "responses": {
39139
+ "200": {
39140
+ "description": "The listing's promotions",
39141
+ "content": {
39142
+ "application/json": {
39143
+ "schema": {
39144
+ "$ref": "#/components/schemas/PromotionListResponse"
39145
+ }
39146
+ }
39147
+ }
39148
+ },
39149
+ "400": {
39150
+ "$ref": "#/components/responses/BadRequest"
39151
+ },
39152
+ "401": {
39153
+ "$ref": "#/components/responses/Unauthorized"
39154
+ },
39155
+ "403": {
39156
+ "$ref": "#/components/responses/ListingInactive"
39157
+ },
39158
+ "404": {
39159
+ "$ref": "#/components/responses/NotFound"
39160
+ }
39161
+ }
39162
+ },
39163
+ "post": {
39164
+ "operationId": "create_listing_promotion",
39165
+ "summary": "Add a promotion to a listing",
39166
+ "description": "Adds an Airbnb or Booking.com promotion. Read `GET /v1/listings/{id}/promotions/catalog` first for the types and fields the listing's channels accept.\n\n**Airbnb:** send `channel` and `type` only (`top_rated_guests`, `mobile`, `new_listing`) \u2014 Airbnb sets the discount. **Booking.com:** a listing's promotion covers that listing's room; send `discountPercent` and the rules the type takes, and `hotelId` when the listing is on several properties.\n\nA request a channel can't take is refused with `422` and a `promotion_*` code; the message names the channel and the field and says what to do, and `channel` / `field` are set. Booking.com writes return once Booking.com shows the change (it lags its own writes by a few seconds).\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.",
39167
+ "tags": [
39168
+ "Promotions"
39169
+ ],
39170
+ "parameters": [
39171
+ {
39172
+ "name": "id",
39173
+ "in": "path",
39174
+ "required": true,
39175
+ "schema": {
39176
+ "type": "integer"
39177
+ },
39178
+ "description": "Listing ID"
39179
+ }
39180
+ ],
39181
+ "requestBody": {
39182
+ "required": true,
39183
+ "content": {
39184
+ "application/json": {
39185
+ "schema": {
39186
+ "$ref": "#/components/schemas/PromotionCreateRequest"
39187
+ }
39188
+ }
39189
+ }
39190
+ },
39191
+ "responses": {
39192
+ "201": {
39193
+ "description": "The new promotion",
39194
+ "content": {
39195
+ "application/json": {
39196
+ "schema": {
39197
+ "$ref": "#/components/schemas/PromotionResponse"
39198
+ }
39199
+ }
39200
+ }
39201
+ },
39202
+ "400": {
39203
+ "$ref": "#/components/responses/BadRequest"
39204
+ },
39205
+ "401": {
39206
+ "$ref": "#/components/responses/Unauthorized"
39207
+ },
39208
+ "403": {
39209
+ "$ref": "#/components/responses/ListingInactive"
39210
+ },
39211
+ "404": {
39212
+ "$ref": "#/components/responses/NotFound"
39213
+ },
39214
+ "409": {
39215
+ "$ref": "#/components/responses/Conflict"
39216
+ },
39217
+ "422": {
39218
+ "$ref": "#/components/responses/UnprocessableEntity"
39219
+ },
39220
+ "502": {
39221
+ "description": "The channel didn't answer"
39222
+ }
39223
+ }
39224
+ }
39225
+ },
39226
+ "/v1/listings/{id}/promotions/catalog": {
39227
+ "get": {
39228
+ "operationId": "get_listing_promotion_catalog",
39229
+ "summary": "Promotion types a listing can use",
39230
+ "description": "Per channel: whether the listing is connected, and each promotion type with a short description for hosts, whether it is new, whether it is available to this listing (and why not), and every field \u2014 supported or not \u2014 with its limits and any value the channel fixes. Booking.com's types and limits come from the property itself; seasonal campaigns appear while Booking.com runs them.\n\nAirbnb asks partners to show newly available discounts with a \u201cNew\u201d badge and to grey out fields a discount does not support rather than hide them; `isNew` and `fields` carry what that needs.\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.",
39231
+ "tags": [
39232
+ "Promotions"
39233
+ ],
39234
+ "parameters": [
39235
+ {
39236
+ "name": "id",
39237
+ "in": "path",
39238
+ "required": true,
39239
+ "schema": {
39240
+ "type": "integer"
39241
+ },
39242
+ "description": "Listing ID"
39243
+ }
39244
+ ],
39245
+ "responses": {
39246
+ "200": {
39247
+ "description": "The catalog",
39248
+ "content": {
39249
+ "application/json": {
39250
+ "schema": {
39251
+ "$ref": "#/components/schemas/PromotionCatalogResponse"
39252
+ }
39253
+ }
39254
+ }
39255
+ },
39256
+ "400": {
39257
+ "$ref": "#/components/responses/BadRequest"
39258
+ },
39259
+ "401": {
39260
+ "$ref": "#/components/responses/Unauthorized"
39261
+ },
39262
+ "403": {
39263
+ "$ref": "#/components/responses/ListingInactive"
39264
+ },
39265
+ "404": {
39266
+ "$ref": "#/components/responses/NotFound"
39267
+ }
39268
+ }
39269
+ }
39270
+ },
39271
+ "/v1/listings/{id}/promotions/refresh": {
39272
+ "post": {
39273
+ "operationId": "refresh_listing_promotions",
39274
+ "summary": "Re-read a listing's promotions now",
39275
+ "description": "Reads the listing's promotions from Airbnb and Booking.com and returns them. Booking.com promotions belong to the property, so the property's other listings are refreshed too. `errors` lists any channel that could not be read.\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.",
39276
+ "tags": [
39277
+ "Promotions"
39278
+ ],
39279
+ "parameters": [
39280
+ {
39281
+ "name": "id",
39282
+ "in": "path",
39283
+ "required": true,
39284
+ "schema": {
39285
+ "type": "integer"
39286
+ },
39287
+ "description": "Listing ID"
39288
+ }
39289
+ ],
39290
+ "responses": {
39291
+ "200": {
39292
+ "description": "The listing's promotions after the read",
39293
+ "content": {
39294
+ "application/json": {
39295
+ "schema": {
39296
+ "$ref": "#/components/schemas/PromotionRefreshResponse"
39297
+ }
39298
+ }
39299
+ }
39300
+ },
39301
+ "400": {
39302
+ "$ref": "#/components/responses/BadRequest"
39303
+ },
39304
+ "401": {
39305
+ "$ref": "#/components/responses/Unauthorized"
39306
+ },
39307
+ "403": {
39308
+ "$ref": "#/components/responses/ListingInactive"
39309
+ },
39310
+ "404": {
39311
+ "$ref": "#/components/responses/NotFound"
39312
+ }
39313
+ }
39314
+ }
39315
+ },
39316
+ "/v1/listings/{id}/promotions/{promotionId}": {
39317
+ "get": {
39318
+ "operationId": "get_listing_promotion",
39319
+ "summary": "Get one promotion",
39320
+ "description": "One promotion on the listing.\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.",
39321
+ "tags": [
39322
+ "Promotions"
39323
+ ],
39324
+ "parameters": [
39325
+ {
39326
+ "name": "id",
39327
+ "in": "path",
39328
+ "required": true,
39329
+ "schema": {
39330
+ "type": "integer"
39331
+ },
39332
+ "description": "Listing ID"
39333
+ },
39334
+ {
39335
+ "name": "promotionId",
39336
+ "in": "path",
39337
+ "required": true,
39338
+ "schema": {
39339
+ "type": "integer"
39340
+ },
39341
+ "description": "Promotion ID (from the list)"
39342
+ }
39343
+ ],
39344
+ "responses": {
39345
+ "200": {
39346
+ "description": "The promotion",
39347
+ "content": {
39348
+ "application/json": {
39349
+ "schema": {
39350
+ "$ref": "#/components/schemas/PromotionResponse"
39351
+ }
39352
+ }
39353
+ }
39354
+ },
39355
+ "400": {
39356
+ "$ref": "#/components/responses/BadRequest"
39357
+ },
39358
+ "401": {
39359
+ "$ref": "#/components/responses/Unauthorized"
39360
+ },
39361
+ "403": {
39362
+ "$ref": "#/components/responses/ListingInactive"
39363
+ },
39364
+ "404": {
39365
+ "$ref": "#/components/responses/NotFound"
39366
+ }
39367
+ }
39368
+ },
39369
+ "patch": {
39370
+ "operationId": "update_listing_promotion",
39371
+ "summary": "Change a promotion",
39372
+ "description": "Booking.com promotions only \u2014 Airbnb's have nothing to change. `rules`, when sent, replaces the promotion's rules whole. A promotion that also covers other listings' rooms changes for them too, so it needs `confirmShared: true` (otherwise `409 promotion_shared_promotion`). Promotions that come from the listing's weekly or monthly discount are changed in its pricing settings.\n\nA request a channel can't take is refused with `422` and a `promotion_*` code; the message names the channel and the field and says what to do, and `channel` / `field` are set. Booking.com writes return once Booking.com shows the change (it lags its own writes by a few seconds).\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.",
39373
+ "tags": [
39374
+ "Promotions"
39375
+ ],
39376
+ "parameters": [
39377
+ {
39378
+ "name": "id",
39379
+ "in": "path",
39380
+ "required": true,
39381
+ "schema": {
39382
+ "type": "integer"
39383
+ },
39384
+ "description": "Listing ID"
39385
+ },
39386
+ {
39387
+ "name": "promotionId",
39388
+ "in": "path",
39389
+ "required": true,
39390
+ "schema": {
39391
+ "type": "integer"
39392
+ },
39393
+ "description": "Promotion ID (from the list)"
39394
+ }
39395
+ ],
39396
+ "requestBody": {
39397
+ "required": true,
39398
+ "content": {
39399
+ "application/json": {
39400
+ "schema": {
39401
+ "$ref": "#/components/schemas/PromotionUpdateRequest"
39402
+ }
39403
+ }
39404
+ }
39405
+ },
39406
+ "responses": {
39407
+ "200": {
39408
+ "description": "The changed promotion",
39409
+ "content": {
39410
+ "application/json": {
39411
+ "schema": {
39412
+ "$ref": "#/components/schemas/PromotionResponse"
39413
+ }
39414
+ }
39415
+ }
39416
+ },
39417
+ "400": {
39418
+ "$ref": "#/components/responses/BadRequest"
39419
+ },
39420
+ "401": {
39421
+ "$ref": "#/components/responses/Unauthorized"
39422
+ },
39423
+ "403": {
39424
+ "$ref": "#/components/responses/ListingInactive"
39425
+ },
39426
+ "404": {
39427
+ "$ref": "#/components/responses/NotFound"
39428
+ },
39429
+ "409": {
39430
+ "$ref": "#/components/responses/Conflict"
39431
+ },
39432
+ "422": {
39433
+ "$ref": "#/components/responses/UnprocessableEntity"
39434
+ },
39435
+ "502": {
39436
+ "description": "The channel didn't answer"
39437
+ }
39438
+ }
39439
+ },
39440
+ "delete": {
39441
+ "operationId": "delete_listing_promotion",
39442
+ "summary": "Remove a promotion from a listing",
39443
+ "description": "Airbnb: ends the listing's enrolment. Booking.com: takes the listing's room off the promotion \u2014 it keeps running for any other rooms it covers, and is deactivated when none are left. Airbnb Custom promotions can only be ended in Airbnb.\n\nA request a channel can't take is refused with `422` and a `promotion_*` code; the message names the channel and the field and says what to do, and `channel` / `field` are set. Booking.com writes return once Booking.com shows the change (it lags its own writes by a few seconds).\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.",
39444
+ "tags": [
39445
+ "Promotions"
39446
+ ],
39447
+ "parameters": [
39448
+ {
39449
+ "name": "id",
39450
+ "in": "path",
39451
+ "required": true,
39452
+ "schema": {
39453
+ "type": "integer"
39454
+ },
39455
+ "description": "Listing ID"
39456
+ },
39457
+ {
39458
+ "name": "promotionId",
39459
+ "in": "path",
39460
+ "required": true,
39461
+ "schema": {
39462
+ "type": "integer"
39463
+ },
39464
+ "description": "Promotion ID (from the list)"
39465
+ }
39466
+ ],
39467
+ "responses": {
39468
+ "200": {
39469
+ "description": "Removed",
39470
+ "content": {
39471
+ "application/json": {
39472
+ "schema": {
39473
+ "$ref": "#/components/schemas/PromotionDeleteResponse"
39474
+ }
39475
+ }
39476
+ }
39477
+ },
39478
+ "400": {
39479
+ "$ref": "#/components/responses/BadRequest"
39480
+ },
39481
+ "401": {
39482
+ "$ref": "#/components/responses/Unauthorized"
39483
+ },
39484
+ "403": {
39485
+ "$ref": "#/components/responses/ListingInactive"
39486
+ },
39487
+ "404": {
39488
+ "$ref": "#/components/responses/NotFound"
39489
+ },
39490
+ "409": {
39491
+ "$ref": "#/components/responses/Conflict"
39492
+ },
39493
+ "422": {
39494
+ "$ref": "#/components/responses/UnprocessableEntity"
39495
+ },
39496
+ "502": {
39497
+ "description": "The channel didn't answer"
39498
+ }
39499
+ }
39500
+ }
38442
39501
  }
38443
39502
  }
38444
39503
  }