External Monitoring Integration Guide

This guide explains how to send normalized external monitoring data into LumeLevel using the External Monitoring HTTP endpoint. It is intended for gateway developers, partner integrators, and advanced installers.

v1 support summary: LumeLevel currently expects vendor reading values in millimeters (mm). The endpoint supports main tank readings, optional reserve readings, level or distance meaning, tank-top state, and source health flags such as fault and stale.

1. What this integration is

This integration allows an outside monitoring system, site gateway, or partner bridge to send normalized tank updates into LumeLevel over HTTP.

  • Main tank readings are supported.
  • Reserve tank readings are supported when the target device is configured for reserve.
  • Values may be sent as distance(UltraSonic sensor) or level(Submersible sensor).
  • Tank-top state may be sent as open or closed.
  • Fault and stale conditions may be reported without overwriting the last good stored reading.

2. Simple meanings

Distance: the source is reporting the distance from the sensor to the product or water surface.

Level: the source is reporting the liquid depth or level. LumeLevel converts that to distance using the configured sensor height.

3. What the vendor must send

  • A target device serial number that already exists in LumeLevel.
  • Reading values in mm.
  • A reading meaning of distance or level.
  • A validity flag for each reading.
  • A reading age in seconds.
  • Source health flags for fault and stale.
  • A unique messageId for idempotency.
Important: If a reading is marked faulted, stale, or invalid, the event may still be accepted and logged, but LumeLevel will not overwrite the last good stored reading.

4. Endpoint and authentication

Method: POST

Endpoint: /external-monitoring/report

Content-Type: application/json

Headers required:

  • X-Integration-Key
  • X-Integration-Secret

5. Payload structure

The payload uses five top-level sections: source, target, event, readings, and discrete/health.

{
  "source": {
    "vendor": "PartnerGateway",
    "siteId": "SITE-123",
    "deviceId": "EXT-001",
    "messageId": "reserve-level-001",
    "sentAt": "2026-04-18T03:52:00Z"
  },
  "target": {
    "deviceSerialNumber": "0E001146"
  },
  "event": {
    "eventType": "periodic"
  },
  "readings": {
    "main": {
      "value": 932.1038,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    },
    "reserve": {
      "value": 450.0,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    }
  },
  "discrete": {
    "tankTopState": "closed",
    "valid": true
  },
  "health": {
    "sourceFault": false,
    "sourceStale": false
  }
}

6. Field meanings

Field Meaning Notes
source.vendor Partner or gateway name Used as part of duplicate detection
source.siteId Partner site identifier Used as part of duplicate detection
source.deviceId Partner source device identifier Used as part of duplicate detection
source.messageId Unique message identifier Required for idempotency
target.deviceSerialNumber LumeLevel target device Must already exist in the platform
readings.*.value Reading value v1 partner support is mm only
readings.*.units Units code mm
readings.*.kind distance or level Controls how LumeLevel normalizes the reading
readings.*.valid Reading validity If false, the reading will not be persisted
readings.*.ageSec Reading age in seconds Used with stale acceptance rules
discrete.tankTopState open or closed Logged as event state
health.sourceFault Source fault flag If true, readings are status-only
health.sourceStale Source stale flag If true, readings are status-only

7. Supported event types

  • periodic
  • reading_change
  • tank_open
  • tank_closed
  • fault
  • stale
  • recovered
  • startup

8. How LumeLevel handles the reading

  1. The platform authenticates the request.
  2. The target device is resolved by deviceSerialNumber.
  3. The platform checks device metadata, including whether reserve is enabled.
  4. If the reading kind is distance, the mm value is converted directly into feet distance.
  5. If the reading kind is level, the mm value is converted into feet level, then normalized using the configured sensor height.
  6. If the reading is accepted, it is written through the normal tank update pipeline.
  7. The inbound event is written to ExternalIntegrationEvent for traceability.

9. Reserve note

Reserve readings are only persisted when the target device is configured to use reserve / second sensor. If a reserve value is supplied for a non-reserve device, it will not be persisted.

10. Duplicate handling

LumeLevel uses the combination of vendor, site ID, device ID, and message ID as the idempotency key.

  • If the same message is received again, the request is treated as a duplicate.
  • The duplicate request does not write to TankUpdate again.
  • The duplicate request does not insert a second ExternalIntegrationEvent row.
  • The response returns duplicate code 1102.

11. Response meanings

Code Meaning Typical use
1101 Accepted and processed At least one accepted reading was persisted
1100 Accepted as status-only event Fault, stale, invalid, or otherwise not accepted for persistence
1102 Duplicate ignored The same idempotency key was already processed
1001 POST required Wrong HTTP method
1002 Invalid JSON payload Malformed body
1003 Missing target device target.deviceSerialNumber not supplied
1004 Target device not found Serial not found in the system
1005 Device metadata not found Associated metadata missing
1006 Processing error Internal failure during processing

12. Timezone behavior

LumeLevel uses the device timezone when storing device-local dates and times. This is intentional because the physical device may be installed in a different timezone than the user account.

Partner sentAt timestamps should be supplied in UTC.

13. Logging behavior

Accepted and status-only events are written to ExternalIntegrationEvent.

  • main and reserve source values are logged
  • level/distance meaning is logged
  • fault/stale flags are logged
  • tank-top state is logged
  • the raw JSON payload is preserved

Duplicate requests are ignored and do not create a second event row in v1.

14. Example payloads

Example A: periodic main-only distance

{
  "source": {
    "vendor": "PartnerGateway",
    "siteId": "SITE-123",
    "deviceId": "EXT-001",
    "messageId": "main-distance-001",
    "sentAt": "2026-04-18T03:50:00Z"
  },
  "target": {
    "deviceSerialNumber": "7602CA7E41"
  },
  "event": {
    "eventType": "periodic"
  },
  "readings": {
    "main": {
      "value": 932.1038,
      "units": "mm",
      "kind": "distance",
      "valid": true,
      "ageSec": 10
    }
  },
  "discrete": {
    "tankTopState": "closed",
    "valid": true
  },
  "health": {
    "sourceFault": false,
    "sourceStale": false
  }
}

Example B: periodic main + reserve level

{
  "source": {
    "vendor": "PartnerGateway",
    "siteId": "SITE-123",
    "deviceId": "EXT-001",
    "messageId": "reserve-level-001",
    "sentAt": "2026-04-18T03:52:00Z"
  },
  "target": {
    "deviceSerialNumber": "0E001146"
  },
  "event": {
    "eventType": "periodic"
  },
  "readings": {
    "main": {
      "value": 932.1038,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    },
    "reserve": {
      "value": 450.0,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    }
  },
  "discrete": {
    "tankTopState": "closed",
    "valid": true
  },
  "health": {
    "sourceFault": false,
    "sourceStale": false
  }
}

Example C: tank open

{
  "source": {
    "vendor": "PartnerGateway",
    "siteId": "SITE-123",
    "deviceId": "EXT-001",
    "messageId": "tank-open-001",
    "sentAt": "2026-04-18T03:55:00Z"
  },
  "target": {
    "deviceSerialNumber": "0E001146"
  },
  "event": {
    "eventType": "tank_open"
  },
  "readings": {
    "main": {
      "value": 932.1038,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    },
    "reserve": {
      "value": 450.0,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    }
  },
  "discrete": {
    "tankTopState": "open",
    "valid": true
  },
  "health": {
    "sourceFault": false,
    "sourceStale": false
  }
}

Example D: stale status-only

{
  "source": {
    "vendor": "PartnerGateway",
    "siteId": "SITE-123",
    "deviceId": "EXT-001",
    "messageId": "stale-001",
    "sentAt": "2026-04-18T03:57:00Z"
  },
  "target": {
    "deviceSerialNumber": "0E001146"
  },
  "event": {
    "eventType": "stale"
  },
  "readings": {
    "main": {
      "value": 932.1038,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    },
    "reserve": {
      "value": 450.0,
      "units": "mm",
      "kind": "level",
      "valid": true,
      "ageSec": 10
    }
  },
  "discrete": {
    "tankTopState": "closed",
    "valid": true
  },
  "health": {
    "sourceFault": false,
    "sourceStale": true
  }
}

15. Final notes

  • Use mm only in v1.
  • Always send a unique message ID.
  • Use the correct target device serial number.
  • Use level only when the source is reporting actual liquid level.
  • Use distance only when the source is reporting sensor-to-surface distance.