Availability
The thing providers get wrong most often is a home that is no longer free. This is how you keep that honest.
See what is blocked
GET /properties/{id|external_id}/availability{
"data": [
{
"id": "001d833f-…",
"start_date": "2026-12-20",
"end_date": "2027-01-05",
"reason": "Owner using the home",
"source": "provider"
},
{
"id": "77c0a1e2-…",
"start_date": "2026-11-01",
"end_date": "2027-01-31",
"reason": "Reserved — lease pending",
"lease_agreement_id": "5ae35393-…",
"source": "havnly"
}
]
}source tells you who put it there:
provider— yours, and yours to removehavnly— a home held for a lease or a booking. Ours, and not removable through the API; cancel the placement instead
Block dates
POST /properties/{id|external_id}/availability{ "start_date": "2026-12-20", "end_date": "2027-01-05", "reason": "Owner using the home" }Release a block
POST /properties/{id}/availability/{block_id}/releaseOnly your own. Releasing one of ours answers:
{
"error": {
"code": "not_yours_to_release",
"message": "This home is held for a lease or a booking. Cancel the placement instead."
}
}Keeping a calendar in sync
If your own system is the source of truth, the simplest reliable pattern is:
read our blocks, ignore anything with source: "havnly", and reconcile the rest
against yours. Blocks are cheap — one per stretch of unavailability is fine.
A whole portfolio at once
The per-home calls above are right for a change somebody just made. They are the wrong shape for the job a thousand-home provider actually does every night, which is “here is my current availability, make it so” — a thousand calls, against a rate limit, mostly to say nothing changed.
POST /properties/availability{
"replace": true,
"blocks": [
{ "external_id": "unit-4021", "start_date": "2026-12-20", "end_date": "2026-12-27", "reason": "Owner use" },
{ "external_id": "unit-4022", "start_date": "2027-01-02", "end_date": "2027-01-09" }
]
}{
"blocked": 2,
"failed": 0,
"replaced_existing": 2,
"homes_touched": 2,
"results": [
{ "index": 0, "ref": "unit-4021", "ok": true, "property_id": "bef180d5-…" }
]
}Up to 500 blocks per call. Homes are named by your own external_id or by
our property_id. Answered per item, so one bad date does not lose the other
499 — 207 when some were refused, with the reason against each.
replace: true is the sync primitive
It clears the blocks you previously set on the homes named in this call, then applies what you sent. That makes the call declarative: send your current calendar and stop tracking what you told us last night.
It never touches a hold Havnly placed. A block created by a lease or a
confirmed booking is a household’s home for those dates, and a nightly sync is
not entitled to free it. Those survive every replace, and they are the ones
GET /properties/{id}/availability marks as source: "havnly".
Homes you do not name are untouched. replace is scoped to the homes in the
payload, not to your whole portfolio — so a partial sync cannot wipe the rest.
Retrying
Send an Idempotency-Key. Without one, a
retried sync re-applies every block, and with replace: true that is harmless
but wasteful; without replace it duplicates them.