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

# Get Started with the Kisi Access Control System

> Learn how to connect your Kisi access control system, grant cloud keys, and remotely unlock doors with the Seam API.

## Overview

Seam provides a universal API to connect and control many brands of access control systems. This guide provides a rapid introduction to connecting your [Kisi](https://www.seam.co/manufacturers/kisi) organization, granting a cloud key, and remotely unlocking a door using the Seam API.

Kisi is cloud-native, so no Seam gateway or bridge is required. Seam models each Kisi organization as an access control system, exposes each door as an entrance, and issues [cloud keys](/use-cases/granting-access/using-cloud-keys) through [Access Grants](/use-cases/granting-access).

To learn more about other access control systems supported by Seam, head over to our [integration page](https://www.seam.co/supported-devices-and-systems).

***

## 1 — Install Seam SDK

Seam provides client libraries for many languages, such as JavaScript, Python, Ruby, PHP, and others, as well as a Postman collection and [OpenAPI](https://connect.getseam.com/openapi.json) spec.

* JavaScript / TypeScript ([npm](https://www.npmjs.com/package/seam), [GitHub](https://github.com/seamapi/javascript))
* Python ([pip](https://pypi.org/project/seam/), [GitHub](https://github.com/seamapi/python))
* Ruby Gem ([rubygem](https://rubygems.org/gems/seam), [GitHub](https://github.com/seamapi/ruby))
* PHP ([packagist](https://packagist.org/packages/seamapi/seam), [GitHub](https://github.com/seamapi/php))
* C# ([nuget](https://www.nuget.org/packages/Seam), [GitHub](https://github.com/seamapi/csharp))
* Java ([Maven](https://central.sonatype.com/artifact/co.seam/java), [GitHub](https://github.com/seamapi/java))

<CodeGroup>
  ```bash JavaScript theme={null}
  npm i seam
  ```

  ```bash Python theme={null}
  pip install seam
  # For some development environments, use pip3 in this command instead of pip.
  ```

  ```bash Ruby theme={null}
  bundle add seam
  ```

  ```bash PHP theme={null}
  composer require seamapi/seam
  ```

  ```bash C# theme={null}
  Install using nuget: https://www.nuget.org/packages/Seam
  ```

  ```bash Java theme={null}
  // Add to your pom.xml or build.gradle — see Maven Central for details.
  ```

  ```bash cURL (bash) theme={null}
  # cURL is already installed on most systems. No additional installation needed.
  # Export your API key as an environment variable:
  export SEAM_API_KEY=seam_test2ZTo_0mEYQW2TvNDCxG5Atpj85Ffw
  ```
</CodeGroup>

Once installed, [sign up for Seam](https://console.seam.co/) to get your API key, and export it as an environment variable:

```bash theme={null}
export SEAM_API_KEY=seam_test2ZTo_0mEYQW2TvNDCxG5Atpj85Ffw
```

<Info>
  This guide uses a Sandbox Workspace. Only virtual systems can be connected. If
  you need to connect a real Kisi organization, use a non-sandbox workspace and
  API key.
</Info>

***

## 2 — Link Kisi System with Seam

To control your Kisi system via the Seam API, you must first authorize your Seam workspace against your Kisi organization. To do so, Seam provides [Connect Webviews](/core-concepts/connect-webviews): pre-built UX flows that walk your users through authorizing your application to control their Kisi system.

Kisi connects using a **Kisi API key**. In production, your users generate this key in their Kisi dashboard under **My Account → API**. See the [Kisi Setup Guide](./kisi-setup-guide) for details.

#### Request a Connect Webview

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Seam } from 'seam'

  const seam = new Seam()

  const connectWebview = await seam.connectWebviews.create({
    accepted_providers: ['kisi'],
  })

  console.log(connectWebview.login_successful) // false

  // Send the webview URL to your user
  console.log(connectWebview.url)
  ```

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

  seam = Seam()

  webview = seam.connect_webviews.create(accepted_providers=["kisi"])

  assert webview.login_successful is False

  # Send the webview URL to your user
  print(webview.url)
  ```

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

  seam = Seam.new(api_key: "MY_API_KEY")

  webview = seam.connect_webviews.create(
    accepted_providers: ["kisi"]
  )

  puts webview.login_successful # false

  # Send the webview URL to your user
  puts webview.url
  ```

  ```php PHP theme={null}
  <?php
  use Seam\SeamClient;

  $seam = new SeamClient("YOUR_API_KEY");

  $webview = $seam->connect_webviews->create(
    accepted_providers: ["kisi"]
  );

  echo $webview->login_successful; // false

  // Send the webview URL to your user
  echo $webview->url;
  ```

  ```csharp C# theme={null}
  using Seam.Client;

  var seam = new SeamClient(apiToken: "YOUR_API_KEY");

  var webview = seam.ConnectWebviews.Create(
    acceptedProviders: new List<string> { "kisi" }
  );

  Console.WriteLine(webview.LoginSuccessful); // false

  // Send the webview URL to your user
  Console.WriteLine(webview.Url);
  ```

  ```java Java theme={null}
  import co.seam.Seam;
  import co.seam.api.types.ConnectWebview;

  Seam seam = Seam.builder().apiKey("YOUR_API_KEY").build();

  ConnectWebview webview = seam.connectWebviews().create(
    ConnectWebviewsCreateRequest.builder()
      .acceptedProviders(List.of("kisi"))
      .build()
  );

  System.out.println(webview.getLoginSuccessful()); // false

  // Send the webview URL to your user
  System.out.println(webview.getUrl());
  ```

  ```bash cURL (bash) theme={null}
  curl -X 'POST' \
    'https://connect.getseam.com/connect_webviews/create' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
      "accepted_providers": ["kisi"]
    }'
  ```
</CodeGroup>

#### Authorize Your Workspace

Navigate to the URL returned by the Webview object. Since you are using a sandbox workspace, complete the login flow by entering the Kisi [sandbox test account](/device-and-system-integration-guides/kisi-access-control-system/sandbox-kisi-access-control-system) API key below:

* **API Key:** `1234`

Confirm the Connect Webview was successful by querying its status:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const updatedWebview = await seam.connectWebviews.get(
    connectWebview.connect_webview_id,
  )

  console.log(updatedWebview.login_successful) // true
  ```

  ```python Python theme={null}
  updated_webview = seam.connect_webviews.get(
      connect_webview_id=webview.connect_webview_id
  )

  assert updated_webview.login_successful  # true
  ```

  ```ruby Ruby theme={null}
  updated_webview = seam.connect_webviews.get(
    connect_webview_id: webview.connect_webview_id
  )

  puts updated_webview.login_successful # true
  ```

  ```php PHP theme={null}
  <?php
  $updated_webview = $seam->connect_webviews->get(
    connect_webview_id: $webview->connect_webview_id
  );

  echo $updated_webview->login_successful; // true
  ```

  ```csharp C# theme={null}
  var updatedWebview = seam.ConnectWebviews.Get(
    connectWebviewId: webview.ConnectWebviewId
  );

  Console.WriteLine(updatedWebview.LoginSuccessful); // true
  ```

  ```java Java theme={null}
  ConnectWebview updatedWebview = seam.connectWebviews().get(
    ConnectWebviewsGetRequest.builder()
      .connectWebviewId(webview.getConnectWebviewId())
      .build()
  );

  System.out.println(updatedWebview.getLoginSuccessful()); // true
  ```

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

***

## 3 — Retrieve the ACS System

After the Kisi system is linked with Seam, you can retrieve the ACS system. This represents the connected Kisi organization.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const systems = await seam.acs.systems.list()

  const kisiSystem = systems[0]

  console.log(kisiSystem)
  /*
  {
    acs_system_id: '...',
    name: 'Example Inc',
    external_type: 'kisi_organization',
    external_type_display_name: 'Kisi Organization',
    connected_account_ids: ['...'],
    ...
  }
  */
  ```

  ```python Python theme={null}
  systems = seam.acs.systems.list()

  kisi_system = systems[0]

  print(kisi_system)
  # AcsSystem(
  #   acs_system_id='...',
  #   name='Example Inc',
  #   external_type='kisi_organization',
  #   ...
  # )
  ```

  ```ruby Ruby theme={null}
  systems = seam.acs.systems.list

  kisi_system = systems.first

  puts kisi_system
  ```

  ```php PHP theme={null}
  <?php
  $systems = $seam->acs->systems->list();

  $kisi_system = $systems[0];

  echo $kisi_system->name; // Example Inc
  ```

  ```csharp C# theme={null}
  var systems = seam.Acs.Systems.List();

  var kisiSystem = systems[0];

  Console.WriteLine(kisiSystem.Name); // Example Inc
  ```

  ```java Java theme={null}
  var systems = seam.acs().systems().list();

  var kisiSystem = systems.get(0);

  System.out.println(kisiSystem.getName()); // Example Inc
  ```

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

***

## 4 — List Entrances

Entrances represent the doors managed by the Kisi system. In the sandbox, this includes **Front Door** (online) and **Back Door** (offline, with restrictions enabled) in a place named **Main Office**.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const entrances = await seam.acs.entrances.list({
    acs_system_id: kisiSystem.acs_system_id,
  })

  console.log(entrances)
  /*
  [
    {
      acs_entrance_id: '...',
      display_name: 'Front Door',
      can_unlock_with_cloud_key: true,
      ...
    },
    ...
  ]
  */
  ```

  ```python Python theme={null}
  entrances = seam.acs.entrances.list(
      acs_system_id=kisi_system.acs_system_id
  )

  print(entrances)
  ```

  ```ruby Ruby theme={null}
  entrances = seam.acs.entrances.list(
    acs_system_id: kisi_system.acs_system_id
  )

  puts entrances
  ```

  ```php PHP theme={null}
  <?php
  $entrances = $seam->acs->entrances->list(
    acs_system_id: $kisi_system->acs_system_id
  );

  print_r($entrances);
  ```

  ```csharp C# theme={null}
  var entrances = seam.Acs.Entrances.List(
    acsSystemId: kisiSystem.AcsSystemId
  );

  foreach (var entrance in entrances)
  {
    Console.WriteLine(entrance.DisplayName);
  }
  ```

  ```java Java theme={null}
  var entrances = seam.acs().entrances().list(
    AcsEntrancesListRequest.builder()
      .acsSystemId(kisiSystem.getAcsSystemId())
      .build()
  );

  System.out.println(entrances);
  ```

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

***

## 5 — Grant a Cloud Key

To give a member access, create an [Access Grant](/use-cases/granting-access). An Access Grant is a single call that defines **who** gets access, **where** they can go, **when** access is valid, and **how** they get in. For Kisi, request the `cloud_key` access method to issue a [cloud key](/use-cases/granting-access/using-cloud-keys) — a web-based unlock attributed to that specific user.

Seam provisions the underlying Kisi user and cloud-key credential for you, so you do not create Kisi users directly.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const frontDoor = entrances.find((e) => e.display_name === 'Front Door')

  const accessGrant = await seam.accessGrants.create({
    user_identity: {
      full_name: 'Jane Guest',
      email_address: 'jane.guest@example.com',
    },
    acs_entrance_ids: [frontDoor.acs_entrance_id],
    requested_access_methods: [{ mode: 'cloud_key' }],
    starts_at: '2025-07-13T15:00:00.000Z',
    ends_at: '2025-07-16T11:00:00.000Z',
  })

  console.log(accessGrant)
  ```

  ```python Python theme={null}
  front_door = next(e for e in entrances if e.display_name == "Front Door")

  access_grant = seam.access_grants.create(
      user_identity={
          "full_name": "Jane Guest",
          "email_address": "jane.guest@example.com",
      },
      acs_entrance_ids=[front_door.acs_entrance_id],
      requested_access_methods=[{"mode": "cloud_key"}],
      starts_at="2025-07-13T15:00:00.000Z",
      ends_at="2025-07-16T11:00:00.000Z",
  )

  print(access_grant)
  ```

  ```ruby Ruby theme={null}
  front_door = entrances.find { |e| e.display_name == "Front Door" }

  access_grant = seam.access_grants.create(
    user_identity: {
      full_name: "Jane Guest",
      email_address: "jane.guest@example.com",
    },
    acs_entrance_ids: [front_door.acs_entrance_id],
    requested_access_methods: [{ "mode": "cloud_key" }],
    starts_at: "2025-07-13T15:00:00.000Z",
    ends_at: "2025-07-16T11:00:00.000Z"
  )

  puts access_grant
  ```

  ```php PHP theme={null}
  <?php
  $front_door = array_values(array_filter(
    $entrances,
    fn($e) => $e->display_name === "Front Door"
  ))[0];

  $access_grant = $seam->access_grants->create(
    user_identity: [
      "full_name" => "Jane Guest",
      "email_address" => "jane.guest@example.com",
    ],
    acs_entrance_ids: [$front_door->acs_entrance_id],
    requested_access_methods: [["mode" => "cloud_key"]],
    starts_at: "2025-07-13T15:00:00.000Z",
    ends_at: "2025-07-16T11:00:00.000Z"
  );

  print_r($access_grant);
  ```

  ```csharp C# theme={null}
  var frontDoor = entrances.First(e => e.DisplayName == "Front Door");

  var accessGrant = seam.AccessGrants.Create(
    userIdentity: new()
    {
      FullName = "Jane Guest",
      EmailAddress = "jane.guest@example.com",
    },
    acsEntranceIds: new List<string> { frontDoor.AcsEntranceId },
    requestedAccessMethods: new List<object> { new { mode = "cloud_key" } },
    startsAt: "2025-07-13T15:00:00.000Z",
    endsAt: "2025-07-16T11:00:00.000Z"
  );

  Console.WriteLine(accessGrant.AccessGrantId);
  ```

  ```java Java theme={null}
  var frontDoor = entrances.stream()
      .filter(e -> "Front Door".equals(e.getDisplayName()))
      .findFirst()
      .orElseThrow();

  var accessGrant = seam.accessGrants().create(
    AccessGrantsCreateRequest.builder()
      .userIdentity(UserIdentity.builder()
        .fullName("Jane Guest")
        .emailAddress("jane.guest@example.com")
        .build())
      .acsEntranceIds(List.of(frontDoor.getAcsEntranceId()))
      .requestedAccessMethods(List.of(
        RequestedAccessMethod.builder().mode("cloud_key").build()))
      .startsAt("2025-07-13T15:00:00.000Z")
      .endsAt("2025-07-16T11:00:00.000Z")
      .build()
  );

  System.out.println(accessGrant.getAccessGrantId());
  ```

  ```bash cURL (bash) theme={null}
  curl -X 'POST' \
    'https://connect.getseam.com/access_grants/create' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d "{
      \"user_identity\": {
        \"full_name\": \"Jane Guest\",
        \"email_address\": \"jane.guest@example.com\"
      },
      \"acs_entrance_ids\": [\"${ACS_ENTRANCE_ID}\"],
      \"requested_access_methods\": [{\"mode\": \"cloud_key\"}],
      \"starts_at\": \"2025-07-13T15:00:00.000Z\",
      \"ends_at\": \"2025-07-16T11:00:00.000Z\"
    }"
  ```
</CodeGroup>

<Info>
  A Kisi cloud key triggers a web-based unlock — the member opens the granted
  doors over the internet from a shareable link or an unlock button in your app,
  and each unlock is attributed to that member in the Kisi audit log. See [Using
  Cloud Keys](/use-cases/granting-access/using-cloud-keys) for details.
</Info>

***

## 6 — Unlock a Door

Each Kisi door is also exposed as a device, which lets you trigger a remote unlock directly. Kisi processes the unlock through its cloud, so the action is fire-and-forget — Seam does not receive confirmation of the physical unlock from the device.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const locks = await seam.locks.list()

  const frontDoorLock = locks.find(
    (l) => l.properties.name === 'Front Door',
  )

  // unlock the door
  await seam.locks.unlockDoor(frontDoorLock.device_id)
  ```

  ```python Python theme={null}
  locks = seam.locks.list()

  front_door_lock = next(
      l for l in locks if l.properties["name"] == "Front Door"
  )

  # unlock the door
  seam.locks.unlock_door(device_id=front_door_lock.device_id)
  ```

  ```ruby Ruby theme={null}
  locks = seam.locks.list

  front_door_lock = locks.find { |l| l.properties.name == "Front Door" }

  # unlock the door
  seam.locks.unlock_door(device_id: front_door_lock.device_id)
  ```

  ```php PHP theme={null}
  <?php
  $locks = $seam->locks->list();

  $front_door_lock = array_values(array_filter(
    $locks,
    fn($l) => $l->properties->name === "Front Door"
  ))[0];

  // unlock the door
  $seam->locks->unlock_door(device_id: $front_door_lock->device_id);
  ```

  ```csharp C# theme={null}
  var locks = seam.Locks.List();

  var frontDoorLock = locks.First(l => l.Properties.Name == "Front Door");

  // unlock the door
  seam.Locks.UnlockDoor(deviceId: frontDoorLock.DeviceId);
  ```

  ```java Java theme={null}
  var locks = seam.locks().list();

  var frontDoorLock = locks.stream()
      .filter(l -> "Front Door".equals(l.getProperties().getName()))
      .findFirst()
      .orElseThrow();

  // unlock the door
  seam.locks().unlockDoor(LocksUnlockDoorRequest.builder()
      .deviceId(frontDoorLock.getDeviceId())
      .build());
  ```

  ```bash cURL (bash) theme={null}
  # unlock the door
  curl -X 'POST' \
    'https://connect.getseam.com/locks/unlock_door' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d "{\"device_id\": \"${DEVICE_ID}\"}"
  ```
</CodeGroup>

<Info>
  Kisi does not support a remote lock action. Only unlock is available. Unlock
  requests against doors with geofence or reader-proximity restrictions may be
  rejected by Kisi.
</Info>

***

## Next Steps

Now that you've completed this guide, you can try to connect a real Kisi organization. To do so, make sure to switch to a non-sandbox workspace and API key, as real systems cannot be connected to sandbox workspaces.

In addition, if you'd like to explore other aspects of Seam, here is a list of helpful resources:

* [Access Grants](/use-cases/granting-access) — the recommended way to manage access across providers
* [Using Cloud Keys](/use-cases/granting-access/using-cloud-keys)
* [Receiving webhooks](/developer-tools/webhooks) for [device events](/api/events/list)
* [Core Concepts](/core-concepts/overview)

If you have any questions or want to report an issue, email us at [support@seam.co](mailto:support@seam.co).
