# GATT operation failed

`readValue`, `writeValue`, `startNotifications`, `getPrimaryService`, or `getCharacteristic` rejected with `NotFoundError`, `NotSupportedError`, `SecurityError`, or a raw CoreBluetooth error bubble-up. This page walks the common causes.

## Symptom

```js
try {
  const char = await service.getCharacteristic('battery_level');
  const value = await char.readValue();
} catch (err) {
  // NotFoundError, SecurityError, NotSupportedError, or NetworkError
  console.error(err.name, err.message);
}
```

One or more GATT operations reject with a `DOMException` — the specific error name indicates which of the causes below applies.

## 1. Service or characteristic not in filters / optionalServices

`requestDevice()` grants access to the services listed in `filters` and `optionalServices`. Attempting to use any other service fails with `SecurityError: Origin is not allowed to access the service`.

**Fix:** include every service you plan to use at `requestDevice()` time.

```js
await navigator.bluetooth.requestDevice({
  filters: [{ services: ['heart_rate'] }],
  optionalServices: ['battery_service', 'device_information']
});
```

## 2. Peripheral does not expose the characteristic

`getCharacteristic` rejects with `NotFoundError` when the characteristic is absent from the service. This is a peripheral firmware fact, not a WebBLE bug.

**Fix:** enumerate the service with `service.getCharacteristics()` and log the UUIDs the peripheral actually exposes. Compare against your expected list.

## 3. Operation not supported by the characteristic

Writing to a read-only characteristic (or subscribing to one without `notify` / `indicate`) rejects with `NotSupportedError`.

**Fix:** inspect `characteristic.properties`:

```js
console.log(char.properties);
// { broadcast, read, writeWithoutResponse, write, notify, indicate, authenticatedSignedWrites, reliableWrite, writableAuxiliaries }
```

Only call operations supported by the characteristic.

## 4. GATT session stale after reconnect

After a disconnect, every `BluetoothRemoteGATTService` / `Characteristic` handle is invalidated. Reusing a handle from before the disconnect fails with `NetworkError`.

**Fix:** re-call `getPrimaryService` + `getCharacteristic` after each reconnect.

```js
device.addEventListener('gattserverdisconnected', async () => {
  await device.gatt.connect();
  const freshService = await device.gatt.getPrimaryService('heart_rate'); // new handle
});
```

## 5. Write payload too large

BLE ATT writes are bounded by the negotiated MTU (default 23 bytes = 20 data bytes on unmodified peripherals). Longer writes either truncate or reject.

**Fix:** chunk writes to ≤ 20 bytes, or rely on the peripheral negotiating a larger MTU. Some profiles define "long writes" (reliable writes) that Web Bluetooth exposes via `writeValueWithResponse`.

## 6. Pairing / bonding requirement not met

A characteristic marked `authenticatedSignedWrites` or protected by an encryption-required permission rejects until the link is paired. iOS normally triggers pairing automatically the first time you attempt such an operation — accept the pairing prompt on the phone.

**Fix:** accept the system pairing dialog when it appears; for persistent issues, "Forget This Device" in Settings → Bluetooth and reconnect.

## Still stuck?

Capture the exact `DOMException` name + message and the peripheral's GATT dump. Open an issue at [github.com/wklm/ioswebble-sdk/issues](https://github.com/wklm/ioswebble-sdk/issues).
