

# Devices API Reference

API endpoints for managing devices.

## List Devices

`GET` `https://api.igloohome.co/works/devices`

### Query Parameters

- **`type`** `string`\
  Comma-separated, from: Lock, Bridge, Keypad, Fob.
- **`search`** `string`\
  Case-insensitive substring match against bluetoothDeviceName or name.
- **`expand`** `string`\
  Set to `propertyId` to populate full property objects on listingsRef.
- **`limit`** `integer`
  - Constraints: `>= 1`, `<= 300`
  - Default: `100`
- **`cursor`** `string`
- **`sort`** `string`
  - Default: `desc`
  - Allowed values: `asc`, `desc`

### Request Example

```bash
curl -X GET "https://api.igloohome.co/works/devices?type=value&search=value&expand=value&limit=value&cursor=value&sort=value" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	url := "https://api.igloohome.co/works/devices?type=value&search=value&expand=value&limit=value&cursor=value&sort=value"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer YOUR_ACCESS_TOKEN")
	req.Header.Set("Accept", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()

	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
```

```javascript
const res = await fetch("https://api.igloohome.co/works/devices?type=value&search=value&expand=value&limit=value&cursor=value&sort=value", {
  method: "GET",
  headers: {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Accept": "application/json"
  }
});

const data = await res.json();
console.log(data);
```

```python
import requests

url = "https://api.igloohome.co/works/devices"
headers = {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Accept": "application/json"
}
params = {
    "type": "value",
    "search": "value",
    "expand": "value",
    "limit": "value",
    "cursor": "value",
    "sort": "value"
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```

```php
<?php
$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => "https://api.igloohome.co/works/devices?type=value&search=value&expand=value&limit=value&cursor=value&sort=value",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer YOUR_ACCESS_TOKEN",
        "Accept: application/json"
    ]
]);

$response = curl_exec($curl);
curl_close($curl);

echo $response;
```

```kotlin
val client = OkHttpClient()

val request = Request.Builder()
    .url("https://api.igloohome.co/works/devices?type=value&search=value&expand=value&limit=value&cursor=value&sort=value")
    .method("GET", null)
    .addHeader("Authorization", "Bearer YOUR_ACCESS_TOKEN")
    .addHeader("Accept", "application/json")
    .build()

val response = client.newCall(request).execute()
println(response.body?.string())
```

```swift
import Foundation

var request = URLRequest(url: URL(string: "https://api.igloohome.co/works/devices?type=value&search=value&expand=value&limit=value&cursor=value&sort=value")!)
request.httpMethod = "GET"
request.setValue("Bearer YOUR_ACCESS_TOKEN", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Accept")

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let data = data {
        print(String(data: data, encoding: .utf8) ?? "")
    }
}
task.resume()
```

### Responses

`200 OK.`
`400 Query/body validation failure.`
`401 Unauthorized, invalid or expired authorization token`
`402-account-suspended Payment Required. Account is billing-suspended or past its trial grace period.`
`402-trial-ended Payment Required. iglooworks API trial has ended.`
`403 Forbidden. One of: (1) the token is valid but lacks the required scope for this endpoint (no body); (2) the user revoked the OAuth client's access ('{error: "The user has revoked access for this application..."}').`

**Response body — `200`:**

- **`nextCursor`** `string`\
  Present only if another page exists.
- **`payload`** `array of object`
  - **`id`** `string`
  - **`type`** `string`
    - Allowed values: `Lock`, `Bridge`, `Keypad`, `Fob`
  - **`deviceId`** `string`
  - **`deviceName`** `string`
  - **`batteryLevel`** `number`
  - **`pairedAt`** `string<date-time>`
  - **`lockStatus`** `string`\
    Present only on `GET /devices/{id}`, and only for a Lock linked to a Bridge. No list endpoint that returns this schema populates it.
  - **`properties`** `array of object`
    - **`id`** `string`
    - **`name`** `string`
    - **`timezone`** `string`
  - **`linkedDevices`** `array of object`
    - **`id`** `string`
    - **`type`** `string`
    - **`deviceId`** `string`
    - **`name`** `string`
  - **`linkedAccessories`** `array of object`
    - **`id`** `string`
    - **`type`** `string`
    - **`deviceId`** `string`
    - **`name`** `string`
  - **`lastSync`** `string<date-time>`

**Response body — `400`:**

- **`error`** `string`

**Response body — `402-account-suspended`:**

- **`error`** `string`

**Response body — `402-trial-ended`:**

- **`error`** `string`

**Response body — `403`:**

- **`error`** `string`

**Example response — `200`:**

```json
{
  "nextCursor": "string",
  "payload": [
    {
      "batteryLevel": 82.5,
      "deviceId": "IGH-LK-3F9A21",
      "deviceName": "Front Door Lock",
      "id": "665f1c2a9b3e4d5f6a7b8c9d",
      "lastSync": "2026-09-13T09:45:00Z",
      "linkedAccessories": [
        {
          "deviceId": "IGH-BR-7C2E19",
          "id": "445d1a0b6c5e8d4f7a1b9c2e",
          "name": "Main Bridge",
          "type": "Bridge"
        }
      ],
      "linkedDevices": [],
      "lockStatus": "locked",
      "pairedAt": "2026-09-08T14:32:00Z",
      "properties": [
        {
          "id": "556e2b1a7c4d9e5f6a2b8c3d",
          "name": "Riverside Apartments - Block A",
          "timezone": "Asia/Singapore"
        }
      ],
      "type": "Lock"
    }
  ]
}
```

**Example response — `400`:**

```json
{
  "error": "string"
}
```

**Example response — `402-account-suspended`:**

```json
{
  "error": "account.payment.is.suspended"
}
```

**Example response — `402-trial-ended`:**

```json
{
  "error": "30 days iglooworks API trial has ended"
}
```

**Example response — `403`:**

```json
{
  "error": "string"
}
```

## Get Device

`GET` `https://api.igloohome.co/works/devices/{id}`

### Path Parameters

| Parameter | Type | Required |
| --- | --- | --- |
| id | string | Yes |

### Request Example

```bash
curl -X GET "https://api.igloohome.co/works/devices/{id}" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	url := "https://api.igloohome.co/works/devices/{id}"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer YOUR_ACCESS_TOKEN")
	req.Header.Set("Accept", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()

	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
```

```javascript
const res = await fetch("https://api.igloohome.co/works/devices/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Accept": "application/json"
  }
});

const data = await res.json();
console.log(data);
```

```python
import requests

url = "https://api.igloohome.co/works/devices/{id}"
headers = {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Accept": "application/json"
}
response = requests.get(url, headers=headers)
print(response.json())
```

```php
<?php
$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => "https://api.igloohome.co/works/devices/{id}",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer YOUR_ACCESS_TOKEN",
        "Accept: application/json"
    ]
]);

$response = curl_exec($curl);
curl_close($curl);

echo $response;
```

```kotlin
val client = OkHttpClient()

val request = Request.Builder()
    .url("https://api.igloohome.co/works/devices/{id}")
    .method("GET", null)
    .addHeader("Authorization", "Bearer YOUR_ACCESS_TOKEN")
    .addHeader("Accept", "application/json")
    .build()

val response = client.newCall(request).execute()
println(response.body?.string())
```

```swift
import Foundation

var request = URLRequest(url: URL(string: "https://api.igloohome.co/works/devices/{id}")!)
request.httpMethod = "GET"
request.setValue("Bearer YOUR_ACCESS_TOKEN", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Accept")

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let data = data {
        print(String(data: data, encoding: .utf8) ?? "")
    }
}
task.resume()
```

### Responses

`200 OK.`
`401 Unauthorized, invalid or expired authorization token`
`402-account-suspended Payment Required. Account is billing-suspended or past its trial grace period.`
`402-trial-ended Payment Required. iglooworks API trial has ended.`
`403 Forbidden. One of: (1) the token is valid but lacks the required scope for this endpoint (no body); (2) the user revoked the OAuth client's access ('{error: "The user has revoked access for this application..."}').`
`404 Device not found.`

**Response body — `200`:**

- **`id`** `string`
- **`type`** `string`
  - Allowed values: `Lock`, `Bridge`, `Keypad`, `Fob`
- **`deviceId`** `string`
- **`deviceName`** `string`
- **`batteryLevel`** `number`
- **`pairedAt`** `string<date-time>`
- **`lockStatus`** `string`\
  Present only on `GET /devices/{id}`, and only for a Lock linked to a Bridge. No list endpoint that returns this schema populates it.
- **`properties`** `array of object`
  - **`id`** `string`
  - **`name`** `string`
  - **`timezone`** `string`
- **`linkedDevices`** `array of object`
  - **`id`** `string`
  - **`type`** `string`
  - **`deviceId`** `string`
  - **`name`** `string`
- **`linkedAccessories`** `array of object`
  - **`id`** `string`
  - **`type`** `string`
  - **`deviceId`** `string`
  - **`name`** `string`
- **`lastSync`** `string<date-time>`

**Response body — `402-account-suspended`:**

- **`error`** `string`

**Response body — `402-trial-ended`:**

- **`error`** `string`

**Response body — `403`:**

- **`error`** `string`

**Response body — `404`:**

- **`message`** `string`

**Example response — `200`:**

```json
{
  "batteryLevel": 0,
  "deviceId": "string",
  "deviceName": "string",
  "id": "string",
  "lastSync": "string",
  "linkedAccessories": [
    {
      "deviceId": "string",
      "id": "string",
      "name": "string",
      "type": "string"
    }
  ],
  "linkedDevices": [
    {
      "deviceId": "string",
      "id": "string",
      "name": "string",
      "type": "string"
    }
  ],
  "lockStatus": "string",
  "pairedAt": "string",
  "properties": [
    {
      "id": "string",
      "name": "string",
      "timezone": "string"
    }
  ],
  "type": "string"
}
```

**Example response — `402-account-suspended`:**

```json
{
  "error": "account.payment.is.suspended"
}
```

**Example response — `402-trial-ended`:**

```json
{
  "error": "30 days iglooworks API trial has ended"
}
```

**Example response — `403`:**

```json
{
  "error": "string"
}
```

**Example response — `404`:**

```json
{
  "message": "string"
}
```

## Get Admin Key

`GET` `https://api.igloohome.co/works/devices/{id}/admin-key`

Returns the device's obfuscated admin key. Supported for G2 devices only. G3 devices keep their admin key encrypted server-side, so this route cannot read it. The response is an obfuscated blob, keyed per device, that you must decrypt client-side.

### Path Parameters

| Parameter | Type | Required |
| --- | --- | --- |
| id | string | Yes |

### Request Example

```bash
curl -X GET "https://api.igloohome.co/works/devices/{id}/admin-key" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	url := "https://api.igloohome.co/works/devices/{id}/admin-key"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer YOUR_ACCESS_TOKEN")
	req.Header.Set("Accept", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()

	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
```

```javascript
const res = await fetch("https://api.igloohome.co/works/devices/{id}/admin-key", {
  method: "GET",
  headers: {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Accept": "application/json"
  }
});

const data = await res.json();
console.log(data);
```

```python
import requests

url = "https://api.igloohome.co/works/devices/{id}/admin-key"
headers = {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Accept": "application/json"
}
response = requests.get(url, headers=headers)
print(response.json())
```

```php
<?php
$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => "https://api.igloohome.co/works/devices/{id}/admin-key",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer YOUR_ACCESS_TOKEN",
        "Accept: application/json"
    ]
]);

$response = curl_exec($curl);
curl_close($curl);

echo $response;
```

```kotlin
val client = OkHttpClient()

val request = Request.Builder()
    .url("https://api.igloohome.co/works/devices/{id}/admin-key")
    .method("GET", null)
    .addHeader("Authorization", "Bearer YOUR_ACCESS_TOKEN")
    .addHeader("Accept", "application/json")
    .build()

val response = client.newCall(request).execute()
println(response.body?.string())
```

```swift
import Foundation

var request = URLRequest(url: URL(string: "https://api.igloohome.co/works/devices/{id}/admin-key")!)
request.httpMethod = "GET"
request.setValue("Bearer YOUR_ACCESS_TOKEN", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Accept")

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let data = data {
        print(String(data: data, encoding: .utf8) ?? "")
    }
}
task.resume()
```

### Responses

`200 OK.`
`400 'unsupported.device.protocol' (not a G2 device), 'device.config.incomplete' (admin_key/password missing), or 'device.gmt_offset.not.configured'.`
`401 Unauthorized, invalid or expired authorization token`
`402-account-suspended Payment Required. Account is billing-suspended or past its trial grace period.`
`402-trial-ended Payment Required. iglooworks API trial has ended.`
`403 Forbidden. One of: (1) the token is valid but lacks the required scope for this endpoint (no body); (2) the user revoked the OAuth client's access ('{error: "The user has revoked access for this application..."}').`
`404 Device not found.`
`500 '{message: "internal.server.error"}'. Uncaught exception.`

**Response body — `200`:**

- **`payload`** `string`\
  Obfuscated blob. Decrypt client-side.

**Response body — `400`:**

- **`message`** `string`

**Response body — `402-account-suspended`:**

- **`error`** `string`

**Response body — `402-trial-ended`:**

- **`error`** `string`

**Response body — `403`:**

- **`error`** `string`

**Response body — `404`:**

- **`message`** `string`

**Response body — `500`:**

- **`message`** `string`

**Example response — `200`:**

```json
{
  "payload": "string"
}
```

**Example response — `400`:**

```json
{
  "message": "string"
}
```

**Example response — `402-account-suspended`:**

```json
{
  "error": "account.payment.is.suspended"
}
```

**Example response — `402-trial-ended`:**

```json
{
  "error": "30 days iglooworks API trial has ended"
}
```

**Example response — `403`:**

```json
{
  "error": "string"
}
```

**Example response — `404`:**

```json
{
  "message": "string"
}
```

**Example response — `500`:**

```json
{
  "message": "string"
}
```

