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

# Integrating a device

> Connection, topic, and message contract for any MQTT-capable device (ESP32 or otherwise)

This is the contract every MQTT-capable device — ESP32 or otherwise — must implement to connect to PumpLink.

## Connection

* **Broker**: `mqtt.pumplink.dev`
* **Port**: `41102`
* **Transport**: TLS required. Plain TCP is not accepted for external connections.
* **Credentials**:
  * Username: the device's numeric database ID, as a string.
  * Password: the 24-byte plaintext token shown once in the admin portal when you register a device or regenerate its credentials. It's bcrypt-hashed server-side and never recoverable after that — your provisioning flow must capture it and burn it into firmware or device config at manufacture or setup time.
* **Client ID**: unconstrained. Not checked by the backend.

<Warning>
  Credentials are issued once and never recoverable. If you lose the plaintext token, you must regenerate credentials from the admin portal and reflash the device. Store it securely at provisioning time.
</Warning>

<Note>
  You can test your device connection using any MQTT client such as [MQTT Explorer](https://mqtt-explorer.com). Connect to `mqtt.pumplink.dev` on port `41102` with TLS enabled, using your device's username and password. Subscribe to `device/{id}/#` to observe all traffic for your device.
</Note>

## Topics

All topics are scoped to `device/{deviceID}/`. The ACL denies anything outside this prefix.

| Topic                 | Direction        | Payload                                | QoS |
| --------------------- | ---------------- | -------------------------------------- | --- |
| `device/{id}/command` | backend → device | `"on"` or `"off"` (not JSON)           | 2   |
| `device/{id}/status`  | device → backend | plain string; `"off"` ends the session | 1   |
| `device/{id}/ack`     | device → backend | any content                            | 2   |

There are no JSON envelopes anywhere in this contract — all three topics carry bare strings.

## Activation handshake

This is the core contract every device must implement:

<Steps>
  <Step title="Subscribe">
    Device subscribes to `device/{id}/command`.
  </Step>

  <Step title="Backend activates">
    On `POST /activate`, the backend publishes `"on"` (QoS 2, not retained).
  </Step>

  <Step title="Device must ack">
    The device must publish anything to `device/{id}/ack` within 10 seconds. If it doesn't, the backend aborts, publishes `"off"`, and returns a 504 to the caller.
  </Step>

  <Step title="Device runs until told to stop">
    The device runs until it receives `"off"` on the command topic. There's no session-duration or quota message sent to the device — the backend's own timer decides when to cut it off. Quota exhaustion, admin force-shutdown, and timer expiry all just result in an unsolicited `"off"`.
  </Step>
</Steps>

<Note>
  No ack is expected or required for `"off"`.
</Note>

## Client behavior expectations

* **Retained messages**: none, on any topic.
* **Keepalive / clean session**: no specific requirement is enforced by the backend.

<Warning>
  No LWT (Last Will and Testament) is wired up. If a device disconnects ungracefully, the backend has no way to know unless the device proactively publishes `"off"` to its own status topic before going dark.

  If you want "power loss → session ends" behavior, you have two options:

  * Configure an LWT pointing at `mqtt.pumplink.dev` on your MQTT client, or
  * Have the device publish `"off"` on graceful shutdown.
</Warning>

## Quota

Per-device, window-based quota is entirely server-side. The device is quota-blind and just responds to `on`/`off` commands.
