Changelog

Every change to the API, dated. Additive changes land here on the day they ship; breaking ones appear here ninety days before they do.

2026-10-26
X-Request-Id, a fuller document, aligned sentences
Every response carries X-Request-Id: yours echoed when you send a safe one, else a fresh 32-hex id; quote it to support. The OpenAPI document gains an operationId on all 26 operations, a Retry-After header on every declared 429, and a request example on every write built from the live vocabularies. Its error descriptions now quote the sentences the API actually answers with (the ticket-create 409 and 422s, the move-in occupied refusal, the occupancy external_id and party caps, and destination_url_required on webhook create). PATCH /tickets/{ticket_id}'s created_by_missing message now names a status change instead of ticket creation (same code). A webhook subscription created with POST /webhooks stores its events sorted, as this site already said. Every API error answers Cache-Control: no-store, the unmatched-path 404 and the wrong-verb 405 included. Additive: no code, field or status changed.
2026-10-14
PATCH refuses a status change on a ticket held offline
PATCH /tickets/{ticket_id} answers 409 ticket_held when it would change the status of a ticket a technician holds offline. Nothing is written and the key is not consumed, so retry the same key once the hold is released. The message names the holder: “<name> has this ticket offline. A manager can release the hold.” A PATCH that keeps the current status, and a replay of a key that already succeeded, answer as before. Additive: a new code on an existing route. Documented here 2026-10-26.
2026-10-13
compliance.report_needed: the eleventh webhook event
A new event key, compliance.report_needed, fires when a compliance log was recorded with the vendor's report to follow and the report is still missing: on day 1, day 7, then every seven days until the report is attached. data is an inline dict (the obligation, the property, the wait in days, the log and ticket ids, a link); change is null. Eleven keys are subscribable. Additive: receivers ignore keys they do not recognise. Documented here 2026-10-26.
2026-10-06
projected_ready_on floors on the property's day
On Units and Turns, projected_ready_on is never earlier than today in the unit's property time zone (it used to floor at the UTC date, so after 8 PM Eastern an on-time turn read tomorrow). turn.schedule_changed fires on the same floor, so a one-day slip in a property's evening is delivered that evening. No field, type or code changed; the two descriptions say so.
2026-09-24
Units: the Employee designation
Employee joins Down and Model as a sticky unit designation. occupancy gains the value Employee (seven values), and unit_status can read Employee. A designation shows only while nobody lives there.
2026-09-24
Unit occupancy; turn.status_changed at the confirmed move-out
Units: occupancy, incoming_lease and ready added to the Unit object, after projected_ready_on and before url, on GET /units and GET /units/{unit_id}. turn.status_changed now fires at the confirmed move-out rather than when a notice is posted; unit_status on a unit under notice reads Occupied until then. A lease that starts fires it once, when that start changes the stored status — typically Vacant → Occupied; no event if the unit already read Occupied, or carries a sticky designation (Down, Model or Employee).
2026-09-12
Turn: open follow-ups and departure codes
blocker_ticket_ids, ntv_reason_code and departure_destination_code added to the Turn object, after url, on GET /turns, GET /turns/{turn_id} and the two turn webhooks. Documented here 2026-09-24.
2026-09-11
Ten webhook events, cancellation codes, occupancy field caps
Five new event keys — ticket.stale, property.notice.published, turn.task.stale, pm.stale and inspection.stale — join ticket.created, ticket.status_changed, turn.status_changed, turn.schedule_changed and vendor.hold; ten keys are subscribable. ticket.stale carries the Ticket and inspection.stale the Inspection; the other three carry a small inline dict with no read entity behind it; change is null on all five. PATCH /tickets/{ticket_id} publishes the seven-value cancellation_reason_code enum. POST /units/{unit_id}/move-in caps each party's first_name (80), last_name (80), email (150), phone (20) and external_id (120) and the top-level external_id (120); POST /units/{unit_id}/notice caps external_id (120). Over a cap is a 400 invalid_request before any write. Move-in parties are stored named leaseholders first; the response echoes the stored order.
2026-09-09
Turns
GET /turns and GET /turns/{turn_id}, scope read. A turn is the transition record — every transition lists, including a first-ever move-in. Filters property_id, unit_id, status and updated_since; status is Active / Completed / Cancelled and an unknown value is a 422 invalid_request. A turn carries projected_ready_on and a url to its transition page.
2026-09-09
Turn events carry the turn
turn.status_changed and turn.schedule_changed now carry entity_type turn, entity_id the turn id, and data = the same Turn object GET /turns/{turn_id} returns, in place of a unit object. change is unchanged. Receive the event, fetch the turn, same object.
2026-09-09
Occupancy ingest
POST /units/{unit_id}/notice and POST /units/{unit_id}/move-in. Scope write. Idempotency-Key and a provider slug are required. The response is an OccupancyRecord. There is no occupancy list or fetch.
2026-09-09
Inspections and meters
List and fetch for ops inspections (consulting-engagement walks excluded) and utility meters, plus GET /meters/{meter_id}/readings. Inspection status is Scheduled / In Progress / Completed; result is passed / failed / null. A reading has no url.
2026-09-09
Ticket writes
POST /tickets creates a ticket (status always Open). PATCH /tickets/{ticket_id} changes status; any status is reachable from any other. Both require Idempotency-Key and scope write. New code created_by_missing (422) when the key has no minting administrator.
2026-09-08
v1 published
Read access to tickets, properties, units, assets and vendors; bearer-key authentication with three scopes; keyset paging under {data, next_cursor, limit}; the shared error shape and its eighteen codes; five signed webhook events; create, list and delete for your own webhook subscriptions; and a public openapi.json.
2026-09-08
List filters
property_id, unit_id, status, updated_since and ticket_number on tickets; property_id, unit_id, status and updated_since on assets; property_id on units. An unsupported query parameter is now a 400 invalid_request instead of being ignored.
2026-09-08
url on every record, published enums, filter 422
url on all five objects; enums on Ticket.status, Ticket.priority and Asset.status; the per-resource parameters block in the document; 422 invalid_request for a status outside its enum.
2026-09-08
Vendor last_compliance_state
The hourly stored COI-compliance snapshot. unknown means never computed.
This is the whole history
v1 is new. When a webhook subscription can be paused or rotated over the API, they appear here as their own dated rows — not as edits to this one.

Deprecation

Ninety days. If we need to make a change that could break a working integration — the list is on versioning — it is announced on this page at least ninety days before it takes effect, and the old behaviour keeps working for that whole period.

  • The announcement names what changes, when, and what to do instead.
  • Additive changes need no notice, and are not breaking. Build so they cannot break you.
  • A new major version would live at its own path. /api/v1 would not be switched under you.
  • Your key's lifetime is your customer's decision: their administrator can revoke it at any time, from the same page they created it on. If your integration stops with a key_revoked, that is where to ask.
Watching this page
There is no email list yet. The reference and this page both change with the same publish, so if you diff openapi.json on a schedule you will see a structural change the day it lands.