Manage Airbnb Listings
List the Airbnb listings connected to your workspace. To create one, publish a Repull listing with POST /v1/listings/{id}/publish/airbnb and a hostId; to push, unlist, relist or deactivate an existing one, use POST /v1/channels/airbnb/listings/{id} with an action. The default response is a fast index of every listing connected to the workspace, served from your synced data — it never waits on Airbnb. Pass ?include=amenities to enrich each connection with its amenity set, and ?include=thumbnail to get each listing's cover photo, so a picker or a dashboard renders from one request instead of one call per property.
/v1/channels/airbnb/listingsParameters
account_idstringScope the response to ONE connected Airbnb account. The value is the Airbnb host id — the same accounts[].externalAccountId that GET /v1/connect/airbnb returns and DELETE /v1/connect/airbnb?accountId= accepts. Omit it to read every connected account (the default). An id that is not connected to this workspace returns 404 not_found with field: "account_id" and your own ids in valid_values. This is not the X-Account-Id header, which carries a connection id and cannot tell two Airbnb hosts apart.
statusstringactive (default) leaves inactive listings out. inactive returns only them, all returns both. Every row carries status. An inactive row has identity only — listingId, name, city, status, inactiveReason and each connection's ids and account — enough to show what can be activated. inactiveReason is plan_limit (held back by the plan), unlisted_on_airbnb, or deactivated (switched off by you).
includestringComma-separated expansions. amenities adds amenities and accessibilityAmenities arrays to each connection. thumbnail adds thumbnailUrl to each listing — the cover photo, or null when the property has none. Combine them: ?include=amenities,thumbnail. Anything else returns 422 invalid_params listing the values that work.
API sync is authorised per listing
Airbnb turns API sync on one listing at a time, not once for the account. Each listing carries its own sync category, and a listing whose category is none is closed to the API — Airbnb refuses every write to it even though the account is connected and every other listing on it writes fine. Reconnecting the Airbnb account does not change a listing's sync category. Only someone with access to the listing on Airbnb can turn sync on for it, on the listing itself.
sync_all— Repull manages content, rates and availability. Every write on this page works.sync_rates_and_availability— Repull manages rates and availability; listing content is managed by the host on Airbnb.none— the listing is not connected to Repull on Airbnb at all. Every write returns403 listing_not_api_connectedand nothing is sent to Airbnb, so nothing is partially applied.- To tell which is which, read
GET /v1/channels/airbnb/listings: every entry of each listing'sconnections[]carriessyncCategory(one of the three above, ornullif it has never been reported) andwritable—falseexactly whensyncCategoryisnone. Checkwritablebefore a write and you never send one that cannot land. Full detail: https://repull.dev/docs/errors/listing_not_api_connected
Cover photos for a selection screen
A screen where someone picks properties — which ones to manage, which to push a rate change to, which to put in a report — needs a picture next to each name. ?include=thumbnail puts one on every row of this list, so the screen renders from a single request.
thumbnailUrlis the property's cover photo, a plain image URL you can put straight in an<img src>. It does not expire and needs no authentication.- It sits on the listing, not on each connection: every Airbnb connection under a property shows the same property, so the picture is the same for all of them.
nullmeans the property has no cover photo yet. The property is still returned — never filter a row out because its picture is missing, or someone loses a property they were looking for.- The field is absent, not
null, when you do not ask for it. That is how you can tell "no photo" apart from "I did not request photos". - It costs nothing: the photo comes back on the query this endpoint already runs, so asking for it does not make the call slower and does not use extra credits.
- For properties that are not connected to Airbnb, or ones you have deactivated, use
GET /v1/listings?status=all&include=thumbnailinstead — same field, your whole catalog.
curl 'https://api.repull.dev/v1/channels/airbnb/listings?include=thumbnail' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# {
# "data": [
# {
# "listingId": 6248,
# "name": "Oceanview Villa",
# "city": "Malibu",
# "thumbnailUrl": "https://a0.muscache.com/im/pictures/oceanview.jpg",
# "connections": [ ... ]
# },
# {
# "listingId": 6249,
# "name": "Harbor Loft",
# "city": "Malibu",
# "thumbnailUrl": null,
# "connections": [ ... ]
# }
# ]
# }Several Airbnb accounts
A workspace can connect more than one Airbnb account. By default this route returns every connected account's rows; pass ?account_id=<airbnb host id> to scope to one. Every row carries accountId + accountName either way (the older hostId / hostName remain as aliases), so you can group without a second call. Airbnb host ids exceed 2^53 — keep them as strings and never parse them as numbers: Number("1000000000000000002") is 1772489413932732200, a different account, and returns 404. A two-account walkthrough lives on the Account scope page (/docs/scoping).
# Every connected account — the default, unchanged
curl 'https://api.repull.dev/v1/channels/airbnb/listings' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# Just one account
curl 'https://api.repull.dev/v1/channels/airbnb/listings?account_id=1000000000000000002' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# An id this workspace has not connected — 404, with your own ids to copy
# {
# "error": {
# "code": "not_found",
# "message": "No connected Airbnb account `1772489413932732200` in this workspace.",
# "field": "account_id",
# "value_received": "1772489413932732200",
# "valid_values": ["1000000000000000002", "80000001"]
# }
# }Data freshness
Airbnb reads are served from Repull's local mirror and never call Airbnb upstream, so every response carries a dataFreshness envelope telling you whether to trust it. lastSyncedAt is the last import that actually landed data — a run that failed or was rate-limited never moves it. accounts[] gives the verdict per connected account; it is omitted when the workspace has no Airbnb account to attribute.
- Top-level
stale: truemeans every connected account is stale — nothing in the response is current. For the single-account workspace this is exactly the old behaviour. - Top-level
stale: falsewithreason: "partial_account_staleness"means some accounts are fine and some are not. The response is usable; readaccounts[]to see which rows to distrust. Thereasonis emitted deliberately even thoughstaleisfalse, so a consumer reading only the aggregate is never told everything is fine while one account is down. - Top-level
stale: falsewith noreasonmeans every account is current. - With
?account_id=,accounts[]holds exactly that account and the top-level fields mirror it. reasonis one ofhost_disconnected_since_<iso>,host_disconnected,host_not_activated,sync_lag_>_24h,never_synced, orpartial_account_staleness.fixUrlis the dashboard screen that resolves it.
{
"dataFreshness": {
"lastSyncedAt": "2026-09-18T04:12:09.000Z",
"stale": false,
"reason": "partial_account_staleness",
"fixUrl": "https://repull.dev/dashboard/connections",
"accounts": [
{
"accountId": "1000000000000000002",
"accountName": "Seaside Stays",
"lastSyncedAt": "2026-09-18T04:12:09.000Z",
"stale": false
},
{
"accountId": "80000001",
"accountName": "Casey",
"lastSyncedAt": null,
"stale": true,
"reason": "host_disconnected",
"fixUrl": "https://repull.dev/dashboard/connections"
}
]
}
}Unlist, relist, push and delete
Every action on one Airbnb listing is a POST /v1/channels/airbnb/listings/{id} with an action in the body. {id} is the Repull listing id. airbnbConnectionId is the id of one entry in that listing's connections (see the list above): a listing can be on more than one Airbnb account, so the actions that change Airbnb ask which one.
unlist—{ "action": "unlist", "airbnbConnectionId": 456 }. Takes the listing off Airbnb so it cannot be booked. The listing, its content, photos and reviews stay on Airbnb, and existing reservations are not affected.relist—{ "action": "relist", "airbnbConnectionId": 456 }. Puts an unlisted listing back up. It sends nothing else: the content on Airbnb is what it was when you unlisted.- Relisting a listing Airbnb has already approved does not send it back for review: it is bookable again straight away. Tested on a live listing:
has_availabilitywas back totrueat once and stilltrue20 seconds later. - Both read the listing back from Airbnb before answering.
verified: truemeans Airbnb now shows the state you asked for. If Airbnb accepts the call but the listing is still live (unlist) or still offline (relist), you get an error, never a success.verified: falsemeans the read-back could not run: treat it as unknown and read the listing again. pushorpublish—{ "action": "push", "airbnbConnectionId": 456 }. Sends the listing's current content to the Airbnb listing. To create a new Airbnb listing, sendhostIdinstead ofairbnbConnectionId, or usePOST /v1/listings/{id}/publish/airbnb(the same thing, and it returns what each part of the listing did).delete—{ "action": "delete" }. Deactivates the listing in Repull only. It does not delete, or even unlist, anything on Airbnb. Nothing in this API deletes an Airbnb listing; to stop bookings,unlistfirst. Sendingdeletetwice is safe.- To take a listing off every channel at once (Airbnb and Booking.com), use
POST /v1/listings/{id}/offlineandPOST /v1/listings/{id}/onlineinstead: one call, one result per channel.
curl -X POST https://api.repull.dev/v1/channels/airbnb/listings/123 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "relist", "airbnbConnectionId": 456}'
# {
# "data": {
# "id": 123,
# "action": "relist",
# "channel": "airbnb",
# "airbnbConnectionId": 456,
# "live": true,
# "verified": true
# }
# }Example
# List Airbnb-connected properties (fast — local data only)
curl https://api.repull.dev/v1/channels/airbnb/listings \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# Same list, with each property's cover photo — everything a picker needs, one request
curl 'https://api.repull.dev/v1/channels/airbnb/listings?include=thumbnail' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# Same list, but include each property's current Airbnb amenities
curl 'https://api.repull.dev/v1/channels/airbnb/listings?include=amenities' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# Get a single listing's Airbnb connections (with amenities)
curl 'https://api.repull.dev/v1/channels/airbnb/listings/6248?include=amenities' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# Create a new Airbnb listing from a Repull listing
curl -X POST https://api.repull.dev/v1/listings/123/publish/airbnb \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-d '{"hostId": "380436627"}'
# Take it off Airbnb, then put it back up
curl -X POST https://api.repull.dev/v1/channels/airbnb/listings/123 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-d '{"action": "unlist", "airbnbConnectionId": 456}'
curl -X POST https://api.repull.dev/v1/channels/airbnb/listings/123 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-d '{"action": "relist", "airbnbConnectionId": 456}'Response
{
"data": [
{
"listingId": 6248,
"name": "Oceanview Villa",
"city": "Malibu",
"thumbnailUrl": "https://a0.muscache.com/im/pictures/oceanview.jpg",
"connections": [
{
"id": 2642,
"airbnbId": "1234567890123456789",
"accountId": "10000001",
"accountName": "Seaside Stays",
"hostId": "10000001",
"hostName": "Seaside Stays",
"active": true,
"syncEnabled": true,
"syncCategory": "sync_all",
"writable": true,
"primary": true,
"markup": "0.16",
"createdAt": "2024-12-15T20:39:07.346Z",
"amenities": [
{ "id": "WIFI", "isPresent": true, "name": "Wifi", "category": "common" },
{ "id": "POOL", "isPresent": true, "name": "Pool", "category": "outdoors" }
],
"accessibilityAmenities": []
}
]
}
],
"pagination": { "nextCursor": null, "hasMore": false, "total": 1 },
"dataFreshness": {
"lastSyncedAt": "2026-09-18T04:12:09.000Z",
"stale": false,
"accounts": [
{
"accountId": "10000001",
"accountName": "Seaside Stays",
"lastSyncedAt": "2026-09-18T04:12:09.000Z",
"stale": false
}
]
}
}