SOUTHEAST BUILDINGSDesign · Build · Host · Manage

Developer API / Home automation

Home automation guide

The booking calendar should drive the house: door codes that follow the reservation, thermostats that warm up before check-in, lights on for arrival night. This is how our own property in Clemson runs, and everything it needs is on this API. There are two ways in — poll the occupancy summary on an interval, and let webhooks push booking events to you the moment they happen. Use both: polling is your source of truth, webhooks make it instant.

What this looks like in practice

Everything below runs today at Lakeside on College Ave. On the day of check-in, the in-unit kiosk updates itself: an AI agent reads the reservation from the booking pipeline and pushes the guest's name, dates, and check-out time to the tablet — so the house greets each guest personally before anyone lifts a finger. The same kiosk gives guests controls for the guest areas, local info, and Wi-Fi access, while the host gets a single Home Assistant dashboard — thermostats, locks, shades, garage doors, cameras, and a remotely rebootable network closet — on a phone, from anywhere.

In-unit guest kiosk greeting an arriving guest by name with their check-in and check-out details
Check-in day. Nobody touched this tablet — an AI agent read the reservation and the kiosk greeted the guest by name, dates and all. (Demo guest pictured.)
Guest kiosk Home Control screen with scene buttons, shade controls, and a live exterior camera
The same kiosk is the guests' control center: one-tap scenes, shades room by room, and a live look at the driveway — guest areas only, with management controls kept private.
Guest kiosk Local Information screen showing the weather forecast and a map around the house
Local information without asking the host: the week's weather and a map of everything around the lake.
Host Home Assistant dashboard with per-unit thermostats, locks, garage doors, cameras, and network controls
The host's side: every unit's thermostat, locks, garage doors, doorbell, live cameras, and a network closet that can be power-cycled remotely — one Home Assistant dashboard, on a phone, from anywhere.

The occupancy summary

GET /api/v1/ha/summary (requires a read-scoped key) returns everything an automation needs in one response — per-property occupancy, today's arrivals and departures, the current and next booking, and operational details like the door code and Wi-Fi network. Dates are YYYY-MM-DD in the property's local sense of "today".

{
  "date": "2026-06-12",
  "generatedAt": "2026-06-12T14:00:00.000Z",
  "totals": {
    "properties": 5,
    "occupiedTonight": 2,
    "checkInsToday": 1,
    "checkOutsToday": 0,
    "pendingBookings": 1
  },
  "properties": [
    {
      "slug": "lakeview-cabin",
      "name": "Lakeview Cabin",
      "occupied": true,
      "checkInToday": false,
      "checkOutToday": false,
      "currentBooking": {
        "code": "SEB-A1B2C3",
        "guestName": "Jane Smith",
        "guests": 4,
        "checkIn": "2026-06-10",
        "checkOut": "2026-06-14"
      },
      "nextArrival": {
        "code": "SEB-D4E5F6",
        "guestName": "Sam Lee",
        "checkIn": "2026-06-20",
        "status": "confirmed"
      },
      "checkInTime": "4:00 PM",
      "checkOutTime": "11:00 AM",
      "doorCode": "4821",
      "wifiSsid": "LakesideGuest"
    }
  ]
}

Notes: occupied counts only confirmed bookings, while checkInToday / checkOutToday include pending requests too — so the house can prepare for an arrival that's still awaiting approval. currentBooking and nextArrival are null when there isn't one.

Wire it into Home Assistant

One REST sensor polls the summary; everything else derives from its attributes. Keep the key in secrets.yaml rather than inline.

# configuration.yaml
rest:
  - resource: https://yourdomain.com/api/v1/ha/summary
    headers:
      Authorization: !secret seb_api_auth   # "Bearer seb_your_key_here"
    scan_interval: 300
    sensor:
      - name: "Rentals occupied tonight"
        value_template: "{{ value_json.totals.occupiedTonight }}"
        json_attributes:
          - date
          - totals
          - properties

Then split the per-property fields into their own entities with template sensors — repeat the block for each listing you automate:

# configuration.yaml
template:
  - binary_sensor:
      - name: "Lakeview Cabin occupied"
        state: >-
          {{ state_attr('sensor.rentals_occupied_tonight', 'properties')
             | selectattr('slug', 'eq', 'lakeview-cabin')
             | map(attribute='occupied') | first }}
      - name: "Lakeview Cabin check-in today"
        state: >-
          {{ state_attr('sensor.rentals_occupied_tonight', 'properties')
             | selectattr('slug', 'eq', 'lakeview-cabin')
             | map(attribute='checkInToday') | first }}
      - name: "Lakeview Cabin checkout today"
        state: >-
          {{ state_attr('sensor.rentals_occupied_tonight', 'properties')
             | selectattr('slug', 'eq', 'lakeview-cabin')
             | map(attribute='checkOutToday') | first }}
  - sensor:
      - name: "Lakeview Cabin door code"
        state: >-
          {{ state_attr('sensor.rentals_occupied_tonight', 'properties')
             | selectattr('slug', 'eq', 'lakeview-cabin')
             | map(attribute='doorCode') | first }}

Recipe: the lock follows the platform

Whatever door code the platform reports is the code the lock should have. Trigger on any change to the door-code sensor and program the lock — shown here with Z-Wave JS; swap the action for your lock integration.

automation:
  - alias: "Keep the front door code in sync"
    triggers:
      - trigger: state
        entity_id: sensor.lakeview_cabin_door_code
    conditions:
      - condition: template
        value_template: >-
          {{ trigger.to_state.state not in ['unknown', 'unavailable', 'none', ''] }}
    actions:
      - action: zwave_js.set_lock_usercode
        target:
          entity_id: lock.front_door
        data:
          code_slot: 2
          usercode: "{{ trigger.to_state.state }}"

Recipe: climate around the stay

Comfortable at check-in, economical the rest of the time: warm (or cool) the unit 45 minutes before check-in, and set back a few minutes after checkout. Adjust the times to your own check-in/checkout schedule.

automation:
  - alias: "Comfort temp before check-in"
    triggers:
      - trigger: time
        at: "15:15"            # 45 min before a 4:00 PM check-in
    conditions:
      - condition: state
        entity_id: binary_sensor.lakeview_cabin_check_in_today
        state: "on"
    actions:
      - action: climate.set_temperature
        target:
          entity_id: climate.lakeview_cabin
        data:
          temperature: 71

  - alias: "Eco setback after checkout"
    triggers:
      - trigger: time
        at: "11:05"            # 5 min after an 11:00 AM checkout
    conditions:
      - condition: state
        entity_id: binary_sensor.lakeview_cabin_checkout_today
        state: "on"
      - condition: state
        entity_id: binary_sensor.lakeview_cabin_occupied
        state: "off"           # skip when a same-day arrival follows
    actions:
      - action: climate.set_preset_mode
        target:
          entity_id: climate.lakeview_cabin
        data:
          preset_mode: eco

Recipe: lights on for arrival night

Guests arriving after dark shouldn't walk up to a black house. On check-in day, bring up the entry and porch lights just before sunset.

automation:
  - alias: "Arrival lighting"
    triggers:
      - trigger: sun
        event: sunset
        offset: "-00:20:00"
    conditions:
      - condition: state
        entity_id: binary_sensor.lakeview_cabin_check_in_today
        state: "on"
    actions:
      - action: light.turn_on
        target:
          area_id: entry
        data:
          brightness_pct: 70

Instant events with webhooks

Polling every five minutes is fine for thermostats; it's slow for "a booking just landed." Register a webhook in the admin (/admin/webhooks) and the platform POSTs to your URL the moment these happen — subscribe to specific events or * for all of them:

booking.createda new request comes in (any source)
booking.confirmedthe host confirms; payment is captured
booking.cancelledcancelled by host or guest; holds released
booking.updatedstatus or details change
booking.payment_authorizedthe guest's card hold succeeds
booking.payment_expiredan unpaid checkout link lapses
calendar.syncedan Airbnb/VRBO iCal pull completes

Every delivery carries the event name in X-SEB-Event and an HMAC-SHA256 signature of the raw body in X-SEB-Signature, computed with the webhook's secret:

POST https://your-endpoint.example/hook
Content-Type: application/json
X-SEB-Event: booking.confirmed
X-SEB-Signature: sha256=2f1d8a9c...

{
  "event": "booking.confirmed",
  "timestamp": "2026-06-12T14:03:22.000Z",
  "data": {
    "code": "SEB-A1B2C3",
    "property": "lakeview-cabin",
    "propertyName": "Lakeview Cabin",
    "guestName": "Jane Smith",
    "checkIn": "2026-07-10",
    "checkOut": "2026-07-13",
    "status": "confirmed",
    "paymentStatus": "paid"
  }
}

(booking.created additionally includes guests, total in integer cents, and source.) In Home Assistant, point the webhook at an automation trigger — and use the event to refresh the REST sensor immediately instead of waiting out the poll interval:

automation:
  - alias: "Booking events from the platform"
    triggers:
      - trigger: webhook
        webhook_id: "seb-3f9c0a7d-long-and-random"   # treat this ID as a secret
        allowed_methods: [POST]
        local_only: false
    actions:
      - action: homeassistant.update_entity
        target:
          entity_id: sensor.rentals_occupied_tonight
      - action: notify.mobile_app_your_phone
        data:
          title: "{{ trigger.json.event }}"
          message: >-
            {{ trigger.json.data.guestName }} · {{ trigger.json.data.propertyName }} ·
            {{ trigger.json.data.checkIn }} → {{ trigger.json.data.checkOut }}

Home Assistant's webhook trigger can't verify HMAC signatures, so its protection is the unguessable webhook_id over HTTPS. If you want real verification, terminate the webhook at something that can check the signature (a Cloudflare Worker, n8n, a small server) and forward to HA from there:

import { createHmac, timingSafeEqual } from "node:crypto";

function verifySignature(rawBody, signatureHeader, secret) {
  const expected =
    "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

Keep channel calendars fresh

Our hosted platform already pulls Airbnb/VRBO iCal feeds every 30 minutes on a cron. If you self-host or want a tighter loop around high-demand weekends, trigger a sync from HA with a write-scoped key:

rest_command:
  sync_channel_calendars:
    url: https://yourdomain.com/api/v1/sync/ical
    method: post
    headers:
      authorization: !secret seb_write_auth

automation:
  - alias: "Pull channel calendars"
    triggers:
      - trigger: time_pattern
        minutes: "/30"
    actions:
      - action: rest_command.sync_channel_calendars

Security notes

The full endpoint reference lives on the Developer API page, with a machine-readable spec at /api/openapi.json. Want a setup like this for your own rental — locks, climate, kiosk, the works? That's what our consulting practice builds.