> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seam.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating an Access Grant

> Create an Access Grant, check the resulting access methods, and deliver credentials to your user.

An Access Grant is a single API call that defines **who** gets access, **where** they can go, **when** access is valid, and **how** they get in. This guide walks through the full flow from creation to delivery.

## Before You Begin

* A Seam workspace with at least one connected device or access system. You can use a [sandbox workspace](/core-concepts/workspaces/index#sandbox-workspaces) with test devices.
* An [API key](/core-concepts/authentication/api-keys).
* The Seam SDK installed in your language of choice (see [Quick Start](/quickstart)).

***

## Step 1: Create the Access Grant

Call `/access_grants/create` with four things:

1. **Who** — a `user_identity_id` for an existing user, or pass `user_identity` to create one inline
2. **Where** — the resources to grant access to
3. **When** — the access schedule (`starts_at` and `ends_at`)
4. **How** — the access method modes to issue (e.g., `code`, `mobile_key`, `card`, `cloud_key`)

### Choosing Resources

You specify *where* to grant access using one or more of these parameters:

| Parameter          | Best for                        | Description                                                                                                                                                              |
| ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `device_ids`       | Standalone smart locks          | Pass IDs of individual devices like August, Yale, or Schlage locks connected through a [Connect Webview](/core-concepts/connect-webviews/index).                         |
| `space_ids`        | Grouped access points           | Grant access to all entrances and devices in one or more [spaces](/api/spaces/object). Useful when a guest needs access to a room door, lobby, and elevator in one call. |
| `acs_entrance_ids` | Access control system entrances | Pass IDs of individual ACS entrances for fine-grained control.                                                                                                           |

You can combine these in a single Access Grant — for example, a standalone smart lock (`device_ids`) plus a set of ACS entrances (`acs_entrance_ids`).

<Info>
  If you're using Dormakaba Ambiance, Dormakaba Community, Visionline, Salto Space, or Vostio, you'll also need to pass a `reservation_key` to coordinate credential override sequencing. See [Reservation Access Grants](/use-cases/granting-access/reservation-access-grants).
</Info>

<Info>
  If you create multiple Access Grants for the same `user_identity_id`, Seam tries to reuse the same access methods across grants — for example, assigning the same PIN code so the user doesn't have to learn a new one for each reservation.
</Info>

### Example

The example below creates an Access Grant for a single smart lock with a PIN code. To grant access to multiple doors at once, pass `space_ids` instead of `device_ids`. To target specific ACS entrances, use `acs_entrance_ids`. You can also request multiple modes in one call — for example, `[{"mode": "code"}, {"mode": "mobile_key"}]`.

<CodeGroup>
  ```python Python theme={null}
  from seam import Seam

  seam = Seam()

  access_grant = seam.access_grants.create(
      user_identity={
          "full_name": "Jane Doe",
          "email_address": "jane@example.com",
      },
      device_ids=["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
      requested_access_methods=[{"mode": "code"}],
      starts_at="2025-07-13T15:00:00.000Z",
      ends_at="2025-07-16T11:00:00.000Z",
  )
  ```

  ```javascript JavaScript theme={null}
  import { Seam } from "seam";

  const seam = new Seam();

  const accessGrant = await seam.accessGrants.create({
    user_identity: {
      full_name: "Jane Doe",
      email_address: "jane@example.com",
    },
    device_ids: ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
    requested_access_methods: [{ mode: "code" }],
    starts_at: "2025-07-13T15:00:00.000Z",
    ends_at: "2025-07-16T11:00:00.000Z",
  });
  ```

  ```ruby Ruby theme={null}
  require "seam"

  seam = Seam.new()

  access_grant = seam.access_grants.create(
    user_identity: {
      full_name: "Jane Doe",
      email_address: "jane@example.com",
    },
    device_ids: ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
    requested_access_methods: [{ "mode": "code" }],
    starts_at: "2025-07-13T15:00:00.000Z",
    ends_at: "2025-07-16T11:00:00.000Z"
  )
  ```

  ```php PHP theme={null}
  <?php
  require 'vendor/autoload.php';

  $seam = new Seam\SeamClient();

  $access_grant = $seam->access_grants->create(
      user_identity: [
          "full_name" => "Jane Doe",
          "email_address" => "jane@example.com",
      ],
      device_ids: ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
      requested_access_methods: [["mode" => "code"]],
      starts_at: "2025-07-13T15:00:00.000Z",
      ends_at: "2025-07-16T11:00:00.000Z"
  );
  ```

  ```csharp C# theme={null}
  // Coming soon!
  ```

  ```java Java theme={null}
  // Coming soon!
  ```

  ```bash cURL (bash) theme={null}
  curl -X 'POST' \
    'https://connect.getseam.com/access_grants/create' \
    -H 'accept: application/json' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
      "user_identity": {
        "full_name": "Jane Doe",
        "email_address": "jane@example.com"
      },
      "device_ids": ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
      "requested_access_methods": [{"mode": "code"}],
      "starts_at": "2025-07-13T15:00:00.000Z",
      "ends_at": "2025-07-16T11:00:00.000Z"
    }'
  ```
</CodeGroup>

<Info>
  When you request `mobile_key`, Seam also generates an `instant_key_url` on the Access Grant — a shareable link that gives your user mobile access without downloading an app. See [Using Instant Keys](/use-cases/granting-access/using-instant-keys).
</Info>

### Requesting a Custom Access Code

By default, Seam generates a PIN code for each `code` access method. To request a specific code instead — for example, to reuse a code a guest already knows — include a `code` value alongside `mode: "code"` in `requested_access_methods`. The code must be **4–9 digits** and contain only digits.

<CodeGroup>
  ```python Python theme={null}
  from seam import Seam

  seam = Seam()

  access_grant = seam.access_grants.create(
      user_identity={
          "full_name": "Jane Doe",
          "email_address": "jane@example.com",
      },
      device_ids=["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
      requested_access_methods=[{"mode": "code", "code": "1234"}],
      starts_at="2025-07-13T15:00:00.000Z",
      ends_at="2025-07-16T11:00:00.000Z",
  )
  ```

  ```javascript JavaScript theme={null}
  import { Seam } from "seam";

  const seam = new Seam();

  const accessGrant = await seam.accessGrants.create({
    user_identity: {
      full_name: "Jane Doe",
      email_address: "jane@example.com",
    },
    device_ids: ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
    requested_access_methods: [{ mode: "code", code: "1234" }],
    starts_at: "2025-07-13T15:00:00.000Z",
    ends_at: "2025-07-16T11:00:00.000Z",
  });
  ```

  ```ruby Ruby theme={null}
  require "seam"

  seam = Seam.new()

  access_grant = seam.access_grants.create(
    user_identity: {
      full_name: "Jane Doe",
      email_address: "jane@example.com",
    },
    device_ids: ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
    requested_access_methods: [{ "mode": "code", "code": "1234" }],
    starts_at: "2025-07-13T15:00:00.000Z",
    ends_at: "2025-07-16T11:00:00.000Z"
  )
  ```

  ```php PHP theme={null}
  <?php
  require 'vendor/autoload.php';

  $seam = new Seam\SeamClient();

  $access_grant = $seam->access_grants->create(
      user_identity: [
          "full_name" => "Jane Doe",
          "email_address" => "jane@example.com",
      ],
      device_ids: ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
      requested_access_methods: [["mode" => "code", "code" => "1234"]],
      starts_at: "2025-07-13T15:00:00.000Z",
      ends_at: "2025-07-16T11:00:00.000Z"
  );
  ```

  ```csharp C# theme={null}
  // Coming soon!
  ```

  ```java Java theme={null}
  // Coming soon!
  ```

  ```bash cURL (bash) theme={null}
  curl -X 'POST' \
    'https://connect.getseam.com/access_grants/create' \
    -H 'accept: application/json' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
      "user_identity": {
        "full_name": "Jane Doe",
        "email_address": "jane@example.com"
      },
      "device_ids": ["6ba7b811-9dad-11d1-80b4-00c04fd430c8"],
      "requested_access_methods": [{"mode": "code", "code": "1234"}],
      "starts_at": "2025-07-13T15:00:00.000Z",
      "ends_at": "2025-07-16T11:00:00.000Z"
    }'
  ```
</CodeGroup>

<Info>
  A requested code is a preference, not a guarantee. Seam applies it when it can and falls back to a generated code when it can't:

  * **The code is already in use on a device:** Seam assigns a different code for that device and records a warning on the Access Grant (`warning_code: "requested_code_unavailable"`) that includes both the original and the replacement code.
  * **The grant spans multiple Salto locks:** Seam ignores the requested code and assigns a single shared generated code across those devices, because Salto can't guarantee the same PIN on every device.
  * **The provider has stricter code-length limits:** Some devices accept a narrower range than 4–9 digits. If the requested code isn't valid for a device, Seam assigns a compatible code for that device instead.

  Always read the resulting `code` back from the access method (see [Step 2: Deliver the Access Methods](#step-2-deliver-the-access-methods)) rather than assuming the requested value was used, and check the grant's `warning_map` for any `requested_code_unavailable` warnings. For more on delivering and updating codes, see [Using PIN Codes](/use-cases/granting-access/using-pin-codes) and [Managing Access Methods](/use-cases/granting-access/managing-access-methods).
</Info>

***

## Step 2: Deliver the Access Methods

After creating the grant, list the access methods to see what was created and check their issuance status.

<CodeGroup>
  ```python Python theme={null}
  access_methods = seam.access_methods.list(
      access_grant_id=access_grant.access_grant_id
  )

  for method in access_methods:
      print(f"{method.mode}: is_issued={method.is_issued}")
  ```

  ```javascript JavaScript theme={null}
  const accessMethods = await seam.accessMethods.list({
    access_grant_id: accessGrant.access_grant_id,
  });

  for (const method of accessMethods) {
    console.log(`${method.mode}: is_issued=${method.is_issued}`);
  }
  ```

  ```ruby Ruby theme={null}
  access_methods = seam.access_methods.list(
    access_grant_id: access_grant.access_grant_id
  )

  access_methods.each do |method|
    puts "#{method.mode}: is_issued=#{method.is_issued}"
  end
  ```

  ```php PHP theme={null}
  $access_methods = $seam->access_methods->list(
      access_grant_id: $access_grant->access_grant_id
  );

  foreach ($access_methods as $method) {
      echo $method->mode . ": is_issued=" . $method->is_issued . "\n";
  }
  ```

  ```csharp C# theme={null}
  // Coming soon!
  ```

  ```java Java theme={null}
  // Coming soon!
  ```

  ```bash cURL (bash) theme={null}
  curl -X 'POST' \
    'https://connect.getseam.com/access_methods/list' \
    -H 'accept: application/json' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d "{\"access_grant_id\": \"${access_grant_id}\"}"
  ```
</CodeGroup>

Each access method has an `is_issued` flag. Once it's `true`, the credential is active and ready to deliver. How you deliver depends on the mode:

| Mode         | What to do                                                                                               | Guide                                                                                                                             |
| ------------ | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `code`       | Read the `code` property and share the PIN with your user.                                               | [Using PIN Codes](/use-cases/granting-access/using-pin-codes)                                                                     |
| `mobile_key` | Initialize the Seam mobile SDK with the `client_session_token`, or send the `instant_key_url`.           | [Using Mobile Keys](/use-cases/granting-access/using-mobile-keys) / [Instant Keys](/use-cases/granting-access/using-instant-keys) |
| `card`       | Encode a fresh card or assign an existing card. `is_issued` becomes `true` after encoding or assignment. | [Using Key Cards](/use-cases/granting-access/using-key-cards)                                                                     |
| `cloud_key`  | Call `access_methods.unlock_door` from your web app to unlock on behalf of the user.                     | [Using Cloud Keys](/use-cases/granting-access/using-cloud-keys)                                                                   |

<Info>
  In a sandbox workspace, automatically-issued access methods are ready almost instantly. On real devices, issuance can take a few moments.
</Info>

### Tracking Issuance with Events

Instead of polling `is_issued`, you can listen for [webhook](/developer-tools/webhooks) events:

| Event                                    | Meaning                                                                 |
| ---------------------------------------- | ----------------------------------------------------------------------- |
| `access_method.issued`                   | The access method is ready to deliver.                                  |
| `access_method.card_encoding_required`   | A card needs to be encoded before it can be delivered.                  |
| `access_method.card_assignment_required` | A card needs to be assigned to a physical card before it can be issued. |
