

# Linking

Link and unlink accessories (Keypads, Key Fobs, Bridges) to igloohome locks.

---

## link

Links an accessory to a lock. The SDK orchestrates server registration, cipher resolution, and BLE link command. If the BLE step fails, the server link is automatically rolled back.

### Signature

```kotlin
suspend fun link(
    accessoryDeviceId: String,
    accessoryGuestKey: String,
    lockDeviceId: String,
    accessToken: String,
): LinkResult
```

```swift
func link(
    accessoryDeviceId: String,
    accessoryGuestKey: String,
    lockBluetoothDeviceName: String,
    accessToken token: String
) async throws -> LinkResult
```

### Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `accessoryDeviceId` | `String` | Yes | Bluetooth device name of the accessory (e.g. `"EK1X01abc123"`). |
| `accessoryGuestKey` | `String` | Yes | BLE key for the accessory. Keypad: eKey with `ADD_LOCK` permission. Fob: admin key. |
| `lockDeviceId` | `String` | Yes | Bluetooth device ID of the lock to link to (e.g. `"IGM401abc123"`). |
| `accessToken` | `String` | Yes | Access token for server calls. |

### Return Type

```kotlin
data class LinkResult(
    val accessoryId: String,
    val lockId: String,
    val linkedAt: String,
)
```

```swift
public struct LinkResult {
    public let accessoryId: String
    public let lockId: String
    public let linkedAt: String
}
```

### Error Codes

| Exception | Code | Description |
|-----------|------|-------------|
| `BluetoothException` | 708 | Bluetooth is off or unavailable. |
| `ConnectionException` | 12 | Device disconnected during linking. |
| `TimeoutException` | 703 | BLE operation exceeded the timeout. |
| `BridgeOfflineException` | 406 | Bridge is offline (bridge link path). |
| `ApiException` | varies | Server API call failed. |

### Example

```kotlin
try {
    val result = sdk.link(
        accessoryDeviceId = "EK1X01abc123",
        accessoryGuestKey = ekeyWithAddLock,
        lockDeviceId = "IGM401abc123",
        accessToken = accessToken,
    )
    showSuccess("Linked ${result.accessoryId} to ${result.lockId}")
} catch (e: IglooHomeException.ConnectionException) {
    showError("unexpected bluetooth connection issue")
} catch (e: IglooHomeException.TimeoutException) {
    showError("Timed out, try again")
} catch (e: IglooHomeException) {
    showError("Link failed: ${e.message}")
}
```

```swift
do {
    let result = try await sdk.link(
        accessoryDeviceId: "EK1X01abc123",
        accessoryGuestKey: ekeyWithAddLock,
        lockBluetoothDeviceName: "IGM401abc123",
        accessToken: accessToken)
    showSuccess(message: "Linked \(result.accessoryId) to \(result.lockId)")
} catch {
    showError(message: "Link failed: \(error.localizedDescription)")
}
```

---

## unlink

Unlinks an accessory from a lock. The SDK removes the link via BLE, then deletes the server record.

BLE errors 890 (`PinNotFoundException`) and 980 (`LockUidNotFoundException`) are treated as "already unlinked on the accessory side" — the SDK continues to remove the server link.

### Signature

```kotlin
suspend fun unlink(
    accessoryDeviceId: String,
    accessoryGuestKey: String,
    lockDeviceId: String,
    accessToken: String,
)
```

```swift
func unlink(
    accessoryDeviceId: String,
    accessoryGuestKey: String,
    lockBluetoothDeviceName: String,
    accessToken token: String
) async throws
```

### Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `accessoryDeviceId` | `String` | Yes | Bluetooth device name of the accessory. |
| `accessoryGuestKey` | `String` | Yes | BLE key for the accessory. Keypad: eKey with `DELETE_LOCK` permission. Fob: admin key. |
| `lockDeviceId` | `String` | Yes | Bluetooth device ID of the lock to unlink from (e.g. `"IGM401abc123"`). |
| `accessToken` | `String` | Yes | Access token for server calls. |

### Error Codes

| Exception | Code | Description |
|-----------|------|-------------|
| `BluetoothException` | 708 | Bluetooth is off or unavailable. |
| `ConnectionException` | 12 | Device disconnected during unlinking. |
| `TimeoutException` | 703 | BLE operation exceeded the timeout. |
| `BridgeOfflineException` | 406 | Bridge is offline (bridge unlink path). |
| `ApiException` | varies | Server API call failed. |

### Example

```kotlin
try {
    sdk.unlink(
        accessoryDeviceId = "EK1X01abc123",
        accessoryGuestKey = ekeyWithDeleteLock,
        lockDeviceId = "IGM401abc123",
        accessToken = accessToken,
    )
    showSuccess("Accessory unlinked")
} catch (e: IglooHomeException.ConnectionException) {
    showError("unexpected bluetooth connection issue")
} catch (e: IglooHomeException.TimeoutException) {
    showError("Timed out, try again")
} catch (e: IglooHomeException) {
    showError("Unlink failed: ${e.message}")
}
```

```swift
do {
    try await sdk.unlink(
        accessoryDeviceId: "EK1X01abc123",
        accessoryGuestKey: ekeyWithDeleteLock,
        lockBluetoothDeviceName: "IGM401abc123",
        accessToken: accessToken)
    showSuccess(message: "Accessory unlinked")
} catch IgloohomeError.bridgeOffline {
    showError(message: "Bridge is offline — try again once it's back online")
} catch {
    showError(message: "Unlink failed: \(error.localizedDescription)")
}
```

### Notes

- Always unlink accessories before unpairing the lock — `unpair()` will throw `HasLinkedDeviceException` (Android) / `IgloohomeError.hasLinkedDevice` (iOS) if linked devices exist.
- The accessory key type depends on the accessory type:
  - **Keypad (EK1, EK2):** Generate an eKey with appropriate permission via the eKey API.
  - **Key Fob (IEF):** Use the admin key from `GET /devices/{device_id}/admin-key`.
- Bridge accessories (`"EB1"` prefix) are dispatched to a server-job link/unlink flow instead of a BLE flow — `link`/`unlink` work for Bridges, throwing `BridgeOfflineException` (Android) / `IgloohomeError.bridgeOffline` (iOS) if the Bridge is offline.
