Guides/How to generate temporary access codes programmatically

Implementation guide

How to generate temporary access codes programmatically

Temporary access codes give a person a PIN that works only for their visit. Seam generates the code, programs it onto every lock you name for the window you set, and shuts it off when the window closes, across every lock brand it supports.

On this page
Quickstart

Create an access code in 60 seconds

Temporary access is one call. Give /access_grants/create a person, the doors and the window, and Seam generates the PIN and programs the lock. There is no cleanup job to write.

1import { Seam } from "seam"
2
3const seam = new Seam()
4
5const devices = await seam.devices.list()
6const device = devices.find((item) => item.can_program_online_access_codes)
7if (!device) throw new Error("Connect a PIN-capable lock first")
8
9// Use your booking's check-in and checkout times in production.
10const startsAt = new Date()
11const endsAt = new Date(startsAt.getTime() + 3 * 24 * 60 * 60 * 1000)
12const accessGrant = await seam.accessGrants.create({
13  user_identity: { full_name: "Jane Doe", email_address: "jane@example.com" },
14  device_ids: [device.device_id],
15  requested_access_methods: [{ mode: "code" }],
16  starts_at: startsAt.toISOString(),
17  ends_at: endsAt.toISOString(),
18})
19console.log(accessGrant.access_grant_id) // Store this on the booking.
20
21const methods = await seam.accessMethods.list({
22  access_grant_id: accessGrant.access_grant_id,
23})
24const pin = methods.find((method) => method.mode === "code" && method.is_issued)
25if (pin) console.log(pin.code) // Pass to your guest messaging service.
26
27// If no PIN is issued yet, handle access_method.issued before sending it.
Fastest path

Build it with your AI agent

Install the Seam plugin and your coding agent picks up three integration skills: Reservation Automations, Access Grants and Access Codes. It then writes the calls the way we would. Paste the prompt below and let it build.

1. Install the plugin

/plugin marketplace add seamapi/seam-plugin
/plugin install seam@seamapi

The 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

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.
Use cases

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.

Walkthrough

How it works

  1. 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 examples read SEAM_API_KEY from the environment. A sandbox workspace comes with virtual locks that behave like real ones.

    1npm install seam
    2
    3# Use an API key from your sandbox workspace.
    4export SEAM_API_KEY="seam_test_your_sandbox_api_key"
  2. Pick 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.

    1import { Seam } from "seam"
    2
    3const seam = new Seam()
    4
    5const devices = await seam.devices.list()
    6const pinCapableDevices = devices.filter((device) => device.can_program_online_access_codes)
    7console.log(pinCapableDevices.map((device) => device.device_id))
    8
    9const entrances = await seam.acs.entrances.list()
    10const codeEntrances = entrances.filter((entrance) => entrance.can_unlock_with_code)
    11console.log(codeEntrances.map((entrance) => entrance.acs_entrance_id))
  3. 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.

    1import { Seam } from "seam"
    2
    3const seam = new Seam()
    4
    5const devices = await seam.devices.list()
    6const device = devices.find((item) => item.can_program_online_access_codes)
    7if (!device) throw new Error("Connect a PIN-capable lock first")
    8
    9// Use your booking's check-in and checkout times in production.
    10const startsAt = new Date()
    11const endsAt = new Date(startsAt.getTime() + 3 * 24 * 60 * 60 * 1000)
    12const accessGrant = await seam.accessGrants.create({
    13  user_identity: { full_name: "Jane Doe", email_address: "jane@example.com" },
    14  device_ids: [device.device_id],
    15  requested_access_methods: [{ mode: "code" }],
    16  starts_at: startsAt.toISOString(),
    17  ends_at: endsAt.toISOString(),
    18})
    19console.log(accessGrant.access_grant_id) // Store this on the booking.
  4. Handle access-system entrances and reservation keys

    For an access control system, use this alternative to the smart-lock grant above. Set SEAM_SPACE_ID to the actual Seam UUID of the booked space. The example checks every targeted entrance and includes reservation_key only when required. Replace the sample booking reference and three-day window with the values from your reservation record.

    1import { Seam } from "seam"
    2
    3const seam = new Seam()
    4
    5const spaceId = process.env.SEAM_SPACE_ID
    6if (!spaceId) throw new Error("Set SEAM_SPACE_ID to the booked space's UUID")
    7const entrances = await seam.acs.entrances.list({ space_id: spaceId })
    8if (!entrances.length || entrances.some((entrance) => !entrance.can_unlock_with_code)) {
    9  throw new Error("Every entrance in this space must support PIN codes")
    10}
    11const needsReservation = entrances.some((entrance) => entrance.can_belong_to_reservation)
    12const startsAt = new Date()
    13const accessGrant = await seam.accessGrants.create({
    14  user_identity: { full_name: "Jane Doe", email_address: "jane@example.com" },
    15  acs_entrance_ids: entrances.map((entrance) => entrance.acs_entrance_id),
    16  requested_access_methods: [{ mode: "code" }],
    17  starts_at: startsAt.toISOString(),
    18  ends_at: new Date(startsAt.getTime() + 3 * 86400000).toISOString(),
    19  ...(needsReservation ? { reservation_key: "booking-8412" } : {}),
    20})
    21console.log(accessGrant.access_grant_id)
  5. 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 methods = await seam.accessMethods.list({
    2  access_grant_id: accessGrant.access_grant_id,
    3})
    4const pin = methods.find((method) => method.mode === "code" && method.is_issued)
    5if (pin) console.log(pin.code) // Pass to your guest messaging service.
  6. 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.

    1import { Seam } from "seam"
    2
    3const seam = new Seam()
    4
    5// Call the appropriate handler for the booking event.
    6async function updateCheckout(accessGrantId, newCheckOut) {
    7  await seam.accessGrants.update({ access_grant_id: accessGrantId, ends_at: newCheckOut })
    8}
    9
    10async function cancelBooking(accessGrantId) {
    11  await seam.accessGrants.delete({ access_grant_id: accessGrantId })
    12}
FAQ

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.

Pricing

Ready to Scale With You

Seam lets developers and businesses connect apps to real-world IoT devices. The free trial is built for small teams to build and test, while enterprise pricing scales with the biggest device fleets out there.
Best Enterprise Solution

Custom Subscription for Your Business

Access tailored solutions, paying a fixed monthly rate that scales as you grow.
Enhanced Security
Beta Features Access
Dedicated Support Channel
Volume Discounts
No Usage Limits
Trusted by Over 1,000 Companies
Start Now

Try It for Free

Start with a free trial and explore the full API and related features.

Have questions? Talk to our team.

Chat with us to learn how Seam can power your access automation.
—“Discovering Seam was one of these Aha moments in the business. I couldn’t believe what I was seeing. I mean, it was exactly what we were looking for: a simple API that we could integrate and have access to all these devices in a matter of seconds.”
François Gouelo
CEO, Enso Connect
Company
How many devices you need to connect?
Name
Email
How did you find us?
Comment
We usually answer within 24 hours.
One API for All Devices
Global Infrastructure for Controlling the Physical World
Seam makes controlling IoT devices seamless and programmable. Our teams operate globally,
enabling businesses of all sizes.
Smart Locks
Access Systems
Knowledge
Lock Capabilities
Thermostats
Sensors
© 2026 Seam Labs, Inc.