Update a property
Change a property you cover: its listing facts, address, base rates, units and your external
id for it. Send only the groups you want to change — everything you omit is left exactly as it
is — and the response is the property block as GET /properties returns it.
{property} is the Doorloom id or your external_id; Doorloom resolves
whichever it recognises. The property must be in your active coverage — one that
Doorloom staff removed from your integration is a 404, and an archived one is
422 PROPERTY_ARCHIVED.
Headers
| Field | Type | Required | Description |
|---|---|---|---|
Authorization
|
string | Required | Bearer <your API key>. |
Content-Type
|
string | Required | application/json. |
No idempotency key, no If-Match
PUT here is naturally idempotent: applying the same body twice reaches the same
state, and units are addressed by id so a retried request cannot create a duplicate. Properties
carry no revision counter, so there is no If-Match: the last write wins, whether it
came from you or from the host in the Doorloom app.Request body
Every field is optional. A group that is present is applied; a group that is absent is untouched. Field rules are the same as on create unless noted.
Body
| Field | Type | Required | Description |
|---|---|---|---|
external_id
|
string|null | Optional | Re-map your id for this property. null clears the mapping. An id already mapped to another of your properties is a 409. |
name
|
string | Optional | The listing name. |
property_type
|
string|null | Optional | One of the listed types, or null to clear. |
property_category
|
string|null | Optional | vacation_rental, resort_hotel or null. |
description
|
string | Optional | Max 2000 characters. |
address
|
object | Optional | Any subset of the address fields. Omitted fields keep their value; formatted_address is re-derived unless you send it. |
space
|
object | Optional | bedrooms and/or washrooms. |
occupancy
|
object | Optional | min_guests and/or max_guests. |
pricing
|
object | Optional | weekday and/or weekend, each with any subset of base_price, per_night_occupancy, extra_adult_price, extra_child_price. |
units
|
array | Optional | 1 to 50 entries. A merge list: each entry changes or creates one unit; units you do not mention are untouched. |
units[].inventory_id
|
integer | one of the two | Address the unit by Doorloom's id. |
units[].external_id
|
string|null | one of the two | Address the unit by your alias. An alias Doorloom does not know creates the unit (name required). With inventory_id as well, sets or changes that unit's alias; null clears it. |
units[].name
|
string | Optional | Rename. Required when the entry creates a unit. |
units[].code
|
string|null | Optional | Max 64 characters. |
units[].max_occupancy
|
integer|null | Optional | Guests the unit sleeps. |
units[].is_active
|
boolean | Optional | false takes the unit off sale, true puts it back. The default unit cannot be deactivated. |
features
|
object | Optional | Any subset of the features fields. |
Example
curl -X PUT https://doorloom.com/api/integrations/v1/properties/VILLA-001 \
-H "Authorization: Bearer $DOORLOOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Amber Villa by the Sea",
"pricing": { "weekend": { "base_price": 16500 } },
"units": [
{ "inventory_id": 3101, "name": "Sea View Suite", "external_id": "U-SEA" },
{ "external_id": "U-GARDEN", "is_active": false },
{ "external_id": "U-ATTIC", "name": "Attic Room", "max_occupancy": 2 }
]
}'
Three things happen to the units: unit 3101 is renamed and given the alias U-SEA;
the unit already aliased U-GARDEN is taken off sale; and because U-ATTIC
is not yet an alias of this property, a new unit is created and mapped to it. The address, the
weekday rate and every other unit are untouched.
Response
{
"success": true,
"message": "Property updated",
"data": {
"doorloom_id": 902,
"external_id": "VILLA-001",
"coverage": "active",
"name": "Amber Villa by the Sea",
"property_type": "villa",
"status": "active",
"is_master": true,
"master_property_id": 902,
"units": [
{
"inventory_id": 3101,
"external_unit_id": "U-SEA",
"name": "Sea View Suite",
"code": "SEA",
"is_active": true,
"is_default": true,
"max_occupancy": 4,
"deleted": false
},
{
"inventory_id": 3104,
"external_unit_id": "U-ATTIC",
"name": "Attic Room",
"code": null,
"is_active": true,
"is_default": false,
"max_occupancy": 2,
"deleted": false
}
]
}
}
units[] lists active units only, as on GET /properties;
the deactivated U-GARDEN unit is absent here and present, flagged, on the
inventory.changed
event that follows.
Units
- Address every entry by
inventory_idorexternal_id. An entry with neither is a422onunits.N— that is what keeps a retried request from adding the same unit twice. - A property always keeps its default unit active; sending
is_active: falsefor it is a422onunits.N.is_active. To retire it, the host promotes another unit in the Doorloom app first. - Units are never deleted through the API; deactivate them instead. Bookings against a deactivated unit keep their history.
- An
inventory_idthat belongs to another property is a422onunits.N.inventory_id; an alias already mapped to another property's unit is a422onunits.N.external_id.
What happens afterwards
A successful update always emits
property.changed
for the property. If you changed prices you also receive
rates.changed;
if you touched units, inventory.changed
and, when a unit went on or off sale,
availability.changed
across your horizon. These are your own writes echoed back as full state; apply them by the
sequence rule like any other event.
Errors
Errors
| Status | Code | Meaning |
|---|---|---|
| 422 | VALIDATION_FAILED |
The request body failed validation. errors is Laravel's field bag: an object keyed by field path, each holding an array of messages. |
| 404 | NOT_FOUND |
No such booking or property for this integration. Another integration's booking or property is not found rather than forbidden, so ids cannot be probed across partners. On a property write, a property whose coverage staff have removed is also not found. |
| 422 | PROPERTY_ARCHIVED |
The property exists and is covered, but the host has archived it. Archived properties are off-market: they cannot take bookings, and they cannot be edited through the API. |
| 409 | PROPERTY_EXTERNAL_ID_EXISTS |
The external_id you sent is already mapped to a property of your integration. On a create, that property was not created; on an update, the mapping was not changed. errors.doorloom_id tells you which property holds the id, so you can store the mapping or pick a different id. |
| 409 | IN_PROGRESS |
An identical request is still being processed. Two requests raced; the first one is still inside its transaction. |
| 429 | RATE_LIMITED |
You exceeded one of the rate limits. The standard Retry-After and X-RateLimit-* headers are set on the response. |
| 401 | UNAUTHENTICATED |
No API key, or a key that is not valid. Rotating your API key invalidates the previous one immediately. |
| 403 | NOT_INTEGRATION_TOKEN |
The bearer token is valid but is not an integration API key — it belongs to another Doorloom token surface. Integration keys are the only credential this API accepts. |
| 403 | INTEGRATION_DISABLED |
Doorloom staff disabled this integration. No events are recorded and no API call succeeds. This is distinct from paused: a paused integration keeps full API access and only stops receiving webhooks. |
Retrying
Every retry of an unchanged PUT is safe: a timeout, a 5xx or a
409 IN_PROGRESS can simply be resent. A 4xx other than those means the body
is wrong or the property is gone from your scope; fix the cause rather than retrying.