Guides/How to generate temporary access codes programmatically

How to generate temporary access codes programmatically

Call POST /access_grants/create with the person, the doors, and a start and end time. Seam generates a PIN, programs it onto every lock in the grant, and the code stops working at ends_at.
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.
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" }'
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.
  • 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@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

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

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

    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})
  4. 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}
  5. 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})
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
Thermostats
Sensors
© 2026 Seam Labs, Inc.