Skip to main content

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's action_attempts until 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's tampered flag (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_id UUIDs.
  • Outbound HTTPS to seam.co. No inbound ports.

Setup

  1. Mint a Seam API key in the Seam console.
  2. Add a device with driver seamapi and paste the key into api_key.
  3. Create one zone per lock under a lock / security subsystem. Set zone.address to the Seam device_id (UUID, case-sensitive).
  4. Reload the device. Zone attributes populate on the next poll.
  5. For access-code workflows, drive create_access_code / list_access_codes etc. from a macro or the Script Console.

Attributes

Device — required

NameTypeDescription
api_keystring (secure)Seam workspace API key.

Device — optional

NameTypeDefaultDescription
status_intervalint (ms)5000How often to poll Seam for lock state. Floor is 2000 ms.
poll_eventsbooltrueWhether to also poll Seam's events stream for state-change events between snapshots.
low_battery_thresholdint (%)20low_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

AttributeTypeDescription
statestringlocked or unlocked.
battery_levelintBattery percentage 0–100 (Seam reports a 0.0–1.0 float; the driver scales it).
battery_statusstringManufacturer category: full, good, low, critical.
low_batteryboolTrue when battery_level is below low_battery_threshold.
onlineboolLock currently reachable by Seam.
tamperedboolSet true by Seam's device.tampered event. Cleared via clear_tamper.
device_errorsstringComma-separated Seam error_codes currently active on the lock (e.g. device_offline,hub_disconnected).
supported_code_lengthsstringComma-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_codesintMax simultaneous access codes the lock supports.

Commands

NameArgsNotes
lockaddressReturns {success, state, action_attempt_id} on confirm, or {error, failure_type} / {error, timeout: true} on a Seam failure / timeout.
unlockaddressSame shape as lock.
refreshRe-pull every managed lock and (if enabled) drain the event queue.
clear_tamperaddressSet tampered back to false on a zone.
create_access_codeaddress, name, code, starts_at, ends_at, is_one_time_use, preferred_code_lengthOmit 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_codesaddressReturns all codes currently provisioned on the lock.
update_access_codeaccess_code_id, name, code, starts_at, ends_atOnly pass the fields you want to change.
delete_access_codeaccess_code_idRemove by Seam access-code id.
generate_codeaddressAsk Seam for a code that matches the lock's constraints, without provisioning it.
pull_eventssince_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_level is a 0.0–1.0 float; GEM scales it to a 0–100 integer.
  • tampered is set by device.tampered but Seam does not emit a corresponding "tamper resolved" event for every vendor — clear with clear_tamper.
  • The driver does not use webhooks; everything is pull-based.

Troubleshooting

SymptomCheck
Zone state never updatesConfirm 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 addressThe zone address and Seam device_id must match. If you changed the address, reload the device.
Lock shows offline but works from the Seam appRun the refresh command. If persistent, inspect the zone's device_errors attribute for Seam's reason.
create_access_code rejects the code lengthSome 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 alarmRun 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