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

# Get Device State

> Live snapshot of a device — battery, storage, recording state, connectivity, last heartbeat

Returns the live runtime snapshot of a device. Same field shape as
[`GET /v1/devices/{id}`](/api-reference/devices/get) for runtime fields,
just curated (no settings, no metadata, lighter payload — designed for
polling at a few-second cadence).

The `recording_state` object is forwarded verbatim from the device's most
recent heartbeat. Whether the device is **currently reachable** is not
returned as a server field; derive it client-side from `last_heartbeat_at`
(`Date.now() - last_heartbeat_at < 15 * 60 * 1000` is the convention).

<ParamField path="id" type="string" required>
  The device's unique identifier (e.g., `dev_abc123`).
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl https://api.bota.dev/v1/devices/dev_abc123/state \
    -H "Authorization: Bearer sk_live_..."
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.bota.dev/v1/devices/dev_abc123/state', {
    headers: {
      'Authorization': 'Bearer sk_live_...',
    },
  });

  const state = await response.json();
  const isOnline = state.last_heartbeat_at &&
    Date.now() - new Date(state.last_heartbeat_at).getTime() < 15 * 60 * 1000;
  const isRecording = state.recording_state?.device_state === 'recording';
  ```

  ```python Python theme={null}
  import requests
  from datetime import datetime, timezone

  response = requests.get(
      'https://api.bota.dev/v1/devices/dev_abc123/state',
      headers={'Authorization': 'Bearer sk_live_...'},
  )

  state = response.json()
  last_hb = state.get('last_heartbeat_at')
  is_online = (
      last_hb is not None
      and (datetime.now(timezone.utc) - datetime.fromisoformat(last_hb.replace('Z', '+00:00'))).total_seconds() < 900
  )
  is_recording = (state.get('recording_state') or {}).get('device_state') == 'recording'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 (Recording) theme={null}
  {
    "device_id": "dev_abc123",
    "recording_state": {
      "device_state": "recording",
      "recording_id": "rec_xyz789",
      "started_at": "2025-01-20T10:30:00Z",
      "initiated_by": "remote",
      "pending_recordings": 2,
      "flags": 24,
      "wifi_status": "connected",
      "wifi_ssid": "office-wifi",
      "lte_status": "off",
      "connection_type": ["wifi"]
    },
    "battery_percent": 85,
    "storage_used_mb": 256,
    "storage_total_mb": 1024,
    "last_heartbeat_at": "2025-01-20T10:35:00Z",
    "last_synced_at": "2025-01-20T09:50:00Z",
    "last_seen_by": "wifi",
    "last_synced_by": "wifi"
  }
  ```

  ```json 200 (Idle) theme={null}
  {
    "device_id": "dev_abc123",
    "recording_state": {
      "device_state": "idle",
      "pending_recordings": 0,
      "flags": 8,
      "wifi_status": "connected",
      "lte_status": "off",
      "connection_type": ["wifi"]
    },
    "battery_percent": 92,
    "storage_used_mb": 128,
    "storage_total_mb": 1024,
    "last_heartbeat_at": "2025-01-20T10:35:00Z",
    "last_synced_at": null,
    "last_seen_by": "wifi",
    "last_synced_by": null
  }
  ```

  ```json 200 (Offline — last heartbeat is stale) theme={null}
  {
    "device_id": "dev_abc123",
    "recording_state": {
      "device_state": "idle",
      "pending_recordings": 0,
      "flags": 0
    },
    "battery_percent": 60,
    "storage_used_mb": 100,
    "storage_total_mb": 1024,
    "last_heartbeat_at": "2025-01-19T15:20:00Z",
    "last_synced_at": null,
    "last_seen_by": "wifi",
    "last_synced_by": null
  }
  ```

  ```json 404 theme={null}
  {
    "error": {
      "code": "not_found",
      "message": "Device not found"
    }
  }
  ```
</ResponseExample>

## Response Fields

| Field               | Type           | Description                                                                                                                       |
| ------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `device_id`         | string         | The device's unique identifier                                                                                                    |
| `recording_state`   | object         | Snapshot from the device's last heartbeat — see below                                                                             |
| `battery_percent`   | number \| null | Battery level (0-100), null if unknown                                                                                            |
| `storage_used_mb`   | number \| null | Storage used in MB, null if unknown                                                                                               |
| `storage_total_mb`  | number \| null | Total storage in MB, null if unknown                                                                                              |
| `last_heartbeat_at` | string \| null | ISO 8601 timestamp of the most recent heartbeat. Derive `online` from this — convention is `now - last_heartbeat_at < 15 minutes` |
| `last_synced_at`    | string \| null | ISO 8601 timestamp of the last recording sync                                                                                     |
| `last_seen_by`      | string \| null | Channel the last heartbeat arrived on: `"bluetooth"`, `"wifi"`, or `"cellular"`                                                   |
| `last_synced_by`    | string \| null | Channel of the last sync                                                                                                          |

### `recording_state` (jsonb passthrough)

Fields the device reports — all optional. The device may not report every field on every heartbeat, so clients should default missing values defensively.

| Field                | Type      | Description                                                                                                                                                                |
| -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device_state`       | string    | What the device is doing **right now** — `"idle"`, `"recording"`, `"syncing"`. The canonical signal for "is the device recording" — there is no separate `active` boolean. |
| `recording_id`       | string    | ID of the current recording when `device_state === "recording"`                                                                                                            |
| `started_at`         | string    | ISO 8601 timestamp when recording started                                                                                                                                  |
| `initiated_by`       | string    | `"local"` (button press) or `"remote"` (API command)                                                                                                                       |
| `pending_recordings` | number    | Number of recordings on the device awaiting sync                                                                                                                           |
| `flags`              | number    | Bitfield: `0x01` charging, `0x02` low battery, `0x04` storage full, `0x08` wifi connected, `0x10` lte connected, `0x20` sync active                                        |
| `connection_type`    | string\[] | Channels the device is currently using: subset of `["bluetooth", "wifi", "cellular"]`                                                                                      |
| `wifi_status`        | string    | `"connected"`, `"connecting"`, `"disconnected"`, `"off"`                                                                                                                   |
| `wifi_ssid`          | string    | Currently connected WiFi network name                                                                                                                                      |
| `lte_status`         | string    | `"connected"`, `"connecting"`, `"disconnected"`, `"off"`                                                                                                                   |
| `lte_operator`       | string    | Currently connected cellular operator                                                                                                                                      |

## Notes on shape

This endpoint returns the **same field names** as [`GET /v1/devices/{id}`](/api-reference/devices/get) for everything in the runtime snapshot. The two endpoints differ only in what they **omit**:

* `GET /v1/devices/{id}` includes the full device entity — `status`, `model`, `firmware_version`, `serial_number`, `settings` (with hierarchical config resolved), `mac_address`, `metadata`, etc.
* `GET /v1/devices/{id}/state` includes only the runtime snapshot — cheaper to poll, no settings resolution cost.

Use `/state` for live UI polling (1–10 s cadence). Use `/devices/{id}` once per session for device identity + configuration.
