From seam
Integrate Seam Access Grants for per-entrance, per-credential control over smart lock access. Create access grants specifying who gets access to which doors, when, and how (PIN code, mobile key, Instant Key). Use this skill when someone needs to control which access methods each guest or member gets, issue both PIN codes and mobile keys, manage access per-door (room + lobby + gym), or deliver Instant Key links. Works with August, Yale, Schlage, Kwikset, and other smart locks. Common use cases: hotel guest apps, gym/fitness access, office visitor management.
How this skill is triggered — by the user, by Claude, or both
Slash command
/seam:seam-access-grantsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are an expert Seam integration engineer. Write the integration code directly into the developer's existing codebase.
You are an expert Seam integration engineer. Write the integration code directly into the developer's existing codebase.
You create an access grant specifying a user identity, target devices, requested access methods (PIN, mobile key), and a time window. Seam provisions the credentials on the locks. You must store the access_grant_id to update or delete it later.
Do NOT pin to a specific version.
npm install seam # Node.js
pip install seam # Python
bundle add seam # Ruby
CRITICAL for Next.js: new Seam() at module scope BREAKS next build. Use a lazy getter:
import { Seam } from "seam";
let _seam: Seam;
function getSeam() {
if (!_seam) _seam = new Seam({ apiKey: process.env.SEAM_API_KEY! });
return _seam;
}
For Express / standard Node.js:
import { Seam } from "seam";
const seam = new Seam({ apiKey: process.env.SEAM_API_KEY });
from seam import Seam
seam = Seam(api_key=os.environ["SEAM_API_KEY"])
Access Grants targets specific devices by device_id. Each room/door must map to its own device ID — never use a single global device for all rooms.
Look for device IDs in:
SEAM_DEVICE_ROOM_101, SEAM_DEVICE_ID_ROOM_A1, etc.room.seamDeviceId, unit.deviceId)If the mapping is missing, fail the operation — do not fall back to a global device. Granting access to the wrong door is worse than failing.
Add directly inside the create function. Store the access_grant_id on the booking object.
// Inside createBooking(), after saving the booking:
const deviceId = getDeviceIdForRoom(room); // Must resolve per-room
if (!deviceId) {
throw new Error(`No Seam device configured for room ${room.id}`);
}
try {
const accessGrant = await seam.accessGrants.create({
user_identity: {
full_name: guest.name,
email_address: guest.email
},
device_ids: [deviceId],
requested_access_methods: [
{ mode: "code" } // PIN code
// { mode: "mobile_key" } // Add for mobile key + Instant Key
],
starts_at: booking.checkIn,
ends_at: booking.checkOut
});
booking.seamAccessGrantId = accessGrant.access_grant_id;
} catch (err) {
console.error("Seam access grant failed:", err);
// Consider: should this fail the booking? If access is required, throw.
}
# Inside create_booking(), after saving:
device_id = get_device_id_for_room(room) # Must resolve per-room
if not device_id:
raise ValueError(f"No Seam device configured for room {room.id}")
try:
access_grant = seam.access_grants.create(
user_identity={"full_name": guest.name, "email_address": guest.email},
device_ids=[device_id],
requested_access_methods=[{"mode": "code"}],
starts_at=booking.check_in,
ends_at=booking.check_out
)
booking.seam_access_grant_id = access_grant.access_grant_id
except Exception as e:
print(f"Seam access grant failed: {e}")
# Consider: should this fail the booking? If access is required, raise.
access_grant_id — you need it for update and delete. Add a field to the booking model if one doesn't exist. If it's null/undefined, the grant failed and needs retry.user_identity takes full_name and email_address, NOT name and email.device_ids is an array — you can grant access to multiple doors in one call.requested_access_methods — "code" for PIN, "mobile_key" for mobile key + Instant Key.if (booking.seamAccessGrantId) {
await seam.accessGrants.update({
access_grant_id: booking.seamAccessGrantId,
starts_at: booking.checkIn,
ends_at: booking.checkOut
});
}
if (booking.seamAccessGrantId) {
await seam.accessGrants.delete({
access_grant_id: booking.seamAccessGrantId
});
}
if booking.seam_access_grant_id:
seam.access_grants.delete(
access_grant_id=booking.seam_access_grant_id
)
Follow the existing webhook pattern in the codebase:
router.post("/seam", (req, res) => {
const { event_type, ...data } = req.body;
switch (event_type) {
case "access_code.set_on_device":
console.log("PIN set on lock:", data.access_code_id);
break;
case "access_code.failed_to_set_on_device":
console.log("PIN failed:", data.access_code_id);
break;
case "device.disconnected":
console.log("Lock offline:", data.device_id);
break;
}
res.json({ received: true });
});
Make service functions async and update callers to await them.
If something goes wrong, read references/troubleshooting.md. For production readiness, read references/production-checklist.md.
npx claudepluginhub seamapi/seam-plugin --plugin seamGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.