Seam API
Cloud-to-cloud integration with Seam, which fronts a wide roster of smart-lock vendors (August, Yale, Schlage, Salto, SmartThings, etc.) behind a single REST + events API. Use this when the integrator already pays for Seam, or when one workspace controls a mixed fleet that GEM would otherwise need several vendor drivers for.
What's controlled
- Remote
lock/unlock— the SDK auto-polls Seam'saction_attemptsuntil the lock physically confirms, so commands return real success or a descriptive failure, not fire-and-forget. refresh— pull fresh lock state for every zone immediately.clear_tamper— clear the zone'stamperedflag (Seam does not always emit a "tamper resolved" event).- Access-code workflows —
create_access_code,list_access_codes,update_access_code,delete_access_code,generate_code. Supports ongoing codes, time-bound codes (starts_at+ends_at), and one-time codes. pull_events— fetch recent Seam events for the managed locks. Does not advance the internal auto-poll cursor.
Prerequisites
- A Seam workspace at console.seam.co and an API key with permission to read/manage your locks.
- The target locks already enrolled in the manufacturer app and visible in the
Seam dashboard with non-empty
device_idUUIDs. - Outbound HTTPS to
seam.co. No inbound ports.
Setup
- Mint a Seam API key in the Seam console.
- Add a device with driver
seamapiand paste the key intoapi_key. - Create one zone per lock under a lock / security subsystem. Set
zone.addressto the Seamdevice_id(UUID, case-sensitive). - Reload the device. Zone attributes populate on the next poll.
- For access-code workflows, drive
create_access_code/list_access_codesetc. from a macro or the Script Console.
Attributes
Device — required
| Name | Type | Description |
|---|---|---|
api_key | string (secure) | Seam workspace API key. |
Device — optional
| Name | Type | Default | Description |
|---|---|---|---|
status_interval | int (ms) | 5000 | How often to poll Seam for lock state. Floor is 2000 ms. |
poll_events | bool | true | Whether to also poll Seam's events stream for state-change events between snapshots. |
low_battery_threshold | int (%) | 20 | low_battery is set true when battery_level falls below this. Re-evaluated every poll, so threshold edits take effect on the next tick. |
Zone — address
zone.address is the Seam device_id UUID exactly as shown in the Seam
dashboard, e.g. 1a2b3c4d-5678-90ab-cdef-1234567890ab.
Zone — read-only attributes
| Attribute | Type | Description |
|---|---|---|
state | string | locked or unlocked. |
battery_level | int | Battery percentage 0–100 (Seam reports a 0.0–1.0 float; the driver scales it). |
battery_status | string | Manufacturer category: full, good, low, critical. |
low_battery | bool | True when battery_level is below low_battery_threshold. |
online | bool | Lock currently reachable by Seam. |
tampered | bool | Set true by Seam's device.tampered event. Cleared via clear_tamper. |
device_errors | string | Comma-separated Seam error_codes currently active on the lock (e.g. device_offline,hub_disconnected). |
supported_code_lengths | string | Comma-separated digit counts the lock accepts for access codes (e.g. 4,5,6,7,8). Used by create_access_code for pre-validation. |
max_active_codes | int | Max simultaneous access codes the lock supports. |
Commands
| Name | Args | Notes |
|---|---|---|
lock | address | Returns {success, state, action_attempt_id} on confirm, or {error, failure_type} / {error, timeout: true} on a Seam failure / timeout. |
unlock | address | Same shape as lock. |
refresh | — | Re-pull every managed lock and (if enabled) drain the event queue. |
clear_tamper | address | Set tampered back to false on a zone. |
create_access_code | address, name, code, starts_at, ends_at, is_one_time_use, preferred_code_length | Omit code to let Seam auto-generate a compatible value. If code is provided, the driver pre-validates its length against the zone's supported_code_lengths. |
list_access_codes | address | Returns all codes currently provisioned on the lock. |
update_access_code | access_code_id, name, code, starts_at, ends_at | Only pass the fields you want to change. |
delete_access_code | access_code_id | Remove by Seam access-code id. |
generate_code | address | Ask Seam for a code that matches the lock's constraints, without provisioning it. |
pull_events | since_minutes (default 60) | Fetch recent events for the managed locks. Does not advance the auto-poll cursor — safe to call ad-hoc for debugging. |
How polling and events fit together
The driver maintains its own poll loop (no webhooks). Each tick lists every
lock in the workspace, applies the snapshot to the matching zone, and — if
poll_events is enabled — drains Seam's events stream since the last cursor.
Events are deduped against a 500-entry in-memory ring so overlapping windows
don't double-fire.
On error the driver backs off exponentially up to 60 s, then resumes the normal
status_interval cadence once a poll succeeds. The device's connected flag
follows the latest poll result.
Subscribed event types
The events poller scopes itself to the event types the driver actually reacts to, to keep Seam quota usage low:
lock.locked, lock.unlocked, lock.access_denied,
device.connected, device.disconnected,
device.connection_became_flaky, device.connection_stabilized,
device.tampered, device.low_battery, device.battery_status_changed,
device.removed, device.deleted,
action_attempt.lock_door.failed, action_attempt.unlock_door.failed,
access_code.set_on_device, access_code.removed_from_device,
access_code.failed_to_set_on_device, access_code.failed_to_remove_from_device.
Known quirks
- Cloud round-trip adds 1–5 s latency on lock/unlock. The SDK auto-polls Seam until the action attempt resolves, so a successful return means the lock truly moved.
- Seam's
battery_levelis a 0.0–1.0 float; GEM scales it to a 0–100 integer. tamperedis set bydevice.tamperedbut Seam does not emit a corresponding "tamper resolved" event for every vendor — clear withclear_tamper.- The driver does not use webhooks; everything is pull-based.
Troubleshooting
| Symptom | Check |
|---|---|
| Zone state never updates | Confirm zone.address matches the Seam device_id exactly (UUID, case-sensitive). Look for seam zone not found warnings in the server log. |
Commands return invalid zone for address | The zone address and Seam device_id must match. If you changed the address, reload the device. |
| Lock shows offline but works from the Seam app | Run the refresh command. If persistent, inspect the zone's device_errors attribute for Seam's reason. |
create_access_code rejects the code length | Some locks only accept specific lengths (4–8 digits typical). Omit code to let Seam pick one, or read the zone's supported_code_lengths attribute. |
tampered stays true after you cleared the alarm | Run clear_tamper on that zone. |
lock/unlock returns {error, timeout: true} | The Seam SDK waited for confirmation and never got one. The lock is likely offline or the hub is unreachable. |
See also
- August Smart Lock — direct August / Yale Home cloud driver, no Seam dependency.
- Schlage Encode / Sense+ — direct Allegion cloud driver.
- Brivo — commercial access-control cloud, similar shape.