repull 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/repull/api/airbnb_api.rb +10 -10
- data/lib/repull/api/booking_com_api.rb +86 -2
- data/lib/repull/api/conversations_api.rb +8 -8
- data/lib/repull/api/health_api.rb +0 -57
- data/lib/repull/api/listings_api.rb +182 -12
- data/lib/repull/api/migrate_api.rb +552 -0
- data/lib/repull/api/reservations_api.rb +6 -6
- data/lib/repull/models/airbnb_permits_response.rb +1 -1
- data/lib/repull/models/airbnb_permits_write_request.rb +1 -1
- data/lib/repull/models/airbnb_permits_write_request_permits_inner.rb +43 -4
- data/lib/repull/models/{airbnb_permits_write_request_permits_inner_answers_inner.rb → airbnb_permits_write_request_permits_inner_answers_value.rb} +22 -34
- data/lib/repull/models/airbnb_publish_result.rb +41 -2
- data/lib/repull/models/booking_property_action_request.rb +176 -0
- data/lib/repull/models/booking_property_action_response.rb +281 -0
- data/lib/repull/models/booking_publish_result.rb +245 -0
- data/lib/repull/models/booking_publish_section_error.rb +220 -0
- data/lib/repull/models/booking_setup_request.rb +44 -5
- data/lib/repull/models/booking_setup_request_legal_entity.rb +235 -0
- data/lib/repull/models/channel_market_state_item.rb +291 -0
- data/lib/repull/models/check_migration_cutover200_response.rb +147 -0
- data/lib/repull/models/check_migration_cutover_request.rb +175 -0
- data/lib/repull/models/check_migration_cutover_request_reservations_inner.rb +216 -0
- data/lib/repull/models/connect_provider.rb +18 -5
- data/lib/repull/models/connect_session.rb +25 -5
- data/lib/repull/models/create_connect_session_request.rb +81 -5
- data/lib/repull/models/create_connect_session_request_copy.rb +251 -0
- data/lib/repull/models/create_connect_session_request_workspace.rb +196 -0
- data/lib/repull/models/delete_migration200_response.rb +147 -0
- data/lib/repull/models/delete_migration200_response_data.rb +165 -0
- data/lib/repull/models/error_error.rb +11 -1
- data/lib/repull/models/get_migration200_response.rb +147 -0
- data/lib/repull/models/get_migration_channel_map200_response.rb +147 -0
- data/lib/repull/models/get_migration_report200_response.rb +147 -0
- data/lib/repull/models/inquiry_updated_payload.rb +1 -1
- data/lib/repull/models/list_inquiries200_response_data_inner.rb +1 -1
- data/lib/repull/models/list_migrations200_response.rb +158 -0
- data/lib/repull/models/listing_address_readiness.rb +171 -0
- data/lib/repull/models/listing_content_update_request.rb +10 -1
- data/lib/repull/models/listing_content_update_request_address.rb +40 -3
- data/lib/repull/models/listing_content_update_request_pricing.rb +235 -0
- data/lib/repull/models/listing_content_update_response.rb +1 -1
- data/lib/repull/models/listing_create_request.rb +71 -2
- data/lib/repull/models/listing_create_response.rb +14 -4
- data/lib/repull/models/listing_market_state_request.rb +149 -0
- data/lib/repull/models/listing_market_state_response.rb +221 -0
- data/lib/repull/models/listing_publish_booking_request.rb +149 -0
- data/lib/repull/models/{listing_publish_response.rb → listing_publish_booking_response.rb} +4 -5
- data/lib/repull/models/listing_publish_status_channel.rb +32 -1
- data/lib/repull/models/listing_publish_status_connection.rb +17 -5
- data/lib/repull/models/listing_publish_status_response.rb +13 -1
- data/lib/repull/models/migration.rb +303 -0
- data/lib/repull/models/migration_channel_map.rb +169 -0
- data/lib/repull/models/migration_channel_map_listings_inner.rb +208 -0
- data/lib/repull/models/migration_channel_map_listings_inner_airbnb.rb +156 -0
- data/lib/repull/models/migration_channel_map_listings_inner_booking.rb +156 -0
- data/lib/repull/models/migration_channel_map_sources_inner.rb +167 -0
- data/lib/repull/models/migration_completed_event.rb +309 -0
- data/lib/repull/models/migration_connections_inner.rb +195 -0
- data/lib/repull/models/migration_counts.rb +183 -0
- data/lib/repull/models/migration_cutover_check.rb +200 -0
- data/lib/repull/models/migration_cutover_check_mismatched_inner.rb +156 -0
- data/lib/repull/models/migration_failed_event.rb +309 -0
- data/lib/repull/models/migration_import_payload.rb +225 -0
- data/lib/repull/models/migration_import_payload_results_inner.rb +165 -0
- data/lib/repull/models/migration_import_run.rb +202 -0
- data/lib/repull/models/migration_import_run_results_inner.rb +165 -0
- data/lib/repull/models/migration_report.rb +179 -0
- data/lib/repull/models/migration_report_issues_inner.rb +229 -0
- data/lib/repull/models/migration_reservation_ref.rb +184 -0
- data/lib/repull/models/reservation.rb +12 -2
- data/lib/repull/models/reservation_request_updated_payload.rb +1 -1
- data/lib/repull/models/run_migration_import202_response.rb +147 -0
- data/lib/repull/models/run_migration_import202_response_data.rb +158 -0
- data/lib/repull/models/run_migration_import202_response_data_queued_inner.rb +174 -0
- data/lib/repull/models/run_migration_import_request.rb +182 -0
- data/lib/repull/models/webhook_event.rb +4 -0
- data/lib/repull/models/webhook_event_type.rb +3 -1
- data/lib/repull/version.rb +1 -1
- data/lib/repull.rb +48 -2
- data/openapi/v1.json +2398 -433
- data/scripts/regen.sh +1 -1
- metadata +50 -4
data/openapi/v1.json
CHANGED
|
@@ -83,6 +83,10 @@
|
|
|
83
83
|
"name": "Connect",
|
|
84
84
|
"description": "Manage PMS/OTA connections. Supports 50+ platforms."
|
|
85
85
|
},
|
|
86
|
+
{
|
|
87
|
+
"name": "Migrate",
|
|
88
|
+
"description": "Repull Migrate \u2014 move a property manager's data out of their old PMS: import, report, channel links, cutover."
|
|
89
|
+
},
|
|
86
90
|
{
|
|
87
91
|
"name": "Webhooks",
|
|
88
92
|
"description": "Subscribe to real-time events (reservation changes, messages, etc.)"
|
|
@@ -572,10 +576,15 @@
|
|
|
572
576
|
"description": "Present only when `status` was derived rather than reported by the channel. `request_expired` \u2014 a booking request nobody answered in time (Airbnb's 24-hour window passed, or the check-in did). Absent otherwise.",
|
|
573
577
|
"example": "request_expired"
|
|
574
578
|
},
|
|
579
|
+
"pendingReason": {
|
|
580
|
+
"type": "string",
|
|
581
|
+
"description": "Why a `pending` reservation is pending \u2014 who has to act next. `host_approval`: a booking request the host must accept or decline (see `respondBy`). `guest_payment`: Airbnb is waiting for the guest to pay. `guest_verification`: Airbnb is holding the booking while the guest completes identity verification. The last two need no action from the host, and Airbnb does not publish a deadline for them. Present only while `status` is `pending`; when it changes you receive `reservation.updated` with the previous raw status in `previousAttributes.status`, even if `status` stays `pending`.",
|
|
582
|
+
"example": "guest_verification"
|
|
583
|
+
},
|
|
575
584
|
"respondBy": {
|
|
576
585
|
"type": "string",
|
|
577
586
|
"format": "date-time",
|
|
578
|
-
"description": "On a `pending` Airbnb booking request that can still be answered: when it lapses (24 hours after the guest asked). Accept or decline before then with `POST /v1/reservations/{id}/accept` / `/decline`. Absent on every other reservation.",
|
|
587
|
+
"description": "On a `pending` Airbnb booking request (`pendingReason: host_approval`) that can still be answered: when it lapses (24 hours after the guest asked). Accept or decline before then with `POST /v1/reservations/{id}/accept` / `/decline`. Absent on every other reservation, including bookings Airbnb is holding for the guest's payment or verification \u2014 those have no deadline we can report.",
|
|
579
588
|
"example": "2026-09-23T09:00:00.000Z"
|
|
580
589
|
},
|
|
581
590
|
"source": {
|
|
@@ -1471,6 +1480,12 @@
|
|
|
1471
1480
|
"abnb"
|
|
1472
1481
|
],
|
|
1473
1482
|
"description": "Optional friendly aliases the picker's search box can match."
|
|
1483
|
+
},
|
|
1484
|
+
"migrationCapabilities": {
|
|
1485
|
+
"type": "object",
|
|
1486
|
+
"nullable": true,
|
|
1487
|
+
"additionalProperties": true,
|
|
1488
|
+
"description": "PMS providers: what Repull Migrate can carry across, per entity \u2014 `{ read: { listings: { level, notes }, \u2026 }, write: { \u2026 } }` with `level` `full` | `partial` | `none`. `null` for channels (OTAs)."
|
|
1474
1489
|
}
|
|
1475
1490
|
},
|
|
1476
1491
|
"required": [
|
|
@@ -1495,332 +1510,694 @@
|
|
|
1495
1510
|
}
|
|
1496
1511
|
}
|
|
1497
1512
|
},
|
|
1498
|
-
"
|
|
1513
|
+
"MigrationImportRun": {
|
|
1499
1514
|
"type": "object",
|
|
1500
|
-
"description": "
|
|
1515
|
+
"description": "The last import run on a connection.",
|
|
1501
1516
|
"properties": {
|
|
1502
|
-
"
|
|
1503
|
-
"type": "string"
|
|
1504
|
-
"example": "cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp"
|
|
1505
|
-
},
|
|
1506
|
-
"url": {
|
|
1507
|
-
"type": "string",
|
|
1508
|
-
"format": "uri",
|
|
1509
|
-
"example": "https://connect.repull.dev/cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp"
|
|
1517
|
+
"status": {
|
|
1518
|
+
"type": "string"
|
|
1510
1519
|
},
|
|
1511
|
-
"
|
|
1520
|
+
"startedAt": {
|
|
1512
1521
|
"type": "string",
|
|
1513
|
-
"format": "date-time"
|
|
1522
|
+
"format": "date-time",
|
|
1523
|
+
"nullable": true
|
|
1514
1524
|
},
|
|
1515
|
-
"
|
|
1525
|
+
"finishedAt": {
|
|
1516
1526
|
"type": "string",
|
|
1517
|
-
"
|
|
1518
|
-
"
|
|
1519
|
-
}
|
|
1520
|
-
},
|
|
1521
|
-
"required": [
|
|
1522
|
-
"sessionId",
|
|
1523
|
-
"url",
|
|
1524
|
-
"expiresAt"
|
|
1525
|
-
]
|
|
1526
|
-
},
|
|
1527
|
-
"SelectProviderResponse": {
|
|
1528
|
-
"type": "object",
|
|
1529
|
-
"description": "Returned by `POST /v1/connect/sessions/{sessionId}/select-provider`. The picker UI navigates the user to `nextUrl` to begin the per-provider handoff.",
|
|
1530
|
-
"properties": {
|
|
1531
|
-
"sessionId": {
|
|
1532
|
-
"type": "string"
|
|
1527
|
+
"format": "date-time",
|
|
1528
|
+
"nullable": true
|
|
1533
1529
|
},
|
|
1534
|
-
"
|
|
1535
|
-
"type": "
|
|
1536
|
-
"
|
|
1530
|
+
"entities": {
|
|
1531
|
+
"type": "array",
|
|
1532
|
+
"items": {
|
|
1533
|
+
"type": "string"
|
|
1534
|
+
},
|
|
1535
|
+
"nullable": true
|
|
1537
1536
|
},
|
|
1538
|
-
"
|
|
1539
|
-
"type": "
|
|
1537
|
+
"results": {
|
|
1538
|
+
"type": "array",
|
|
1539
|
+
"nullable": true,
|
|
1540
|
+
"items": {
|
|
1541
|
+
"type": "object",
|
|
1542
|
+
"properties": {
|
|
1543
|
+
"entityType": {
|
|
1544
|
+
"type": "string",
|
|
1545
|
+
"example": "listings"
|
|
1546
|
+
},
|
|
1547
|
+
"processed": {
|
|
1548
|
+
"type": "integer"
|
|
1549
|
+
},
|
|
1550
|
+
"errors": {
|
|
1551
|
+
"type": "integer"
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1540
1555
|
},
|
|
1541
|
-
"
|
|
1556
|
+
"error": {
|
|
1542
1557
|
"type": "string",
|
|
1543
|
-
"
|
|
1544
|
-
"description": "Where to send the user next \u2014 OAuth consent, credentials form, activation checklist, or claim form."
|
|
1558
|
+
"nullable": true
|
|
1545
1559
|
}
|
|
1546
1560
|
}
|
|
1547
1561
|
},
|
|
1548
|
-
"
|
|
1562
|
+
"Migration": {
|
|
1549
1563
|
"type": "object",
|
|
1550
|
-
"description": "
|
|
1564
|
+
"description": "One migration: a property manager moved through Repull Migrate, living in its own workspace.",
|
|
1565
|
+
"required": [
|
|
1566
|
+
"workspaceId",
|
|
1567
|
+
"name",
|
|
1568
|
+
"state",
|
|
1569
|
+
"connections",
|
|
1570
|
+
"counts"
|
|
1571
|
+
],
|
|
1551
1572
|
"properties": {
|
|
1552
|
-
"
|
|
1553
|
-
"type": "boolean",
|
|
1554
|
-
"example": true
|
|
1555
|
-
},
|
|
1556
|
-
"provider": {
|
|
1557
|
-
"type": "string",
|
|
1558
|
-
"example": "airbnb"
|
|
1559
|
-
},
|
|
1560
|
-
"id": {
|
|
1573
|
+
"workspaceId": {
|
|
1561
1574
|
"type": "string",
|
|
1562
|
-
"
|
|
1563
|
-
"
|
|
1575
|
+
"example": "1204",
|
|
1576
|
+
"description": "Pass as `X-Workspace-Id` to read this property manager's listings, reservations and conversations through the regular endpoints."
|
|
1564
1577
|
},
|
|
1565
|
-
"
|
|
1578
|
+
"name": {
|
|
1566
1579
|
"type": "string",
|
|
1567
|
-
"example": "
|
|
1580
|
+
"example": "Seaside Rentals"
|
|
1568
1581
|
},
|
|
1569
|
-
"
|
|
1582
|
+
"externalRef": {
|
|
1570
1583
|
"type": "string",
|
|
1571
1584
|
"nullable": true,
|
|
1572
|
-
"description": "
|
|
1573
|
-
"example": "23998907"
|
|
1585
|
+
"description": "Your own id for this property manager, as sent when the migration was created."
|
|
1574
1586
|
},
|
|
1575
1587
|
"createdAt": {
|
|
1576
1588
|
"type": "string",
|
|
1577
1589
|
"format": "date-time"
|
|
1578
1590
|
},
|
|
1579
|
-
"
|
|
1580
|
-
"
|
|
1581
|
-
"
|
|
1582
|
-
{
|
|
1583
|
-
"$ref": "#/components/schemas/ConnectHost"
|
|
1584
|
-
}
|
|
1585
|
-
],
|
|
1586
|
-
"description": "Host metadata, populated for Airbnb when the host row exists. Null for other providers (per-provider enrichment is incremental)."
|
|
1591
|
+
"state": {
|
|
1592
|
+
"type": "string",
|
|
1593
|
+
"description": "`awaiting_connection` \u2014 not connected yet. `importing` \u2014 the first import is running. `imported` \u2014 data is in and kept fresh until cutover. `failed` \u2014 the last import failed (see `connections[].import.error`). `cut_over` \u2014 the source was disconnected. `deactivated` \u2014 the migration was deleted."
|
|
1587
1594
|
},
|
|
1588
|
-
"
|
|
1595
|
+
"cutoverAt": {
|
|
1596
|
+
"type": "string",
|
|
1597
|
+
"format": "date-time",
|
|
1598
|
+
"nullable": true
|
|
1599
|
+
},
|
|
1600
|
+
"connections": {
|
|
1589
1601
|
"type": "array",
|
|
1590
|
-
"description": "Airbnb only: every Airbnb account this workspace has connected, including ones since disconnected. Pass `externalAccountId` as `accountId` to `DELETE /v1/connect/airbnb` to disconnect one account.",
|
|
1591
1602
|
"items": {
|
|
1592
1603
|
"type": "object",
|
|
1593
1604
|
"properties": {
|
|
1594
|
-
"
|
|
1605
|
+
"id": {
|
|
1606
|
+
"type": "string"
|
|
1607
|
+
},
|
|
1608
|
+
"provider": {
|
|
1595
1609
|
"type": "string",
|
|
1596
|
-
"
|
|
1597
|
-
"example": "79730216"
|
|
1610
|
+
"example": "guesty"
|
|
1598
1611
|
},
|
|
1599
|
-
"
|
|
1612
|
+
"status": {
|
|
1600
1613
|
"type": "string",
|
|
1601
|
-
"
|
|
1602
|
-
"example": "Raiden"
|
|
1614
|
+
"example": "active"
|
|
1603
1615
|
},
|
|
1604
|
-
"
|
|
1616
|
+
"connectedAt": {
|
|
1605
1617
|
"type": "string",
|
|
1606
|
-
"
|
|
1618
|
+
"format": "date-time"
|
|
1607
1619
|
},
|
|
1608
|
-
"
|
|
1620
|
+
"lastPolledAt": {
|
|
1609
1621
|
"type": "string",
|
|
1610
|
-
"
|
|
1611
|
-
"
|
|
1622
|
+
"format": "date-time",
|
|
1623
|
+
"nullable": true
|
|
1612
1624
|
},
|
|
1613
|
-
"
|
|
1614
|
-
"
|
|
1615
|
-
"description": "
|
|
1616
|
-
"
|
|
1625
|
+
"import": {
|
|
1626
|
+
"nullable": true,
|
|
1627
|
+
"description": "The last import run, or null before the first one (and for channels, which sync on their own schedule).",
|
|
1628
|
+
"allOf": [
|
|
1629
|
+
{
|
|
1630
|
+
"$ref": "#/components/schemas/MigrationImportRun"
|
|
1631
|
+
}
|
|
1632
|
+
]
|
|
1617
1633
|
}
|
|
1618
1634
|
}
|
|
1619
1635
|
}
|
|
1636
|
+
},
|
|
1637
|
+
"counts": {
|
|
1638
|
+
"type": "object",
|
|
1639
|
+
"properties": {
|
|
1640
|
+
"listings": {
|
|
1641
|
+
"type": "integer"
|
|
1642
|
+
},
|
|
1643
|
+
"reservations": {
|
|
1644
|
+
"type": "integer"
|
|
1645
|
+
},
|
|
1646
|
+
"upcomingReservations": {
|
|
1647
|
+
"type": "integer"
|
|
1648
|
+
},
|
|
1649
|
+
"guests": {
|
|
1650
|
+
"type": "integer"
|
|
1651
|
+
},
|
|
1652
|
+
"conversations": {
|
|
1653
|
+
"type": "integer"
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1620
1656
|
}
|
|
1621
1657
|
}
|
|
1622
1658
|
},
|
|
1623
|
-
"
|
|
1659
|
+
"MigrationReport": {
|
|
1624
1660
|
"type": "object",
|
|
1625
|
-
"description": "A registered webhook endpoint. The `secret` field is only present in the response of `POST /v1/webhooks` and `POST /v1/webhooks/{id}/rotate-secret` (Stripe pattern \u2014 capture it then; it is masked everywhere else).",
|
|
1626
1661
|
"properties": {
|
|
1627
|
-
"
|
|
1628
|
-
"type": "string",
|
|
1629
|
-
"format": "uuid"
|
|
1630
|
-
},
|
|
1631
|
-
"url": {
|
|
1632
|
-
"type": "string",
|
|
1633
|
-
"format": "uri"
|
|
1634
|
-
},
|
|
1635
|
-
"description": {
|
|
1636
|
-
"type": "string",
|
|
1637
|
-
"nullable": true
|
|
1638
|
-
},
|
|
1639
|
-
"events": {
|
|
1640
|
-
"type": "array",
|
|
1641
|
-
"items": {
|
|
1642
|
-
"$ref": "#/components/schemas/WebhookEventType"
|
|
1643
|
-
},
|
|
1644
|
-
"example": [
|
|
1645
|
-
"reservation.created",
|
|
1646
|
-
"reservation.updated"
|
|
1647
|
-
]
|
|
1648
|
-
},
|
|
1649
|
-
"apiVersion": {
|
|
1650
|
-
"type": "string",
|
|
1651
|
-
"example": "2026-04"
|
|
1652
|
-
},
|
|
1653
|
-
"status": {
|
|
1662
|
+
"workspaceId": {
|
|
1654
1663
|
"type": "string"
|
|
1655
1664
|
},
|
|
1656
|
-
"
|
|
1657
|
-
"type": "integer"
|
|
1658
|
-
},
|
|
1659
|
-
"lastDeliveredAt": {
|
|
1660
|
-
"type": "string",
|
|
1661
|
-
"format": "date-time",
|
|
1662
|
-
"nullable": true
|
|
1663
|
-
},
|
|
1664
|
-
"lastSuccessAt": {
|
|
1665
|
-
"type": "string",
|
|
1666
|
-
"format": "date-time",
|
|
1667
|
-
"nullable": true
|
|
1668
|
-
},
|
|
1669
|
-
"lastFailureAt": {
|
|
1670
|
-
"type": "string",
|
|
1671
|
-
"format": "date-time",
|
|
1672
|
-
"nullable": true
|
|
1673
|
-
},
|
|
1674
|
-
"lastDeliveryStatus": {
|
|
1675
|
-
"type": "integer",
|
|
1676
|
-
"nullable": true
|
|
1677
|
-
},
|
|
1678
|
-
"disabledAt": {
|
|
1679
|
-
"type": "string",
|
|
1680
|
-
"format": "date-time",
|
|
1681
|
-
"nullable": true
|
|
1682
|
-
},
|
|
1683
|
-
"createdAt": {
|
|
1684
|
-
"type": "string",
|
|
1685
|
-
"format": "date-time"
|
|
1686
|
-
},
|
|
1687
|
-
"updatedAt": {
|
|
1665
|
+
"generatedAt": {
|
|
1688
1666
|
"type": "string",
|
|
1689
1667
|
"format": "date-time"
|
|
1690
1668
|
},
|
|
1691
|
-
"
|
|
1692
|
-
"type": "
|
|
1693
|
-
"
|
|
1694
|
-
"
|
|
1669
|
+
"capabilities": {
|
|
1670
|
+
"type": "object",
|
|
1671
|
+
"description": "Per source provider: what it can carry, per entity \u2014 `{ read: { listings: { level, notes } \u2026 }, write: { \u2026 } }`, level `full` | `partial` | `none`.",
|
|
1672
|
+
"additionalProperties": true
|
|
1695
1673
|
},
|
|
1696
|
-
"
|
|
1697
|
-
"type": "
|
|
1698
|
-
"
|
|
1699
|
-
|
|
1674
|
+
"issues": {
|
|
1675
|
+
"type": "array",
|
|
1676
|
+
"items": {
|
|
1677
|
+
"type": "object",
|
|
1678
|
+
"properties": {
|
|
1679
|
+
"severity": {
|
|
1680
|
+
"type": "string",
|
|
1681
|
+
"enum": [
|
|
1682
|
+
"error",
|
|
1683
|
+
"warning",
|
|
1684
|
+
"info"
|
|
1685
|
+
]
|
|
1686
|
+
},
|
|
1687
|
+
"entity": {
|
|
1688
|
+
"type": "string",
|
|
1689
|
+
"example": "reservations"
|
|
1690
|
+
},
|
|
1691
|
+
"code": {
|
|
1692
|
+
"type": "string",
|
|
1693
|
+
"example": "reservation_missing_guest_contact"
|
|
1694
|
+
},
|
|
1695
|
+
"message": {
|
|
1696
|
+
"type": "string"
|
|
1697
|
+
},
|
|
1698
|
+
"count": {
|
|
1699
|
+
"type": "integer"
|
|
1700
|
+
},
|
|
1701
|
+
"sampleIds": {
|
|
1702
|
+
"type": "array",
|
|
1703
|
+
"items": {
|
|
1704
|
+
"type": "integer"
|
|
1705
|
+
},
|
|
1706
|
+
"description": "Up to 10 affected listing / reservation ids."
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1700
1710
|
}
|
|
1701
1711
|
}
|
|
1702
1712
|
},
|
|
1703
|
-
"
|
|
1713
|
+
"MigrationChannelMap": {
|
|
1704
1714
|
"type": "object",
|
|
1705
|
-
"description": "A single delivery attempt for a webhook event. The actual `WebhookEvent` envelope POSTed to the subscription URL is captured on `WebhookDeliveryDetail.payload` (this list view omits the body for size).",
|
|
1706
1715
|
"properties": {
|
|
1707
|
-
"
|
|
1708
|
-
"type": "string"
|
|
1709
|
-
"format": "uuid"
|
|
1716
|
+
"workspaceId": {
|
|
1717
|
+
"type": "string"
|
|
1710
1718
|
},
|
|
1711
|
-
"
|
|
1712
|
-
"type": "
|
|
1713
|
-
"
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1719
|
+
"sources": {
|
|
1720
|
+
"type": "array",
|
|
1721
|
+
"items": {
|
|
1722
|
+
"type": "object",
|
|
1723
|
+
"properties": {
|
|
1724
|
+
"provider": {
|
|
1725
|
+
"type": "string"
|
|
1726
|
+
},
|
|
1727
|
+
"supported": {
|
|
1728
|
+
"type": "boolean",
|
|
1729
|
+
"description": "False when this PMS does not expose channel links."
|
|
1730
|
+
},
|
|
1731
|
+
"error": {
|
|
1732
|
+
"type": "string",
|
|
1733
|
+
"nullable": true
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1718
1737
|
},
|
|
1719
|
-
"
|
|
1720
|
-
"type": "
|
|
1721
|
-
"
|
|
1738
|
+
"listings": {
|
|
1739
|
+
"type": "array",
|
|
1740
|
+
"items": {
|
|
1741
|
+
"type": "object",
|
|
1742
|
+
"properties": {
|
|
1743
|
+
"listingId": {
|
|
1744
|
+
"type": "string",
|
|
1745
|
+
"nullable": true,
|
|
1746
|
+
"description": "The property in this workspace, when it came across."
|
|
1747
|
+
},
|
|
1748
|
+
"name": {
|
|
1749
|
+
"type": "string",
|
|
1750
|
+
"nullable": true
|
|
1751
|
+
},
|
|
1752
|
+
"provider": {
|
|
1753
|
+
"type": "string"
|
|
1754
|
+
},
|
|
1755
|
+
"externalListingId": {
|
|
1756
|
+
"type": "string",
|
|
1757
|
+
"description": "The listing id in the source PMS."
|
|
1758
|
+
},
|
|
1759
|
+
"airbnb": {
|
|
1760
|
+
"type": "object",
|
|
1761
|
+
"nullable": true,
|
|
1762
|
+
"properties": {
|
|
1763
|
+
"listingId": {
|
|
1764
|
+
"type": "string"
|
|
1765
|
+
},
|
|
1766
|
+
"url": {
|
|
1767
|
+
"type": "string"
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
},
|
|
1771
|
+
"booking": {
|
|
1772
|
+
"type": "object",
|
|
1773
|
+
"nullable": true,
|
|
1774
|
+
"properties": {
|
|
1775
|
+
"hotelId": {
|
|
1776
|
+
"type": "string"
|
|
1777
|
+
},
|
|
1778
|
+
"roomId": {
|
|
1779
|
+
"type": "string"
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
},
|
|
1783
|
+
"vrbo": {
|
|
1784
|
+
"type": "object",
|
|
1785
|
+
"nullable": true,
|
|
1786
|
+
"properties": {
|
|
1787
|
+
"listingId": {
|
|
1788
|
+
"type": "string"
|
|
1789
|
+
},
|
|
1790
|
+
"url": {
|
|
1791
|
+
"type": "string"
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
"MigrationCutoverCheck": {
|
|
1801
|
+
"type": "object",
|
|
1802
|
+
"properties": {
|
|
1803
|
+
"workspaceId": {
|
|
1804
|
+
"type": "string"
|
|
1722
1805
|
},
|
|
1723
|
-
"
|
|
1724
|
-
"type": "
|
|
1725
|
-
"
|
|
1806
|
+
"checkedAt": {
|
|
1807
|
+
"type": "string",
|
|
1808
|
+
"format": "date-time"
|
|
1726
1809
|
},
|
|
1727
|
-
"
|
|
1810
|
+
"matched": {
|
|
1728
1811
|
"type": "integer"
|
|
1729
1812
|
},
|
|
1730
|
-
"
|
|
1731
|
-
"type": "
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
"
|
|
1736
|
-
},
|
|
1737
|
-
"createdAt": {
|
|
1738
|
-
"type": "string",
|
|
1739
|
-
"format": "date-time"
|
|
1813
|
+
"missing": {
|
|
1814
|
+
"type": "array",
|
|
1815
|
+
"items": {
|
|
1816
|
+
"$ref": "#/components/schemas/MigrationReservationRef"
|
|
1817
|
+
},
|
|
1818
|
+
"description": "Upcoming in the source, absent from the destination."
|
|
1740
1819
|
},
|
|
1741
|
-
"
|
|
1742
|
-
"type": "
|
|
1743
|
-
"
|
|
1744
|
-
|
|
1820
|
+
"extra": {
|
|
1821
|
+
"type": "array",
|
|
1822
|
+
"items": {
|
|
1823
|
+
"$ref": "#/components/schemas/MigrationReservationRef"
|
|
1824
|
+
},
|
|
1825
|
+
"description": "In the destination, not an upcoming reservation in the source."
|
|
1745
1826
|
},
|
|
1746
|
-
"
|
|
1747
|
-
"type": "
|
|
1748
|
-
"
|
|
1749
|
-
|
|
1827
|
+
"mismatched": {
|
|
1828
|
+
"type": "array",
|
|
1829
|
+
"items": {
|
|
1830
|
+
"type": "object",
|
|
1831
|
+
"properties": {
|
|
1832
|
+
"source": {
|
|
1833
|
+
"$ref": "#/components/schemas/MigrationReservationRef"
|
|
1834
|
+
},
|
|
1835
|
+
"destination": {
|
|
1836
|
+
"$ref": "#/components/schemas/MigrationReservationRef"
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1750
1840
|
}
|
|
1751
1841
|
}
|
|
1752
1842
|
},
|
|
1753
|
-
"
|
|
1843
|
+
"MigrationReservationRef": {
|
|
1754
1844
|
"type": "object",
|
|
1755
|
-
"description": "Full request + response capture for one delivery attempt. `payload` is the exact `WebhookEvent` envelope that was (or would have been) POSTed to the subscription URL.",
|
|
1756
1845
|
"properties": {
|
|
1757
|
-
"
|
|
1758
|
-
"type": "string"
|
|
1759
|
-
},
|
|
1760
|
-
"eventId": {
|
|
1846
|
+
"confirmationCode": {
|
|
1761
1847
|
"type": "string"
|
|
1762
1848
|
},
|
|
1763
|
-
"
|
|
1764
|
-
"
|
|
1765
|
-
|
|
1766
|
-
"payload": {
|
|
1767
|
-
"$ref": "#/components/schemas/WebhookEvent"
|
|
1768
|
-
},
|
|
1769
|
-
"requestHeaders": {
|
|
1770
|
-
"type": "object",
|
|
1771
|
-
"nullable": true
|
|
1849
|
+
"checkIn": {
|
|
1850
|
+
"type": "string",
|
|
1851
|
+
"format": "date"
|
|
1772
1852
|
},
|
|
1773
|
-
"
|
|
1774
|
-
"type": "
|
|
1775
|
-
"
|
|
1853
|
+
"checkOut": {
|
|
1854
|
+
"type": "string",
|
|
1855
|
+
"format": "date"
|
|
1776
1856
|
},
|
|
1777
|
-
"
|
|
1778
|
-
"type": "
|
|
1779
|
-
"nullable": true
|
|
1857
|
+
"reservationId": {
|
|
1858
|
+
"type": "string"
|
|
1780
1859
|
},
|
|
1781
|
-
"
|
|
1860
|
+
"platform": {
|
|
1782
1861
|
"type": "string",
|
|
1783
1862
|
"nullable": true
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
},
|
|
1866
|
+
"ConnectSession": {
|
|
1867
|
+
"type": "object",
|
|
1868
|
+
"description": "A multi-channel Connect picker session. The `url` is the hosted picker page on connect.repull.dev \u2014 redirect the host to it, they pick a channel, and the picker takes them through the per-provider flow before redirecting back to the original `redirectUrl`.",
|
|
1869
|
+
"properties": {
|
|
1870
|
+
"sessionId": {
|
|
1871
|
+
"type": "string",
|
|
1872
|
+
"example": "cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp"
|
|
1784
1873
|
},
|
|
1785
|
-
"
|
|
1786
|
-
"type": "
|
|
1787
|
-
"
|
|
1874
|
+
"url": {
|
|
1875
|
+
"type": "string",
|
|
1876
|
+
"format": "uri",
|
|
1877
|
+
"example": "https://connect.repull.dev/cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp"
|
|
1788
1878
|
},
|
|
1789
|
-
"
|
|
1790
|
-
"type": "
|
|
1879
|
+
"expiresAt": {
|
|
1880
|
+
"type": "string",
|
|
1881
|
+
"format": "date-time"
|
|
1791
1882
|
},
|
|
1792
|
-
"
|
|
1793
|
-
"type": "
|
|
1883
|
+
"state": {
|
|
1884
|
+
"type": "string",
|
|
1885
|
+
"nullable": true,
|
|
1886
|
+
"description": "Echoed back from the request body for SDK consumers that pass an opaque correlation token."
|
|
1794
1887
|
},
|
|
1795
|
-
"
|
|
1888
|
+
"purpose": {
|
|
1796
1889
|
"type": "string",
|
|
1797
|
-
"
|
|
1890
|
+
"description": "Present only on a Repull Migrate session."
|
|
1798
1891
|
},
|
|
1799
|
-
"
|
|
1892
|
+
"workspaceId": {
|
|
1800
1893
|
"type": "string",
|
|
1801
|
-
"
|
|
1894
|
+
"description": "Repull Migrate only: the workspace the property manager's data lands in. Read it with `X-Workspace-Id`, track it with `GET /v1/migrations/{workspaceId}`.",
|
|
1895
|
+
"example": "1204"
|
|
1802
1896
|
}
|
|
1803
|
-
}
|
|
1897
|
+
},
|
|
1898
|
+
"required": [
|
|
1899
|
+
"sessionId",
|
|
1900
|
+
"url",
|
|
1901
|
+
"expiresAt"
|
|
1902
|
+
]
|
|
1804
1903
|
},
|
|
1805
|
-
"
|
|
1904
|
+
"SelectProviderResponse": {
|
|
1806
1905
|
"type": "object",
|
|
1906
|
+
"description": "Returned by `POST /v1/connect/sessions/{sessionId}/select-provider`. The picker UI navigates the user to `nextUrl` to begin the per-provider handoff.",
|
|
1807
1907
|
"properties": {
|
|
1808
|
-
"
|
|
1809
|
-
"type": "
|
|
1810
|
-
"items": {
|
|
1811
|
-
"$ref": "#/components/schemas/WebhookDelivery"
|
|
1812
|
-
}
|
|
1908
|
+
"sessionId": {
|
|
1909
|
+
"type": "string"
|
|
1813
1910
|
},
|
|
1814
|
-
"
|
|
1815
|
-
"
|
|
1911
|
+
"provider": {
|
|
1912
|
+
"type": "string",
|
|
1913
|
+
"example": "airbnb"
|
|
1914
|
+
},
|
|
1915
|
+
"pattern": {
|
|
1916
|
+
"type": "string"
|
|
1917
|
+
},
|
|
1918
|
+
"nextUrl": {
|
|
1919
|
+
"type": "string",
|
|
1920
|
+
"format": "uri",
|
|
1921
|
+
"description": "Where to send the user next \u2014 OAuth consent, credentials form, activation checklist, or claim form."
|
|
1816
1922
|
}
|
|
1817
1923
|
}
|
|
1818
1924
|
},
|
|
1819
|
-
"
|
|
1925
|
+
"ConnectStatus": {
|
|
1820
1926
|
"type": "object",
|
|
1821
|
-
"description": "
|
|
1927
|
+
"description": "Connection status response for a single provider. When `connected` is false, all other fields except `provider` and `host` may be omitted, and `host` is null.",
|
|
1822
1928
|
"properties": {
|
|
1823
|
-
"
|
|
1929
|
+
"connected": {
|
|
1930
|
+
"type": "boolean",
|
|
1931
|
+
"example": true
|
|
1932
|
+
},
|
|
1933
|
+
"provider": {
|
|
1934
|
+
"type": "string",
|
|
1935
|
+
"example": "airbnb"
|
|
1936
|
+
},
|
|
1937
|
+
"id": {
|
|
1938
|
+
"type": "string",
|
|
1939
|
+
"description": "Repull-side connection ID. Stable across token refreshes.",
|
|
1940
|
+
"example": "3"
|
|
1941
|
+
},
|
|
1942
|
+
"status": {
|
|
1943
|
+
"type": "string",
|
|
1944
|
+
"example": "active"
|
|
1945
|
+
},
|
|
1946
|
+
"externalAccountId": {
|
|
1947
|
+
"type": "string",
|
|
1948
|
+
"nullable": true,
|
|
1949
|
+
"description": "Provider-side account ID (e.g. the Airbnb host ID).",
|
|
1950
|
+
"example": "23998907"
|
|
1951
|
+
},
|
|
1952
|
+
"createdAt": {
|
|
1953
|
+
"type": "string",
|
|
1954
|
+
"format": "date-time"
|
|
1955
|
+
},
|
|
1956
|
+
"host": {
|
|
1957
|
+
"nullable": true,
|
|
1958
|
+
"allOf": [
|
|
1959
|
+
{
|
|
1960
|
+
"$ref": "#/components/schemas/ConnectHost"
|
|
1961
|
+
}
|
|
1962
|
+
],
|
|
1963
|
+
"description": "Host metadata, populated for Airbnb when the host row exists. Null for other providers (per-provider enrichment is incremental)."
|
|
1964
|
+
},
|
|
1965
|
+
"accounts": {
|
|
1966
|
+
"type": "array",
|
|
1967
|
+
"description": "Airbnb only: every Airbnb account this workspace has connected, including ones since disconnected. Pass `externalAccountId` as `accountId` to `DELETE /v1/connect/airbnb` to disconnect one account.",
|
|
1968
|
+
"items": {
|
|
1969
|
+
"type": "object",
|
|
1970
|
+
"properties": {
|
|
1971
|
+
"externalAccountId": {
|
|
1972
|
+
"type": "string",
|
|
1973
|
+
"description": "Airbnb host ID, as a string (it can exceed 2^53).",
|
|
1974
|
+
"example": "79730216"
|
|
1975
|
+
},
|
|
1976
|
+
"name": {
|
|
1977
|
+
"type": "string",
|
|
1978
|
+
"nullable": true,
|
|
1979
|
+
"example": "Raiden"
|
|
1980
|
+
},
|
|
1981
|
+
"pictureUrl": {
|
|
1982
|
+
"type": "string",
|
|
1983
|
+
"nullable": true
|
|
1984
|
+
},
|
|
1985
|
+
"status": {
|
|
1986
|
+
"type": "string",
|
|
1987
|
+
"nullable": true,
|
|
1988
|
+
"example": "active"
|
|
1989
|
+
},
|
|
1990
|
+
"connected": {
|
|
1991
|
+
"type": "boolean",
|
|
1992
|
+
"description": "True while the account is active and its authorization is usable.",
|
|
1993
|
+
"example": true
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
},
|
|
2000
|
+
"WebhookSubscription": {
|
|
2001
|
+
"type": "object",
|
|
2002
|
+
"description": "A registered webhook endpoint. The `secret` field is only present in the response of `POST /v1/webhooks` and `POST /v1/webhooks/{id}/rotate-secret` (Stripe pattern \u2014 capture it then; it is masked everywhere else).",
|
|
2003
|
+
"properties": {
|
|
2004
|
+
"id": {
|
|
2005
|
+
"type": "string",
|
|
2006
|
+
"format": "uuid"
|
|
2007
|
+
},
|
|
2008
|
+
"url": {
|
|
2009
|
+
"type": "string",
|
|
2010
|
+
"format": "uri"
|
|
2011
|
+
},
|
|
2012
|
+
"description": {
|
|
2013
|
+
"type": "string",
|
|
2014
|
+
"nullable": true
|
|
2015
|
+
},
|
|
2016
|
+
"events": {
|
|
2017
|
+
"type": "array",
|
|
2018
|
+
"items": {
|
|
2019
|
+
"$ref": "#/components/schemas/WebhookEventType"
|
|
2020
|
+
},
|
|
2021
|
+
"example": [
|
|
2022
|
+
"reservation.created",
|
|
2023
|
+
"reservation.updated"
|
|
2024
|
+
]
|
|
2025
|
+
},
|
|
2026
|
+
"apiVersion": {
|
|
2027
|
+
"type": "string",
|
|
2028
|
+
"example": "2026-04"
|
|
2029
|
+
},
|
|
2030
|
+
"status": {
|
|
2031
|
+
"type": "string"
|
|
2032
|
+
},
|
|
2033
|
+
"consecutiveFailures": {
|
|
2034
|
+
"type": "integer"
|
|
2035
|
+
},
|
|
2036
|
+
"lastDeliveredAt": {
|
|
2037
|
+
"type": "string",
|
|
2038
|
+
"format": "date-time",
|
|
2039
|
+
"nullable": true
|
|
2040
|
+
},
|
|
2041
|
+
"lastSuccessAt": {
|
|
2042
|
+
"type": "string",
|
|
2043
|
+
"format": "date-time",
|
|
2044
|
+
"nullable": true
|
|
2045
|
+
},
|
|
2046
|
+
"lastFailureAt": {
|
|
2047
|
+
"type": "string",
|
|
2048
|
+
"format": "date-time",
|
|
2049
|
+
"nullable": true
|
|
2050
|
+
},
|
|
2051
|
+
"lastDeliveryStatus": {
|
|
2052
|
+
"type": "integer",
|
|
2053
|
+
"nullable": true
|
|
2054
|
+
},
|
|
2055
|
+
"disabledAt": {
|
|
2056
|
+
"type": "string",
|
|
2057
|
+
"format": "date-time",
|
|
2058
|
+
"nullable": true
|
|
2059
|
+
},
|
|
2060
|
+
"createdAt": {
|
|
2061
|
+
"type": "string",
|
|
2062
|
+
"format": "date-time"
|
|
2063
|
+
},
|
|
2064
|
+
"updatedAt": {
|
|
2065
|
+
"type": "string",
|
|
2066
|
+
"format": "date-time"
|
|
2067
|
+
},
|
|
2068
|
+
"secretMasked": {
|
|
2069
|
+
"type": "string",
|
|
2070
|
+
"nullable": true,
|
|
2071
|
+
"example": "whsec_a1b\u2026f9c2"
|
|
2072
|
+
},
|
|
2073
|
+
"secret": {
|
|
2074
|
+
"type": "string",
|
|
2075
|
+
"nullable": true,
|
|
2076
|
+
"description": "Plaintext signing secret. Only returned by create + rotate. Capture and store securely."
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
},
|
|
2080
|
+
"WebhookDelivery": {
|
|
2081
|
+
"type": "object",
|
|
2082
|
+
"description": "A single delivery attempt for a webhook event. The actual `WebhookEvent` envelope POSTed to the subscription URL is captured on `WebhookDeliveryDetail.payload` (this list view omits the body for size).",
|
|
2083
|
+
"properties": {
|
|
2084
|
+
"id": {
|
|
2085
|
+
"type": "string",
|
|
2086
|
+
"format": "uuid"
|
|
2087
|
+
},
|
|
2088
|
+
"eventId": {
|
|
2089
|
+
"type": "string",
|
|
2090
|
+
"format": "uuid",
|
|
2091
|
+
"description": "Stable across retries of the same logical event."
|
|
2092
|
+
},
|
|
2093
|
+
"eventType": {
|
|
2094
|
+
"$ref": "#/components/schemas/WebhookEventType"
|
|
2095
|
+
},
|
|
2096
|
+
"statusCode": {
|
|
2097
|
+
"type": "integer",
|
|
2098
|
+
"nullable": true
|
|
2099
|
+
},
|
|
2100
|
+
"responseTimeMs": {
|
|
2101
|
+
"type": "integer",
|
|
2102
|
+
"nullable": true
|
|
2103
|
+
},
|
|
2104
|
+
"attempt": {
|
|
2105
|
+
"type": "integer"
|
|
2106
|
+
},
|
|
2107
|
+
"success": {
|
|
2108
|
+
"type": "boolean"
|
|
2109
|
+
},
|
|
2110
|
+
"errorMessage": {
|
|
2111
|
+
"type": "string",
|
|
2112
|
+
"nullable": true
|
|
2113
|
+
},
|
|
2114
|
+
"createdAt": {
|
|
2115
|
+
"type": "string",
|
|
2116
|
+
"format": "date-time"
|
|
2117
|
+
},
|
|
2118
|
+
"succeededAt": {
|
|
2119
|
+
"type": "string",
|
|
2120
|
+
"format": "date-time",
|
|
2121
|
+
"nullable": true
|
|
2122
|
+
},
|
|
2123
|
+
"failedAt": {
|
|
2124
|
+
"type": "string",
|
|
2125
|
+
"format": "date-time",
|
|
2126
|
+
"nullable": true
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
"WebhookDeliveryDetail": {
|
|
2131
|
+
"type": "object",
|
|
2132
|
+
"description": "Full request + response capture for one delivery attempt. `payload` is the exact `WebhookEvent` envelope that was (or would have been) POSTed to the subscription URL.",
|
|
2133
|
+
"properties": {
|
|
2134
|
+
"id": {
|
|
2135
|
+
"type": "string"
|
|
2136
|
+
},
|
|
2137
|
+
"eventId": {
|
|
2138
|
+
"type": "string"
|
|
2139
|
+
},
|
|
2140
|
+
"eventType": {
|
|
2141
|
+
"$ref": "#/components/schemas/WebhookEventType"
|
|
2142
|
+
},
|
|
2143
|
+
"payload": {
|
|
2144
|
+
"$ref": "#/components/schemas/WebhookEvent"
|
|
2145
|
+
},
|
|
2146
|
+
"requestHeaders": {
|
|
2147
|
+
"type": "object",
|
|
2148
|
+
"nullable": true
|
|
2149
|
+
},
|
|
2150
|
+
"statusCode": {
|
|
2151
|
+
"type": "integer",
|
|
2152
|
+
"nullable": true
|
|
2153
|
+
},
|
|
2154
|
+
"responseHeaders": {
|
|
2155
|
+
"type": "object",
|
|
2156
|
+
"nullable": true
|
|
2157
|
+
},
|
|
2158
|
+
"responseBody": {
|
|
2159
|
+
"type": "string",
|
|
2160
|
+
"nullable": true
|
|
2161
|
+
},
|
|
2162
|
+
"responseTimeMs": {
|
|
2163
|
+
"type": "integer",
|
|
2164
|
+
"nullable": true
|
|
2165
|
+
},
|
|
2166
|
+
"attempt": {
|
|
2167
|
+
"type": "integer"
|
|
2168
|
+
},
|
|
2169
|
+
"success": {
|
|
2170
|
+
"type": "boolean"
|
|
2171
|
+
},
|
|
2172
|
+
"errorMessage": {
|
|
2173
|
+
"type": "string",
|
|
2174
|
+
"nullable": true
|
|
2175
|
+
},
|
|
2176
|
+
"createdAt": {
|
|
2177
|
+
"type": "string",
|
|
2178
|
+
"format": "date-time"
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
},
|
|
2182
|
+
"WebhookDeliveryListResponse": {
|
|
2183
|
+
"type": "object",
|
|
2184
|
+
"properties": {
|
|
2185
|
+
"data": {
|
|
2186
|
+
"type": "array",
|
|
2187
|
+
"items": {
|
|
2188
|
+
"$ref": "#/components/schemas/WebhookDelivery"
|
|
2189
|
+
}
|
|
2190
|
+
},
|
|
2191
|
+
"pagination": {
|
|
2192
|
+
"$ref": "#/components/schemas/Pagination"
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2196
|
+
"WebhookEventCatalog": {
|
|
2197
|
+
"type": "object",
|
|
2198
|
+
"description": "Canonical catalog of every event the API can deliver, grouped by domain. Each entry includes a realistic `samplePayload` matching the discriminated `WebhookEvent` union \u2014 so SDKs can render docs and dashboards from this single source of truth.",
|
|
2199
|
+
"properties": {
|
|
2200
|
+
"domains": {
|
|
1824
2201
|
"type": "array",
|
|
1825
2202
|
"items": {
|
|
1826
2203
|
"type": "object",
|
|
@@ -1898,6 +2275,8 @@
|
|
|
1898
2275
|
"ai.operation.failed",
|
|
1899
2276
|
"payment.completed",
|
|
1900
2277
|
"payment.refunded",
|
|
2278
|
+
"migration.completed",
|
|
2279
|
+
"migration.failed",
|
|
1901
2280
|
"repull.ping",
|
|
1902
2281
|
"usage.quota.warning"
|
|
1903
2282
|
]
|
|
@@ -2232,7 +2611,7 @@
|
|
|
2232
2611
|
},
|
|
2233
2612
|
"ReservationRequestUpdatedPayload": {
|
|
2234
2613
|
"type": "object",
|
|
2235
|
-
"description": "Payload for `reservation.request.updated`. A booking request stopped waiting on the host. `requestStatus` says how; `data.object` is the reservation after the change (`status` `confirmed` once accepted, `cancelled` otherwise). An accepted request also fires `reservation.created`. Fires when the channel reports the outcome, whoever acted \u2014 the API,
|
|
2614
|
+
"description": "Payload for `reservation.request.updated`. A booking request stopped waiting on the host. `requestStatus` says how; `data.object` is the reservation after the change (`status` `confirmed` once accepted, `cancelled` otherwise). An accepted request also fires `reservation.created`. Fires when the channel reports the outcome, whoever acted \u2014 the API, a connected app or the channel's own app.",
|
|
2236
2615
|
"required": [
|
|
2237
2616
|
"object",
|
|
2238
2617
|
"requestStatus",
|
|
@@ -2412,7 +2791,7 @@
|
|
|
2412
2791
|
},
|
|
2413
2792
|
"InquiryUpdatedPayload": {
|
|
2414
2793
|
"type": "object",
|
|
2415
|
-
"description": "Payload for `inquiry.updated`. The inquiry's status, dates, guest count or the reservation it became changed. `previousAttributes` holds only what moved, with prior values. Fires whether the host acted through the API,
|
|
2794
|
+
"description": "Payload for `inquiry.updated`. The inquiry's status, dates, guest count or the reservation it became changed. `previousAttributes` holds only what moved, with prior values. Fires whether the host acted through the API, a connected app or the Airbnb app. An inquiry whose dates simply pass is `expired` in `GET /v1/inquiries` but fires no event unless the channel reports it.",
|
|
2416
2795
|
"required": [
|
|
2417
2796
|
"object",
|
|
2418
2797
|
"previousAttributes"
|
|
@@ -4082,22 +4461,160 @@
|
|
|
4082
4461
|
}
|
|
4083
4462
|
}
|
|
4084
4463
|
},
|
|
4085
|
-
"
|
|
4086
|
-
"
|
|
4087
|
-
|
|
4088
|
-
|
|
4464
|
+
"MigrationImportPayload": {
|
|
4465
|
+
"type": "object",
|
|
4466
|
+
"description": "One import run into a migration workspace.",
|
|
4467
|
+
"properties": {
|
|
4468
|
+
"workspaceId": {
|
|
4469
|
+
"type": "integer"
|
|
4089
4470
|
},
|
|
4090
|
-
{
|
|
4091
|
-
"
|
|
4471
|
+
"provider": {
|
|
4472
|
+
"type": "string",
|
|
4473
|
+
"example": "guesty"
|
|
4092
4474
|
},
|
|
4093
|
-
{
|
|
4094
|
-
"
|
|
4475
|
+
"connectionId": {
|
|
4476
|
+
"type": "integer"
|
|
4095
4477
|
},
|
|
4096
|
-
{
|
|
4097
|
-
"
|
|
4478
|
+
"status": {
|
|
4479
|
+
"type": "string"
|
|
4098
4480
|
},
|
|
4099
|
-
{
|
|
4100
|
-
"
|
|
4481
|
+
"startedAt": {
|
|
4482
|
+
"type": "string",
|
|
4483
|
+
"format": "date-time"
|
|
4484
|
+
},
|
|
4485
|
+
"finishedAt": {
|
|
4486
|
+
"type": "string",
|
|
4487
|
+
"format": "date-time"
|
|
4488
|
+
},
|
|
4489
|
+
"entities": {
|
|
4490
|
+
"type": "array",
|
|
4491
|
+
"items": {
|
|
4492
|
+
"type": "string"
|
|
4493
|
+
}
|
|
4494
|
+
},
|
|
4495
|
+
"results": {
|
|
4496
|
+
"type": "array",
|
|
4497
|
+
"items": {
|
|
4498
|
+
"type": "object",
|
|
4499
|
+
"properties": {
|
|
4500
|
+
"entityType": {
|
|
4501
|
+
"type": "string"
|
|
4502
|
+
},
|
|
4503
|
+
"processed": {
|
|
4504
|
+
"type": "integer"
|
|
4505
|
+
},
|
|
4506
|
+
"errors": {
|
|
4507
|
+
"type": "integer"
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
},
|
|
4512
|
+
"error": {
|
|
4513
|
+
"type": "string",
|
|
4514
|
+
"description": "Present on `migration.failed`."
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
},
|
|
4518
|
+
"MigrationCompletedEvent": {
|
|
4519
|
+
"type": "object",
|
|
4520
|
+
"required": [
|
|
4521
|
+
"event",
|
|
4522
|
+
"eventId",
|
|
4523
|
+
"apiVersion",
|
|
4524
|
+
"timestamp",
|
|
4525
|
+
"workspaceId",
|
|
4526
|
+
"data"
|
|
4527
|
+
],
|
|
4528
|
+
"properties": {
|
|
4529
|
+
"event": {
|
|
4530
|
+
"type": "string",
|
|
4531
|
+
"description": "The event name. This field is `event`, not `type`."
|
|
4532
|
+
},
|
|
4533
|
+
"eventId": {
|
|
4534
|
+
"type": "string",
|
|
4535
|
+
"format": "uuid",
|
|
4536
|
+
"description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
|
|
4537
|
+
},
|
|
4538
|
+
"apiVersion": {
|
|
4539
|
+
"type": "string",
|
|
4540
|
+
"example": "2026-04"
|
|
4541
|
+
},
|
|
4542
|
+
"timestamp": {
|
|
4543
|
+
"type": "string",
|
|
4544
|
+
"format": "date-time",
|
|
4545
|
+
"description": "When this delivery was built."
|
|
4546
|
+
},
|
|
4547
|
+
"account": {
|
|
4548
|
+
"$ref": "#/components/schemas/WebhookEventAccount"
|
|
4549
|
+
},
|
|
4550
|
+
"workspaceId": {
|
|
4551
|
+
"type": "integer",
|
|
4552
|
+
"description": "The migration's workspace \u2014 pass it as `X-Workspace-Id`, or to `GET /v1/migrations/{workspaceId}`."
|
|
4553
|
+
},
|
|
4554
|
+
"data": {
|
|
4555
|
+
"$ref": "#/components/schemas/MigrationImportPayload"
|
|
4556
|
+
}
|
|
4557
|
+
},
|
|
4558
|
+
"description": "An import into a migration workspace finished."
|
|
4559
|
+
},
|
|
4560
|
+
"MigrationFailedEvent": {
|
|
4561
|
+
"type": "object",
|
|
4562
|
+
"required": [
|
|
4563
|
+
"event",
|
|
4564
|
+
"eventId",
|
|
4565
|
+
"apiVersion",
|
|
4566
|
+
"timestamp",
|
|
4567
|
+
"workspaceId",
|
|
4568
|
+
"data"
|
|
4569
|
+
],
|
|
4570
|
+
"properties": {
|
|
4571
|
+
"event": {
|
|
4572
|
+
"type": "string",
|
|
4573
|
+
"description": "The event name. This field is `event`, not `type`."
|
|
4574
|
+
},
|
|
4575
|
+
"eventId": {
|
|
4576
|
+
"type": "string",
|
|
4577
|
+
"format": "uuid",
|
|
4578
|
+
"description": "Stable across every delivery and replay of this logical event \u2014 dedupe on it."
|
|
4579
|
+
},
|
|
4580
|
+
"apiVersion": {
|
|
4581
|
+
"type": "string",
|
|
4582
|
+
"example": "2026-04"
|
|
4583
|
+
},
|
|
4584
|
+
"timestamp": {
|
|
4585
|
+
"type": "string",
|
|
4586
|
+
"format": "date-time",
|
|
4587
|
+
"description": "When this delivery was built."
|
|
4588
|
+
},
|
|
4589
|
+
"account": {
|
|
4590
|
+
"$ref": "#/components/schemas/WebhookEventAccount"
|
|
4591
|
+
},
|
|
4592
|
+
"workspaceId": {
|
|
4593
|
+
"type": "integer",
|
|
4594
|
+
"description": "The migration's workspace \u2014 pass it as `X-Workspace-Id`, or to `GET /v1/migrations/{workspaceId}`."
|
|
4595
|
+
},
|
|
4596
|
+
"data": {
|
|
4597
|
+
"$ref": "#/components/schemas/MigrationImportPayload"
|
|
4598
|
+
}
|
|
4599
|
+
},
|
|
4600
|
+
"description": "An import into a migration workspace stopped with an error."
|
|
4601
|
+
},
|
|
4602
|
+
"WebhookEvent": {
|
|
4603
|
+
"oneOf": [
|
|
4604
|
+
{
|
|
4605
|
+
"$ref": "#/components/schemas/ReservationCreatedEvent"
|
|
4606
|
+
},
|
|
4607
|
+
{
|
|
4608
|
+
"$ref": "#/components/schemas/ReservationUpdatedEvent"
|
|
4609
|
+
},
|
|
4610
|
+
{
|
|
4611
|
+
"$ref": "#/components/schemas/ReservationCancelledEvent"
|
|
4612
|
+
},
|
|
4613
|
+
{
|
|
4614
|
+
"$ref": "#/components/schemas/ReservationMessageReceivedEvent"
|
|
4615
|
+
},
|
|
4616
|
+
{
|
|
4617
|
+
"$ref": "#/components/schemas/ReservationAlterationCreatedEvent"
|
|
4101
4618
|
},
|
|
4102
4619
|
{
|
|
4103
4620
|
"$ref": "#/components/schemas/ReservationAlterationRespondedEvent"
|
|
@@ -4161,6 +4678,12 @@
|
|
|
4161
4678
|
},
|
|
4162
4679
|
{
|
|
4163
4680
|
"$ref": "#/components/schemas/UsageQuotaWarningEvent"
|
|
4681
|
+
},
|
|
4682
|
+
{
|
|
4683
|
+
"$ref": "#/components/schemas/MigrationCompletedEvent"
|
|
4684
|
+
},
|
|
4685
|
+
{
|
|
4686
|
+
"$ref": "#/components/schemas/MigrationFailedEvent"
|
|
4164
4687
|
}
|
|
4165
4688
|
],
|
|
4166
4689
|
"discriminator": {
|
|
@@ -4191,7 +4714,9 @@
|
|
|
4191
4714
|
"payment.completed": "#/components/schemas/PaymentCompletedEvent",
|
|
4192
4715
|
"payment.refunded": "#/components/schemas/PaymentRefundedEvent",
|
|
4193
4716
|
"repull.ping": "#/components/schemas/RepullPingEvent",
|
|
4194
|
-
"usage.quota.warning": "#/components/schemas/UsageQuotaWarningEvent"
|
|
4717
|
+
"usage.quota.warning": "#/components/schemas/UsageQuotaWarningEvent",
|
|
4718
|
+
"migration.completed": "#/components/schemas/MigrationCompletedEvent",
|
|
4719
|
+
"migration.failed": "#/components/schemas/MigrationFailedEvent"
|
|
4195
4720
|
}
|
|
4196
4721
|
},
|
|
4197
4722
|
"description": "The full event envelope POSTed to your webhook URL. Discriminated on `type` \u2014 narrow `event.data` by switching on `event.type`. Use the matching `*Event` variant directly if your SDK lacks discriminator support. Events about an inactive listing (reservations, messages, alterations, reviews, payments, calendar and listing events) are not delivered. The data keeps syncing while the listing is inactive, but its events are never sent \u2014 including after you reactivate it; webhooks resume for events that happen from reactivation on. Account-level events are always delivered."
|
|
@@ -5291,6 +5816,12 @@
|
|
|
5291
5816
|
"description": "Suggestion for typos and near-matches. Present when the server can guess the intent.",
|
|
5292
5817
|
"example": "check_in_after"
|
|
5293
5818
|
},
|
|
5819
|
+
"previous_code": {
|
|
5820
|
+
"type": "string",
|
|
5821
|
+
"deprecated": true,
|
|
5822
|
+
"description": "The `code` THIS response used to carry, for callers whose branch still matches the old string. A migration aid with a deprecation window \u2014 **`code` is canonical, always match on that.**\n\nPresent only where an endpoint's classification actually changed, never as a permanent synonym, and it disappears from a response as soon as the canonical code and the old one agree.\n\nThe live case: the reviews, messaging, check-in-guide, alteration-answer and Airbnb-pull endpoints used to report EVERY Airbnb failure as `500 airbnb_error`, including refusals Airbnb will repeat forever. They now classify the same way every other Airbnb write does \u2014 an Airbnb 4xx is `422 airbnb_rejected` (fix the request), 5xx and timeouts stay `502 airbnb_error` (retry with backoff), and a dead grant is `403 connection_reauth_required`. Those responses carry `previous_code: \"airbnb_error\"`. **Removed in v2** \u2014 migrate your branches to `code` before then.",
|
|
5823
|
+
"example": "airbnb_error"
|
|
5824
|
+
},
|
|
5294
5825
|
"listing_ids": {
|
|
5295
5826
|
"type": "array",
|
|
5296
5827
|
"items": {
|
|
@@ -6025,7 +6556,7 @@
|
|
|
6025
6556
|
"required": [
|
|
6026
6557
|
"name"
|
|
6027
6558
|
],
|
|
6028
|
-
"description": "Inputs for `POST /v1/listings
|
|
6559
|
+
"description": "Inputs for `POST /v1/listings`.\n\n**Address requirements \u2014 read this before you build the payload.** Publishing to Airbnb runs a create preflight that refuses the listing outright if the address is incomplete, and the refusal only surfaces later, at publish time. Airbnb requires `street` and `city` for every country. For a **US** property it additionally requires `state` and `postalCode`. Crucially, **omitting `countryCode` makes the listing behave as US**, so a listing created without a country needs `state` and `postalCode` too. Send `countryCode` explicitly for a non-US property. `lat`/`lng` alone are not enough \u2014 Airbnb rejects coordinates that are not backed by a full postal address. Use `GET /v1/listings/{id}/publish-status` to see which parts are still missing before you attempt a publish.",
|
|
6029
6560
|
"properties": {
|
|
6030
6561
|
"name": {
|
|
6031
6562
|
"type": "string",
|
|
@@ -6036,29 +6567,54 @@
|
|
|
6036
6567
|
"type": "string",
|
|
6037
6568
|
"example": "apartment"
|
|
6038
6569
|
},
|
|
6570
|
+
"roomTypeCategory": {
|
|
6571
|
+
"type": "string",
|
|
6572
|
+
"example": "entire_home",
|
|
6573
|
+
"description": "What the guest actually gets. Airbnb refuses to activate a listing that has not stated one, answering \"Please specify a valid room type\" \u2014 which reads like a beds problem and is not. It is never defaulted: most listings are an entire home, but hundreds are a private or hotel room, and publishing one of those as an entire home is a false claim about someone's property. Settable later with `PUT /v1/listings/{id}/content` under `details`."
|
|
6574
|
+
},
|
|
6575
|
+
"propertyTypeCategory": {
|
|
6576
|
+
"type": "string",
|
|
6577
|
+
"description": "Airbnb's finer property-type category, when you know it. Optional."
|
|
6578
|
+
},
|
|
6039
6579
|
"street": {
|
|
6040
6580
|
"type": "string",
|
|
6041
|
-
"example": "123 Main St"
|
|
6581
|
+
"example": "123 Main St",
|
|
6582
|
+
"description": "Street address including the number. Required by Airbnb for every country \u2014 a publish is refused without it."
|
|
6042
6583
|
},
|
|
6043
6584
|
"city": {
|
|
6044
6585
|
"type": "string",
|
|
6045
|
-
"example": "Miami Beach"
|
|
6586
|
+
"example": "Miami Beach",
|
|
6587
|
+
"description": "City / town. Required by Airbnb for every country \u2014 a publish is refused without it."
|
|
6046
6588
|
},
|
|
6047
6589
|
"state": {
|
|
6048
6590
|
"type": "string",
|
|
6049
|
-
"example": "FL"
|
|
6591
|
+
"example": "FL",
|
|
6592
|
+
"description": "State, province or region. **Required for a US property**, and a listing with no `countryCode` counts as US. Optional elsewhere, but stored and used wherever the channel carries it."
|
|
6593
|
+
},
|
|
6594
|
+
"postalCode": {
|
|
6595
|
+
"type": "string",
|
|
6596
|
+
"example": "33139",
|
|
6597
|
+
"description": "Postal code \u2014 ZIP in the US, postcode in the UK, and so on. **Required for a US property**, and a listing with no `countryCode` counts as US. Send the complete code: Booking.com rejects a partial postcode such as `SW6` where the full value is `SW6 1EP`. Alias: `zipcode`."
|
|
6598
|
+
},
|
|
6599
|
+
"zipcode": {
|
|
6600
|
+
"type": "string",
|
|
6601
|
+
"example": "33139",
|
|
6602
|
+
"description": "Alias for `postalCode`, accepted because it is the field name on the Airbnb mirror. `postalCode` wins if you send both. Prefer `postalCode` \u2014 the field holds non-US postcodes too."
|
|
6050
6603
|
},
|
|
6051
6604
|
"countryCode": {
|
|
6052
6605
|
"type": "string",
|
|
6053
|
-
"example": "US"
|
|
6606
|
+
"example": "US",
|
|
6607
|
+
"description": "ISO-3166 alpha-2 country code. **Send this for any non-US property.** Omitting it does not mean \"unknown\" \u2014 the publish path treats a listing with no country as US, which then requires `state` and `postalCode` and will refuse the listing when they are absent."
|
|
6054
6608
|
},
|
|
6055
6609
|
"lat": {
|
|
6056
6610
|
"type": "number",
|
|
6057
|
-
"example": 25.7617
|
|
6611
|
+
"example": 25.7617,
|
|
6612
|
+
"description": "Latitude. Useful for map search, but never a substitute for the postal address \u2014 Airbnb rejects coordinates it cannot reconcile with a full address."
|
|
6058
6613
|
},
|
|
6059
6614
|
"lng": {
|
|
6060
6615
|
"type": "number",
|
|
6061
|
-
"example": -80.1918
|
|
6616
|
+
"example": -80.1918,
|
|
6617
|
+
"description": "Longitude. See `lat`."
|
|
6062
6618
|
},
|
|
6063
6619
|
"bedrooms": {
|
|
6064
6620
|
"type": "integer",
|
|
@@ -6083,7 +6639,18 @@
|
|
|
6083
6639
|
"type": "string"
|
|
6084
6640
|
},
|
|
6085
6641
|
"defaultDailyPrice": {
|
|
6086
|
-
"type": "number"
|
|
6642
|
+
"type": "number",
|
|
6643
|
+
"description": "Nightly rate for every night that is not a weekend night. Stating it is what gives the new listing a calendar: 365 nights are written from it, and that calendar is what a publish sends to the channel. Without a price the listing has no availability to publish, which Booking.com refuses with \"No availability pushed\"."
|
|
6644
|
+
},
|
|
6645
|
+
"weekendPrice": {
|
|
6646
|
+
"type": "number",
|
|
6647
|
+
"nullable": true,
|
|
6648
|
+
"description": "Nightly rate for Saturday and Sunday nights (UTC). Omit it and those nights take `defaultDailyPrice`. It is the same rate the direct-booking quoter charges for a weekend night, so the calendar and a quote cannot disagree."
|
|
6649
|
+
},
|
|
6650
|
+
"pricePerExtraGuest": {
|
|
6651
|
+
"type": "number",
|
|
6652
|
+
"nullable": true,
|
|
6653
|
+
"description": "Charged per guest above the number included in the nightly rate."
|
|
6087
6654
|
},
|
|
6088
6655
|
"cleaningFee": {
|
|
6089
6656
|
"type": "number"
|
|
@@ -6119,6 +6686,10 @@
|
|
|
6119
6686
|
"id": {
|
|
6120
6687
|
"type": "string",
|
|
6121
6688
|
"description": "New listing ID \u2014 use for follow-up generate-content / publish calls"
|
|
6689
|
+
},
|
|
6690
|
+
"calendarDaysSeeded": {
|
|
6691
|
+
"type": "integer",
|
|
6692
|
+
"description": "Nights of calendar written from the price you stated. `0` means the listing has no calendar and a publish will send no availability \u2014 state `defaultDailyPrice` on the create, or set it later with `PUT /v1/listings/{id}/content` under `pricing`."
|
|
6122
6693
|
}
|
|
6123
6694
|
}
|
|
6124
6695
|
},
|
|
@@ -6274,28 +6845,51 @@
|
|
|
6274
6845
|
},
|
|
6275
6846
|
"address": {
|
|
6276
6847
|
"type": "object",
|
|
6277
|
-
"description": "Partial address. Only provided sub-fields are written.",
|
|
6848
|
+
"description": "Partial address. Only provided sub-fields are written; the ones you omit keep their current value, and an explicit `null` clears one.\n\nThis is also the repair path for a listing that cannot be published: Airbnb requires `street` and `city` for every country and additionally `state` and `postalCode` for a **US** property \u2014 and a listing with no `countryCode` behaves as US. Send just the missing part, e.g. `{ \"address\": { \"state\": \"FL\" } }`. `GET /v1/listings/{id}/publish-status` names what is missing.",
|
|
6278
6849
|
"properties": {
|
|
6279
6850
|
"street": {
|
|
6280
6851
|
"type": "string",
|
|
6281
|
-
"nullable": true
|
|
6852
|
+
"nullable": true,
|
|
6853
|
+
"description": "Street address including the number. Required by Airbnb for every country."
|
|
6282
6854
|
},
|
|
6283
6855
|
"city": {
|
|
6284
6856
|
"type": "string",
|
|
6285
|
-
"nullable": true
|
|
6857
|
+
"nullable": true,
|
|
6858
|
+
"description": "City / town. Required by Airbnb for every country."
|
|
6859
|
+
},
|
|
6860
|
+
"state": {
|
|
6861
|
+
"type": "string",
|
|
6862
|
+
"nullable": true,
|
|
6863
|
+
"example": "FL",
|
|
6864
|
+
"description": "State, province or region. **Required for a US property**, and a listing with no `countryCode` counts as US."
|
|
6865
|
+
},
|
|
6866
|
+
"postalCode": {
|
|
6867
|
+
"type": "string",
|
|
6868
|
+
"nullable": true,
|
|
6869
|
+
"example": "33139",
|
|
6870
|
+
"description": "Postal code \u2014 ZIP in the US, postcode in the UK, and so on. **Required for a US property**, and a listing with no `countryCode` counts as US. Send the complete code; a partial postcode is rejected downstream. Alias: `zipcode`."
|
|
6871
|
+
},
|
|
6872
|
+
"zipcode": {
|
|
6873
|
+
"type": "string",
|
|
6874
|
+
"nullable": true,
|
|
6875
|
+
"example": "33139",
|
|
6876
|
+
"description": "Alias for `postalCode`, accepted because it is the field name on the Airbnb mirror. `postalCode` wins if you send both."
|
|
6286
6877
|
},
|
|
6287
6878
|
"countryCode": {
|
|
6288
6879
|
"type": "string",
|
|
6289
6880
|
"nullable": true,
|
|
6290
|
-
"
|
|
6881
|
+
"example": "US",
|
|
6882
|
+
"description": "ISO-3166 alpha-2 country code. **Send this for any non-US property.** Leaving it unset does not mean \"unknown\" \u2014 the publish path treats a listing with no country as US and then demands `state` and `postalCode`."
|
|
6291
6883
|
},
|
|
6292
6884
|
"lat": {
|
|
6293
6885
|
"type": "number",
|
|
6294
|
-
"nullable": true
|
|
6886
|
+
"nullable": true,
|
|
6887
|
+
"description": "Latitude. Never a substitute for the postal address \u2014 Airbnb rejects coordinates it cannot reconcile with a full address."
|
|
6295
6888
|
},
|
|
6296
6889
|
"lng": {
|
|
6297
6890
|
"type": "number",
|
|
6298
|
-
"nullable": true
|
|
6891
|
+
"nullable": true,
|
|
6892
|
+
"description": "Longitude. See `lat`."
|
|
6299
6893
|
}
|
|
6300
6894
|
}
|
|
6301
6895
|
},
|
|
@@ -6349,6 +6943,54 @@
|
|
|
6349
6943
|
}
|
|
6350
6944
|
}
|
|
6351
6945
|
},
|
|
6946
|
+
"pricing": {
|
|
6947
|
+
"type": "object",
|
|
6948
|
+
"description": "The listing's standing rates. Partial like every other section: only the fields you send are written, and `null` clears one.\n\nChanging `defaultDailyPrice` or `weekendPrice` also moves the nights on the calendar that still carry the old rate and were written by us \u2014 a night you or a channel priced yourself is never touched, and neither is a blocked or reserved one. So a price change reaches the calendar without overwriting anyone's work.\n\nThis is still a local write. Publish to send the new rates to a channel.",
|
|
6949
|
+
"properties": {
|
|
6950
|
+
"defaultDailyPrice": {
|
|
6951
|
+
"type": "number",
|
|
6952
|
+
"nullable": true,
|
|
6953
|
+
"description": "Nightly rate for every night that is not a weekend night."
|
|
6954
|
+
},
|
|
6955
|
+
"weekendPrice": {
|
|
6956
|
+
"type": "number",
|
|
6957
|
+
"nullable": true,
|
|
6958
|
+
"description": "Nightly rate for Saturday and Sunday nights (UTC)."
|
|
6959
|
+
},
|
|
6960
|
+
"cleaningFee": {
|
|
6961
|
+
"type": "number",
|
|
6962
|
+
"nullable": true
|
|
6963
|
+
},
|
|
6964
|
+
"pricePerExtraGuest": {
|
|
6965
|
+
"type": "number",
|
|
6966
|
+
"nullable": true
|
|
6967
|
+
},
|
|
6968
|
+
"securityDeposit": {
|
|
6969
|
+
"type": "number",
|
|
6970
|
+
"nullable": true
|
|
6971
|
+
},
|
|
6972
|
+
"weeklyDiscount": {
|
|
6973
|
+
"type": "number",
|
|
6974
|
+
"nullable": true,
|
|
6975
|
+
"description": "Fraction, not a percentage: `0.1` is 10% off a stay of a week or more."
|
|
6976
|
+
},
|
|
6977
|
+
"monthlyDiscount": {
|
|
6978
|
+
"type": "number",
|
|
6979
|
+
"nullable": true,
|
|
6980
|
+
"description": "Fraction, not a percentage."
|
|
6981
|
+
},
|
|
6982
|
+
"guestsIncluded": {
|
|
6983
|
+
"type": "integer",
|
|
6984
|
+
"nullable": true,
|
|
6985
|
+
"description": "Guests covered by the nightly rate before `pricePerExtraGuest` applies."
|
|
6986
|
+
},
|
|
6987
|
+
"currency": {
|
|
6988
|
+
"type": "string",
|
|
6989
|
+
"nullable": true,
|
|
6990
|
+
"description": "ISO 4217, e.g. `USD`."
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
},
|
|
6352
6994
|
"policies": {
|
|
6353
6995
|
"type": "object",
|
|
6354
6996
|
"properties": {
|
|
@@ -6519,7 +7161,7 @@
|
|
|
6519
7161
|
"items": {
|
|
6520
7162
|
"type": "string"
|
|
6521
7163
|
},
|
|
6522
|
-
"description": "Content slabs that were actually written, e.g. [\"title\",\"occupancy\",\"amenities\"]. A non-English write also reports `locale:<tag>` so you can see which row was written."
|
|
7164
|
+
"description": "Content slabs that were actually written, e.g. [\"title\",\"occupancy\",\"amenities\"]. A non-English write also reports `locale:<tag>` so you can see which row was written. A rate change reports `pricing`, and `calendar` as well when nights on the calendar moved to the new rate."
|
|
6523
7165
|
},
|
|
6524
7166
|
"deferred": {
|
|
6525
7167
|
"type": "array",
|
|
@@ -6878,18 +7520,13 @@
|
|
|
6878
7520
|
}
|
|
6879
7521
|
}
|
|
6880
7522
|
},
|
|
6881
|
-
"
|
|
7523
|
+
"ListingPublishBookingRequest": {
|
|
6882
7524
|
"type": "object",
|
|
7525
|
+
"description": "Optional. Omit the body entirely when the listing is mapped to exactly one Booking.com property.",
|
|
6883
7526
|
"properties": {
|
|
6884
|
-
"
|
|
6885
|
-
"type": "string"
|
|
6886
|
-
|
|
6887
|
-
"channel": {
|
|
6888
|
-
"type": "string"
|
|
6889
|
-
},
|
|
6890
|
-
"result": {
|
|
6891
|
-
"type": "object",
|
|
6892
|
-
"description": "Channel-specific push result (sections pushed, errors, etc.)"
|
|
7527
|
+
"hotelId": {
|
|
7528
|
+
"type": "string",
|
|
7529
|
+
"description": "Booking.com property to publish into. Required when this listing is mapped to more than one \u2014 without it the push is refused with `409 ambiguous_booking_mapping` rather than written into a property chosen for you. `GET /v1/channels/booking/properties` lists every property in the workspace with the listings mapped under it. `?hotel_id=` in the query string is accepted as well and means the same thing; the body wins if you send both."
|
|
6893
7530
|
}
|
|
6894
7531
|
}
|
|
6895
7532
|
},
|
|
@@ -6959,6 +7596,7 @@
|
|
|
6959
7596
|
},
|
|
6960
7597
|
"ListingPublishStatusChannel": {
|
|
6961
7598
|
"type": "object",
|
|
7599
|
+
"description": "Sync activity for one channel. `pushStatus` says whether the last push landed; `pushError` says why it did not.",
|
|
6962
7600
|
"properties": {
|
|
6963
7601
|
"platform": {
|
|
6964
7602
|
"type": "string",
|
|
@@ -6968,6 +7606,12 @@
|
|
|
6968
7606
|
"type": "string",
|
|
6969
7607
|
"nullable": true
|
|
6970
7608
|
},
|
|
7609
|
+
"pushError": {
|
|
7610
|
+
"type": "string",
|
|
7611
|
+
"nullable": true,
|
|
7612
|
+
"description": "Why the last push failed \u2014 the channel's own reason, verbatim, sanitised for display.\n\nThis is the field to render when `pushStatus` is `error`. It carries what Airbnb or Booking.com actually objected to, which is almost always something the operator can fix in the listing content: `\"Airbnb error (400): We can't save your info yet. Links and contact info can't be shared.\"`, `\"Check-in start time must be before end time\"`, `\"property_type_group must be one of [apartments, houses, \u2026]\"`, `\"Rate limited by provider\"`.\n\n**Free text, not an enum.** It is written by the channel and changes without notice: show it to a human, log it, put it next to the retry button \u2014 but never parse it or branch on its contents. When a push fails for several reasons at once the reasons are joined with `; `.\n\n`null` when the last push succeeded, and when no push has run yet \u2014 the two are told apart by `pushStatus` and `lastPushedAt`, not by this field.",
|
|
7613
|
+
"example": "Airbnb error (400): property_type_group must be one of [apartments, houses, secondary_units, unique_homes, bnb]"
|
|
7614
|
+
},
|
|
6971
7615
|
"lastPushedAt": {
|
|
6972
7616
|
"type": "string",
|
|
6973
7617
|
"format": "date-time",
|
|
@@ -6987,7 +7631,11 @@
|
|
|
6987
7631
|
"platformHasChanges": {
|
|
6988
7632
|
"type": "boolean"
|
|
6989
7633
|
}
|
|
6990
|
-
}
|
|
7634
|
+
},
|
|
7635
|
+
"required": [
|
|
7636
|
+
"platform",
|
|
7637
|
+
"pushError"
|
|
7638
|
+
]
|
|
6991
7639
|
},
|
|
6992
7640
|
"ListingPublishStatusConnection": {
|
|
6993
7641
|
"type": "object",
|
|
@@ -7011,6 +7659,43 @@
|
|
|
7011
7659
|
"format": "date-time",
|
|
7012
7660
|
"nullable": true,
|
|
7013
7661
|
"description": "ISO timestamp the connection was first established."
|
|
7662
|
+
},
|
|
7663
|
+
"lockedFields": {
|
|
7664
|
+
"type": "array",
|
|
7665
|
+
"items": {
|
|
7666
|
+
"type": "string"
|
|
7667
|
+
},
|
|
7668
|
+
"description": "Fields the channel will not let this listing change. **Airbnb only** \u2014 present on the `airbnb` entry and absent on every other channel, because no other channel has the concept.\n\nAirbnb does not refuse a write to a locked field: the request returns 200, reports the field as locked, and applies nothing. So a write to one of these looks exactly like a write that worked. Read this before you let a user edit \u2014 it is here, rather than only on `GET /v1/channels/airbnb/listings/{id}`, because this is the endpoint a listing editor already calls.\n\nEmpty for a listing with nothing locked, and for one that has not synced since we began recording them \u2014 the two are not distinguished, because a caller acts the same way on both. This is what Airbnb last told us, not a promise: a lock can appear between syncs, which is why a publish result also reports `lockedFields`.",
|
|
7669
|
+
"example": [
|
|
7670
|
+
"name",
|
|
7671
|
+
"property_type_category"
|
|
7672
|
+
]
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
},
|
|
7676
|
+
"ListingAddressReadiness": {
|
|
7677
|
+
"type": "object",
|
|
7678
|
+
"description": "Whether one channel would accept this listing's postal address, answered WITHOUT attempting a publish.",
|
|
7679
|
+
"properties": {
|
|
7680
|
+
"ready": {
|
|
7681
|
+
"type": "boolean",
|
|
7682
|
+
"description": "True when the address satisfies this channel's create preflight. False means a publish would be refused for the address alone."
|
|
7683
|
+
},
|
|
7684
|
+
"missing": {
|
|
7685
|
+
"type": "array",
|
|
7686
|
+
"items": {
|
|
7687
|
+
"type": "string"
|
|
7688
|
+
},
|
|
7689
|
+
"description": "The address parts still needed, named as the REQUEST fields you send \u2014 `street`, `city`, `state`, `postalCode` \u2014 so the value can be acted on directly. Empty when `ready` is true.",
|
|
7690
|
+
"example": [
|
|
7691
|
+
"state",
|
|
7692
|
+
"postalCode"
|
|
7693
|
+
]
|
|
7694
|
+
},
|
|
7695
|
+
"have": {
|
|
7696
|
+
"type": "string",
|
|
7697
|
+
"description": "The address as currently resolved, for debugging.",
|
|
7698
|
+
"example": "street=123 Main St, city=Miami Beach, state=\u2205, postalCode=\u2205, country=US"
|
|
7014
7699
|
}
|
|
7015
7700
|
}
|
|
7016
7701
|
},
|
|
@@ -7020,6 +7705,22 @@
|
|
|
7020
7705
|
"listingId": {
|
|
7021
7706
|
"type": "string"
|
|
7022
7707
|
},
|
|
7708
|
+
"addressReadiness": {
|
|
7709
|
+
"type": "object",
|
|
7710
|
+
"description": "Address readiness per channel, keyed by channel name (`airbnb` today). Airbnb requires `street` and `city` for every country and additionally `state` and `postalCode` for a **US** property \u2014 and a listing with no `countryCode` behaves as US. Check this BEFORE calling a publish endpoint: an incomplete address is refused at the create preflight and never reaches the channel.\n\nIt sits here rather than inside `channels[]` because `channels` reports sync activity and is empty for a listing that has never been pushed \u2014 exactly the listing whose address blocker you need to see. Repair a gap with `PUT /v1/listings/{id}/content`, sending only the missing parts under `address`. An empty object means readiness was not reported; it never means ready.",
|
|
7711
|
+
"additionalProperties": {
|
|
7712
|
+
"$ref": "#/components/schemas/ListingAddressReadiness"
|
|
7713
|
+
},
|
|
7714
|
+
"example": {
|
|
7715
|
+
"airbnb": {
|
|
7716
|
+
"ready": false,
|
|
7717
|
+
"missing": [
|
|
7718
|
+
"state",
|
|
7719
|
+
"postalCode"
|
|
7720
|
+
]
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
},
|
|
7023
7724
|
"channels": {
|
|
7024
7725
|
"type": "array",
|
|
7025
7726
|
"description": "Sync activity per channel \u2014 empty if the listing has never been pushed/pulled. Empty does NOT mean \"not connected\"; check `connections` for that.",
|
|
@@ -10518,11 +11219,12 @@
|
|
|
10518
11219
|
},
|
|
10519
11220
|
"AirbnbPublishResult": {
|
|
10520
11221
|
"type": "object",
|
|
10521
|
-
"description": "A publish is not one call to Airbnb: it is up to eight independent ones (details, description, amenities, rooms, policies, photos, pricing, checkout_tasks), each of which can fail on its own. A PARTIAL publish is normal \u2014 what succeeded stays applied; there is no rollback.",
|
|
11222
|
+
"description": "A publish is not one call to Airbnb: it is up to eight independent ones (details, description, amenities, rooms, policies, photos, pricing, checkout_tasks), each of which can fail on its own. A PARTIAL publish is normal \u2014 what succeeded stays applied; there is no rollback.\n\n**Content landing and the listing being live are two different answers.** `published` is about content; `live` is about whether the listing takes bookings. Read both.",
|
|
10522
11223
|
"required": [
|
|
10523
11224
|
"published",
|
|
10524
11225
|
"sections",
|
|
10525
11226
|
"errors",
|
|
11227
|
+
"warnings",
|
|
10526
11228
|
"lockedFields"
|
|
10527
11229
|
],
|
|
10528
11230
|
"properties": {
|
|
@@ -10530,6 +11232,10 @@
|
|
|
10530
11232
|
"type": "boolean",
|
|
10531
11233
|
"description": "True only when EVERY attempted section reached Airbnb."
|
|
10532
11234
|
},
|
|
11235
|
+
"live": {
|
|
11236
|
+
"type": "boolean",
|
|
11237
|
+
"description": "Whether the listing is active and bookable on Airbnb \u2014 that is, whether activation was actually performed and succeeded.\n\n`published: true` with `live: false` is a real and common outcome: every content section landed, but the listing was never activated, because activation is skipped when instant-booking cannot be confirmed to be off. `warnings` says why.\n\n**Absent is not `false`.** The field is omitted entirely when activation was never part of the operation \u2014 publishing to an already-mapped Airbnb listing updates content and activates nothing, so there is nothing to report. Only treat the listing as not-live when `live` is present and false."
|
|
11238
|
+
},
|
|
10533
11239
|
"sections": {
|
|
10534
11240
|
"type": "array",
|
|
10535
11241
|
"items": {
|
|
@@ -10549,16 +11255,26 @@
|
|
|
10549
11255
|
},
|
|
10550
11256
|
"description": "Per-section failures. Empty when `published` is true."
|
|
10551
11257
|
},
|
|
10552
|
-
"
|
|
10553
|
-
"type": "string",
|
|
10554
|
-
"description": "Set when the publish never started at all (no connection, address missing, subscription gate)."
|
|
10555
|
-
},
|
|
10556
|
-
"lockedFields": {
|
|
11258
|
+
"warnings": {
|
|
10557
11259
|
"type": "array",
|
|
10558
11260
|
"items": {
|
|
10559
11261
|
"type": "string"
|
|
10560
11262
|
},
|
|
10561
|
-
"description": "
|
|
11263
|
+
"description": "Steps that failed WITHOUT failing the publish \u2014 optional work the push carried on past, each in the push's own words. These used to be swallowed silently, so the only sign of one was a listing that was somehow not quite right afterwards. A publish can be `published: true` and still carry warnings; read them before concluding nothing needs doing.",
|
|
11264
|
+
"example": [
|
|
11265
|
+
"instant booking could not be confirmed off \u2014 listing left inactive"
|
|
11266
|
+
]
|
|
11267
|
+
},
|
|
11268
|
+
"reason": {
|
|
11269
|
+
"type": "string",
|
|
11270
|
+
"description": "Set when the publish never started at all (no connection, address missing, subscription gate)."
|
|
11271
|
+
},
|
|
11272
|
+
"lockedFields": {
|
|
11273
|
+
"type": "array",
|
|
11274
|
+
"items": {
|
|
11275
|
+
"type": "string"
|
|
11276
|
+
},
|
|
11277
|
+
"description": "Fields Airbnb will not let this listing change \u2014 collected from the failures above and from the `locked_attributes` Airbnb recorded for the listing. Sending them again returns success and changes nothing."
|
|
10562
11278
|
}
|
|
10563
11279
|
}
|
|
10564
11280
|
},
|
|
@@ -10576,6 +11292,227 @@
|
|
|
10576
11292
|
}
|
|
10577
11293
|
}
|
|
10578
11294
|
},
|
|
11295
|
+
"BookingPublishSectionError": {
|
|
11296
|
+
"type": "object",
|
|
11297
|
+
"description": "One section of a publish that did not reach Booking.com.",
|
|
11298
|
+
"required": [
|
|
11299
|
+
"section",
|
|
11300
|
+
"message",
|
|
11301
|
+
"code"
|
|
11302
|
+
],
|
|
11303
|
+
"properties": {
|
|
11304
|
+
"section": {
|
|
11305
|
+
"type": "string",
|
|
11306
|
+
"description": "Which part of the listing this failure is about \u2014 e.g. `details`, `description`, `amenities`, `rooms`, `photos`, `pricing`.",
|
|
11307
|
+
"example": "description"
|
|
11308
|
+
},
|
|
11309
|
+
"message": {
|
|
11310
|
+
"type": "string",
|
|
11311
|
+
"description": "Booking.com's own reason, verbatim, or ours when we refused to send an empty section."
|
|
11312
|
+
},
|
|
11313
|
+
"code": {
|
|
11314
|
+
"type": "string",
|
|
11315
|
+
"description": "`no_content` \u2014 there was nothing canonical to send for this section; write the content, then publish again. `rejected` \u2014 Booking.com refused the section as sent; fix the content, or the property's Content API permissions, and publish again.\n\nAirbnb's third code, `locked`, has no Booking.com counterpart and never appears here."
|
|
11316
|
+
}
|
|
11317
|
+
}
|
|
11318
|
+
},
|
|
11319
|
+
"BookingPublishResult": {
|
|
11320
|
+
"type": "object",
|
|
11321
|
+
"description": "A publish is not one call to Booking.com: it is several independent Content API calls (details, description, amenities, rooms, photos, pricing), each of which can fail on its own. A PARTIAL publish is normal \u2014 what succeeded stays applied; there is no rollback. Fix the failing sections and publish again; re-publishing an unchanged section is harmless.\n\nA property whose Content API credentials do not cover a section answers 403 for that section alone \u2014 the rest still land, and the failure is reported here rather than swallowed.",
|
|
11322
|
+
"required": [
|
|
11323
|
+
"published",
|
|
11324
|
+
"sections",
|
|
11325
|
+
"errors"
|
|
11326
|
+
],
|
|
11327
|
+
"properties": {
|
|
11328
|
+
"published": {
|
|
11329
|
+
"type": "boolean",
|
|
11330
|
+
"description": "True only when EVERY attempted section reached Booking.com."
|
|
11331
|
+
},
|
|
11332
|
+
"sections": {
|
|
11333
|
+
"type": "array",
|
|
11334
|
+
"items": {
|
|
11335
|
+
"type": "string"
|
|
11336
|
+
},
|
|
11337
|
+
"description": "Sections that landed on Booking.com.",
|
|
11338
|
+
"example": [
|
|
11339
|
+
"details",
|
|
11340
|
+
"description",
|
|
11341
|
+
"photos"
|
|
11342
|
+
]
|
|
11343
|
+
},
|
|
11344
|
+
"errors": {
|
|
11345
|
+
"type": "array",
|
|
11346
|
+
"items": {
|
|
11347
|
+
"$ref": "#/components/schemas/BookingPublishSectionError"
|
|
11348
|
+
},
|
|
11349
|
+
"description": "Per-section failures. Empty when `published` is true."
|
|
11350
|
+
},
|
|
11351
|
+
"reason": {
|
|
11352
|
+
"type": "string",
|
|
11353
|
+
"description": "Set when the publish never started at all \u2014 most often because the listing is not mapped to any Booking.com property yet. Finish the Connect flow (`POST /v1/connect/booking/map-rooms`) and publish again."
|
|
11354
|
+
},
|
|
11355
|
+
"hotelId": {
|
|
11356
|
+
"type": "string",
|
|
11357
|
+
"nullable": true,
|
|
11358
|
+
"description": "The Booking.com property this publish wrote into \u2014 resolved from the listing's mapping, or the one you named. Always read it back: a listing can be mapped to several properties, and this states which one actually received the content. Null when the listing is mapped to no property, in which case nothing was pushed."
|
|
11359
|
+
}
|
|
11360
|
+
}
|
|
11361
|
+
},
|
|
11362
|
+
"ListingPublishBookingResponse": {
|
|
11363
|
+
"type": "object",
|
|
11364
|
+
"properties": {
|
|
11365
|
+
"listingId": {
|
|
11366
|
+
"type": "string"
|
|
11367
|
+
},
|
|
11368
|
+
"channel": {
|
|
11369
|
+
"type": "string"
|
|
11370
|
+
},
|
|
11371
|
+
"result": {
|
|
11372
|
+
"$ref": "#/components/schemas/BookingPublishResult"
|
|
11373
|
+
}
|
|
11374
|
+
}
|
|
11375
|
+
},
|
|
11376
|
+
"ListingMarketStateRequest": {
|
|
11377
|
+
"type": "object",
|
|
11378
|
+
"description": "Optional. Send no body at all unless this listing is mapped to more than one Booking.com property.",
|
|
11379
|
+
"properties": {
|
|
11380
|
+
"hotelId": {
|
|
11381
|
+
"type": "string",
|
|
11382
|
+
"description": "Booking.com property to act on, for a listing mapped to more than one. Without it the Booking.com item comes back refused with `ambiguous_booking_mapping` \u2014 closing the wrong property's availability takes real inventory off sale, so it is never guessed. The Airbnb items are unaffected and still run. `GET /v1/channels/booking/properties` lists every property in the workspace with the listings mapped under it. `?hotel_id=` in the query string means the same thing; the body wins if you send both."
|
|
11383
|
+
}
|
|
11384
|
+
}
|
|
11385
|
+
},
|
|
11386
|
+
"ChannelMarketStateItem": {
|
|
11387
|
+
"type": "object",
|
|
11388
|
+
"description": "What happened on ONE channel item \u2014 one Airbnb connection, or one Booking.com property. A listing can carry several Airbnb connections (a re-list, or a move between host accounts) and each gets its own entry.",
|
|
11389
|
+
"required": [
|
|
11390
|
+
"channel",
|
|
11391
|
+
"state",
|
|
11392
|
+
"ok"
|
|
11393
|
+
],
|
|
11394
|
+
"properties": {
|
|
11395
|
+
"channel": {
|
|
11396
|
+
"type": "string"
|
|
11397
|
+
},
|
|
11398
|
+
"state": {
|
|
11399
|
+
"type": "string",
|
|
11400
|
+
"description": "**What is now true of this item**, not what you asked for.\n\n`offline` \u2014 it is off the market. `online` \u2014 it is back on. `unchanged` \u2014 nothing was sent, or what was sent did not take; `code` and `message` say why.\n\n`unchanged` never means \"it was already like that\": it means we did not put it there, and it is still in whatever state it was in before the call."
|
|
11401
|
+
},
|
|
11402
|
+
"ok": {
|
|
11403
|
+
"type": "boolean",
|
|
11404
|
+
"description": "True only when the channel confirmed the change."
|
|
11405
|
+
},
|
|
11406
|
+
"connectionId": {
|
|
11407
|
+
"type": "string",
|
|
11408
|
+
"nullable": true,
|
|
11409
|
+
"description": "Airbnb connection row id \u2014 the `id` from `GET /v1/channels/airbnb/listings/{id}`. Present on Airbnb items."
|
|
11410
|
+
},
|
|
11411
|
+
"hotelId": {
|
|
11412
|
+
"type": "string",
|
|
11413
|
+
"nullable": true,
|
|
11414
|
+
"description": "The Booking.com property acted on. Present on Booking.com items; null when the property could not be resolved."
|
|
11415
|
+
},
|
|
11416
|
+
"code": {
|
|
11417
|
+
"type": "string",
|
|
11418
|
+
"description": "Error code when `ok` is false \u2014 the SAME code the channel-specific endpoint returns for this failure, so one vocabulary covers both surfaces. Absent when `ok` is true.\n\nThe channel codes come in pairs, and the pair is the retryable split \u2014 the most useful bit in the whole item:\n\n- `airbnb_rejected` / `booking_rejected` \u2014 the channel refused the request AS SENT. `message` carries its own reason. Correct it and send again; resending the same thing is refused again.\n- `airbnb_error` / `booking_error` \u2014 the channel did not complete the request (outage, timeout, server error). Nothing about the request needs to change: retry with backoff.\n\nPlus `ambiguous_booking_mapping` (name the property with `hotelId`) and `payment_required` (a billing refusal, which keeps its own code rather than being buried under a channel one)."
|
|
11419
|
+
},
|
|
11420
|
+
"previousCode": {
|
|
11421
|
+
"type": "string",
|
|
11422
|
+
"deprecated": true,
|
|
11423
|
+
"description": "The `code` this item used to carry, for callers still branching on the old string. A migration aid with a deprecation window \u2014 **`code` is canonical.**\n\nThis fan-out reaches Airbnb through an internal hop that flattens a refusal into its own 500, so an unambiguous Airbnb 400 (\"Please specify a valid room type\") was reported as `airbnb_error` \u2014 whose published advice is to retry with backoff, forever, for something Airbnb will never accept. It now reads Airbnb's real status and answers `airbnb_rejected`, and the classification covers the whole 4xx range rather than only `400`. Items whose code changed carry `previousCode`. **Removed in v2.**",
|
|
11424
|
+
"example": "airbnb_error"
|
|
11425
|
+
},
|
|
11426
|
+
"message": {
|
|
11427
|
+
"type": "string",
|
|
11428
|
+
"description": "The channel's own reason, verbatim. Absent when `ok` is true."
|
|
11429
|
+
},
|
|
11430
|
+
"fix": {
|
|
11431
|
+
"type": "string",
|
|
11432
|
+
"description": "What to do about it, phrased for the direction you asked for \u2014 \"still live and taking bookings\" and \"still down\" call for different reactions. Absent when `ok` is true."
|
|
11433
|
+
},
|
|
11434
|
+
"verified": {
|
|
11435
|
+
"type": "boolean",
|
|
11436
|
+
"description": "Airbnb only, and only when going offline: the listing was READ BACK after the deactivation and confirmed down. Airbnb accepts a deactivation and leaves some listings live, so \"we sent the request\" is a weaker claim than this one and is never reported as success."
|
|
11437
|
+
}
|
|
11438
|
+
}
|
|
11439
|
+
},
|
|
11440
|
+
"ListingMarketStateResponse": {
|
|
11441
|
+
"type": "object",
|
|
11442
|
+
"description": "The per-item result of a fan-out. **There is deliberately no top-level success flag**: a listing can sit on two Airbnb connections and a Booking.com property, they fail independently, and partial success is the ordinary outcome \u2014 any single boolean would be wrong for exactly the calls that need reading. Walk `channels` and check each `ok`.\n\nNothing here is rolled back. What landed stays landed; re-send the same request to retry the items that did not, which is safe.",
|
|
11443
|
+
"required": [
|
|
11444
|
+
"listingId",
|
|
11445
|
+
"state",
|
|
11446
|
+
"channels"
|
|
11447
|
+
],
|
|
11448
|
+
"properties": {
|
|
11449
|
+
"listingId": {
|
|
11450
|
+
"type": "string"
|
|
11451
|
+
},
|
|
11452
|
+
"state": {
|
|
11453
|
+
"type": "string",
|
|
11454
|
+
"description": "The state you asked for. Compare each item's own `state` against it."
|
|
11455
|
+
},
|
|
11456
|
+
"channels": {
|
|
11457
|
+
"type": "array",
|
|
11458
|
+
"description": "One entry per channel item acted on \u2014 Airbnb connections first, then the Booking.com property. Never empty: a listing connected to nothing is refused with `422 no_connected_channels` rather than answered with an empty array.",
|
|
11459
|
+
"items": {
|
|
11460
|
+
"$ref": "#/components/schemas/ChannelMarketStateItem"
|
|
11461
|
+
}
|
|
11462
|
+
}
|
|
11463
|
+
}
|
|
11464
|
+
},
|
|
11465
|
+
"BookingPropertyActionRequest": {
|
|
11466
|
+
"type": "object",
|
|
11467
|
+
"required": [
|
|
11468
|
+
"action"
|
|
11469
|
+
],
|
|
11470
|
+
"description": "Take this listing's Booking.com property off sale, or put it back.",
|
|
11471
|
+
"properties": {
|
|
11472
|
+
"action": {
|
|
11473
|
+
"type": "string",
|
|
11474
|
+
"description": "`unlist` closes the room's availability across the whole forward window, so the property stops selling. `relist` re-syncs the real calendar: dates that are genuinely blocked (a reservation, an owner stay) stay blocked, and only the closure `unlist` wrote lifts. They are not mirror images, and that is deliberate."
|
|
11475
|
+
},
|
|
11476
|
+
"hotelId": {
|
|
11477
|
+
"type": "string",
|
|
11478
|
+
"description": "Booking.com property to act on, for a listing mapped to more than one. Without it the request is refused with `409 ambiguous_booking_mapping` and nothing is written. `?hotel_id=` means the same thing; the body wins if you send both."
|
|
11479
|
+
}
|
|
11480
|
+
}
|
|
11481
|
+
},
|
|
11482
|
+
"BookingPropertyActionResponse": {
|
|
11483
|
+
"type": "object",
|
|
11484
|
+
"required": [
|
|
11485
|
+
"listingId",
|
|
11486
|
+
"channel",
|
|
11487
|
+
"action",
|
|
11488
|
+
"hotelId",
|
|
11489
|
+
"selling"
|
|
11490
|
+
],
|
|
11491
|
+
"properties": {
|
|
11492
|
+
"listingId": {
|
|
11493
|
+
"type": "string"
|
|
11494
|
+
},
|
|
11495
|
+
"channel": {
|
|
11496
|
+
"type": "string"
|
|
11497
|
+
},
|
|
11498
|
+
"action": {
|
|
11499
|
+
"type": "string"
|
|
11500
|
+
},
|
|
11501
|
+
"hotelId": {
|
|
11502
|
+
"type": "string",
|
|
11503
|
+
"description": "The Booking.com property that was acted on. Always read it back \u2014 a listing can be mapped to several, and this states which one changed."
|
|
11504
|
+
},
|
|
11505
|
+
"roomBookingId": {
|
|
11506
|
+
"type": "string",
|
|
11507
|
+
"nullable": true,
|
|
11508
|
+
"description": "Booking.com's own room id the availability write addressed."
|
|
11509
|
+
},
|
|
11510
|
+
"selling": {
|
|
11511
|
+
"type": "boolean",
|
|
11512
|
+
"description": "Whether the property is now on sale. **This is the state of the property on Booking.com, not of the listing in Repull** \u2014 `active` (what Repull bills and serves) is untouched by both actions and is deliberately not echoed here so the two can never be read as one field."
|
|
11513
|
+
}
|
|
11514
|
+
}
|
|
11515
|
+
},
|
|
10579
11516
|
"AirbnbDescriptionWriteRequest": {
|
|
10580
11517
|
"type": "object",
|
|
10581
11518
|
"required": [
|
|
@@ -10699,7 +11636,7 @@
|
|
|
10699
11636
|
"permits": {
|
|
10700
11637
|
"type": "array",
|
|
10701
11638
|
"nullable": true,
|
|
10702
|
-
"description": "The live permit flows from Airbnb \u2014 present only with `?source=live`, `null` otherwise. Each flow names its `regulatory_body`, `regulation_type`, `status`,
|
|
11639
|
+
"description": "The live permit flows from Airbnb \u2014 present only with `?source=live`, `null` otherwise. Each flow names its `regulatory_body`, `regulation_type`, `status`, its `flows[]` with the `answer_key` / `type` / `choices` of every question you have to answer, plus the answers already on file.",
|
|
10703
11640
|
"items": {
|
|
10704
11641
|
"type": "object",
|
|
10705
11642
|
"additionalProperties": true
|
|
@@ -10747,7 +11684,7 @@
|
|
|
10747
11684
|
"required": [
|
|
10748
11685
|
"permits"
|
|
10749
11686
|
],
|
|
10750
|
-
"description": "Answer the regulatory permit questions Airbnb asks for this listing. Read them first with `?source=live` on the GET
|
|
11687
|
+
"description": "Answer the regulatory permit questions Airbnb asks for this listing, in Airbnb's Listing Permits shape. Read them first with `?source=live` on the GET: each permit lists its `flows[]`, and each flow its `questions[]` with an `answer_key` and a `type`.",
|
|
10751
11688
|
"properties": {
|
|
10752
11689
|
"permits": {
|
|
10753
11690
|
"type": "array",
|
|
@@ -10757,46 +11694,53 @@
|
|
|
10757
11694
|
"required": [
|
|
10758
11695
|
"regulatory_body",
|
|
10759
11696
|
"regulation_type",
|
|
11697
|
+
"flow_slug",
|
|
10760
11698
|
"answers"
|
|
10761
11699
|
],
|
|
10762
11700
|
"additionalProperties": false,
|
|
10763
11701
|
"properties": {
|
|
10764
11702
|
"regulatory_body": {
|
|
10765
11703
|
"type": "string",
|
|
10766
|
-
"description": "As
|
|
11704
|
+
"description": "As returned by the GET, e.g. `maui_county_hawaii`."
|
|
10767
11705
|
},
|
|
10768
11706
|
"regulation_type": {
|
|
10769
|
-
"type": "string"
|
|
11707
|
+
"type": "string",
|
|
11708
|
+
"description": "As returned by the GET."
|
|
11709
|
+
},
|
|
11710
|
+
"regulation_context": {
|
|
11711
|
+
"type": "string",
|
|
11712
|
+
"description": "Echo the GET's `regulation_context` (e.g. `initial`) when present."
|
|
11713
|
+
},
|
|
11714
|
+
"flow_slug": {
|
|
11715
|
+
"type": "string",
|
|
11716
|
+
"description": "The `slug` of the flow you are answering, e.g. `existing_registration` or `exemption_claim`."
|
|
10770
11717
|
},
|
|
10771
11718
|
"answers": {
|
|
10772
|
-
"type": "
|
|
10773
|
-
"
|
|
10774
|
-
"
|
|
11719
|
+
"type": "object",
|
|
11720
|
+
"minProperties": 1,
|
|
11721
|
+
"description": "Keyed by each question's `answer_key`. Each value carries exactly one field, chosen by the question's `type`: TEXT \u2192 `text_value`, ATTESTATION \u2192 `attestation_value`, RADIO \u2192 `radio_value`, DATE \u2192 `date_value`, SELECT \u2192 `selected_options_value`.",
|
|
11722
|
+
"additionalProperties": {
|
|
10775
11723
|
"type": "object",
|
|
10776
|
-
"required": [
|
|
10777
|
-
"question_key"
|
|
10778
|
-
],
|
|
10779
11724
|
"additionalProperties": false,
|
|
10780
|
-
"description": "Exactly one value field applies, decided by the question's `answer_type`.",
|
|
10781
11725
|
"properties": {
|
|
10782
|
-
"
|
|
11726
|
+
"text_value": {
|
|
10783
11727
|
"type": "string"
|
|
10784
11728
|
},
|
|
10785
|
-
"
|
|
10786
|
-
"type": "
|
|
10787
|
-
|
|
11729
|
+
"attestation_value": {
|
|
11730
|
+
"type": "boolean"
|
|
11731
|
+
},
|
|
11732
|
+
"radio_value": {
|
|
11733
|
+
"type": "string"
|
|
10788
11734
|
},
|
|
10789
11735
|
"date_value": {
|
|
10790
11736
|
"type": "string",
|
|
10791
|
-
"nullable": true,
|
|
10792
11737
|
"description": "ISO date, YYYY-MM-DD."
|
|
10793
11738
|
},
|
|
10794
11739
|
"selected_options_value": {
|
|
10795
11740
|
"type": "array",
|
|
10796
11741
|
"items": {
|
|
10797
11742
|
"type": "string"
|
|
10798
|
-
}
|
|
10799
|
-
"nullable": true
|
|
11743
|
+
}
|
|
10800
11744
|
}
|
|
10801
11745
|
}
|
|
10802
11746
|
}
|
|
@@ -10804,6 +11748,24 @@
|
|
|
10804
11748
|
}
|
|
10805
11749
|
}
|
|
10806
11750
|
}
|
|
11751
|
+
},
|
|
11752
|
+
"example": {
|
|
11753
|
+
"permits": [
|
|
11754
|
+
{
|
|
11755
|
+
"regulatory_body": "maui_county_hawaii",
|
|
11756
|
+
"regulation_type": "registration",
|
|
11757
|
+
"regulation_context": "initial",
|
|
11758
|
+
"flow_slug": "existing_registration",
|
|
11759
|
+
"answers": {
|
|
11760
|
+
"attestation": {
|
|
11761
|
+
"attestation_value": true
|
|
11762
|
+
},
|
|
11763
|
+
"permit_number": {
|
|
11764
|
+
"text_value": "TMK-2-3-004-005"
|
|
11765
|
+
}
|
|
11766
|
+
}
|
|
11767
|
+
}
|
|
11768
|
+
]
|
|
10807
11769
|
}
|
|
10808
11770
|
},
|
|
10809
11771
|
"AirbnbSafetyDisclosure": {
|
|
@@ -11400,7 +12362,7 @@
|
|
|
11400
12362
|
}
|
|
11401
12363
|
},
|
|
11402
12364
|
"AirbnbWriteRejected": {
|
|
11403
|
-
"description": "The request was refused as sent; resending the same body will be refused again. Two cases:\n\n- `invalid_params` \u2014 the body failed validation before anything was sent to Airbnb. `field` names the offending field (e.g. `operations.0.max_nights`), `value_received` echoes it, and `fix` says what to send.\n- `airbnb_rejected` \u2014 Airbnb refused the change; `message` carries Airbnb's own reason.",
|
|
12365
|
+
"description": "The request was refused as sent; resending the same body will be refused again. Two cases:\n\n- `invalid_params` \u2014 the body failed validation before anything was sent to Airbnb. `field` names the offending field (e.g. `operations.0.max_nights`), `value_received` echoes it, and `fix` says what to send.\n- `airbnb_rejected` \u2014 Airbnb refused the change; `message` carries Airbnb's own reason.\n\n**This is the opposite answer to `502 airbnb_error`, not another name for it.** A refusal is permanent until you change the request; `airbnb_error` means Airbnb did not answer and the same request should be retried. Branch on that split rather than on \"Airbnb failed\".\n\nSome endpoints \u2014 reviews, messaging, the check-in guide, alteration answers and the Airbnb pull \u2014 used to report every Airbnb failure as `500 airbnb_error`, refusals included. They now classify like every other Airbnb write, and the responses whose code changed carry a deprecated `previous_code: \"airbnb_error\"` so an existing handler keeps matching while you migrate.",
|
|
11404
12366
|
"content": {
|
|
11405
12367
|
"application/json": {
|
|
11406
12368
|
"schema": {
|
|
@@ -11507,7 +12469,7 @@
|
|
|
11507
12469
|
}
|
|
11508
12470
|
},
|
|
11509
12471
|
"AirbnbUpstreamError": {
|
|
11510
|
-
"description": "Airbnb did not complete the request \u2014 an outage, timeout or server error on their side (`airbnb_error`). The request itself is fine: retry with exponential backoff.",
|
|
12472
|
+
"description": "Airbnb did not complete the request \u2014 an outage, timeout or server error on their side (`airbnb_error`). The request itself is fine: retry with exponential backoff.\n\n**`airbnb_error` never means Airbnb refused something.** A refusal is `422 airbnb_rejected` and retrying it cannot succeed; an expired or revoked grant is `403 connection_reauth_required` and retrying that cannot succeed either. If you have one branch for \"Airbnb failed\", split it here \u2014 this is the only one of the three where a retry is the right move.",
|
|
11511
12473
|
"content": {
|
|
11512
12474
|
"application/json": {
|
|
11513
12475
|
"schema": {
|
|
@@ -11544,6 +12506,26 @@
|
|
|
11544
12506
|
}
|
|
11545
12507
|
}
|
|
11546
12508
|
},
|
|
12509
|
+
"PublishBillingRefused": {
|
|
12510
|
+
"description": "The publish was refused for BILLING reasons and **nothing was sent to the channel**. Two codes:\n\n - `payment_required` \u2014 publishing a listing to a channel needs an active paid subscription. `upgrade_url` is where to start or restore it.\n - `channel_publish_limit_reached` \u2014 the plan's published-listing quota is full. `used` and `limit` say by how much. Take another listing off the channel to free a slot, or raise the quota.\n\nThis is not a channel fault, and the two things that fix a channel fault \u2014 retrying with backoff, reconnecting the channel \u2014 do nothing here. Once the billing state changes, send exactly the same request again; it is safe to repeat.\n\n**It applies to putting a listing back on the market as well as to publishing content.** Taking a listing down passes no billing gate and putting it back up does, so a lapsed subscription can leave a listing unlisted until billing is sorted out.",
|
|
12511
|
+
"content": {
|
|
12512
|
+
"application/json": {
|
|
12513
|
+
"schema": {
|
|
12514
|
+
"$ref": "#/components/schemas/Error"
|
|
12515
|
+
}
|
|
12516
|
+
}
|
|
12517
|
+
}
|
|
12518
|
+
},
|
|
12519
|
+
"ChannelActionUnavailable": {
|
|
12520
|
+
"description": "The action is not reachable from the API yet and the request was refused before it got to the channel (`channel_action_unavailable`). **Nothing was changed** \u2014 whatever you were acting on is in the state it was in before the call.\n\nThis is a gap on our side, not a problem with your request: there is nothing to correct and retrying returns the same answer until the gap is closed. `fix` names where the operation can be performed in the meantime.",
|
|
12521
|
+
"content": {
|
|
12522
|
+
"application/json": {
|
|
12523
|
+
"schema": {
|
|
12524
|
+
"$ref": "#/components/schemas/Error"
|
|
12525
|
+
}
|
|
12526
|
+
}
|
|
12527
|
+
}
|
|
12528
|
+
},
|
|
11547
12529
|
"TooManyRequests": {
|
|
11548
12530
|
"description": "Quota exceeded. Wait `retry_after` seconds (or until `X-RateLimit-Reset`) before retrying.",
|
|
11549
12531
|
"headers": {
|
|
@@ -11987,7 +12969,7 @@
|
|
|
11987
12969
|
"completed"
|
|
11988
12970
|
]
|
|
11989
12971
|
},
|
|
11990
|
-
"description": "Filter by lifecycle status. **Case-insensitive** \u2014 `confirmed`, `Confirmed`, and `CONFIRMED` all match. Each public value expands to the full set of internal sub-states server-side: `confirmed` matches `accept`/`confirmed`/`modified`, `cancelled` matches every cancellation sub-state (`cancelled_by_host`, `declined`, `expired`, etc.), `pending`
|
|
12972
|
+
"description": "Filter by lifecycle status. **Case-insensitive** \u2014 `confirmed`, `Confirmed`, and `CONFIRMED` all match. Each public value expands to the full set of internal sub-states server-side: `confirmed` matches `accept`/`confirmed`/`modified`, `cancelled` matches every cancellation sub-state (`cancelled_by_host`, `declined`, `expired`, etc.), `pending` covers three different situations, told apart by `pendingReason` on each reservation: a booking **request** awaiting the host (`host_approval` \u2014 answer it with `POST /v1/reservations/{id}/accept` or `/decline` before its `respondBy`), and a booking Airbnb is holding for the guest to pay (`guest_payment`) or to complete identity verification (`guest_verification`), which need no action from the host. `completed` is a derived state \u2014 combine `status=confirmed` with `check_out_before=<today>` to filter for past stays. `pending` lists only bookings that can still go ahead: a request the channel already let lapse (Airbnb expires a request 24 hours after the guest asks) and any pending booking whose check-in date has passed are left out and appear under `cancelled` with `statusDetail: \"request_expired\"` instead. The stored record is not changed \u2014 this is derived when you read it. Airbnb **inquiries** (questions before booking) are not reservations: list them with `GET /v1/inquiries`."
|
|
11991
12973
|
},
|
|
11992
12974
|
{
|
|
11993
12975
|
"name": "listingId",
|
|
@@ -12819,7 +13801,7 @@
|
|
|
12819
13801
|
"post": {
|
|
12820
13802
|
"operationId": "send_conversation_message",
|
|
12821
13803
|
"summary": "Send a message to the guest",
|
|
12822
|
-
"description": "Sends a message to the guest on this conversation and records it in the thread.\n\nOmit `channel` and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, SMS, email or the direct-booking site) \u2014 that is the right default. Pass `channel` only to force a specific one.\n\nThe message is attributed to the API
|
|
13804
|
+
"description": "Sends a message to the guest on this conversation and records it in the thread.\n\nOmit `channel` and the message goes out on whichever channel the conversation already uses (Airbnb, Booking.com, SMS, email or the direct-booking site) \u2014 that is the right default. Pass `channel` only to force a specific one.\n\nThe message is attributed to the API: it is recorded with `aiGenerated` false so an API send is never counted as an automated reply.\n\n### Airbnb rewrites links \u2014 check `contentRewritten`\n\nAirbnb rejects guest messages containing a link, an email address or a phone number, and names the offending text. When that happens the offending fragment is stripped and the remainder is re-sent once, which means **the guest receives a message that is not the one you wrote**. Reporting that as a plain success would be a lie, so every response carries `contentRewritten`; when it is `true`, `deliveredContent` is the text that actually reached the guest. Check it before assuming your message went out verbatim.\n\nWhen the text cannot be salvaged (the link is most of the message) nothing is delivered and the call returns `422 message_not_sent` with the channel's verbatim refusal in `statusReason`.\n\nSend `Idempotency-Key` \u2014 without it, retrying after a network timeout sends the guest the same message twice.\n\n### Attachments\n\nSend files with `attachments: [{ url, contentType?, filename? }]` \u2014 public `https://` URLs, up to 5 per request, 10 MB each. `message` may be omitted when there are attachments (except on Booking.com). Repull downloads each file, reads its real type from the bytes, keeps a durable copy and delivers it through the channel's own file flow. **Every file is checked before anything is sent**: if one is unreachable, too large or of a type the channel refuses, the call returns 422 naming the file (`index`) and the guest receives nothing.\n\n| Channel | Accepted types | Text | How it arrives |\n|---|---|---|---|\n| Airbnb | JPEG, PNG, GIF, WebP (converted to JPEG), MP4, QuickTime | optional | each file as its own message, then the text as a separate message |\n| Booking.com | JPEG, PNG | **required** | one message carrying the text and every file |\n| SMS, email, direct-booking site chat | \u2014 | \u2014 | `422 attachments_not_supported`, nothing sent |\n\nAirbnb does not allow files in pre-booking (inquiry) conversations; that refusal comes back as `422 message_not_sent`. Because Airbnb delivers files one message at a time, a later file can be refused after earlier ones arrived \u2014 that returns `422 message_partially_sent` with `parts` saying exactly which messages reached the guest; resend only the rest.\n\nThe response's `attachments` lists each file's durable `url`, and `parts` lists every channel message the send produced. Read-back (`GET /v1/conversations/{id}/messages`) shows the same files in each message's `attachments`.\n\n**Inactive listings:** a conversation that belongs to an inactive listing returns `403 listing_inactive` and no message is sent. Activate the listing first.",
|
|
12823
13805
|
"tags": [
|
|
12824
13806
|
"Conversations"
|
|
12825
13807
|
],
|
|
@@ -13468,35 +14450,108 @@
|
|
|
13468
14450
|
"nullable": true,
|
|
13469
14451
|
"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.",
|
|
13470
14452
|
"example": "fr"
|
|
13471
|
-
}
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
14453
|
+
},
|
|
14454
|
+
"purpose": {
|
|
14455
|
+
"type": "string",
|
|
14456
|
+
"enum": [
|
|
14457
|
+
"connect",
|
|
14458
|
+
"migrate"
|
|
14459
|
+
],
|
|
14460
|
+
"default": "connect",
|
|
14461
|
+
"description": "`migrate` starts a Repull Migrate session: the property manager connects their current PMS (or channel) and their data is copied into a new workspace of theirs, which you read with `X-Workspace-Id`. The hosted pages use migration wording, and after connecting they show the import's progress."
|
|
14462
|
+
},
|
|
14463
|
+
"workspace": {
|
|
14464
|
+
"type": "object",
|
|
14465
|
+
"description": "Migrate only \u2014 the property manager being moved. Required unless you send `X-Workspace-Id` to reconnect an existing migration.",
|
|
14466
|
+
"properties": {
|
|
14467
|
+
"name": {
|
|
14468
|
+
"type": "string",
|
|
14469
|
+
"maxLength": 120,
|
|
14470
|
+
"example": "Seaside Rentals"
|
|
14471
|
+
},
|
|
14472
|
+
"externalRef": {
|
|
14473
|
+
"type": "string",
|
|
14474
|
+
"maxLength": 120,
|
|
14475
|
+
"description": "Your own id for this property manager. Returned on every migration read.",
|
|
14476
|
+
"example": "acct_8812"
|
|
14477
|
+
}
|
|
14478
|
+
}
|
|
14479
|
+
},
|
|
14480
|
+
"copy": {
|
|
14481
|
+
"type": "object",
|
|
14482
|
+
"description": "Migrate only \u2014 your wording for the hosted pages. Anything you leave out uses Repull's localized migration copy.",
|
|
14483
|
+
"properties": {
|
|
14484
|
+
"title": {
|
|
14485
|
+
"type": "string",
|
|
14486
|
+
"maxLength": 300
|
|
14487
|
+
},
|
|
14488
|
+
"subtitle": {
|
|
14489
|
+
"type": "string",
|
|
14490
|
+
"maxLength": 300
|
|
14491
|
+
},
|
|
14492
|
+
"completedTitle": {
|
|
14493
|
+
"type": "string",
|
|
14494
|
+
"maxLength": 300
|
|
14495
|
+
},
|
|
14496
|
+
"completedBody": {
|
|
14497
|
+
"type": "string",
|
|
14498
|
+
"maxLength": 300
|
|
14499
|
+
}
|
|
14500
|
+
},
|
|
14501
|
+
"additionalProperties": false
|
|
14502
|
+
},
|
|
14503
|
+
"scope": {
|
|
14504
|
+
"type": "array",
|
|
14505
|
+
"description": "Migrate only \u2014 what you want brought across, listed to the property manager before they connect.",
|
|
14506
|
+
"items": {
|
|
14507
|
+
"type": "string",
|
|
14508
|
+
"enum": [
|
|
14509
|
+
"listings",
|
|
14510
|
+
"photos",
|
|
14511
|
+
"amenities",
|
|
14512
|
+
"rooms",
|
|
14513
|
+
"houseRules",
|
|
14514
|
+
"fees",
|
|
14515
|
+
"taxes",
|
|
14516
|
+
"owners",
|
|
14517
|
+
"guests",
|
|
14518
|
+
"reservations",
|
|
14519
|
+
"payments",
|
|
14520
|
+
"conversations",
|
|
14521
|
+
"calendar",
|
|
14522
|
+
"rates",
|
|
14523
|
+
"channelIds"
|
|
14524
|
+
]
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14527
|
+
}
|
|
14528
|
+
}
|
|
14529
|
+
}
|
|
14530
|
+
}
|
|
14531
|
+
},
|
|
14532
|
+
"responses": {
|
|
14533
|
+
"201": {
|
|
14534
|
+
"description": "Picker session created",
|
|
14535
|
+
"content": {
|
|
14536
|
+
"application/json": {
|
|
14537
|
+
"schema": {
|
|
14538
|
+
"$ref": "#/components/schemas/ConnectSession"
|
|
14539
|
+
}
|
|
14540
|
+
}
|
|
14541
|
+
}
|
|
14542
|
+
}
|
|
14543
|
+
}
|
|
14544
|
+
}
|
|
14545
|
+
},
|
|
14546
|
+
"/v1/connect/providers": {
|
|
14547
|
+
"get": {
|
|
14548
|
+
"operationId": "listConnectProviders",
|
|
14549
|
+
"summary": "List Connect channels",
|
|
14550
|
+
"description": "Returns the public registry of every channel the picker supports. No customer-specific data \u2014 display metadata only. Cached for 5 minutes at the edge.",
|
|
14551
|
+
"tags": [
|
|
14552
|
+
"Connect"
|
|
14553
|
+
],
|
|
14554
|
+
"security": [],
|
|
13500
14555
|
"responses": {
|
|
13501
14556
|
"200": {
|
|
13502
14557
|
"description": "Channel registry",
|
|
@@ -14414,30 +15469,6 @@
|
|
|
14414
15469
|
}
|
|
14415
15470
|
}
|
|
14416
15471
|
},
|
|
14417
|
-
"/v1/health/atlas": {
|
|
14418
|
-
"get": {
|
|
14419
|
-
"operationId": "getAtlasHealth",
|
|
14420
|
-
"summary": "Atlas market-intelligence backend health",
|
|
14421
|
-
"description": "Component-level probe. `GET /v1/health` reports the API as a whole; this reports one dependency so an incident can be localised without guessing.",
|
|
14422
|
-
"tags": [
|
|
14423
|
-
"Health"
|
|
14424
|
-
],
|
|
14425
|
-
"security": [],
|
|
14426
|
-
"responses": {
|
|
14427
|
-
"200": {
|
|
14428
|
-
"description": "Component status",
|
|
14429
|
-
"content": {
|
|
14430
|
-
"application/json": {
|
|
14431
|
-
"schema": {
|
|
14432
|
-
"type": "object",
|
|
14433
|
-
"additionalProperties": true
|
|
14434
|
-
}
|
|
14435
|
-
}
|
|
14436
|
-
}
|
|
14437
|
-
}
|
|
14438
|
-
}
|
|
14439
|
-
}
|
|
14440
|
-
},
|
|
14441
15472
|
"/v1/health/auth": {
|
|
14442
15473
|
"get": {
|
|
14443
15474
|
"operationId": "getAuthHealth",
|
|
@@ -14808,7 +15839,13 @@
|
|
|
14808
15839
|
"$ref": "#/components/responses/NotFound"
|
|
14809
15840
|
},
|
|
14810
15841
|
"422": {
|
|
14811
|
-
"$ref": "#/components/responses/
|
|
15842
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
15843
|
+
},
|
|
15844
|
+
"429": {
|
|
15845
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
15846
|
+
},
|
|
15847
|
+
"502": {
|
|
15848
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
14812
15849
|
}
|
|
14813
15850
|
}
|
|
14814
15851
|
}
|
|
@@ -15699,7 +16736,7 @@
|
|
|
15699
16736
|
"post": {
|
|
15700
16737
|
"operationId": "airbnb_listing_action",
|
|
15701
16738
|
"summary": "Listing action (delete/push/publish/unlist/relist)",
|
|
15702
|
-
"description": "Apply a state action to a listing by id. The path `id` is the canonical Repull listing id.\n\n
|
|
16739
|
+
"description": "Apply a state action to a listing by id. The path `id` is the canonical Repull listing id.\n\n**`delete` here never touches Airbnb. Read this before you call it.**\n\n| | `action: \"delete\"` (this endpoint) | `action: \"unlist\"` (this endpoint) |\n|---|---|---|\n| What it changes | The Repull record | The live Airbnb listing |\n| Calls Airbnb | **No. Never.** | Yes |\n| The guest-facing listing | Stays live and keeps taking bookings | **Goes down** and stops taking bookings |\n| Billing and plan limits | No longer billed, no longer counts toward the cap | Unchanged |\n| API access to the listing | `403 listing_inactive` until reactivated | Unchanged \u2014 you can still read and write it |\n| Reverse it with | `PATCH /v1/listings/{id}` `{ \"active\": true }` | `action: \"relist\"` |\n| Data kept | Yes, and it keeps syncing | Yes |\n\nNeither one deletes anything on Airbnb. **There is no endpoint on this API that deletes an Airbnb listing** \u2014 the word `delete` on this route means \"deactivate the Repull record\" and nothing else. (Main vanio's internal listing-sync layer has a same-named action that DOES hard-delete on Airbnb; it is not exposed here, by any endpoint, deliberately. If you have read that code, note that the two names do not mean the same thing.)\n\n`delete` is idempotent. To take a listing off the market on every channel at once \u2014 Airbnb and Booking.com together \u2014 use `POST /v1/listings/{id}/offline`.\n\n`unlist` calls Airbnb and **takes the live listing down**: it is deactivated with a valid deactivation reason and then READ BACK, so \"Airbnb accepted the call but the listing is still live\" is reported as a failure rather than a success. Requires `airbnbConnectionId` \u2014 a listing can be connected to more than one Airbnb listing, and taking down the wrong one is not undoable through this API. `relist` puts it back up (re-enables sync and makes the listing available again); it does not push content.\n\n`relist` goes through the channel-publish billing gate and `unlist` does not, so on a workspace whose subscription has lapsed a listing can be taken down and not put back until billing is sorted out. That refusal comes back as `402` with the action that fixes it \u2014 never as an Airbnb error, because retrying and reconnecting Airbnb do nothing for it.\n\n`push` / `publish` push the listing's content to Airbnb via the same host-side sync orchestrator as `POST /v1/listings/{id}/publish/airbnb` \u2014 pass `airbnbConnectionId` to update an already-mapped Airbnb listing, or `hostId` to create + publish a new one under that host. `force` re-pushes every field, ignoring dirty-field tracking. The result is per-section: see `AirbnbPublishResult`.\n\nAny other action (e.g. `pull`) returns a structured 422 naming the supported actions.\n\nReturns `403 listing_inactive` for `push`/`publish`/`unlist`/`relist` when the listing is inactive. `delete` (deactivation) is always accepted.",
|
|
15703
16740
|
"tags": [
|
|
15704
16741
|
"Airbnb"
|
|
15705
16742
|
],
|
|
@@ -15770,6 +16807,9 @@
|
|
|
15770
16807
|
}
|
|
15771
16808
|
}
|
|
15772
16809
|
},
|
|
16810
|
+
"402": {
|
|
16811
|
+
"$ref": "#/components/responses/PublishBillingRefused"
|
|
16812
|
+
},
|
|
15773
16813
|
"403": {
|
|
15774
16814
|
"$ref": "#/components/responses/AirbnbWriteForbidden"
|
|
15775
16815
|
},
|
|
@@ -16475,7 +17515,7 @@
|
|
|
16475
17515
|
"post": {
|
|
16476
17516
|
"operationId": "send_airbnb_message",
|
|
16477
17517
|
"summary": "Send Airbnb message",
|
|
16478
|
-
"description": "Send a message in an Airbnb thread as the host. Airbnb enforces content rules (no off-platform contact info, no external URLs) \u2014 violating messages are rejected upstream and surface as `airbnb_error
|
|
17518
|
+
"description": "Send a message in an Airbnb thread as the host. Airbnb enforces content rules (no off-platform contact info, no external URLs) \u2014 violating messages are rejected upstream and surface as `422 airbnb_rejected` carrying Airbnb's own reason. Resending the same text is refused again; edit it first. `502 airbnb_error` is the other answer and means something else entirely: Airbnb did not complete the send, so retry it unchanged.\n\n### Sending a photo or video (`mediaUrl`)\n\nAirbnb only accepts media uploaded to a signed URL it issues, one file per message and no text on the same message. With `mediaUrl`, Repull downloads the file (public `https://` only, 10 MB max), reads its real type from the bytes (JPEG, PNG, GIF, WebP \u2014 converted to JPEG \u2014 or MP4/QuickTime), uploads it to Airbnb and sends it; `message`, if given, follows as a separate message. This is the same flow as `POST /v1/conversations/{id}/messages` with `attachments` \u2014 prefer that endpoint, which also takes several files per request. The response is a `SendMessageResponse`, the send is recorded in the conversation, and failures are the 422 codes documented there (`attachment_type_not_supported`, `attachment_too_large`, `message_not_sent` for a pre-booking thread, \u2026). The thread must already be synced to Repull (`GET /v1/conversations` lists them), otherwise `404`.\n\nText-only sends (no `mediaUrl`) go straight to Airbnb and return Airbnb's message object.\n\nThe `{threadId}` is the Airbnb thread id \u2014 the `externalThreadId` field on a unified `Conversation` (`GET /v1/conversations`).\n\nReturns `403 listing_inactive` when the listing this resolves to is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.",
|
|
16479
17519
|
"tags": [
|
|
16480
17520
|
"Airbnb"
|
|
16481
17521
|
],
|
|
@@ -16548,17 +17588,16 @@
|
|
|
16548
17588
|
"$ref": "#/components/responses/NotFound"
|
|
16549
17589
|
},
|
|
16550
17590
|
"422": {
|
|
16551
|
-
"
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
"$ref": "#/components/schemas/Error"
|
|
16556
|
-
}
|
|
16557
|
-
}
|
|
16558
|
-
}
|
|
17591
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
17592
|
+
},
|
|
17593
|
+
"429": {
|
|
17594
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
16559
17595
|
},
|
|
16560
17596
|
"500": {
|
|
16561
17597
|
"$ref": "#/components/responses/InternalError"
|
|
17598
|
+
},
|
|
17599
|
+
"502": {
|
|
17600
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
16562
17601
|
}
|
|
16563
17602
|
}
|
|
16564
17603
|
}
|
|
@@ -16707,7 +17746,7 @@
|
|
|
16707
17746
|
"post": {
|
|
16708
17747
|
"operationId": "airbnb_reservation_action",
|
|
16709
17748
|
"summary": "Accept, decline or cancel an Airbnb reservation",
|
|
16710
|
-
"description": "Act on an Airbnb reservation by its Airbnb confirmation code. **Write-side** \u2014 calls Airbnb upstream, as the Airbnb account that owns the booking.\n\n- `accept` \u2014 accept a pending booking request.\n- `decline` \u2014 decline a pending booking request. Requires `reason` (one of Airbnb's decline reasons) and `message` (sent to the guest, at most 500 characters).\n- `cancel` \u2014 cancel a confirmed booking as the host. Requires `reason` (one of Airbnb's host-cancellation reasons). **Host cancellations carry Airbnb penalties.**\n\nThe body is validated before anything reaches Airbnb; unknown fields are refused. There is no `pre-approve` action: a pre-approval answers an inquiry, which has no confirmation code \u2014 use `POST /v1/conversations/{id}/pre-approval`. For accept/decline, `POST /v1/reservations/{id}/accept` and `/decline` do the same by Repull id and
|
|
17749
|
+
"description": "Act on an Airbnb reservation by its Airbnb confirmation code. **Write-side** \u2014 calls Airbnb upstream, as the Airbnb account that owns the booking.\n\n- `accept` \u2014 accept a pending booking request.\n- `decline` \u2014 decline a pending booking request. Requires `reason` (one of Airbnb's decline reasons) and `message` (sent to the guest, at most 500 characters).\n- `cancel` \u2014 cancel a confirmed booking as the host. Requires `reason` (one of Airbnb's host-cancellation reasons). **Host cancellations carry Airbnb penalties.**\n\nThe body is validated before anything reaches Airbnb; unknown fields are refused. There is no `pre-approve` action: a pre-approval answers an inquiry, which has no confirmation code \u2014 use `POST /v1/conversations/{id}/pre-approval`. For accept/decline, `POST /v1/reservations/{id}/accept` and `/decline` do the same by Repull id and keep the reservation in Repull in sync.\n\nAirbnb refusals are mapped rather than returned as a 500: a request that already moved on is `409 request_no_longer_pending` (do not retry), an expired one `409 request_expired`, any other refusal `422 airbnb_rejected` with Airbnb's reason.\n\nReturns `403 listing_inactive` when the listing this resolves to is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.\n\nSend `Idempotency-Key` to make a retry safe.",
|
|
16711
17750
|
"tags": [
|
|
16712
17751
|
"Airbnb"
|
|
16713
17752
|
],
|
|
@@ -17065,6 +18104,15 @@
|
|
|
17065
18104
|
},
|
|
17066
18105
|
"403": {
|
|
17067
18106
|
"$ref": "#/components/responses/ListingInactive"
|
|
18107
|
+
},
|
|
18108
|
+
"422": {
|
|
18109
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
18110
|
+
},
|
|
18111
|
+
"429": {
|
|
18112
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
18113
|
+
},
|
|
18114
|
+
"502": {
|
|
18115
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
17068
18116
|
}
|
|
17069
18117
|
}
|
|
17070
18118
|
}
|
|
@@ -17119,10 +18167,16 @@
|
|
|
17119
18167
|
"$ref": "#/components/responses/NotFound"
|
|
17120
18168
|
},
|
|
17121
18169
|
"422": {
|
|
17122
|
-
"$ref": "#/components/responses/
|
|
18170
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
18171
|
+
},
|
|
18172
|
+
"429": {
|
|
18173
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
17123
18174
|
},
|
|
17124
18175
|
"500": {
|
|
17125
18176
|
"$ref": "#/components/responses/InternalError"
|
|
18177
|
+
},
|
|
18178
|
+
"502": {
|
|
18179
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
17126
18180
|
}
|
|
17127
18181
|
}
|
|
17128
18182
|
}
|
|
@@ -17187,10 +18241,16 @@
|
|
|
17187
18241
|
"$ref": "#/components/responses/NotFound"
|
|
17188
18242
|
},
|
|
17189
18243
|
"422": {
|
|
17190
|
-
"$ref": "#/components/responses/
|
|
18244
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
18245
|
+
},
|
|
18246
|
+
"429": {
|
|
18247
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
17191
18248
|
},
|
|
17192
18249
|
"500": {
|
|
17193
18250
|
"$ref": "#/components/responses/InternalError"
|
|
18251
|
+
},
|
|
18252
|
+
"502": {
|
|
18253
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
17194
18254
|
}
|
|
17195
18255
|
}
|
|
17196
18256
|
}
|
|
@@ -17424,8 +18484,17 @@
|
|
|
17424
18484
|
"404": {
|
|
17425
18485
|
"$ref": "#/components/responses/NotFound"
|
|
17426
18486
|
},
|
|
18487
|
+
"422": {
|
|
18488
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
18489
|
+
},
|
|
18490
|
+
"429": {
|
|
18491
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
18492
|
+
},
|
|
17427
18493
|
"500": {
|
|
17428
18494
|
"$ref": "#/components/responses/InternalError"
|
|
18495
|
+
},
|
|
18496
|
+
"502": {
|
|
18497
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
17429
18498
|
}
|
|
17430
18499
|
}
|
|
17431
18500
|
}
|
|
@@ -17474,8 +18543,17 @@
|
|
|
17474
18543
|
"404": {
|
|
17475
18544
|
"$ref": "#/components/responses/NotFound"
|
|
17476
18545
|
},
|
|
18546
|
+
"422": {
|
|
18547
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
18548
|
+
},
|
|
18549
|
+
"429": {
|
|
18550
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
18551
|
+
},
|
|
17477
18552
|
"500": {
|
|
17478
18553
|
"$ref": "#/components/responses/InternalError"
|
|
18554
|
+
},
|
|
18555
|
+
"502": {
|
|
18556
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
17479
18557
|
}
|
|
17480
18558
|
}
|
|
17481
18559
|
}
|
|
@@ -17741,10 +18819,16 @@
|
|
|
17741
18819
|
"$ref": "#/components/responses/NotFound"
|
|
17742
18820
|
},
|
|
17743
18821
|
"422": {
|
|
17744
|
-
"$ref": "#/components/responses/
|
|
18822
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
18823
|
+
},
|
|
18824
|
+
"429": {
|
|
18825
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
17745
18826
|
},
|
|
17746
18827
|
"500": {
|
|
17747
18828
|
"$ref": "#/components/responses/InternalError"
|
|
18829
|
+
},
|
|
18830
|
+
"502": {
|
|
18831
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
17748
18832
|
}
|
|
17749
18833
|
}
|
|
17750
18834
|
}
|
|
@@ -18084,8 +19168,17 @@
|
|
|
18084
19168
|
"404": {
|
|
18085
19169
|
"$ref": "#/components/responses/NotFound"
|
|
18086
19170
|
},
|
|
19171
|
+
"422": {
|
|
19172
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
19173
|
+
},
|
|
19174
|
+
"429": {
|
|
19175
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
19176
|
+
},
|
|
18087
19177
|
"500": {
|
|
18088
19178
|
"$ref": "#/components/responses/InternalError"
|
|
19179
|
+
},
|
|
19180
|
+
"502": {
|
|
19181
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
18089
19182
|
}
|
|
18090
19183
|
}
|
|
18091
19184
|
}
|
|
@@ -20161,7 +21254,7 @@
|
|
|
20161
21254
|
"post": {
|
|
20162
21255
|
"operationId": "publishListingToAirbnb",
|
|
20163
21256
|
"summary": "Publish a listing to Airbnb",
|
|
20164
|
-
"description": "Push a Repull listing's canonical content to Airbnb. Pass `airbnbConnectionId` to update an already-mapped Airbnb listing, or `hostId` to create a brand-new Airbnb listing under that host.\n\n**A publish is not one call to Airbnb.** It is up to eight independent ones \u2014 details, description, amenities, rooms, policies, photos, pricing, checkout_tasks \u2014 and each can fail on its own. `result.published` is true only when every attempted section landed; `result.sections` lists the ones that did and `result.errors[]` carries Airbnb's own reason, per section, for the ones that did not. **A partial publish is normal and is not rolled back**: what succeeded stays applied. Publish again once you have fixed the failing sections \u2014 a re-publish of an unchanged section is harmless.\n\n`result.lockedFields` names the fields Airbnb will not let this listing change at all. They are not retryable by anyone: Airbnb answers 200 and applies nothing. `GET /v1/channels/airbnb/listings/{id}` reports the same list up front.\n\n**Which fields this pushes** \u2014 title, description sections and house rules (English/primary locale), amenities, rooms and beds, photos, nightly price and fees, cancellation policy and guest controls, check-in/out times, quiet hours, property and room type, checkout tasks. **Not pushed by this endpoint:** non-primary locales (`PUT /v1/channels/airbnb/listings/{id}/descriptions`), guest-safety disclosures (`PUT \u2026/safety-disclosures`), check-in method (`PUT \u2026/details`), permits (`PUT \u2026/permits`), and the calendar (`PUT \u2026/availability`).\n\n`force: true` re-pushes every section, ignoring dirty-field tracking. Without it only the sections changed since the last successful publish are sent.\n\nSend `Idempotency-Key` to make a retry safe: a timeout on a publish otherwise leaves you unable to tell whether it ran.\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.",
|
|
21257
|
+
"description": "Push a Repull listing's canonical content to Airbnb. Pass `airbnbConnectionId` to update an already-mapped Airbnb listing, or `hostId` to create a brand-new Airbnb listing under that host.\n\n**A publish is not one call to Airbnb.** It is up to eight independent ones \u2014 details, description, amenities, rooms, policies, photos, pricing, checkout_tasks \u2014 and each can fail on its own. `result.published` is true only when every attempted section landed; `result.sections` lists the ones that did and `result.errors[]` carries Airbnb's own reason, per section, for the ones that did not. **A partial publish is normal and is not rolled back**: what succeeded stays applied. Publish again once you have fixed the failing sections \u2014 a re-publish of an unchanged section is harmless.\n\n`result.live` is a different question from `result.published`. `published` is about CONTENT \u2014 every attempted section landed. `live` is about whether the listing takes bookings: it is true only when activation was actually performed and succeeded. A create can land all eight sections and still leave the listing inactive, because activation is skipped when instant-booking cannot be confirmed to be off \u2014 so `published: true` with `live: false` is a real and common outcome, and `result.warnings` says why. `live` is ABSENT, not `false`, when activation was never part of the operation: publishing to an already-mapped listing updates content and activates nothing. Only treat a listing as not-live when `live` is present and false.\n\n`result.warnings[]` lists steps that failed WITHOUT failing the publish \u2014 optional work the push carried on past. They were previously swallowed, so the only sign of one was a listing that was somehow not quite right afterwards. A publish can be `published: true` and still carry warnings; read them before concluding nothing needs doing.\n\n`result.lockedFields` names the fields Airbnb will not let this listing change at all. They are not retryable by anyone: Airbnb answers 200 and applies nothing. `GET /v1/channels/airbnb/listings/{id}` reports the same list up front.\n\n**Which fields this pushes** \u2014 title, description sections and house rules (English/primary locale), amenities, rooms and beds, photos, nightly price and fees, cancellation policy and guest controls, check-in/out times, quiet hours, property and room type, checkout tasks. **Not pushed by this endpoint:** non-primary locales (`PUT /v1/channels/airbnb/listings/{id}/descriptions`), guest-safety disclosures (`PUT \u2026/safety-disclosures`), check-in method (`PUT \u2026/details`), permits (`PUT \u2026/permits`), and the calendar (`PUT \u2026/availability`).\n\n`force: true` re-pushes every section, ignoring dirty-field tracking. Without it only the sections changed since the last successful publish are sent.\n\nSend `Idempotency-Key` to make a retry safe: a timeout on a publish otherwise leaves you unable to tell whether it ran.\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.",
|
|
20165
21258
|
"tags": [
|
|
20166
21259
|
"Listings"
|
|
20167
21260
|
],
|
|
@@ -20201,6 +21294,9 @@
|
|
|
20201
21294
|
"400": {
|
|
20202
21295
|
"$ref": "#/components/responses/BadRequest"
|
|
20203
21296
|
},
|
|
21297
|
+
"402": {
|
|
21298
|
+
"$ref": "#/components/responses/PublishBillingRefused"
|
|
21299
|
+
},
|
|
20204
21300
|
"403": {
|
|
20205
21301
|
"$ref": "#/components/responses/ListingInactive"
|
|
20206
21302
|
},
|
|
@@ -20214,7 +21310,7 @@
|
|
|
20214
21310
|
"post": {
|
|
20215
21311
|
"operationId": "publishListingToBooking",
|
|
20216
21312
|
"summary": "Publish a listing to Booking.com",
|
|
20217
|
-
"description": "Push a Repull listing to Booking.com. The listing must already be mapped to a Booking property + room
|
|
21313
|
+
"description": "Push a Repull listing's content to Booking.com. The listing must already be mapped to a Booking.com property + room \u2014 claim the hotel through the Connect Booking flow, then map its rooms with `POST /v1/connect/booking/map-rooms`.\n\n**Which property the content lands in.** A listing can be mapped to more than one Booking.com property; the same unit re-listed under a new property keeps its old mapping, and workspaces routinely sit on five or six. When the listing has exactly one property you need send nothing. When it has several, name one with `hotelId` in the body (or `?hotel_id=` \u2014 the same value, accepted either way, body wins if you send both). Omit it on such a listing and the push is refused with **`409 ambiguous_booking_mapping`**, listing the candidate ids: content pushed into a property chosen for you lands on the wrong listing and reports success, which is worse than a refusal. `GET /v1/channels/booking/properties` lists every property with the listings mapped under it. Naming a property this listing is not mapped to is a `404` that names the ones it is.\n\nThe property that actually received the content comes back as `result.hotelId`.\n\n**A publish is not one call to Booking.com.** It is several independent Content API calls \u2014 details, description, amenities, rooms, photos, pricing \u2014 and each can fail on its own. `result.published` is true only when every attempted section landed; `result.sections` lists the ones that did and `result.errors[]` carries Booking.com's own reason, per section, for the ones that did not. A property whose Content API credentials do not cover a section answers 403 for that section alone. **A partial publish is normal and is not rolled back**: what succeeded stays applied. Fix the failing sections and publish again \u2014 re-publishing an unchanged section is harmless.\n\nA listing with no Booking.com property mapped at all is not an error: the call returns `result.published: false` with `result.reason` and `result.hotelId: null`, and nothing is pushed.\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.",
|
|
20218
21314
|
"tags": [
|
|
20219
21315
|
"Listings"
|
|
20220
21316
|
],
|
|
@@ -20225,16 +21321,36 @@
|
|
|
20225
21321
|
"required": true,
|
|
20226
21322
|
"schema": {
|
|
20227
21323
|
"type": "integer"
|
|
20228
|
-
}
|
|
21324
|
+
},
|
|
21325
|
+
"description": "Repull listing id \u2014 NOT a Booking.com hotel id."
|
|
21326
|
+
},
|
|
21327
|
+
{
|
|
21328
|
+
"name": "hotel_id",
|
|
21329
|
+
"in": "query",
|
|
21330
|
+
"required": false,
|
|
21331
|
+
"schema": {
|
|
21332
|
+
"type": "string"
|
|
21333
|
+
},
|
|
21334
|
+
"description": "Booking.com property to publish into, for a listing mapped to more than one. The query-string spelling of the body's `hotelId`, accepted so this route reads the same as every other Booking listing-addressed route. The body wins when both are sent."
|
|
20229
21335
|
}
|
|
20230
21336
|
],
|
|
21337
|
+
"requestBody": {
|
|
21338
|
+
"required": false,
|
|
21339
|
+
"content": {
|
|
21340
|
+
"application/json": {
|
|
21341
|
+
"schema": {
|
|
21342
|
+
"$ref": "#/components/schemas/ListingPublishBookingRequest"
|
|
21343
|
+
}
|
|
21344
|
+
}
|
|
21345
|
+
}
|
|
21346
|
+
},
|
|
20231
21347
|
"responses": {
|
|
20232
21348
|
"200": {
|
|
20233
|
-
"description": "
|
|
21349
|
+
"description": "Publish attempted \u2014 read `result.published`, `result.errors` and `result.hotelId`",
|
|
20234
21350
|
"content": {
|
|
20235
21351
|
"application/json": {
|
|
20236
21352
|
"schema": {
|
|
20237
|
-
"$ref": "#/components/schemas/
|
|
21353
|
+
"$ref": "#/components/schemas/ListingPublishBookingResponse"
|
|
20238
21354
|
}
|
|
20239
21355
|
}
|
|
20240
21356
|
}
|
|
@@ -20242,8 +21358,17 @@
|
|
|
20242
21358
|
"400": {
|
|
20243
21359
|
"$ref": "#/components/responses/BadRequest"
|
|
20244
21360
|
},
|
|
21361
|
+
"402": {
|
|
21362
|
+
"$ref": "#/components/responses/PublishBillingRefused"
|
|
21363
|
+
},
|
|
20245
21364
|
"403": {
|
|
20246
21365
|
"$ref": "#/components/responses/ListingInactive"
|
|
21366
|
+
},
|
|
21367
|
+
"404": {
|
|
21368
|
+
"$ref": "#/components/responses/NotFound"
|
|
21369
|
+
},
|
|
21370
|
+
"409": {
|
|
21371
|
+
"$ref": "#/components/responses/Conflict"
|
|
20247
21372
|
}
|
|
20248
21373
|
}
|
|
20249
21374
|
}
|
|
@@ -20252,7 +21377,7 @@
|
|
|
20252
21377
|
"get": {
|
|
20253
21378
|
"operationId": "getListingPublishStatus",
|
|
20254
21379
|
"summary": "Per-channel publish status",
|
|
20255
|
-
"description": "Returns connection state and sync activity per channel. `channels` is sync activity (empty until first push). `connections` is connection state (populated as soon as a channel is linked). Recommended polling cadence: at most once per 30s per listing \u2014 for bulk views, prefer `GET /v1/listings` and filter client-side.\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.",
|
|
21380
|
+
"description": "Returns connection state and sync activity per channel. `channels` is sync activity (empty until first push). `connections` is connection state (populated as soon as a channel is linked). Recommended polling cadence: at most once per 30s per listing \u2014 for bulk views, prefer `GET /v1/listings` and filter client-side.\n\n**When a push fails, this endpoint says why.** `channels[].pushError` carries the channel's own reason for the last failed push, verbatim \u2014 `\"Links and contact info can't be shared\"`, `\"Check-in start time must be before end time\"`, `\"property_type_group must be one of [\u2026]\"`. It is free text written by the channel, so render it next to the retry button rather than parsing it. `null` when the last push succeeded or none has run; pair it with `pushStatus` to tell those two apart.\n\n**It also says what you will not be allowed to change.** The `airbnb` entry in `connections` carries `lockedFields` \u2014 attributes Airbnb has locked on this listing. Airbnb does not refuse a write to one: it answers 200, reports the field as locked, and applies nothing, so a locked write is indistinguishable from a successful one unless you looked first. Read it before you let someone edit. Airbnb-only; no other channel has the concept, and no other entry carries the field.\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.",
|
|
20256
21381
|
"tags": [
|
|
20257
21382
|
"Listings"
|
|
20258
21383
|
],
|
|
@@ -21216,31 +22341,23 @@
|
|
|
21216
22341
|
"$ref": "#/components/responses/InternalError"
|
|
21217
22342
|
}
|
|
21218
22343
|
}
|
|
21219
|
-
}
|
|
21220
|
-
|
|
21221
|
-
|
|
21222
|
-
|
|
21223
|
-
"
|
|
21224
|
-
"summary": "List Booking.com reservations",
|
|
21225
|
-
"description": "Pull reservations from Booking.com. `type=new` (default) returns un-acknowledged bookings; `type=modified` returns changed bookings. Pass both `reservation_id` and `hotel_id` to fetch a single reservation's full details. Acknowledge processed reservations with the POST so Booking stops re-serving them in the `new` queue.\n\nScoped to this workspace. `hotel_id` (or its alias `property_id`) must be a property connected to this workspace; any other id returns `404 not_found`, the same as an id that does not exist. Without a hotel, `new`/`modified` cover every Booking.com property this workspace holds (and return `404 not_found` if it holds none). A `reservation_id` that belongs to another workspace returns `404 not_found`.\n\nReturns `403 listing_inactive` when any listing mapped to the Booking.com property is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.",
|
|
22344
|
+
},
|
|
22345
|
+
"post": {
|
|
22346
|
+
"operationId": "booking_property_action",
|
|
22347
|
+
"summary": "Take a property off sale / put it back (unlist/relist)",
|
|
22348
|
+
"description": "Stop this listing's Booking.com property being sold, or start it again. `id` is a **Repull listing id**, not a Booking.com hotel id, as on the GET.\n\n**Booking.com has no unlist, so this is an availability write.** Airbnb has a real deactivate; Booking.com does not. `unlist` closes the mapped room across the whole forward window, so the property stops selling. `relist` is not its mirror image: it re-syncs the true calendar, so dates that are genuinely blocked (a reservation, an owner stay) stay blocked and only the closure `unlist` wrote lifts. Re-opening everything would sell dates that are not for sale.\n\n**Which property gets closed.** A listing can be mapped to more than one Booking.com property \u2014 the same unit re-listed under a new property keeps its old mapping, and workspaces routinely sit on five or six. With exactly one, send nothing. With several, name one with `hotelId` (or `?hotel_id=`); omit it and the request is refused with **`409 ambiguous_booking_mapping`** listing the candidates, and nothing is written. That refusal matters more here than on a publish: writing content into the wrong property is recoverable, closing the wrong property's availability takes real inventory off sale while the property you meant keeps selling. Naming a property this listing is not mapped to is a `404` that names the ones it is.\n\n**This does not change the listing in Repull.** `active` \u2014 what Repull bills and serves \u2014 is untouched by both actions and is deliberately not echoed in the response, so the two ideas can never be read as one field. To take a listing off the market on every channel at once, use `POST /v1/listings/{id}/offline`.\n\nAny other action returns a structured `422` naming the ones that are supported. To push content use `POST /v1/listings/{id}/publish/booking`; to map rooms use `POST /v1/connect/booking/map-rooms`.\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.",
|
|
21226
22349
|
"tags": [
|
|
21227
22350
|
"Booking.com"
|
|
21228
22351
|
],
|
|
21229
22352
|
"parameters": [
|
|
21230
22353
|
{
|
|
21231
|
-
"name": "
|
|
21232
|
-
"in": "
|
|
21233
|
-
"required":
|
|
22354
|
+
"name": "id",
|
|
22355
|
+
"in": "path",
|
|
22356
|
+
"required": true,
|
|
21234
22357
|
"schema": {
|
|
21235
|
-
"type": "
|
|
21236
|
-
"enum": [
|
|
21237
|
-
"new",
|
|
21238
|
-
"modified",
|
|
21239
|
-
"details"
|
|
21240
|
-
],
|
|
21241
|
-
"default": "new"
|
|
22358
|
+
"type": "integer"
|
|
21242
22359
|
},
|
|
21243
|
-
"description": "
|
|
22360
|
+
"description": "Repull listing id \u2014 NOT a Booking.com hotel id."
|
|
21244
22361
|
},
|
|
21245
22362
|
{
|
|
21246
22363
|
"name": "hotel_id",
|
|
@@ -21249,34 +22366,29 @@
|
|
|
21249
22366
|
"schema": {
|
|
21250
22367
|
"type": "string"
|
|
21251
22368
|
},
|
|
21252
|
-
"description": "Booking.com
|
|
22369
|
+
"description": "Booking.com property to act on, for a listing mapped to more than one. The query-string spelling of the body's `hotelId`; the body wins when both are sent."
|
|
21253
22370
|
},
|
|
21254
22371
|
{
|
|
21255
|
-
"
|
|
21256
|
-
"in": "query",
|
|
21257
|
-
"required": false,
|
|
21258
|
-
"schema": {
|
|
21259
|
-
"type": "string"
|
|
21260
|
-
},
|
|
21261
|
-
"description": "Booking.com reservation id \u2014 with `hotel_id`, returns that reservation's details."
|
|
22372
|
+
"$ref": "#/components/parameters/IdempotencyKey"
|
|
21262
22373
|
}
|
|
21263
22374
|
],
|
|
22375
|
+
"requestBody": {
|
|
22376
|
+
"required": true,
|
|
22377
|
+
"content": {
|
|
22378
|
+
"application/json": {
|
|
22379
|
+
"schema": {
|
|
22380
|
+
"$ref": "#/components/schemas/BookingPropertyActionRequest"
|
|
22381
|
+
}
|
|
22382
|
+
}
|
|
22383
|
+
}
|
|
22384
|
+
},
|
|
21264
22385
|
"responses": {
|
|
21265
22386
|
"200": {
|
|
21266
|
-
"description": "
|
|
22387
|
+
"description": "Done \u2014 `hotelId` names the property that changed and `selling` says whether it is now on sale",
|
|
21267
22388
|
"content": {
|
|
21268
22389
|
"application/json": {
|
|
21269
22390
|
"schema": {
|
|
21270
|
-
"
|
|
21271
|
-
{
|
|
21272
|
-
"$ref": "#/components/schemas/BookingReservation"
|
|
21273
|
-
},
|
|
21274
|
-
{
|
|
21275
|
-
"type": "object",
|
|
21276
|
-
"additionalProperties": true,
|
|
21277
|
-
"description": "Raw Booking.com OTA collection wrapper (`new`/`modified` list types) \u2014 reservations are nested under the connector's XML-derived collection shape as `BookingReservation` objects."
|
|
21278
|
-
}
|
|
21279
|
-
]
|
|
22391
|
+
"$ref": "#/components/schemas/BookingPropertyActionResponse"
|
|
21280
22392
|
}
|
|
21281
22393
|
}
|
|
21282
22394
|
}
|
|
@@ -21287,17 +22399,113 @@
|
|
|
21287
22399
|
"401": {
|
|
21288
22400
|
"$ref": "#/components/responses/Unauthorized"
|
|
21289
22401
|
},
|
|
22402
|
+
"402": {
|
|
22403
|
+
"$ref": "#/components/responses/PublishBillingRefused"
|
|
22404
|
+
},
|
|
21290
22405
|
"403": {
|
|
21291
22406
|
"$ref": "#/components/responses/ListingInactive"
|
|
21292
22407
|
},
|
|
21293
22408
|
"404": {
|
|
21294
22409
|
"$ref": "#/components/responses/NotFound"
|
|
21295
22410
|
},
|
|
21296
|
-
"
|
|
21297
|
-
"$ref": "#/components/responses/
|
|
21298
|
-
}
|
|
21299
|
-
|
|
21300
|
-
|
|
22411
|
+
"409": {
|
|
22412
|
+
"$ref": "#/components/responses/Conflict"
|
|
22413
|
+
},
|
|
22414
|
+
"422": {
|
|
22415
|
+
"$ref": "#/components/responses/BookingWriteRejected"
|
|
22416
|
+
},
|
|
22417
|
+
"500": {
|
|
22418
|
+
"$ref": "#/components/responses/InternalError"
|
|
22419
|
+
},
|
|
22420
|
+
"502": {
|
|
22421
|
+
"$ref": "#/components/responses/BookingUpstreamError"
|
|
22422
|
+
},
|
|
22423
|
+
"503": {
|
|
22424
|
+
"$ref": "#/components/responses/ChannelActionUnavailable"
|
|
22425
|
+
}
|
|
22426
|
+
}
|
|
22427
|
+
}
|
|
22428
|
+
},
|
|
22429
|
+
"/v1/channels/booking/reservations": {
|
|
22430
|
+
"get": {
|
|
22431
|
+
"operationId": "list_booking_reservations",
|
|
22432
|
+
"summary": "List Booking.com reservations",
|
|
22433
|
+
"description": "Pull reservations from Booking.com. `type=new` (default) returns un-acknowledged bookings; `type=modified` returns changed bookings. Pass both `reservation_id` and `hotel_id` to fetch a single reservation's full details. Acknowledge processed reservations with the POST so Booking stops re-serving them in the `new` queue.\n\nScoped to this workspace. `hotel_id` (or its alias `property_id`) must be a property connected to this workspace; any other id returns `404 not_found`, the same as an id that does not exist. Without a hotel, `new`/`modified` cover every Booking.com property this workspace holds (and return `404 not_found` if it holds none). A `reservation_id` that belongs to another workspace returns `404 not_found`.\n\nReturns `403 listing_inactive` when any listing mapped to the Booking.com property is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.",
|
|
22434
|
+
"tags": [
|
|
22435
|
+
"Booking.com"
|
|
22436
|
+
],
|
|
22437
|
+
"parameters": [
|
|
22438
|
+
{
|
|
22439
|
+
"name": "type",
|
|
22440
|
+
"in": "query",
|
|
22441
|
+
"required": false,
|
|
22442
|
+
"schema": {
|
|
22443
|
+
"type": "string",
|
|
22444
|
+
"enum": [
|
|
22445
|
+
"new",
|
|
22446
|
+
"modified",
|
|
22447
|
+
"details"
|
|
22448
|
+
],
|
|
22449
|
+
"default": "new"
|
|
22450
|
+
},
|
|
22451
|
+
"description": "Which set to pull. `details` requires `reservation_id` + `hotel_id`."
|
|
22452
|
+
},
|
|
22453
|
+
{
|
|
22454
|
+
"name": "hotel_id",
|
|
22455
|
+
"in": "query",
|
|
22456
|
+
"required": false,
|
|
22457
|
+
"schema": {
|
|
22458
|
+
"type": "string"
|
|
22459
|
+
},
|
|
22460
|
+
"description": "Booking.com hotel id \u2014 filters `new`/`modified`, and is required with `reservation_id` for details."
|
|
22461
|
+
},
|
|
22462
|
+
{
|
|
22463
|
+
"name": "reservation_id",
|
|
22464
|
+
"in": "query",
|
|
22465
|
+
"required": false,
|
|
22466
|
+
"schema": {
|
|
22467
|
+
"type": "string"
|
|
22468
|
+
},
|
|
22469
|
+
"description": "Booking.com reservation id \u2014 with `hotel_id`, returns that reservation's details."
|
|
22470
|
+
}
|
|
22471
|
+
],
|
|
22472
|
+
"responses": {
|
|
22473
|
+
"200": {
|
|
22474
|
+
"description": "Raw Booking.com reservation payload \u2014 passed through from the connector unmodified (NOT the unified `/v1/reservations` shape). With `type=details` (plus `reservation_id` + `hotel_id`) the body is a single `BookingReservation`; with `type=new`/`type=modified` it is the raw Booking OTA collection wrapper whose reservations are `BookingReservation` objects. See the `BookingReservation` schema for the per-reservation field set and caveats.",
|
|
22475
|
+
"content": {
|
|
22476
|
+
"application/json": {
|
|
22477
|
+
"schema": {
|
|
22478
|
+
"oneOf": [
|
|
22479
|
+
{
|
|
22480
|
+
"$ref": "#/components/schemas/BookingReservation"
|
|
22481
|
+
},
|
|
22482
|
+
{
|
|
22483
|
+
"type": "object",
|
|
22484
|
+
"additionalProperties": true,
|
|
22485
|
+
"description": "Raw Booking.com OTA collection wrapper (`new`/`modified` list types) \u2014 reservations are nested under the connector's XML-derived collection shape as `BookingReservation` objects."
|
|
22486
|
+
}
|
|
22487
|
+
]
|
|
22488
|
+
}
|
|
22489
|
+
}
|
|
22490
|
+
}
|
|
22491
|
+
},
|
|
22492
|
+
"400": {
|
|
22493
|
+
"$ref": "#/components/responses/BadRequest"
|
|
22494
|
+
},
|
|
22495
|
+
"401": {
|
|
22496
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
22497
|
+
},
|
|
22498
|
+
"403": {
|
|
22499
|
+
"$ref": "#/components/responses/ListingInactive"
|
|
22500
|
+
},
|
|
22501
|
+
"404": {
|
|
22502
|
+
"$ref": "#/components/responses/NotFound"
|
|
22503
|
+
},
|
|
22504
|
+
"500": {
|
|
22505
|
+
"$ref": "#/components/responses/InternalError"
|
|
22506
|
+
}
|
|
22507
|
+
}
|
|
22508
|
+
},
|
|
21301
22509
|
"post": {
|
|
21302
22510
|
"operationId": "acknowledge_booking_reservations",
|
|
21303
22511
|
"summary": "Acknowledge Booking.com reservations",
|
|
@@ -21351,7 +22559,7 @@
|
|
|
21351
22559
|
"post": {
|
|
21352
22560
|
"operationId": "booking_setup",
|
|
21353
22561
|
"summary": "Booking.com property setup actions",
|
|
21354
|
-
"description": "Action-router for
|
|
22562
|
+
"description": "Action-router for putting a property onto Booking.com \u2014 including building one from nothing. Select the step with `action`.\n\n## Opening a property\n\n- `create-property` \u2014 create a NEW Booking.com property for a Repull listing (`listing_id`). Creates the property, its first room, a rate plan and the room-rate product that makes the room sellable, seeds availability and rates, syncs the calendar, then sends the notification that starts Booking's validation. Returns 201.\n- `add-room` \u2014 add another room type (and its sellable product) to a property (`listing_id`, `property_id`). Returns 201.\n- `add-unit` \u2014 raise the number of identical units on an existing room (`listing_id`, `property_id`, `room_id`).\n- `advance` \u2014 re-send the summary notification for a property (`property_id`) to move it out of the \"XML: Being built\" stage.\n\n## Account and policy steps\n\n- `create-legal-entity` \u2014 register a legal entity directly (returns 201). Not normally needed: see the legal-entity rules below.\n- `check-legal-status` \u2014 always `404`. A legal entity's details are readable for any id on the connectivity-provider credentials every workspace shares, and nothing records which workspace registered which entity, so no entity can be shown to be yours. `create-property` resolves it for you.\n- `check-readiness` \u2014 check whether a property is ready to open (`property_id`).\n- `open-property` \u2014 open the property for sale (`property_id`).\n- `set-contacts` \u2014 set property contacts (`property_id`, `contacts`).\n- `set-policies` \u2014 set property policies (`property_id`, plus policy fields).\n\n## Three things about Booking.com that cost real money\n\n**A newly created property is NOT sellable.** Booking holds it at \"XML: Being built\" until it validates the summary notification. `create-property` sends that notification, but it can fail on its own after everything else succeeded \u2014 the response always reports `status: \"being_built\"` and `sellable: false`, never a guess. Use `advance` to re-send it, and check the Extranet for the stage.\n\n**A room with no ACTIVE rate plan is invisible.** Booking only renders rooms that have at least one active product linkage (room \u00d7 rate plan). A room can be created successfully, return a `roomId`, and never appear on the property page. If `rateId` comes back `null` from `create-property` or `add-room`, that is exactly what happened: activate a rate plan on the property in the Extranet, then add the room again.\n\n**The room name is shown to travellers.** It is taken from the listing's name and appears on the Booking.com property page. Internal nicknames belong on the property's partner reference, not on the room.\n\n## The legal entity is resolved, not asked for\n\nA property is created against the legal entity Booking.com contracts with, invoices and pays. You do not normally send one:\n\n1. If this workspace already creates properties under a legal entity, that one is reused. A second is never registered.\n2. If it has none and the request carries `legal_entity` (`company_name`, `legal_contact_name`, `legal_contact_email`), one is registered and used. Booking.com emails the legal contact a contract; creation only succeeds once it is signed.\n3. If it has none and no `legal_entity`, the request is refused with `422 legal_entity_required` naming the fields \u2014 a contracted company is never invented.\n\n`legal_entity_id` overrides all of that. An id that already carries another workspace's properties is refused with `403 legal_entity_not_yours` before anything is created.\n\n## What you do not control\n\nProperties are created against Booking's **production** target only. A test-target property cannot be sold through and there is no route back from one, so `target` is not a parameter \u2014 sending it changes nothing.\n\nThese are fixed on every created property and are not parameters: property category (Apartment), initial room count (1), and the property contact record (a placeholder name, email and phone). Set the real contacts afterwards with `set-contacts`. Latitude and longitude come from the listing and are adjusted slightly to clear Booking.com's duplicate detection \u2014 send the property's true position on the listing and do not pre-adjust it yourself.\n\nThe listing's name, check-in/check-out times, currency, capacity and price come from the listing. Its postal code is taken from the listing's own `postalCode`; when the listing has none, it falls back to a connected Airbnb listing. A listing with neither is created without a postal code, so set `postalCode` on the listing first.\n\n## Guards\n\nEvery action that takes a `property_id` requires a property connected to this workspace; any other id returns `404 not_found`. Every action that takes a `listing_id` requires a listing in this workspace; any other id returns `404 not_found`.\n\n`create-property` refuses a listing with no coordinates (`422 missing_coordinates`) before anything is created \u2014 creating a Booking.com property cannot be undone.\n\n`create-property` is subject to the same published-listing gate as the dashboard: no plan, or the plan's listing limit reached, returns `403 billing_error` with `used` and `limit`, and nothing is created.\n\nReturns `403 listing_inactive` when the listing \u2014 or any listing mapped to the Booking.com property \u2014 is inactive. An inactive listing keeps syncing, but cannot be read or changed through the API until it is activated.\n\nIf a property is created and a later step fails, the response is `422 booking_create_partial` carrying `property_id`. The property EXISTS. Do not retry `create-property`, which would open a second one \u2014 continue with `add-room` and `advance`.",
|
|
21355
22563
|
"tags": [
|
|
21356
22564
|
"Booking.com"
|
|
21357
22565
|
],
|
|
@@ -21368,6 +22576,10 @@
|
|
|
21368
22576
|
"action": {
|
|
21369
22577
|
"type": "string",
|
|
21370
22578
|
"enum": [
|
|
22579
|
+
"create-property",
|
|
22580
|
+
"add-room",
|
|
22581
|
+
"add-unit",
|
|
22582
|
+
"advance",
|
|
21371
22583
|
"create-legal-entity",
|
|
21372
22584
|
"check-legal-status",
|
|
21373
22585
|
"check-readiness",
|
|
@@ -21376,13 +22588,53 @@
|
|
|
21376
22588
|
"set-policies"
|
|
21377
22589
|
]
|
|
21378
22590
|
},
|
|
22591
|
+
"listing_id": {
|
|
22592
|
+
"type": "integer",
|
|
22593
|
+
"description": "Repull listing id \u2014 required for `create-property`, `add-room` and `add-unit`. NOT a Booking.com Hotel ID. `listingId` is accepted as an alias."
|
|
22594
|
+
},
|
|
21379
22595
|
"property_id": {
|
|
21380
22596
|
"type": "string",
|
|
21381
|
-
"description": "Booking.com
|
|
22597
|
+
"description": "Booking.com Hotel ID \u2014 required for `add-room`, `add-unit`, `advance`, and the readiness/open/contacts/policies actions."
|
|
22598
|
+
},
|
|
22599
|
+
"room_id": {
|
|
22600
|
+
"type": "integer",
|
|
22601
|
+
"description": "Booking.com room id \u2014 required for `add-unit`. `GET /v1/channels/booking/properties/{listingId}/rooms` lists them. `roomId` is accepted as an alias."
|
|
22602
|
+
},
|
|
22603
|
+
"legal_entity_id": {
|
|
22604
|
+
"type": "integer",
|
|
22605
|
+
"description": "Optional override for `create-property`. Omit it: the legal entity this workspace already uses is resolved automatically. An id that carries another workspace's properties is refused with `403 legal_entity_not_yours`. `legalEntityId` is accepted as an alias."
|
|
22606
|
+
},
|
|
22607
|
+
"legal_entity": {
|
|
22608
|
+
"type": "object",
|
|
22609
|
+
"description": "Used by `create-property` ONLY when this workspace has no legal entity yet \u2014 one is registered with Booking.com from these details and used for the property. Ignored when the workspace already has one, so a second is never registered.",
|
|
22610
|
+
"properties": {
|
|
22611
|
+
"company_name": {
|
|
22612
|
+
"type": "string"
|
|
22613
|
+
},
|
|
22614
|
+
"legal_contact_name": {
|
|
22615
|
+
"type": "string"
|
|
22616
|
+
},
|
|
22617
|
+
"legal_contact_email": {
|
|
22618
|
+
"type": "string",
|
|
22619
|
+
"format": "email"
|
|
22620
|
+
},
|
|
22621
|
+
"country": {
|
|
22622
|
+
"type": "string"
|
|
22623
|
+
},
|
|
22624
|
+
"city": {
|
|
22625
|
+
"type": "string"
|
|
22626
|
+
}
|
|
22627
|
+
},
|
|
22628
|
+
"required": [
|
|
22629
|
+
"company_name",
|
|
22630
|
+
"legal_contact_name",
|
|
22631
|
+
"legal_contact_email"
|
|
22632
|
+
],
|
|
22633
|
+
"additionalProperties": true
|
|
21382
22634
|
},
|
|
21383
22635
|
"leid": {
|
|
21384
22636
|
"type": "integer",
|
|
21385
|
-
"description": "Legal entity id \u2014 required for `check-legal-status
|
|
22637
|
+
"description": "Legal entity id \u2014 required for `check-legal-status`, which always answers 404."
|
|
21386
22638
|
},
|
|
21387
22639
|
"contacts": {
|
|
21388
22640
|
"type": "array",
|
|
@@ -21400,10 +22652,10 @@
|
|
|
21400
22652
|
},
|
|
21401
22653
|
"responses": {
|
|
21402
22654
|
"200": {
|
|
21403
|
-
"description": "Action result"
|
|
22655
|
+
"description": "Action result. `add-unit` returns `{ action, listingId, propertyId, roomId, units }`; `advance` returns `{ action, propertyId, checked, opened, sellable }`."
|
|
21404
22656
|
},
|
|
21405
22657
|
"201": {
|
|
21406
|
-
"description": "
|
|
22658
|
+
"description": "Created. `create-property` returns `{ action, listingId, propertyId, roomId, legalEntity: { id, source }, target, status, sellable, nextSteps }` \u2014 `status` is `being_built` and `sellable` is `false` until Booking.com validates the property. `add-room` returns `{ action, listingId, propertyId, roomId, rateId, guestFacingName, nextSteps }` \u2014 a `null` `rateId` means the room has no sellable product and will not appear on Booking.com. `create-legal-entity` returns Booking.com's response."
|
|
21407
22659
|
},
|
|
21408
22660
|
"400": {
|
|
21409
22661
|
"$ref": "#/components/responses/BadRequest"
|
|
@@ -21412,13 +22664,40 @@
|
|
|
21412
22664
|
"$ref": "#/components/responses/Unauthorized"
|
|
21413
22665
|
},
|
|
21414
22666
|
"403": {
|
|
21415
|
-
"
|
|
22667
|
+
"description": "`listing_inactive` \u2014 the listing is inactive. `billing_error` \u2014 no plan, or the published-listing limit is reached (carries `used` and `limit`). `legal_entity_not_yours` \u2014 the named legal entity carries another workspace's properties. Nothing was created in any case.",
|
|
22668
|
+
"content": {
|
|
22669
|
+
"application/json": {
|
|
22670
|
+
"schema": {
|
|
22671
|
+
"$ref": "#/components/schemas/Error"
|
|
22672
|
+
}
|
|
22673
|
+
}
|
|
22674
|
+
}
|
|
21416
22675
|
},
|
|
21417
22676
|
"404": {
|
|
21418
22677
|
"$ref": "#/components/responses/NotFound"
|
|
21419
22678
|
},
|
|
22679
|
+
"422": {
|
|
22680
|
+
"description": "`missing_coordinates` \u2014 the listing has no latitude/longitude and nothing was created. `legal_entity_required` \u2014 this workspace has no legal entity and the request carried no details to register one. `booking_create_partial` \u2014 the property WAS created (see `property_id`) and a later step failed; do not retry `create-property`. `booking_error` \u2014 Booking.com refused the request as sent.",
|
|
22681
|
+
"content": {
|
|
22682
|
+
"application/json": {
|
|
22683
|
+
"schema": {
|
|
22684
|
+
"$ref": "#/components/schemas/Error"
|
|
22685
|
+
}
|
|
22686
|
+
}
|
|
22687
|
+
}
|
|
22688
|
+
},
|
|
21420
22689
|
"500": {
|
|
21421
22690
|
"$ref": "#/components/responses/InternalError"
|
|
22691
|
+
},
|
|
22692
|
+
"502": {
|
|
22693
|
+
"description": "`upstream_error` / `service_misconfigured` \u2014 the setup service could not be reached or could not complete the action. Nothing was created on Booking.com.",
|
|
22694
|
+
"content": {
|
|
22695
|
+
"application/json": {
|
|
22696
|
+
"schema": {
|
|
22697
|
+
"$ref": "#/components/schemas/Error"
|
|
22698
|
+
}
|
|
22699
|
+
}
|
|
22700
|
+
}
|
|
21422
22701
|
}
|
|
21423
22702
|
}
|
|
21424
22703
|
}
|
|
@@ -23731,8 +25010,17 @@
|
|
|
23731
25010
|
"404": {
|
|
23732
25011
|
"$ref": "#/components/responses/NotFound"
|
|
23733
25012
|
},
|
|
25013
|
+
"422": {
|
|
25014
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
25015
|
+
},
|
|
25016
|
+
"429": {
|
|
25017
|
+
"$ref": "#/components/responses/AirbnbRateLimited"
|
|
25018
|
+
},
|
|
23734
25019
|
"500": {
|
|
23735
25020
|
"$ref": "#/components/responses/InternalError"
|
|
25021
|
+
},
|
|
25022
|
+
"502": {
|
|
25023
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
23736
25024
|
}
|
|
23737
25025
|
}
|
|
23738
25026
|
}
|
|
@@ -23786,10 +25074,13 @@
|
|
|
23786
25074
|
"$ref": "#/components/responses/Conflict"
|
|
23787
25075
|
},
|
|
23788
25076
|
"422": {
|
|
23789
|
-
"$ref": "#/components/responses/
|
|
25077
|
+
"$ref": "#/components/responses/AirbnbWriteRejected"
|
|
23790
25078
|
},
|
|
23791
25079
|
"429": {
|
|
23792
25080
|
"$ref": "#/components/responses/TooManyRequests"
|
|
25081
|
+
},
|
|
25082
|
+
"502": {
|
|
25083
|
+
"$ref": "#/components/responses/AirbnbUpstreamError"
|
|
23793
25084
|
}
|
|
23794
25085
|
}
|
|
23795
25086
|
}
|
|
@@ -24813,7 +26104,7 @@
|
|
|
24813
26104
|
"get": {
|
|
24814
26105
|
"operationId": "listAirbnbListingPermits",
|
|
24815
26106
|
"summary": "List Airbnb permits and licences",
|
|
24816
|
-
"description": "The regulatory permits, licences and registration numbers attached to an Airbnb listing.\n\n**DB-only by default.** `?source=cache` (the default) returns the permits as last mirrored by the sync worker \u2014 regulatory body, regulation type, status, permit number \u2014 with no upstream call.\n\n**`?source=live` also returns the QUESTIONS.** The mirror stores the RESULT of a permit, not what Airbnb asks for it, so a caller that is about to write needs `?source=live` once: it returns each permit
|
|
26107
|
+
"description": "The regulatory permits, licences and registration numbers attached to an Airbnb listing.\n\n**DB-only by default.** `?source=cache` (the default) returns the permits as last mirrored by the sync worker \u2014 regulatory body, regulation type, status, permit number \u2014 with no upstream call.\n\n**`?source=live` also returns the QUESTIONS.** The mirror stores the RESULT of a permit, not what Airbnb asks for it, so a caller that is about to write needs `?source=live` once: it returns each permit's `flows[]` with the `answer_key`, `type` and `choices` of every question, and the answers already on file. Airbnb refuses an `answer_key` it did not ask for on this listing, so this is not optional guesswork you can skip.\n\nReturns `404` when the listing has no Airbnb connection in this workspace, and `403 listing_inactive` when the listing is inactive.",
|
|
24817
26108
|
"tags": [
|
|
24818
26109
|
"Airbnb"
|
|
24819
26110
|
],
|
|
@@ -24888,7 +26179,7 @@
|
|
|
24888
26179
|
"put": {
|
|
24889
26180
|
"operationId": "updateAirbnbListingPermits",
|
|
24890
26181
|
"summary": "Answer Airbnb permit questions",
|
|
24891
|
-
"description": "Answer the regulatory permit questions for a listing \u2014 the licence or registration number a city requires to keep the listing up.\n\nRead the questions first with `GET \u2026/permits?source=live
|
|
26182
|
+
"description": "Answer the regulatory permit questions for a listing \u2014 the licence or registration number a city requires to keep the listing up.\n\nRead the questions first with `GET \u2026/permits?source=live`. For each permit, pick one of its `flows[]` and send its `slug` as `flow_slug`; key every answer by the question's `answer_key`, and let the question's `type` decide the value field (`text_value`, `attestation_value`, `radio_value`, `date_value` or `selected_options_value`). Answers are forwarded verbatim \u2014 nothing is defaulted or inferred, because a wrong licence number can take a listing down in a regulated city.\n\nSend `Idempotency-Key`: a timeout here leaves you unable to tell \"never arrived\" from \"arrived, response lost\", and this is a compliance filing.\n\nAirbnb refusing the answers (an unknown `answer_key`, a malformed licence number) is `422 airbnb_rejected` carrying Airbnb's own reason. An expired or revoked Airbnb connection is `403 connection_reauth_required`.",
|
|
24892
26183
|
"tags": [
|
|
24893
26184
|
"Airbnb"
|
|
24894
26185
|
],
|
|
@@ -25116,7 +26407,7 @@
|
|
|
25116
26407
|
"post": {
|
|
25117
26408
|
"operationId": "preapprove_conversation",
|
|
25118
26409
|
"summary": "Pre-approve an inquiry",
|
|
25119
|
-
"description": "Pre-approve the Airbnb inquiry on this conversation: the guest who asked about dates may now book them at the listed price, without waiting on you. To change the dates, guests or price, send a special offer instead (`POST /v1/conversations/{id}/special-offers`).\n\nFind inquiries that need an answer with `GET /v1/inquiries` (default `status=open`); each carries the `conversationId` to use here.\n\n**Airbnb only**, and only for listings connected to Airbnb directly. A Booking.com, VRBO or direct-booking conversation, or an Airbnb one relayed through a PMS (Hostaway, Guesty), returns `422 channel_not_supported` and nothing is sent.\n\
|
|
26410
|
+
"description": "Pre-approve the Airbnb inquiry on this conversation: the guest who asked about dates may now book them at the listed price, without waiting on you. To change the dates, guests or price, send a special offer instead (`POST /v1/conversations/{id}/special-offers`).\n\nFind inquiries that need an answer with `GET /v1/inquiries` (default `status=open`); each carries the `conversationId` to use here.\n\n**Airbnb only**, and only for listings connected to Airbnb directly. A Booking.com, VRBO or direct-booking conversation, or an Airbnb one relayed through a PMS (Hostaway, Guesty), returns `422 channel_not_supported` and nothing is sent.\n\nThe inquiry is marked `pre_approved` everywhere, the same as pre-approving in Airbnb.\n\nAn Airbnb refusal is never reported as a success: an inquiry that already moved on is `409 inquiry_no_longer_open`, an expired one `409 inquiry_expired`, a conversation that already has a booking `409 conversation_already_booked`.\n\nSend `Idempotency-Key`: a repeat with the same key replays the first response instead of acting twice (a `409 idempotency_key_in_use` while the first is still running). A 5xx, a `429 airbnb_rate_limited` or a `403 connection_reauth_required` is not stored \u2014 nothing was done \u2014 so retrying with the same key reaches Airbnb again.",
|
|
25120
26411
|
"tags": [
|
|
25121
26412
|
"Conversations"
|
|
25122
26413
|
],
|
|
@@ -25465,7 +26756,7 @@
|
|
|
25465
26756
|
"post": {
|
|
25466
26757
|
"operationId": "create_conversation_special_offer",
|
|
25467
26758
|
"summary": "Send a special offer",
|
|
25468
|
-
"description": "Send the guest on this conversation an Airbnb special offer: your own dates, guest count and total price. The guest has 24 hours to book it. Use it to answer an inquiry with different terms, or to make a returning guest a custom price. To accept the guest\u2019s own dates and price as they asked, pre-approve instead (`POST /v1/conversations/{id}/pre-approval`).\n\n`listingId` is optional: omit it to offer the listing the guest asked about. It is a **Repull** listing id; Repull sends Airbnb its own listing id, using the link that belongs to this conversation\u2019s Airbnb account.\n\n`totalPrice` is the whole stay, in the listing\u2019s Airbnb currency \u2014 Airbnb does not take a currency on an offer.\n\n**Airbnb only**, and only for listings connected to Airbnb directly; anything else is `422 channel_not_supported` and nothing is sent.
|
|
26759
|
+
"description": "Send the guest on this conversation an Airbnb special offer: your own dates, guest count and total price. The guest has 24 hours to book it. Use it to answer an inquiry with different terms, or to make a returning guest a custom price. To accept the guest\u2019s own dates and price as they asked, pre-approve instead (`POST /v1/conversations/{id}/pre-approval`).\n\n`listingId` is optional: omit it to offer the listing the guest asked about. It is a **Repull** listing id; Repull sends Airbnb its own listing id, using the link that belongs to this conversation\u2019s Airbnb account.\n\n`totalPrice` is the whole stay, in the listing\u2019s Airbnb currency \u2014 Airbnb does not take a currency on an offer.\n\n**Airbnb only**, and only for listings connected to Airbnb directly; anything else is `422 channel_not_supported` and nothing is sent. The inquiry is marked `special_offer_sent`.\n\nAn offer Airbnb refuses is never a `201`: dates that are taken, a price below Airbnb\u2019s minimum, too many guests and the like are `422 airbnb_rejected` with Airbnb\u2019s own reason in `message`.\n\nSend `Idempotency-Key`: a repeat with the same key replays the first response instead of acting twice (a `409 idempotency_key_in_use` while the first is still running). A 5xx, a `429 airbnb_rate_limited` or a `403 connection_reauth_required` is not stored \u2014 nothing was done \u2014 so retrying with the same key reaches Airbnb again. Without it, a retry after a timeout can send the guest two offers.\n\nRead or withdraw the offer with `GET` / `DELETE /v1/conversations/{id}/special-offers/{offerId}`.",
|
|
25469
26760
|
"tags": [
|
|
25470
26761
|
"Conversations"
|
|
25471
26762
|
],
|
|
@@ -26294,7 +27585,7 @@
|
|
|
26294
27585
|
"delete": {
|
|
26295
27586
|
"operationId": "withdraw_conversation_special_offer",
|
|
26296
27587
|
"summary": "Withdraw a special offer",
|
|
26297
|
-
"description": "Withdraw a special offer the guest has not booked yet, so it can no longer be booked.
|
|
27588
|
+
"description": "Withdraw a special offer the guest has not booked yet, so it can no longer be booked. An offer the guest already booked cannot be withdrawn \u2014 Airbnb refuses with `409 inquiry_no_longer_open`; cancel the booking instead.",
|
|
26298
27589
|
"tags": [
|
|
26299
27590
|
"Conversations"
|
|
26300
27591
|
],
|
|
@@ -26689,7 +27980,7 @@
|
|
|
26689
27980
|
"declined",
|
|
26690
27981
|
"not_possible"
|
|
26691
27982
|
],
|
|
26692
|
-
"description": "`open` \u2014 nobody has answered and the stay is still ahead; `pre_approved`; `special_offer_sent` (from the API,
|
|
27983
|
+
"description": "`open` \u2014 nobody has answered and the stay is still ahead; `pre_approved`; `special_offer_sent` (from the API, a connected app, or Airbnb\u2019s own app); `booked` \u2014 the guest booked (`reservationId`); `expired` \u2014 the stay has started or Airbnb expired it; `declined`; `not_possible` \u2014 Airbnb says the dates cannot be booked."
|
|
26693
27984
|
},
|
|
26694
27985
|
"checkIn": {
|
|
26695
27986
|
"type": "string",
|
|
@@ -26807,7 +28098,7 @@
|
|
|
26807
28098
|
"post": {
|
|
26808
28099
|
"operationId": "accept_reservation_request",
|
|
26809
28100
|
"summary": "Accept a booking request",
|
|
26810
|
-
"description": "Accept a pending Airbnb booking request \u2014 a reservation with status `pending`, made on a listing without Instant Book. Find them with `GET /v1/reservations?status=pending`. Airbnb expires a request the host has not answered within 24 hours.\n\
|
|
28101
|
+
"description": "Accept a pending Airbnb booking request \u2014 a reservation with status `pending`, made on a listing without Instant Book. Find them with `GET /v1/reservations?status=pending`. Airbnb expires a request the host has not answered within 24 hours.\n\nAirbnb confirms asynchronously: the reservation\u2019s status moves to confirmed, and a `reservation.updated` webhook fires, when Airbnb\u2019s notification lands (usually within seconds). The response reports what Airbnb was asked to do.\n\n**Airbnb only**, and only for listings connected to Airbnb directly: other channels have no request step (`422 channel_not_supported`). A reservation that is not pending is refused before Airbnb is contacted (`409 reservation_not_pending`); one Airbnb says already moved on is `409 request_no_longer_pending`. Neither is worth retrying.\n\nTakes no body.\n\nSend `Idempotency-Key`: a repeat with the same key replays the first response instead of acting twice (a `409 idempotency_key_in_use` while the first is still running). A 5xx, a `429 airbnb_rate_limited` or a `403 connection_reauth_required` is not stored \u2014 nothing was done \u2014 so retrying with the same key reaches Airbnb again.",
|
|
26811
28102
|
"tags": [
|
|
26812
28103
|
"Reservations"
|
|
26813
28104
|
],
|
|
@@ -27155,7 +28446,7 @@
|
|
|
27155
28446
|
"post": {
|
|
27156
28447
|
"operationId": "decline_reservation_request",
|
|
27157
28448
|
"summary": "Decline a booking request",
|
|
27158
|
-
"description": "Decline a pending Airbnb booking request (a reservation with status `pending`; find them with `GET /v1/reservations?status=pending`).\n\n`reason` must be one of Airbnb\u2019s own decline reasons. `message` is required: Airbnb sends it to the guest with the decline (at most 500 characters). It is not defaulted \u2014 a canned message would put words in your mouth.\n\
|
|
28449
|
+
"description": "Decline a pending Airbnb booking request (a reservation with status `pending`; find them with `GET /v1/reservations?status=pending`).\n\n`reason` must be one of Airbnb\u2019s own decline reasons. `message` is required: Airbnb sends it to the guest with the decline (at most 500 characters). It is not defaulted \u2014 a canned message would put words in your mouth.\n\nAirbnb confirms asynchronously; the reservation\u2019s status moves, and `reservation.updated` fires, when its notification lands. Same channel and status rules as `POST /v1/reservations/{id}/accept`.\n\nSend `Idempotency-Key`: a repeat with the same key replays the first response instead of acting twice (a `409 idempotency_key_in_use` while the first is still running). A 5xx, a `429 airbnb_rate_limited` or a `403 connection_reauth_required` is not stored \u2014 nothing was done \u2014 so retrying with the same key reaches Airbnb again.",
|
|
27159
28450
|
"tags": [
|
|
27160
28451
|
"Reservations"
|
|
27161
28452
|
],
|
|
@@ -27546,6 +28837,680 @@
|
|
|
27546
28837
|
}
|
|
27547
28838
|
}
|
|
27548
28839
|
}
|
|
28840
|
+
},
|
|
28841
|
+
"/v1/listings/{id}/offline": {
|
|
28842
|
+
"post": {
|
|
28843
|
+
"operationId": "takeListingOffline",
|
|
28844
|
+
"summary": "Take a listing off the market",
|
|
28845
|
+
"description": "Stop this listing being sold, on every channel it is connected to, in one call.\n\nWhat that means differs per channel and you do not have to know which is which. On **Airbnb** the live listing is deactivated with a valid deactivation reason and then READ BACK \u2014 Airbnb accepts some deactivations and leaves the listing up, so \"we sent the request\" is never reported as success. On **Booking.com** there is no unlist at all; the equivalent is closing the room's availability across the whole forward window, which is what happens.\n\n**This is not the same as deactivating the listing in Repull.** The two get confused because both sound like removal, and they have opposite consequences:\n\n| | Take offline (this endpoint) | Deactivate in Repull (`PATCH /v1/listings/{id}` `{\"active\": false}`) |\n|---|---|---|\n| The guest-facing listing | **Stops taking bookings** | Stays live and keeps taking bookings |\n| Billing and plan limits | Unchanged | No longer billed, no longer counts toward the cap |\n| API access to the listing | Unchanged \u2014 you can still read and write it | `403 listing_inactive` until reactivated |\n| Reverse it with | `POST /v1/listings/{id}/online` | `PATCH /v1/listings/{id}` `{\"active\": true}` |\n| Data kept | Yes | Yes, and it keeps syncing |\n\nNeither one deletes anything, on either side.\n\n**The answer is per channel item.** A listing can sit on several Airbnb connections and a Booking.com property at once; they fail independently and a partial result is the ordinary outcome, so every item reports its own `state`, `code` and `message` and there is no top-level success flag to mislead you. Nothing is rolled back \u2014 re-send the same request to retry the items that did not land.\n\n**Booking.com ambiguity is reported, not fanned out.** A listing mapped to more than one active Booking.com property comes back with that item refused (`ambiguous_booking_mapping`) while the Airbnb items still run: closing the wrong property's availability takes real inventory off sale, and taking a listing off Airbnb is not less urgent because its Booking.com mapping is untidy. Name the property with `hotelId` and send it again.\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.",
|
|
28846
|
+
"tags": [
|
|
28847
|
+
"Listings"
|
|
28848
|
+
],
|
|
28849
|
+
"parameters": [
|
|
28850
|
+
{
|
|
28851
|
+
"name": "id",
|
|
28852
|
+
"in": "path",
|
|
28853
|
+
"required": true,
|
|
28854
|
+
"schema": {
|
|
28855
|
+
"type": "integer"
|
|
28856
|
+
},
|
|
28857
|
+
"description": "Repull listing id."
|
|
28858
|
+
},
|
|
28859
|
+
{
|
|
28860
|
+
"name": "hotel_id",
|
|
28861
|
+
"in": "query",
|
|
28862
|
+
"required": false,
|
|
28863
|
+
"schema": {
|
|
28864
|
+
"type": "string"
|
|
28865
|
+
},
|
|
28866
|
+
"description": "Booking.com property to act on, for a listing mapped to more than one. The query-string spelling of the body's `hotelId`; the body wins when both are sent."
|
|
28867
|
+
},
|
|
28868
|
+
{
|
|
28869
|
+
"$ref": "#/components/parameters/IdempotencyKey"
|
|
28870
|
+
}
|
|
28871
|
+
],
|
|
28872
|
+
"requestBody": {
|
|
28873
|
+
"required": false,
|
|
28874
|
+
"content": {
|
|
28875
|
+
"application/json": {
|
|
28876
|
+
"schema": {
|
|
28877
|
+
"$ref": "#/components/schemas/ListingMarketStateRequest"
|
|
28878
|
+
}
|
|
28879
|
+
}
|
|
28880
|
+
}
|
|
28881
|
+
},
|
|
28882
|
+
"responses": {
|
|
28883
|
+
"200": {
|
|
28884
|
+
"description": "Attempted on every connected channel \u2014 read each item's `ok` and `state`",
|
|
28885
|
+
"content": {
|
|
28886
|
+
"application/json": {
|
|
28887
|
+
"schema": {
|
|
28888
|
+
"$ref": "#/components/schemas/ListingMarketStateResponse"
|
|
28889
|
+
}
|
|
28890
|
+
}
|
|
28891
|
+
}
|
|
28892
|
+
},
|
|
28893
|
+
"402": {
|
|
28894
|
+
"$ref": "#/components/responses/PublishBillingRefused"
|
|
28895
|
+
},
|
|
28896
|
+
"403": {
|
|
28897
|
+
"$ref": "#/components/responses/ListingInactive"
|
|
28898
|
+
},
|
|
28899
|
+
"404": {
|
|
28900
|
+
"$ref": "#/components/responses/NotFound"
|
|
28901
|
+
},
|
|
28902
|
+
"422": {
|
|
28903
|
+
"description": "`no_connected_channels` \u2014 this listing is not connected to any channel, so there was nothing to take offline. Connect one with `POST /v1/connect/sessions` and publish it first. Answered as a refusal rather than a 200 with an empty array, because \"taken off the market everywhere\" over zero channels is not something a caller should have to notice for themselves.",
|
|
28904
|
+
"content": {
|
|
28905
|
+
"application/json": {
|
|
28906
|
+
"schema": {
|
|
28907
|
+
"$ref": "#/components/schemas/Error"
|
|
28908
|
+
}
|
|
28909
|
+
}
|
|
28910
|
+
}
|
|
28911
|
+
},
|
|
28912
|
+
"429": {
|
|
28913
|
+
"$ref": "#/components/responses/TooManyRequests"
|
|
28914
|
+
}
|
|
28915
|
+
}
|
|
28916
|
+
}
|
|
28917
|
+
},
|
|
28918
|
+
"/v1/listings/{id}/online": {
|
|
28919
|
+
"post": {
|
|
28920
|
+
"operationId": "takeListingOnline",
|
|
28921
|
+
"summary": "Put a listing back on the market",
|
|
28922
|
+
"description": "Put this listing back on sale, on every channel it is connected to. The counterpart of `POST /v1/listings/{id}/offline`, which documents the per-item response and the difference between this and deactivating a listing in Repull.\n\n**It does not push content.** On **Airbnb** it re-enables sync and makes the listing available again; anything that changed while the listing was down is still unpublished, so follow with `POST /v1/listings/{id}/publish/airbnb` if the content moved. On **Booking.com** it re-syncs the true calendar rather than opening everything: dates that are genuinely blocked \u2014 a reservation, an owner stay \u2014 stay blocked, and only the closure `offline` wrote lifts. The two directions are not mirror images, and that is deliberate.\n\n**One asymmetry worth planning for.** Taking a listing down passes no billing gate; putting it back up goes through the channel-publish gate. So on a workspace whose subscription has lapsed, `offline` still works and this endpoint answers `402 payment_required` \u2014 a listing can be left off the market until billing is sorted out. That refusal is reported as a billing refusal with the action that fixes it, never as a channel error: retrying, or reconnecting the channel, does nothing for it.\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.",
|
|
28923
|
+
"tags": [
|
|
28924
|
+
"Listings"
|
|
28925
|
+
],
|
|
28926
|
+
"parameters": [
|
|
28927
|
+
{
|
|
28928
|
+
"name": "id",
|
|
28929
|
+
"in": "path",
|
|
28930
|
+
"required": true,
|
|
28931
|
+
"schema": {
|
|
28932
|
+
"type": "integer"
|
|
28933
|
+
},
|
|
28934
|
+
"description": "Repull listing id."
|
|
28935
|
+
},
|
|
28936
|
+
{
|
|
28937
|
+
"name": "hotel_id",
|
|
28938
|
+
"in": "query",
|
|
28939
|
+
"required": false,
|
|
28940
|
+
"schema": {
|
|
28941
|
+
"type": "string"
|
|
28942
|
+
},
|
|
28943
|
+
"description": "Booking.com property to act on, for a listing mapped to more than one. The query-string spelling of the body's `hotelId`; the body wins when both are sent."
|
|
28944
|
+
},
|
|
28945
|
+
{
|
|
28946
|
+
"$ref": "#/components/parameters/IdempotencyKey"
|
|
28947
|
+
}
|
|
28948
|
+
],
|
|
28949
|
+
"requestBody": {
|
|
28950
|
+
"required": false,
|
|
28951
|
+
"content": {
|
|
28952
|
+
"application/json": {
|
|
28953
|
+
"schema": {
|
|
28954
|
+
"$ref": "#/components/schemas/ListingMarketStateRequest"
|
|
28955
|
+
}
|
|
28956
|
+
}
|
|
28957
|
+
}
|
|
28958
|
+
},
|
|
28959
|
+
"responses": {
|
|
28960
|
+
"200": {
|
|
28961
|
+
"description": "Attempted on every connected channel \u2014 read each item's `ok` and `state`",
|
|
28962
|
+
"content": {
|
|
28963
|
+
"application/json": {
|
|
28964
|
+
"schema": {
|
|
28965
|
+
"$ref": "#/components/schemas/ListingMarketStateResponse"
|
|
28966
|
+
}
|
|
28967
|
+
}
|
|
28968
|
+
}
|
|
28969
|
+
},
|
|
28970
|
+
"402": {
|
|
28971
|
+
"$ref": "#/components/responses/PublishBillingRefused"
|
|
28972
|
+
},
|
|
28973
|
+
"403": {
|
|
28974
|
+
"$ref": "#/components/responses/ListingInactive"
|
|
28975
|
+
},
|
|
28976
|
+
"404": {
|
|
28977
|
+
"$ref": "#/components/responses/NotFound"
|
|
28978
|
+
},
|
|
28979
|
+
"422": {
|
|
28980
|
+
"description": "`no_connected_channels` \u2014 this listing is not connected to any channel, so there was nothing to put back on the market. Connect one with `POST /v1/connect/sessions` and publish it first.",
|
|
28981
|
+
"content": {
|
|
28982
|
+
"application/json": {
|
|
28983
|
+
"schema": {
|
|
28984
|
+
"$ref": "#/components/schemas/Error"
|
|
28985
|
+
}
|
|
28986
|
+
}
|
|
28987
|
+
}
|
|
28988
|
+
},
|
|
28989
|
+
"429": {
|
|
28990
|
+
"$ref": "#/components/responses/TooManyRequests"
|
|
28991
|
+
}
|
|
28992
|
+
}
|
|
28993
|
+
}
|
|
28994
|
+
},
|
|
28995
|
+
"/v1/migrations": {
|
|
28996
|
+
"get": {
|
|
28997
|
+
"operationId": "listMigrations",
|
|
28998
|
+
"summary": "List migrations",
|
|
28999
|
+
"description": "Every property manager your workspace has moved through Repull Migrate, newest first, each with where it stands. Page with `pagination.nextCursor`.",
|
|
29000
|
+
"tags": [
|
|
29001
|
+
"Migrate"
|
|
29002
|
+
],
|
|
29003
|
+
"parameters": [
|
|
29004
|
+
{
|
|
29005
|
+
"name": "limit",
|
|
29006
|
+
"in": "query",
|
|
29007
|
+
"schema": {
|
|
29008
|
+
"type": "integer",
|
|
29009
|
+
"minimum": 1,
|
|
29010
|
+
"maximum": 100,
|
|
29011
|
+
"default": 25
|
|
29012
|
+
}
|
|
29013
|
+
},
|
|
29014
|
+
{
|
|
29015
|
+
"name": "cursor",
|
|
29016
|
+
"in": "query",
|
|
29017
|
+
"schema": {
|
|
29018
|
+
"type": "string"
|
|
29019
|
+
},
|
|
29020
|
+
"description": "`pagination.nextCursor` from the previous page."
|
|
29021
|
+
}
|
|
29022
|
+
],
|
|
29023
|
+
"responses": {
|
|
29024
|
+
"200": {
|
|
29025
|
+
"description": "Migrations",
|
|
29026
|
+
"content": {
|
|
29027
|
+
"application/json": {
|
|
29028
|
+
"schema": {
|
|
29029
|
+
"type": "object",
|
|
29030
|
+
"properties": {
|
|
29031
|
+
"data": {
|
|
29032
|
+
"type": "array",
|
|
29033
|
+
"items": {
|
|
29034
|
+
"$ref": "#/components/schemas/Migration"
|
|
29035
|
+
}
|
|
29036
|
+
},
|
|
29037
|
+
"pagination": {
|
|
29038
|
+
"type": "object",
|
|
29039
|
+
"properties": {
|
|
29040
|
+
"nextCursor": {
|
|
29041
|
+
"type": "string",
|
|
29042
|
+
"nullable": true
|
|
29043
|
+
},
|
|
29044
|
+
"hasMore": {
|
|
29045
|
+
"type": "boolean"
|
|
29046
|
+
},
|
|
29047
|
+
"total": {
|
|
29048
|
+
"type": "integer"
|
|
29049
|
+
}
|
|
29050
|
+
}
|
|
29051
|
+
}
|
|
29052
|
+
}
|
|
29053
|
+
}
|
|
29054
|
+
}
|
|
29055
|
+
}
|
|
29056
|
+
},
|
|
29057
|
+
"401": {
|
|
29058
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29059
|
+
},
|
|
29060
|
+
"422": {
|
|
29061
|
+
"$ref": "#/components/responses/UnprocessableEntity"
|
|
29062
|
+
},
|
|
29063
|
+
"500": {
|
|
29064
|
+
"$ref": "#/components/responses/InternalError"
|
|
29065
|
+
}
|
|
29066
|
+
}
|
|
29067
|
+
}
|
|
29068
|
+
},
|
|
29069
|
+
"/v1/migrations/{workspaceId}": {
|
|
29070
|
+
"get": {
|
|
29071
|
+
"operationId": "getMigration",
|
|
29072
|
+
"summary": "Get a migration",
|
|
29073
|
+
"description": "Where one migration stands: its state, the source connections with their last import run, and how much data has landed.",
|
|
29074
|
+
"tags": [
|
|
29075
|
+
"Migrate"
|
|
29076
|
+
],
|
|
29077
|
+
"parameters": [
|
|
29078
|
+
{
|
|
29079
|
+
"name": "workspaceId",
|
|
29080
|
+
"in": "path",
|
|
29081
|
+
"required": true,
|
|
29082
|
+
"schema": {
|
|
29083
|
+
"type": "integer"
|
|
29084
|
+
},
|
|
29085
|
+
"description": "The migration's workspace id (from `POST /v1/connect` with `purpose: \"migrate\"`, or `GET /v1/migrations`)."
|
|
29086
|
+
}
|
|
29087
|
+
],
|
|
29088
|
+
"responses": {
|
|
29089
|
+
"200": {
|
|
29090
|
+
"description": "The migration",
|
|
29091
|
+
"content": {
|
|
29092
|
+
"application/json": {
|
|
29093
|
+
"schema": {
|
|
29094
|
+
"type": "object",
|
|
29095
|
+
"properties": {
|
|
29096
|
+
"data": {
|
|
29097
|
+
"$ref": "#/components/schemas/Migration"
|
|
29098
|
+
}
|
|
29099
|
+
}
|
|
29100
|
+
}
|
|
29101
|
+
}
|
|
29102
|
+
}
|
|
29103
|
+
},
|
|
29104
|
+
"401": {
|
|
29105
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29106
|
+
},
|
|
29107
|
+
"404": {
|
|
29108
|
+
"$ref": "#/components/responses/NotFound"
|
|
29109
|
+
},
|
|
29110
|
+
"500": {
|
|
29111
|
+
"$ref": "#/components/responses/InternalError"
|
|
29112
|
+
}
|
|
29113
|
+
}
|
|
29114
|
+
},
|
|
29115
|
+
"delete": {
|
|
29116
|
+
"operationId": "deleteMigration",
|
|
29117
|
+
"summary": "End a migration",
|
|
29118
|
+
"description": "Disconnects the source (it stops syncing) and deactivates the migration's workspace. The imported data is kept and stays readable.",
|
|
29119
|
+
"tags": [
|
|
29120
|
+
"Migrate"
|
|
29121
|
+
],
|
|
29122
|
+
"parameters": [
|
|
29123
|
+
{
|
|
29124
|
+
"name": "workspaceId",
|
|
29125
|
+
"in": "path",
|
|
29126
|
+
"required": true,
|
|
29127
|
+
"schema": {
|
|
29128
|
+
"type": "integer"
|
|
29129
|
+
},
|
|
29130
|
+
"description": "The migration's workspace id (from `POST /v1/connect` with `purpose: \"migrate\"`, or `GET /v1/migrations`)."
|
|
29131
|
+
}
|
|
29132
|
+
],
|
|
29133
|
+
"responses": {
|
|
29134
|
+
"200": {
|
|
29135
|
+
"description": "Ended",
|
|
29136
|
+
"content": {
|
|
29137
|
+
"application/json": {
|
|
29138
|
+
"schema": {
|
|
29139
|
+
"type": "object",
|
|
29140
|
+
"properties": {
|
|
29141
|
+
"data": {
|
|
29142
|
+
"type": "object",
|
|
29143
|
+
"properties": {
|
|
29144
|
+
"workspaceId": {
|
|
29145
|
+
"type": "string"
|
|
29146
|
+
},
|
|
29147
|
+
"deactivated": {
|
|
29148
|
+
"type": "boolean"
|
|
29149
|
+
},
|
|
29150
|
+
"deactivatedAt": {
|
|
29151
|
+
"type": "string",
|
|
29152
|
+
"format": "date-time"
|
|
29153
|
+
}
|
|
29154
|
+
}
|
|
29155
|
+
}
|
|
29156
|
+
}
|
|
29157
|
+
}
|
|
29158
|
+
}
|
|
29159
|
+
}
|
|
29160
|
+
},
|
|
29161
|
+
"401": {
|
|
29162
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29163
|
+
},
|
|
29164
|
+
"404": {
|
|
29165
|
+
"$ref": "#/components/responses/NotFound"
|
|
29166
|
+
},
|
|
29167
|
+
"500": {
|
|
29168
|
+
"$ref": "#/components/responses/InternalError"
|
|
29169
|
+
}
|
|
29170
|
+
}
|
|
29171
|
+
}
|
|
29172
|
+
},
|
|
29173
|
+
"/v1/migrations/{workspaceId}/channel-map": {
|
|
29174
|
+
"get": {
|
|
29175
|
+
"operationId": "getMigrationChannelMap",
|
|
29176
|
+
"summary": "Get channel links",
|
|
29177
|
+
"description": "The Airbnb, Booking.com and VRBO listing each migrated property is linked to in the source PMS, read live. Use it to link each listing to the right property when the channels are reconnected in the destination \u2014 channel connections themselves cannot be moved between PMSs.",
|
|
29178
|
+
"tags": [
|
|
29179
|
+
"Migrate"
|
|
29180
|
+
],
|
|
29181
|
+
"parameters": [
|
|
29182
|
+
{
|
|
29183
|
+
"name": "workspaceId",
|
|
29184
|
+
"in": "path",
|
|
29185
|
+
"required": true,
|
|
29186
|
+
"schema": {
|
|
29187
|
+
"type": "integer"
|
|
29188
|
+
},
|
|
29189
|
+
"description": "The migration's workspace id (from `POST /v1/connect` with `purpose: \"migrate\"`, or `GET /v1/migrations`)."
|
|
29190
|
+
}
|
|
29191
|
+
],
|
|
29192
|
+
"responses": {
|
|
29193
|
+
"200": {
|
|
29194
|
+
"description": "Channel links",
|
|
29195
|
+
"content": {
|
|
29196
|
+
"application/json": {
|
|
29197
|
+
"schema": {
|
|
29198
|
+
"type": "object",
|
|
29199
|
+
"properties": {
|
|
29200
|
+
"data": {
|
|
29201
|
+
"$ref": "#/components/schemas/MigrationChannelMap"
|
|
29202
|
+
}
|
|
29203
|
+
}
|
|
29204
|
+
}
|
|
29205
|
+
}
|
|
29206
|
+
}
|
|
29207
|
+
},
|
|
29208
|
+
"401": {
|
|
29209
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29210
|
+
},
|
|
29211
|
+
"404": {
|
|
29212
|
+
"$ref": "#/components/responses/NotFound"
|
|
29213
|
+
},
|
|
29214
|
+
"500": {
|
|
29215
|
+
"$ref": "#/components/responses/InternalError"
|
|
29216
|
+
}
|
|
29217
|
+
}
|
|
29218
|
+
}
|
|
29219
|
+
},
|
|
29220
|
+
"/v1/migrations/{workspaceId}/cutover": {
|
|
29221
|
+
"post": {
|
|
29222
|
+
"operationId": "cutoverMigration",
|
|
29223
|
+
"summary": "Cut over",
|
|
29224
|
+
"description": "The property manager has switched: disconnect the source so it stops syncing. The imported data stays readable. Idempotent.",
|
|
29225
|
+
"tags": [
|
|
29226
|
+
"Migrate"
|
|
29227
|
+
],
|
|
29228
|
+
"parameters": [
|
|
29229
|
+
{
|
|
29230
|
+
"name": "workspaceId",
|
|
29231
|
+
"in": "path",
|
|
29232
|
+
"required": true,
|
|
29233
|
+
"schema": {
|
|
29234
|
+
"type": "integer"
|
|
29235
|
+
},
|
|
29236
|
+
"description": "The migration's workspace id (from `POST /v1/connect` with `purpose: \"migrate\"`, or `GET /v1/migrations`)."
|
|
29237
|
+
}
|
|
29238
|
+
],
|
|
29239
|
+
"responses": {
|
|
29240
|
+
"200": {
|
|
29241
|
+
"description": "The migration after cutover",
|
|
29242
|
+
"content": {
|
|
29243
|
+
"application/json": {
|
|
29244
|
+
"schema": {
|
|
29245
|
+
"type": "object",
|
|
29246
|
+
"properties": {
|
|
29247
|
+
"data": {
|
|
29248
|
+
"$ref": "#/components/schemas/Migration"
|
|
29249
|
+
}
|
|
29250
|
+
}
|
|
29251
|
+
}
|
|
29252
|
+
}
|
|
29253
|
+
}
|
|
29254
|
+
},
|
|
29255
|
+
"401": {
|
|
29256
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29257
|
+
},
|
|
29258
|
+
"404": {
|
|
29259
|
+
"$ref": "#/components/responses/NotFound"
|
|
29260
|
+
},
|
|
29261
|
+
"500": {
|
|
29262
|
+
"$ref": "#/components/responses/InternalError"
|
|
29263
|
+
}
|
|
29264
|
+
}
|
|
29265
|
+
}
|
|
29266
|
+
},
|
|
29267
|
+
"/v1/migrations/{workspaceId}/cutover-check": {
|
|
29268
|
+
"post": {
|
|
29269
|
+
"operationId": "checkMigrationCutover",
|
|
29270
|
+
"summary": "Check reservations before switching",
|
|
29271
|
+
"description": "Send every upcoming reservation the destination holds; get back what is missing, extra, or on different dates compared with the source. Read-only \u2014 run it as often as you like before cutover.",
|
|
29272
|
+
"tags": [
|
|
29273
|
+
"Migrate"
|
|
29274
|
+
],
|
|
29275
|
+
"parameters": [
|
|
29276
|
+
{
|
|
29277
|
+
"name": "workspaceId",
|
|
29278
|
+
"in": "path",
|
|
29279
|
+
"required": true,
|
|
29280
|
+
"schema": {
|
|
29281
|
+
"type": "integer"
|
|
29282
|
+
},
|
|
29283
|
+
"description": "The migration's workspace id (from `POST /v1/connect` with `purpose: \"migrate\"`, or `GET /v1/migrations`)."
|
|
29284
|
+
}
|
|
29285
|
+
],
|
|
29286
|
+
"requestBody": {
|
|
29287
|
+
"required": true,
|
|
29288
|
+
"content": {
|
|
29289
|
+
"application/json": {
|
|
29290
|
+
"schema": {
|
|
29291
|
+
"type": "object",
|
|
29292
|
+
"required": [
|
|
29293
|
+
"reservations"
|
|
29294
|
+
],
|
|
29295
|
+
"properties": {
|
|
29296
|
+
"reservations": {
|
|
29297
|
+
"type": "array",
|
|
29298
|
+
"maxItems": 10000,
|
|
29299
|
+
"items": {
|
|
29300
|
+
"type": "object",
|
|
29301
|
+
"required": [
|
|
29302
|
+
"confirmationCode",
|
|
29303
|
+
"checkIn",
|
|
29304
|
+
"checkOut"
|
|
29305
|
+
],
|
|
29306
|
+
"properties": {
|
|
29307
|
+
"confirmationCode": {
|
|
29308
|
+
"type": "string"
|
|
29309
|
+
},
|
|
29310
|
+
"checkIn": {
|
|
29311
|
+
"type": "string",
|
|
29312
|
+
"format": "date"
|
|
29313
|
+
},
|
|
29314
|
+
"checkOut": {
|
|
29315
|
+
"type": "string",
|
|
29316
|
+
"format": "date"
|
|
29317
|
+
}
|
|
29318
|
+
},
|
|
29319
|
+
"additionalProperties": false
|
|
29320
|
+
}
|
|
29321
|
+
}
|
|
29322
|
+
},
|
|
29323
|
+
"additionalProperties": false
|
|
29324
|
+
}
|
|
29325
|
+
}
|
|
29326
|
+
}
|
|
29327
|
+
},
|
|
29328
|
+
"responses": {
|
|
29329
|
+
"200": {
|
|
29330
|
+
"description": "Comparison",
|
|
29331
|
+
"content": {
|
|
29332
|
+
"application/json": {
|
|
29333
|
+
"schema": {
|
|
29334
|
+
"type": "object",
|
|
29335
|
+
"properties": {
|
|
29336
|
+
"data": {
|
|
29337
|
+
"$ref": "#/components/schemas/MigrationCutoverCheck"
|
|
29338
|
+
}
|
|
29339
|
+
}
|
|
29340
|
+
}
|
|
29341
|
+
}
|
|
29342
|
+
}
|
|
29343
|
+
},
|
|
29344
|
+
"401": {
|
|
29345
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29346
|
+
},
|
|
29347
|
+
"404": {
|
|
29348
|
+
"$ref": "#/components/responses/NotFound"
|
|
29349
|
+
},
|
|
29350
|
+
"422": {
|
|
29351
|
+
"$ref": "#/components/responses/UnprocessableEntity"
|
|
29352
|
+
},
|
|
29353
|
+
"500": {
|
|
29354
|
+
"$ref": "#/components/responses/InternalError"
|
|
29355
|
+
}
|
|
29356
|
+
}
|
|
29357
|
+
}
|
|
29358
|
+
},
|
|
29359
|
+
"/v1/migrations/{workspaceId}/import": {
|
|
29360
|
+
"post": {
|
|
29361
|
+
"operationId": "runMigrationImport",
|
|
29362
|
+
"summary": "Run the import again",
|
|
29363
|
+
"description": "Queue another import from the source PMS \u2014 for example messages after the first pass, or only reservations changed since a date. Progress shows on `GET /v1/migrations/{workspaceId}`.",
|
|
29364
|
+
"tags": [
|
|
29365
|
+
"Migrate"
|
|
29366
|
+
],
|
|
29367
|
+
"parameters": [
|
|
29368
|
+
{
|
|
29369
|
+
"name": "workspaceId",
|
|
29370
|
+
"in": "path",
|
|
29371
|
+
"required": true,
|
|
29372
|
+
"schema": {
|
|
29373
|
+
"type": "integer"
|
|
29374
|
+
},
|
|
29375
|
+
"description": "The migration's workspace id (from `POST /v1/connect` with `purpose: \"migrate\"`, or `GET /v1/migrations`)."
|
|
29376
|
+
}
|
|
29377
|
+
],
|
|
29378
|
+
"requestBody": {
|
|
29379
|
+
"required": false,
|
|
29380
|
+
"content": {
|
|
29381
|
+
"application/json": {
|
|
29382
|
+
"schema": {
|
|
29383
|
+
"type": "object",
|
|
29384
|
+
"properties": {
|
|
29385
|
+
"entities": {
|
|
29386
|
+
"type": "array",
|
|
29387
|
+
"items": {
|
|
29388
|
+
"type": "string",
|
|
29389
|
+
"enum": [
|
|
29390
|
+
"listings",
|
|
29391
|
+
"reservations",
|
|
29392
|
+
"messages",
|
|
29393
|
+
"calendar"
|
|
29394
|
+
]
|
|
29395
|
+
},
|
|
29396
|
+
"description": "Defaults to listings and reservations."
|
|
29397
|
+
},
|
|
29398
|
+
"since": {
|
|
29399
|
+
"type": "string",
|
|
29400
|
+
"format": "date-time",
|
|
29401
|
+
"description": "Only reservations changed after this."
|
|
29402
|
+
}
|
|
29403
|
+
},
|
|
29404
|
+
"additionalProperties": false
|
|
29405
|
+
}
|
|
29406
|
+
}
|
|
29407
|
+
}
|
|
29408
|
+
},
|
|
29409
|
+
"responses": {
|
|
29410
|
+
"202": {
|
|
29411
|
+
"description": "Queued",
|
|
29412
|
+
"content": {
|
|
29413
|
+
"application/json": {
|
|
29414
|
+
"schema": {
|
|
29415
|
+
"type": "object",
|
|
29416
|
+
"properties": {
|
|
29417
|
+
"data": {
|
|
29418
|
+
"type": "object",
|
|
29419
|
+
"properties": {
|
|
29420
|
+
"workspaceId": {
|
|
29421
|
+
"type": "string"
|
|
29422
|
+
},
|
|
29423
|
+
"queued": {
|
|
29424
|
+
"type": "array",
|
|
29425
|
+
"items": {
|
|
29426
|
+
"type": "object",
|
|
29427
|
+
"properties": {
|
|
29428
|
+
"connectionId": {
|
|
29429
|
+
"type": "string"
|
|
29430
|
+
},
|
|
29431
|
+
"provider": {
|
|
29432
|
+
"type": "string"
|
|
29433
|
+
},
|
|
29434
|
+
"queued": {
|
|
29435
|
+
"type": "boolean"
|
|
29436
|
+
},
|
|
29437
|
+
"error": {
|
|
29438
|
+
"type": "string"
|
|
29439
|
+
}
|
|
29440
|
+
}
|
|
29441
|
+
}
|
|
29442
|
+
}
|
|
29443
|
+
}
|
|
29444
|
+
}
|
|
29445
|
+
}
|
|
29446
|
+
}
|
|
29447
|
+
}
|
|
29448
|
+
}
|
|
29449
|
+
},
|
|
29450
|
+
"401": {
|
|
29451
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29452
|
+
},
|
|
29453
|
+
"404": {
|
|
29454
|
+
"$ref": "#/components/responses/NotFound"
|
|
29455
|
+
},
|
|
29456
|
+
"409": {
|
|
29457
|
+
"$ref": "#/components/responses/Conflict"
|
|
29458
|
+
},
|
|
29459
|
+
"422": {
|
|
29460
|
+
"$ref": "#/components/responses/UnprocessableEntity"
|
|
29461
|
+
},
|
|
29462
|
+
"500": {
|
|
29463
|
+
"$ref": "#/components/responses/InternalError"
|
|
29464
|
+
}
|
|
29465
|
+
}
|
|
29466
|
+
}
|
|
29467
|
+
},
|
|
29468
|
+
"/v1/migrations/{workspaceId}/report": {
|
|
29469
|
+
"get": {
|
|
29470
|
+
"operationId": "getMigrationReport",
|
|
29471
|
+
"summary": "Get a migration report",
|
|
29472
|
+
"description": "What came across and what needs a decision in the destination: properties without an address, upcoming reservations with no guest contact, channel reservations that must not be re-created, and anything the source PMS cannot carry. Includes the source's capability matrix.",
|
|
29473
|
+
"tags": [
|
|
29474
|
+
"Migrate"
|
|
29475
|
+
],
|
|
29476
|
+
"parameters": [
|
|
29477
|
+
{
|
|
29478
|
+
"name": "workspaceId",
|
|
29479
|
+
"in": "path",
|
|
29480
|
+
"required": true,
|
|
29481
|
+
"schema": {
|
|
29482
|
+
"type": "integer"
|
|
29483
|
+
},
|
|
29484
|
+
"description": "The migration's workspace id (from `POST /v1/connect` with `purpose: \"migrate\"`, or `GET /v1/migrations`)."
|
|
29485
|
+
}
|
|
29486
|
+
],
|
|
29487
|
+
"responses": {
|
|
29488
|
+
"200": {
|
|
29489
|
+
"description": "The report",
|
|
29490
|
+
"content": {
|
|
29491
|
+
"application/json": {
|
|
29492
|
+
"schema": {
|
|
29493
|
+
"type": "object",
|
|
29494
|
+
"properties": {
|
|
29495
|
+
"data": {
|
|
29496
|
+
"$ref": "#/components/schemas/MigrationReport"
|
|
29497
|
+
}
|
|
29498
|
+
}
|
|
29499
|
+
}
|
|
29500
|
+
}
|
|
29501
|
+
}
|
|
29502
|
+
},
|
|
29503
|
+
"401": {
|
|
29504
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
29505
|
+
},
|
|
29506
|
+
"404": {
|
|
29507
|
+
"$ref": "#/components/responses/NotFound"
|
|
29508
|
+
},
|
|
29509
|
+
"500": {
|
|
29510
|
+
"$ref": "#/components/responses/InternalError"
|
|
29511
|
+
}
|
|
29512
|
+
}
|
|
29513
|
+
}
|
|
27549
29514
|
}
|
|
27550
29515
|
}
|
|
27551
29516
|
}
|