Reservation Access Grants
Learn how to use reservation_key with Access Grants to handle guest and resident credentials on offline access systems.
Overview
In hotel or multifamily buildings, many locks aren’t always connected to the internet. Instead, they make decisions locally using information stored on cards, fobs, or mobile keys. When someone issues a new credential for a room or unit, the lock uses an override model, where the new credential replaces or invalidates previous ones, like from a past guest or tenant.
For these systems, you need to use the reservation model to manage override behavior. This scope includes Dormakaba Community and Ambiance, Visionline, and Vostio. This differs from fully online systems like Brivo or Avigilon Alta, where access decisions happen in real time through the cloud.
When to use reservations
Guest vs. Staff Credentials
Not all credentials behave the same way, and this determines when you need to use the reservation model.
Guest or resident credentials usually follow an override model: issuing a new credential overrides previous ones for that space. For these credentials, using the reservation model is required on these override-based access systems.
Staff credentials typically use separate credential types and lifecycles. They don’t override guest credentials, and guest credentials don’t override them. You can issue these as standard Access Grants without setting a
reservation_key
.
Use reservations when issuing guest or resident credentials on systems that rely on offline override behavior. For staff or persistent shared access, reservations usually aren’t needed.
Override and Joiner Behavior
In these systems, locks don’t receive live updates from the cloud. Instead, access changes happen through credentials (cards, fobs, or mobile keys) that carry data to the lock.
Override behavior: When a new credential is issued for a room or unit, the new credential contains data that causes the lock to replace or invalidate previous ones. For example, when a new guest checks in, their credential overrides the previous guest’s access without requiring the lock to be online.
Joiner behavior: These systems also support joiner behavior, where multiple valid credentials can coexist for a stay. This happens, for example, when multiple guests need access under the same reservation. In these cases, the lock grants access to all valid credentials simultaneously.
This behavior applies only to guest and resident credentials. Staff credentials follow a separate path and aren’t subject to override or joiner rules.
How reservation_key
works
reservation_key
worksA reservation groups related Access Grants under a shared reservation_key
, letting Seam apply the override and joiner behavior consistently across providers. When you create the first Access Grant with a new reservation_key
for a space, Seam issues credentials that start a new reservation and override any previous reservation. Additional Access Grants with the same key join that reservation, so multiple guests or residents can share access during the same stay. Creating an Access Grant with a different key for the same space starts a new reservation and overrides access for the existing one. Entrances that don’t support reservation behavior are provisioned as standard access, such as common area doors where overrides aren’t needed.
Issuing Access Grants with Reservations
1. Identify the user and entrances to grant access to.
Create a user identity for the user to whom you want to grant access.
Identify the entrances (or spaces) the user should have access to.
2. Check entrance capabilities
If you’re looking to issue a guest or resident credential, use the Entrances API to confirm which entrances support reservation semantics.
const entrances = await seam.acs.entrances.list({ space_id: "room-101" })
const entrances_that_support_reservations = entrances.filter(e => e.can_belong_to_reservation)
If at least one entrance supports reservations → include a
reservation_key
.If none do → issue a standard access grant (omit
reservation_key
).
For mixed grants: Reservation-capable + non-capable entrances
When a single Access Grant includes a mix of entrances, Seam applies reservation behavior per entrance.
Entrances with
can_belong_to_reservation: true
join or override based onreservation_key
.Entrances without the
can_belong_to_reservation
capability ignorereservation_key
and do not override credentials.
await seam.accessGrants.create({
user_identity_id: "guest-1",
acs_entrance_ids: ["room-101-id", "gym-id", "lobby-id"],
starts_at: "2025-05-10T15:00:00Z",
ends_at: "2025-05-14T11:00:00Z",
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1", // applied only to room-101
})
What to expect
Room 101 (capable): credentials participate in override / join for
stay-1
.Gym/Lobby (non-capable): credentials are provisioned normally and never override credentials based on
reservation_key
.
3. Create the access grant with reservation_key
reservation_key
Pass the reservation_key
to group related grants and let Seam apply override/joiner behavior consistently.
await seam.accessGrants.create({
user_identity_id: "guest-1",
acs_entrance_ids: ["room-101-id"],
starts_at: "2025-05-10T15:00:00Z",
ends_at: "2025-05-14T11:00:00Z",
requested_access_methods: [{ mode: "card" }],
reservation_key: "stay-1",
})
4. Seam provisions credentials
Seam chooses the correct credential type (e.g., guest vs. staff) and applies provider-native behavior. When the new credential is used at the lock:
If it’s a new reservation, it overrides previous guest credentials.
If it’s the same reservation, it joins.
Staff credentials remain unaffected.
After provisioning, Seam exposes the issued credentials as access methods, which represent the actual, deliverable means of entry (e.g., encoded cards, mobile keys, PINs). You can fetch these via the Access Methods API, display them in your app, or encode them onto physical cards for delivery to the user.
See the Access Methods guide for details on retrieving and delivering credentials.
Rules and Validation
Rule 1: reservation_key
requires at least one capable entrance
reservation_key
requires at least one capable entranceIf none of the targeted entrances support reservations, creation fails.
await seam.accessGrants.create({
user_identity_id: "guest-1",
acs_entrance_ids: ["gym-id", "pool-id"],
starts_at, ends_at,
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
/*
HTTP 400 VALIDATION_ERROR
title: "reservation_key not supported."
message: "No targeted entrances support reservation behavior. Remove reservation_key or include at least one reservation-capable entrance."
*/
Rule 2: The reservation door set is fixed once established
For reservation-capable entrances, the first access grant with a new reservation_key
establishes the reservation door set.
You can’t issue concurrent grants that change this set (e.g., switch rooms) without clearing the existing grants first.
// existing: stay-1 on room-101
// creating a new access grant with different doors
await seam.accessGrants.create({
user_identity_id: "guest-1",
space_ids: ["room-203"], // different door
starts_at, ends_at,
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
/*
HTTP 409 CONFLICT
title: "Reservation entrance change requires cleanup."
message: "To change the doors for this reservation, delete the existing access
grants first."
*/
const access_grants = await seam.accessGrants.list({ reservation_key: "stay-1" })
for (const { access_grant_id } of access_grants) {
await seam.accessGrants.delete({ access_grant_id })
}
await seam.accessGrants.create({
user_identity_id: "guest-1",
space_ids: ["room-203"], // different door
starts_at, ends_at,
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
Rule 3: Joiners can target subsets of reservation-capable doors
For reservation-capable entrances, additional access grants with the same reservation_key
can target subsets of the established reservation door set.
This allows, for example, giving one guest full access and another guest limited access within the same stay.
Issuing a superset of reservation-capable doors isn’t allowed. If you need to expand the set (e.g., adding a new unit), clear the existing grants first and reissue with the new set.
<aside> 💡
Entrances that don’t support reservations are not included in the subset/superset check. You can include any combination of non-capable entrances without triggering this rule.
</aside>
// Reservation has room-101 (reservation-capable door)
// ✅ Valid: subset joiner
await seam.accessGrants.create({
user_identity_id: "guest-2",
space_ids: ["room-101"], // subset
starts_at, ends_at,
reservation_key: "stay-1",
})
// ❌ Invalid: superset joiner (adds 102)
await seam.accessGrants.create({
user_identity_id: "guest-3",
space_ids: ["room-101", "room-102"], // superset of reservation-capable set
starts_at, ends_at,
reservation_key: "stay-1",
})
/*
HTTP 409 CONFLICT
title: "Reservation joiner entrance set conflict."
message: "Joiner access grants must use the same or a subset of the
reservation-capable doors."
*/
Rule 4: Time frames must overlap
All access grants within the same reservation_key
must have overlapping time frames for their reservation-capable entrances.
This ensures the underlying system treats the credentials as part of the same stay or lease and applies proper joiner behavior.
If the time frames overlap, the new grant joins the existing reservation.
If the time frames don’t overlap, the new grant is treated as a separate stay and must use a different reservation_key.
// ✅ Valid: overlapping time frames
await seam.accessGrants.create({
user_identity_id: "guest-1",
space_ids: ["room-101"],
starts_at: "2025-05-10T15:00:00Z",
ends_at: "2025-05-14T11:00:00Z",
reservation_key: "stay-1",
})
// joiner with overlapping window
await seam.accessGrants.create({
user_identity_id: "guest-2",
space_ids: ["room-101"],
starts_at: "2025-05-11T11:00:00Z", // overlaps with guest-1
ends_at: "2025-05-14T11:00:00Z",
reservation_key: "stay-1",
})
// ❌ Invalid: non-overlapping time frames
await seam.accessGrants.create({
user_identity_id: "guest-3",
space_ids: ["room-101"],
starts_at: "2025-05-15T15:00:00Z", // no overlap with guest-1
ends_at: "2025-05-18T11:00:00Z",
reservation_key: "stay-1",
})
/*
HTTP 409 CONFLICT
title: "Reservation time frames must overlap."
message: "Access grants in the same reservation must have overlapping time
frames."
*/
Common scenarios
This section walks through common hospitality and multifamily scenarios to illustrate how reservation_key
drives override and joiner behavior in Seam. Each example shows how Access Grants interact with locks under real operational conditions.
1. Back-to-back reservations
Two reservations for the same unit are scheduled back-to-back, with one guest checking out at 11 AM and the next checking in at 3 PM. When you issue the second Access Grant with a different reservation_key
, Seam provisions it to override the previous stay. Once the new guest uses their credential at the door, the prior guest’s credential is invalidated automatically.
// guest 1 (sat → wed)
await seam.accessGrants.create({
user_identity_id: "guest-1",
space_ids: ["room-101"],
starts_at: "2025-05-10T15:00:00Z",
ends_at: "2025-05-14T11:00:00Z",
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
// guest 2 (wed → fri) — different key → overrides guest 1 on first use
await seam.accessGrants.create({
user_identity_id: "guest-2",
space_ids: ["room-101"],
starts_at: "2025-05-14T15:00:00Z",
ends_at: "2025-05-16T11:00:00Z",
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-2",
})
2. Multiple guests on the same reservation
A single reservation includes multiple guests who all need access during the same stay. By issuing Access Grants with the same reservation_key
, Seam provisions the credentials as joiners. All guests’ credentials remain valid for the duration of the reservation, and the lock treats them as part of the same lease.
// guest 1
await seam.accessGrants.create({
user_identity_id: "g1",
space_ids: ["room-101"],
starts_at,
ends_at,
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
// guest 2 joins the same lease
await seam.accessGrants.create({
user_identity_id: "g2",
space_ids: ["room-101"],
starts_at,
ends_at,
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
3. Relocate a reservation to new room
When a guest switches rooms mid-stay, the existing Access Grants for the original room must be fully cleared before creating new ones. Seam doesn’t allow issuing Access Grants with different door assignments for the same reservation_key
at the same time. This reflects how underlying systems work—they require the previous access to be overridden before new room assignments can take effect.
// original room
const oldGrant = await seam.accessGrants.create({
user_identity_id: "guest-1",
space_ids: ["room-101"],
starts_at: "2025-05-10T15:00:00Z",
ends_at: "2025-05-14T11:00:00Z",
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
// move to new room with same reservation identity
await seam.accessGrants.delete({ access_grant_id: oldGrant.access_grant_id })
// seam may surface warnings if physical cards still exist (collect/cancel)
await seam.accessGrants.create({
user_identity_id: "guest-1",
space_ids: ["room-203"],
starts_at: "2025-05-10T15:30:00Z",
ends_at: "2025-05-14T11:00:00Z",
requested_access_methods: [{"mode": "card"}],
reservation_key: "stay-1",
})
4. Reservation time frame changes
When you change starts_at
/ ends_at
, Seam updates the Access Grant, but already-issued physical credentials on offline systems don’t automatically change. Seam will flag what you need to do next.
// extend the reservation window
await seam.accessGrants.update({
access_grant_id: "ag_123",
ends_at: "2025-05-16T11:00:00Z",
})
// fetch current status and next steps
const grant = await seam.accessGrants.get({ access_grant_id: "ag_123" })
/*
grant.warnings might include:
- "encoding_required": re-encode a plastic card for the new dates
*/
The warning can be resolved by re-encoding the card.
const cards = await seam.access_methods.list({
access_grant_id
})
// (example) re-encode the plastic card for the new reservation window
await seam.acs.encoders.encode_card({
cards[0].access_method_id,
acs_encoder_id: "encoder-1"
})
Error codes & troubleshooting
This section lists common validation and conflict errors you may encounter when creating Access Grants with reservation_key
, along with what they mean and how to resolve them.
400
reservation_key
not supported.
You passed a reservation_key
but none of the targeted entrances support reservations.
Remove reservation_key
or include at least one reservation-capable entrance.
400
reservation_key
required for guest cards.
The targeted provider requires a reservation for guest credentials (e.g., Visionline) but none was provided.
Add a reservation_key
when issuing guest credentials for these entrances.
409
Reservation entrance change requires cleanup.
You tried to issue an access grant with a different door set for an existing reservation.
List and delete existing access grants for that reservation_key
, then reissue with the new door set.
409
Reservation joiner entrance set conflict.
A joiner grant includes a superset of the reservation-capable doors.
Joiners must use the same or a subset of the reservation-capable doors. Adjust the door set or clear/reissue.
409
Reservation time frames must overlap.
A new access grant’s time frame doesn’t overlap with existing grants for the same reservation.
Either adjust the time frame to overlap or use a new reservation_key
for the separate stay.
Last updated
Was this helpful?