seam-access-grants

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Seam Access Grants

Seam Access Grants

You are an expert Seam integration engineer. Write the integration code directly into the developer's existing codebase.
您是一名资深的Seam集成工程师。请直接将集成代码写入开发者的现有代码库中。

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.
  1. 快速推进:查找关键文件(预订/预约处理器、路由、模型),读取后直接编写代码。
  2. 在现有文件中编写代码:将Seam调用直接添加到现有服务/处理器函数中,不要创建包装服务。
  3. 最小化改动:仅修改需要添加Seam调用的文件以及Webhook路由。安装SDK、添加导入语句、添加调用代码。

How Access Grants works

Access Grants 工作原理

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.
您可以创建一个访问授权,指定用户身份、目标设备、请求的访问方式(PIN码、移动密钥)以及时间窗口。Seam会在锁上配置凭证。您必须存储
access_grant_id
,以便后续更新或删除该授权。

1. Install SDK + initialize

1. 安装SDK并初始化

Do NOT pin to a specific version.
bash
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:
typescript
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:
typescript
import { Seam } from "seam";
const seam = new Seam({ apiKey: process.env.SEAM_API_KEY });
python
from seam import Seam
seam = Seam(api_key=os.environ["SEAM_API_KEY"])
请勿固定到特定版本。
bash
npm install seam        # Node.js
pip install seam        # Python
bundle add seam         # Ruby
Next.js 关键注意事项:在模块作用域使用
new Seam()
会导致
next build
失败,请使用延迟获取器:
typescript
import { Seam } from "seam";
let _seam: Seam;
function getSeam() {
  if (!_seam) _seam = new Seam({ apiKey: process.env.SEAM_API_KEY! });
  return _seam;
}
对于 Express / 标准 Node.js:
typescript
import { Seam } from "seam";
const seam = new Seam({ apiKey: process.env.SEAM_API_KEY });
python
from seam import Seam
seam = Seam(api_key=os.environ["SEAM_API_KEY"])

2. Get the device ID

2. 获取设备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.
Access Grants 通过
device_id
定位特定设备。每个房间/门必须映射到其专属的设备ID——切勿为所有房间使用单一全局设备。
可在以下位置查找设备ID:
  • 按房间设置的环境变量:
    SEAM_DEVICE_ROOM_101
    SEAM_DEVICE_ID_ROOM_A1
    等。
  • 应用的数据模型(例如:
    room.seamDeviceId
    unit.deviceId
如果缺少映射关系,请终止操作——不要回退到全局设备。授权错误的门比授权失败更严重。

3. Create access grant on booking creation

3. 创建预订时生成访问授权

Add directly inside the create function. Store the
access_grant_id
on the booking object.
typescript
// 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.
}
python
undefined
直接在创建函数内添加代码。
access_grant_id
存储在预订对象上。
typescript
// 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.
}
python
undefined

Inside create_booking(), after saving:

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.
undefined
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.
undefined

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.
  • 存储
    access_grant_id
    ——您需要它来进行更新和删除操作。如果预订模型中没有对应字段,请添加一个。如果该字段为
    null
    /
    undefined
    ,说明授权失败,需要重试。
  • 切勿使用全局设备回退——每个房间必须映射到其特定设备。错误房间的访问权限比无权限更糟糕。
  • **
    user_identity
    **需要
    full_name
    email_address
    参数,而非
    name
    email
  • **
    device_ids
    **是一个数组——您可以在一次调用中为多个门授予访问权限。
  • requested_access_methods
    ——
    "code"
    对应PIN码,
    "mobile_key"
    对应移动密钥+Instant Key。
  • 确定失败处理模式:如果预订需要访问权限(例如酒店房间),当Seam操作失败时抛出异常,确保预订不会在没有有效访问码的情况下确认。如果访问权限是可选的(例如健身房),则记录日志并继续执行。

4. Update access grant on booking changes

4. 预订变更时更新访问授权

typescript
if (booking.seamAccessGrantId) {
  await seam.accessGrants.update({
    access_grant_id: booking.seamAccessGrantId,
    starts_at: booking.checkIn,
    ends_at: booking.checkOut
  });
}
typescript
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

5. 取消预订时删除访问授权

typescript
if (booking.seamAccessGrantId) {
  await seam.accessGrants.delete({
    access_grant_id: booking.seamAccessGrantId
  });
}
python
if booking.seam_access_grant_id:
    seam.access_grants.delete(
        access_grant_id=booking.seam_access_grant_id
    )
typescript
if (booking.seamAccessGrantId) {
  await seam.accessGrants.delete({
    access_grant_id: booking.seamAccessGrantId
  });
}
python
if booking.seam_access_grant_id:
    seam.access_grants.delete(
        access_grant_id=booking.seam_access_grant_id
    )

6. Add webhook endpoint

6. 添加Webhook端点

Follow the existing webhook pattern in the codebase:
typescript
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 });
});
遵循代码库中现有的Webhook模式:
typescript
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

7. 将函数改为异步

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
.
将服务函数改为异步,并更新调用者以使用await等待结果。
如果出现问题,请阅读
references/troubleshooting.md
。如需确保生产环境就绪,请阅读
references/production-checklist.md