seam-access-grants
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSeam 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
实施方法
- Move fast. Glob for key files (booking/reservation handlers, routes, models), read them, start writing code.
- Write code in existing files. Add Seam calls directly into existing service/handler functions. Don't create wrapper services.
- Minimize changes. Only touch files that need Seam calls + webhook route. Install SDK, add import, add calls.
- 快速推进:查找关键文件(预订/预约处理器、路由、模型),读取后直接编写代码。
- 在现有文件中编写代码:将Seam调用直接添加到现有服务/处理器函数中,不要创建包装服务。
- 最小化改动:仅修改需要添加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 to update or delete it later.
access_grant_id您可以创建一个访问授权,指定用户身份、目标设备、请求的访问方式(PIN码、移动密钥)以及时间窗口。Seam会在锁上配置凭证。您必须存储,以便后续更新或删除该授权。
access_grant_id1. 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 # RubyCRITICAL for Next.js: at module scope BREAKS . Use a lazy getter:
new Seam()next buildtypescript
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 # RubyNext.js 关键注意事项:在模块作用域使用会导致失败,请使用延迟获取器:
new Seam()next buildtypescript
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 . Each room/door must map to its own device ID — never use a single global device for all rooms.
device_idLook for device IDs in:
- Environment variables per room: ,
SEAM_DEVICE_ROOM_101, etc.SEAM_DEVICE_ID_ROOM_A1 - 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 通过定位特定设备。每个房间/门必须映射到其专属的设备ID——切勿为所有房间使用单一全局设备。
device_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 on the booking object.
access_grant_idtypescript
// 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_idtypescript
// 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
undefinedInside 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.
undefineddevice_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.
undefinedGotchas
注意事项
- Store — you need it for update and delete. Add a field to the booking model if one doesn't exist. If it's
access_grant_id/null, the grant failed and needs retry.undefined - Never use a global device fallback — each room must map to its specific device. Wrong-room access is worse than no access.
- takes
user_identityandfull_name, NOTemail_addressandname.email - is an array — you can grant access to multiple doors in one call.
device_ids - —
requested_access_methodsfor PIN,"code"for mobile key + Instant Key."mobile_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对应PIN码,"code"对应移动密钥+Instant Key。"mobile_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 . For production readiness, read .
references/troubleshooting.mdreferences/production-checklist.md将服务函数改为异步,并更新调用者以使用await等待结果。
如果出现问题,请阅读。如需确保生产环境就绪,请阅读。
references/troubleshooting.mdreferences/production-checklist.md