mcpField
S
seam-access-grants

seam-access-grants

github.com/seamapiSecurity244 installsUpdated 26w ago
Repository

About seam-access-grants

A coding skill that guides developers in adding Seam Access Grants to an existing codebase, including booking-related grant creation, updates, and deletion. It emphasizes room-specific device mapping and storing grant IDs for later operations.

Key features

  1. 01Add Seam calls to existing handlers
  2. 02Create grants for booking time windows
  3. 03Map each room to its device ID
  4. 04Update or delete grants using stored IDs
  5. 05Add a webhook endpoint for access events

Use cases

  1. 01Grant door access when a booking is created
  2. 02Adjust access dates after booking changes
  3. 03Revoke access when a booking is cancelled

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/seamapi/seam-plugin --skill seam-access-grants

After installing, type/seam-access-grantsin Claude Code to confirm it loaded.

Bundled filesreferences/production-checklist.mdreferences/troubleshooting.md

Details

From the project README

Seam Access Grants

You are an expert Seam integration engineer. Write the integration code directly into the developer's existing codebase.

Approach

  1. Move fast. Glob for key files (booking/reservation handlers, routes, models), read them, start writing code.
  2. Write code in existing files. Add Seam calls directly into existing service/handler functions. Don't create wrapper services.
  3. Minimize changes. Only touch files that need Seam calls + webhook route. Install SDK, add import, add calls.

How Access Grants works

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.

1. Install SDK + initialize

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"])

2. Get the device ID

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:

  • Environment variables per room: SEAM_DEVICE_ROOM_101, SEAM_DEVICE_ID_ROOM_A1, etc.
  • The app's data model (e.g., 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.

3. Create access grant on booking creation

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.

Gotchas

  • Store 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.
  • Never use a global device fallback — each room must map to its specific device. Wrong-room access is worse than no access.
  • 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.
  • Decide your failure mode: if access is required for the booking (e.g., hotel room), throw on Seam failure so the booking doesn't confirm without a working code. If access is optional (e.g., gym), log and continue.

4. Update access grant on booking changes

if (booking.seamAccessGrantId) {
  await seam.accessGrants.update({
    access_grant_id: booking.seamAccessGrantId,
    starts_at: booking.checkIn,
    ends_at: booking.checkOut
  });
}

5. Delete access grant on cancellation

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
    )

6. Add webhook endpoint

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 });
});

7. Make functions async

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.

Comments

Markdown supported