Publishing a listing
Putting a Repull listing on Airbnb and on Booking.com, taking it back off, and finding out why a push did not land. Publishing is the one step that leaves Repull, so it is the one step where a channel gets to say no — most of this page is about reading that answer.
Before you publish
GET /v1/listings/{id}/publish-status tells you whether you are there yet before you spend a publish finding out.Airbnb
One endpoint does both jobs, and which one it does depends on a single field:
| Send | And you get |
|---|---|
hostId | A new Airbnb listing, created under that host account |
airbnbConnectionId | An update to the Airbnb listing that connection already points at |
force: true | Every section re-pushed, instead of only the ones that changed since the last successful publish |
# Create a brand-new Airbnb listing under a host
curl -X POST https://api.repull.dev/v1/listings/48211/publish/airbnb \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"hostId": "112233"}'
# Update the listing an existing connection points at
curl -X POST https://api.repull.dev/v1/listings/48211/publish/airbnb \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"airbnbConnectionId": 77, "force": true}'What a create needs
- A
full_accessconnection. Amessagingorread_onlyconnection cannot create a listing, whatever else it can do. latandlngon the listing.- The full postal address —
streetandcityalways, plusstateand the postal code for a US property, andcountryCodefor anything outside the US. See the address rule.
published and live answer different questions
The response carries a result object with published, sections, errors, reason, lockedFields, live and warnings. Two of those are routinely read as the same thing and are not.
| Field | The question it answers |
|---|---|
published | Did every content section that was attempted reach Airbnb? |
live | Is the listing active and bookable on Airbnb? |
published: true with live: false is normal, not a contradiction
warnings, never in errors: nothing failed, something was declined to be done on your behalf.{
"listingId": "48211",
"channel": "airbnb",
"result": {
"published": true,
"live": false,
"sections": ["details", "description", "amenities", "rooms", "policies", "photos", "pricing"],
"errors": [],
"warnings": [
"Listing was not activated: instant booking could not be confirmed as off. Activate it in Airbnb once the booking settings are as you want them."
],
"lockedFields": []
}
}An absent live is not live: false
liveis absent when activation was never part of the operation at all — which is the case every time you publish to an existinglisting, since that listing is already whatever it is. Treat absent as “not asked”, and read GET /v1/channels/airbnb/listings/{id}/details if you need the current state. Branching on !result.live will tell you every update un-listed the property.Partial success is the normal case
A publish is up to eight independent calls to Airbnb, each of which can fail on its own, and a partial publish is not rolled back — the sections that landed stay applied. That is why the result is per section rather than one boolean, and why re-publishing after a failure is safe.
The rules behind that — which fields push and which are creation-only, the fields Airbnb locks on an established listing and answers 200 to without applying, the per-section codevalues and what to do about each, idempotency, and how to read back what actually took effect — are all in the Airbnb publication contract. Read it once before you write the code that calls this endpoint.
Editing afterwards, one thing at a time
A publish sends the canonical content as a block. When you want to change one thing on a live Airbnb listing — a price, the cover photo, a permit number — write it directly instead. These take the Airbnb listing id.
| To change | Endpoint, under /v1/channels/airbnb/listings/{id} |
|---|---|
| Title and the description sections, per language | PUT …/descriptions — its name field is the title |
| Amenities | PUT …/amenities |
| Property type, room type, capacity, Wi-Fi, check-in method | PUT …/details |
| Rooms and the beds in them | PUT …/rooms |
| The photo set | POST / PATCH / DELETE …/photos |
| Which photo is the cover | PUT …/photos/cover |
| The order photos appear in | PUT …/photos/order |
| Nightly price, fees, discounts | PUT …/pricing |
| Calendar: rates, availability, min and max stay | PUT …/availability |
| Instant book, booking window, notice period | PUT …/booking-settings |
| The check-in guide a guest sees before arrival | PUT …/checkin-guide |
| Permits, licences, registration numbers | PUT …/permits |
| Guest-safety disclosures | PUT …/safety-disclosures |
Booking.com
There is no single create, and the reason is structural rather than an omission. Booking.com does not model a listing. It models a property, which contains rooms, which are sold under rate plans; what a guest books is a room-and-rate product, not the property. One Repull listing therefore becomes several Booking.com objects, and they have to be built in order.
1. Create the property
One call builds the whole first floor of that structure: the property, its first room, a rate plan, the room-and-rate product that links them, seeded availability and rates, and the notification that asks Booking.com to take the property live.
curl -X POST https://api.repull.dev/v1/channels/booking/setup \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "create-property", "listing_id": "48211"}'
{
"action": "create-property",
"listingId": "48211",
"propertyId": "1234567",
"roomId": "1234567001",
"legalEntity": { "id": "59731", "source": "workspace" },
"target": "production",
"status": "being_built",
"sellable": false,
"nextSteps": ["..."]
}You do not have to find a legal entity first. If your workspace already has one registered, it is reused. If it does not, send legal_entity with the registration details in the same request and it is created as part of the setup. There is no id for you to look up and no separate call to sequence.
2. Add the rest of the rooms
add-room adds another room type to the property; add-unit adds another physical unit of a room type that already exists. A single-unit rental needs neither.
curl -X POST https://api.repull.dev/v1/channels/booking/setup \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "add-room", "property_id": "1234567", "listing_id": "48212"}'
{
"roomId": "1234567002",
"rateId": null
}A room with no active rate plan is invisible, and it is not an error
rateId: nullin the response is exactly this case — the room exists, the product does not. Check rateId on every add-room, and treat null as work still to do rather than as a missing field.3. Advance it to sellable
A newly created property does not arrive on the market. It sits in a being-built state until the go-live notification is accepted, and creating it is not the same thing as finishing it. advance re-sends that notification, and sellablein the response is the answer — not a status you infer from the absence of an error.
curl -X POST https://api.repull.dev/v1/channels/booking/setup \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "advance", "property_id": "1234567"}'
{
"action": "advance",
"propertyId": "1234567",
"checked": true,
"opened": true,
"sellable": true
}The other setup actions — contacts, policies, readiness, opening the property for sale — are on Booking.com setup & go live.
4. Push the content
curl -X POST https://api.repull.dev/v1/listings/48211/publish/booking \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"hotelId": "1234567"}'hotelId is optional in the body and also accepted as ?hotel_id= on the query string. It names which Booking.com property the content is for.
A listing can live under more than one Booking.com property
409 ambiguous_booking_mappingrather than picking one. Guessing here means pushing one property's description, photos and prices onto a different property, and nothing about the response would tell you it happened. Send hotelId.Taking a listing on and off the market
POST /v1/listings/{id}/offline and POST /v1/listings/{id}/online fan out across every channel the listing is connected to. There is no single success boolean in the response, because there is no single outcome: one listing can hold several Airbnb connections as well as a Booking.com property, and each of them answers for itself.
curl -X POST https://api.repull.dev/v1/listings/48211/offline \
-H "Authorization: Bearer sk_live_YOUR_KEY"
{
"listingId": "48211",
"state": "offline",
"action": "offline",
"channels": [
{
"channel": "airbnb",
"connectionId": "77",
"state": "offline",
"ok": true,
"verified": true
},
{
"channel": "airbnb",
"connectionId": "91",
"state": "unchanged",
"ok": false,
"code": "airbnb_rejected",
"message": "The Airbnb authorization for this listing has expired.",
"fix": "Reconnect the Airbnb account and include this listing in the authorization. It is still live and still taking bookings."
},
{
"channel": "booking",
"hotelId": null,
"state": "unchanged",
"ok": false,
"code": "ambiguous_booking_mapping",
"message": "This listing is mapped to 6 Booking.com properties (10167991, 14478352, 15279817, 15434777, 15576344, 17174993).",
"fix": "Name the one you mean with hotelId on POST /v1/channels/booking/properties/48211 — nothing was changed."
}
]
}Read every entry. A response where one connection went offline and another did not is a property that is still taking bookings, and the only thing that says so is the second entry.
state: unchanged means we did not change it
unchanged with ok: false is work that did not happen, and fix says what to do about it.To act on one channel rather than all of them:
# Airbnb — one connection at a time, because taking down the wrong
# listing is not something this API can undo for you
curl -X POST https://api.repull.dev/v1/channels/airbnb/listings/23900 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "unlist", "airbnbConnectionId": 77}'
# Booking.com — by property
curl -X POST https://api.repull.dev/v1/channels/booking/properties/1234567 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "unlist"}'relist is the reverse of each. Booking.com has no listed-or-unlisted concept of its own, so unlist there closes availability and relistreopens it — the property stays where it is, with nothing left to sell.
This is not what deactivating does
PATCH /v1/listings/{id} with active: false does not take anything off the market
403 listing_inactive to reads and writes until it is activated again. The listing stays live on the channel and keeps taking bookings the whole time— and you can no longer see them through the API. Reaching for it when you meant to unlist is the expensive version of this mistake.The full comparison — what each one does to the guest-facing listing, to billing, to API access, and how to reverse it — is in unlisting vs deactivating, with the rest of the deactivation behaviour on active & inactive listings.
Finding out what went wrong
The result of a publish tells you about that publish. When you are looking at a listing hours or days later — a support queue, a dashboard, a nightly reconciliation — the question is different: why is this one not live, and who can fix it. GET /v1/listings/{id}/publish-status answers it without attempting anything.
curl -s https://api.repull.dev/v1/listings/48211/publish-status \
-H "Authorization: Bearer sk_live_YOUR_KEY"
{
"listingId": "48211",
"channels": [
{
"platform": "airbnb",
"pushStatus": "error",
"lastPushedAt": "2026-09-19T08:14:02.000Z",
"pushError": "Check-in start time must be before end time; Links and contact info can't be shared",
"dirtyFields": ["policies", "description"],
"platformHasChanges": true
}
],
"connections": [
{
"channel": "airbnb",
"connected": true,
"syncEnabled": true,
"since": "2026-04-02T11:20:41.000Z",
"lockedFields": ["name", "property_type_category", "amenities.wifi"]
}
]
}pushError — the channel's own words
pushError is the reason the channel gave for the last failed push, verbatim, per channel. Where several sections failed, the reasons are joined with ; . It is free text written by the channel, not by this API: render it, never branch on it. The wording changes upstream without notice, and code that matches on it breaks silently. Branch on the per-section code from a publish result instead.
What it is for is putting a human on the right task in one step. These are real reasons, and each one is something a person can act on in a couple of minutes:
| What comes back | What it means for whoever is fixing it |
|---|---|
Check-in start time must be before end time | The check-in window is inverted. Fix the times on the listing and publish again. |
Links and contact info can't be shared | The description or house rules contain a URL, an email address or a phone number. Take it out. |
property_type_group must be one of … | The property type does not map onto a type the channel accepts. Pick one from the list in the message. |
Rate limited by provider | Nothing is wrong with the content. Publish again later. |
Locked fields, without attempting a publish
Each Airbnb entry in connections carries lockedFields: the fields Airbnb will not let a partner change on that listing. Writing one returns success and applies nothing, so it is not an error you can retry your way out of — it is a fact about the listing.
Reading it here rather than from a publish result is what a listing editor needs. You can disable the title field, or mark it as host-managed, before someone spends ten minutes rewriting a title that was never going to save. There is more on what gets locked and why in fields Airbnb refuses to change.
See also
- Creating a listing — everything that has to be true before this page applies
- The Airbnb publication contract — the limits of a publish, in full
- Booking.com properties & listings — how listings map onto properties and rooms
- Idempotency — making a publish retry safe
- Errors — the full error catalogue