How to generate temporary access codes programmatically
Create an access code in 60 seconds
1# A three-night stay starting now. Computed so the snippet never goes stale.
2STARTS_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
3ENDS_AT=$(date -u -v+3d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '+3 days' +%Y-%m-%dT%H:%M:%SZ)
4
5# 1. Grant Jane a PIN code on the front door for that stay.
6curl -X POST "https://connect.getseam.com/access_grants/create" \
7 -H "Authorization: Bearer $SEAM_API_KEY" \
8 -H "Content-Type: application/json" \
9 -d '{
10 "user_identity": {
11 "full_name": "Jane Doe",
12 "email_address": "jane@example.com"
13 },
14 "device_ids": ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
15 "requested_access_methods": [{ "mode": "code" }],
16 "starts_at": "'"$STARTS_AT"'",
17 "ends_at": "'"$ENDS_AT"'"
18 }'
19
20# 2. Read back the PIN Seam generated, using the access_grant_id from step 1.
21# "code" is null until "is_issued" is true, so check that before you send the
22# digits to anyone. On real hardware, programming the lock takes a moment.
23curl -X POST "https://connect.getseam.com/access_methods/list" \
24 -H "Authorization: Bearer $SEAM_API_KEY" \
25 -H "Content-Type: application/json" \
26 -d '{ "access_grant_id": "ef83cca9-5fdf-4ac2-93f3-c21c5a8be54b" }'Build it with your AI agent
- Reservation AutomationsPush reservations to Seam and let it manage codes across the booking lifecycle.
- Access GrantsPer-entrance, per-credential access: PIN codes, mobile keys and Instant Keys.
- Access CodesDirect, manual control of individual time-bound codes on a specific device.
1. Install the plugin
/plugin marketplace add seamapi/seam-plugin
/plugin install seam@seamapiThe Claude Code install also adds our documentation MCP server, so the agent can look up endpoints and device capabilities while it works. Cursor, Codex and other agents get the skills through npx skills add and can add the MCP server there separately.
2. Paste this prompt
Add temporary access codes to my app with Seam, using the Access Grants skill.
When a booking is confirmed, call /access_grants/create with:
- user_identity built from the guest's name and email
- device_ids for the doors that booking covers
- requested_access_methods: [{ mode: "code" }]
- starts_at and ends_at from the booking window
Then read the PIN back with /access_methods/list and include it in the confirmation email, but only once the access method reports is_issued: true. Subscribe to the access_method.issued webhook instead of polling in a loop.
When a booking's dates change, call /access_grants/update with the new starts_at or ends_at instead of creating a second grant. The PIN digits survive, so there is nothing to re-send. /access_grants/update moves the time window only, so when a booking moves to different doors, delete the grant and create a new one. When a booking is cancelled, call /access_grants/delete.
Use my existing HTTP client, config and logging conventions, read the API key from the environment, and add tests for the shortened-stay and cancelled-booking paths.What you can build
Check-in with nobody in the loop
Issue a code the moment a booking is confirmed and let it stop working at checkout. No key handover, no lockbox, and no reminder to delete the code next week.
One integration, every lock
The same grant programs August, Yale, Schlage, Lockly, TTLock and Tedee locks, plus Salto, Brivo and dormakaba entrances. Offline hotel systems ask for one extra field, a reservation_key. Supporting another brand is a device to connect, not a project to scope.
Codes that follow your data
Grants are keyed to your reservation or membership record, so a shortened stay updates the code in place and a cancellation removes it. Nothing is left stranded on the lock.
How it works
Install the SDK and export your API key
Create a workspace in Seam Console, generate an API key under Developer > API Keys, and export it. The SDK reads SEAM_API_KEY from the environment. A sandbox workspace comes with virtual locks that behave like real ones.
1npm i seam 2 3# The SDK picks this up automatically. 4export SEAM_API_KEY=seam_test2bMS_94SrGUXuNR2JmJkjtvBQDg5cPick a door that can take a PIN code
Not every lock accepts a remotely programmed code. On a standalone smart lock, confirm can_program_online_access_codes is true. On an access control system, confirm can_unlock_with_code is true on the entrance. Checking first turns a runtime failure into a branch you control.
1// Standalone smart locks: check can_program_online_access_codes. 2const devices = await seam.devices.list() 3 4const pinCapableDevices = devices.filter( 5 (device) => device.can_program_online_access_codes, 6) 7 8// Access control systems: check can_unlock_with_code on the entrance instead. 9const entrances = await seam.acs.entrances.list({ 10 acs_system_id: "c359cba2-8ef2-47fc-bee0-1c7c2a886339", 11}) 12 13const codeEntrances = entrances.filter((entrance) => entrance.can_unlock_with_code)Create an access grant with a code access method
On a standalone smart lock or a fully online access system, this is the only call you need. Name the person with user_identity_id or an inline user_identity, the doors with device_ids, acs_entrance_ids or space_ids, and the window with starts_at and ends_at. Ask for mode: code and Seam generates a PIN that satisfies the device's own rules. Offline hotel and multifamily systems are the exception. Salto Space, dormakaba Ambiance and Community, and ASSA ABLOY Visionline and Vostio override the previous guest's credential, so guest access on their entrances also needs a reservation_key. Check can_belong_to_reservation on the entrance instead of branching on the brand: pass a key when at least one targeted entrance reports true, and leave it out when none do.
1// Standalone smart locks and fully online access systems: this is the call. 2const accessGrant = await seam.accessGrants.create({ 3 user_identity_id: "22222222-2222-2222-2222-222222222222", 4 device_ids: ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"], 5 requested_access_methods: [{ mode: "code" }], 6 starts_at: booking.checkIn, // ISO 8601, e.g. from your reservation record 7 ends_at: booking.checkOut, 8}) 9 10// Offline hotel and multifamily systems decide at the door, not in the cloud, 11// so a new guest's credential has to override the last one. Guest access on 12// those entrances needs a reservation_key. Let the entrance tell you which 13// kind you have instead of branching on the brand name. 14const entrances = await seam.acs.entrances.list({ space_id: "room-101" }) 15 16const needsReservation = entrances.some( 17 (entrance) => entrance.can_belong_to_reservation, 18) 19 20await seam.accessGrants.create({ 21 user_identity_id: "22222222-2222-2222-2222-222222222222", 22 acs_entrance_ids: entrances.map((entrance) => entrance.acs_entrance_id), 23 requested_access_methods: [{ mode: "code" }], 24 starts_at: booking.checkIn, // ISO 8601, e.g. from your reservation record 25 ends_at: booking.checkOut, 26 // Your booking reference works well here: grants sharing a key join the 27 // same stay, a new key starts one that overrides the previous guest. 28 ...(needsReservation ? { reservation_key: "booking-8412" } : {}), 29})Read the code back and send it to your guest
The grant response tells you what was requested; the access method tells you what was issued. List the access methods for the grant to get the digits, and only deliver them once is_issued is true. On real hardware, programming takes a few moments, and the access_method.issued webhook saves you a polling loop.
1const accessMethods = await seam.accessMethods.list({ 2 access_grant_id: accessGrant.access_grant_id, 3}) 4 5const [pinCode] = accessMethods 6 7if (pinCode?.is_issued) { 8 await sendGuestEmail({ code: pinCode.code }) 9}Let the window close, or move it
You do not schedule a deletion. The code stops working at ends_at because Seam programmed that window onto the lock. If the dates change, update the grant instead of creating a second one. For PIN codes the digits stay the same, so there is no new code to re-send. /access_grants/update moves the window, not the doors: a booking that changes room is a delete and a fresh grant. Deleting the grant revokes every credential under it.
1// The guest extended checkout. Same grant, same PIN digits. 2await seam.accessGrants.update({ 3 access_grant_id: accessGrant.access_grant_id, 4 ends_at: booking.newCheckOut, 5}) 6 7// The booking was cancelled. This removes every credential under the grant. 8await seam.accessGrants.delete({ 9 access_grant_id: accessGrant.access_grant_id, 10})
Frequently asked questions
What is the difference between an access grant and an access code?
The grant is the request: this person, these doors, this window. The access code is one of the credentials Seam creates to satisfy it. Call /access_grants/create and Seam owns the credential lifecycle from there. The lower-level /access_codes API gives you direct control of one code on one device, and hands that lifecycle back to you.
How long before a temporary code works on the lock?
In a sandbox workspace, almost instantly. On real hardware, Seam has to reach the lock or its hub, so issuance takes a few moments. Wait for is_issued to be true on the access method, or listen for the access_method.issued event, before you send the code to a guest.
Can I choose the PIN myself?
Yes. Pass code on the requested access method, between four and nine digits, subject to what the lock accepts. Leave it out and Seam generates a code that already satisfies the device's constraints, which is what you want at scale.
What happens when a booking changes after the code is issued?
Call /access_grants/update with the new starts_at or ends_at. Seam re-programs the affected doors, and each access method reports is_issued: false until it reissues. For a PIN code the digits are unchanged, so you do not have to email a new one. The endpoint moves the time window only: a booking that moves to different doors is a delete and a fresh grant. Call /access_grants/delete to end access entirely.
Which locks and access systems support temporary PIN codes?
Any connected lock reporting can_program_online_access_codes, including August, Yale, Schlage, Lockly, TTLock, Tedee and igloohome, plus access control system entrances reporting can_unlock_with_code, such as Salto KS, Salto Space, Brivo and dormakaba. Read the capability flag instead of assuming per brand. Support varies by model and by how the device is connected. Salto Space and dormakaba also ask for a reservation_key on guest access; see the next question.
Why do Salto Space and dormakaba need a reservation_key?
Because their locks are not online. They decide at the door, using data carried on the credential, and a new guest's credential overrides the previous one. Seam needs a reservation_key to sequence that: grants sharing a key join the same stay, and a new key starts a stay that supersedes the last. It applies to Salto Space, dormakaba Ambiance and Community, and ASSA ABLOY Visionline and Vostio, and only to guest or resident credentials. Staff credentials override nothing and need no key. Check can_belong_to_reservation on the entrance: pass a key when at least one targeted entrance reports true, and omit it when none do, or the call is rejected.
Do I need a smart lock to try this?
No. Sandbox workspaces come with virtual locks and access systems that behave like the real thing, so you can issue a code and watch it expire without buying hardware. Virtual devices only connect in a sandbox workspace, so switch to a production workspace when you are ready for real locks.
Related
- Access Grants API referenceEvery field on /access_grants/create, plus the access methods it can request.
- Reservation Access GrantsWhen offline hotel and multifamily systems need a reservation_key, and how override and joiner behavior works.
- Supported devices and systemsWhich locks and access control systems Seam programs codes onto today.
- Short-term rental access automationThe same grant, driven by reservations from your PMS or booking platform.
- Coworking and shared space accessBooking-based codes for meeting rooms, studios and shared desks.
- All Seam guidesImplementation walkthroughs and hardware deep dives from the Seam team.