HOP Sensors Ingest API

Connect your substation systems to HOP Sensors by sending HTTPS POST requests to three ingest endpoints. No proprietary hardware or protocol adapters required — any system capable of making HTTP requests can integrate, including SCADA platforms, DMS systems, RTU data collectors, and custom middleware.

Overview Start Here

The HOP Sensors API is a JSON REST API. All requests must be authenticated with a pre-shared API key passed as a request header. All ingest endpoints accept JSON payloads. All query endpoints return JSON arrays.

PropertyValue
ProtocolHTTPS only
FormatJSON (Content-Type: application/json)
AuthX-API-Key header (pre-shared key)
Ingest endpoints3 — telemetry, event, outage
Query endpoints8 — dashboard read access
TimestampsISO 8601 UTC — e.g. 2026-06-11T14:32:00Z

Authentication

Every request must include your API key in the X-API-Key header. Your key is provisioned when your account is created. Keep it secret — it grants full read/write access to your substation data.

# All requests — required header
X-API-Key: your-api-key-here
Never expose your API key in client-side code, public repositories, or logs. If you believe your key has been compromised, contact info@hopsensorsllc.com immediately for a rotation.

Base URL

All API endpoints share a single base URL:

https://portal.hopsensorsllc.com/api

Full example for the telemetry ingest endpoint:

POST https://portal.hopsensorsllc.com/api/ingest/telemetry

Errors

StatusMeaning
200 OKRequest succeeded. Response body confirms what was written.
401 UnauthorizedMissing or invalid X-API-Key header.
422 Unprocessable EntityRequest body failed validation. Response includes field-level error detail.
500 Internal Server ErrorDatabase write or query failed. Response includes detail string.

POST /ingest/telemetry Ingest

Write a real-time telemetry reading for one feeder at one substation. Send one record per feeder per polling cycle. Your SCADA system or middleware should call this endpoint at whatever interval it samples data — the platform supports any rate up to once per second per feeder.

Request Body

Field Type Description
substation_id REQUIRED string Your identifier for this substation. Must be consistent across all records. E.g. SUB001
voltage_kv REQUIRED float Bus voltage in kilovolts.
current_a REQUIRED float Line current in amperes.
power_mw REQUIRED float Active power in megawatts.
frequency_hz REQUIRED float Grid frequency in hertz. This field drives ML anomaly scoring.
feeder_id optional string Feeder identifier. E.g. FDR002. Enables per-feeder event correlation.
device_id optional string Device or RTU identifier. E.g. SUB001-FDR002.
site_name optional string Human-readable site label shown in the dashboard. E.g. Portland North.
latitude optional float Substation latitude. Required for map view. E.g. 45.5231
longitude optional float Substation longitude. Required for map view. E.g. -122.6765
status_code optional string Operational status tag from your SCADA system. E.g. NORMAL, OUTAGE.
timestamp optional string ISO 8601 UTC timestamp. If omitted, the server uses the time of receipt.

Example Request

// POST /api/ingest/telemetry
// Header: X-API-Key: your-api-key-here
// Header: Content-Type: application/json

{
  "substation_id": "SUB001",
  "feeder_id":     "FDR002",
  "device_id":     "SUB001-FDR002",
  "site_name":     "Portland North",
  "latitude":      45.5231,
  "longitude":     -122.6765,
  "voltage_kv":    229.8,
  "current_a":     1187.4,
  "power_mw":      274.2,
  "frequency_hz":  60.002,
  "status_code":   "NORMAL",
  "timestamp":     "2026-06-11T14:32:00Z"
}
Response — 200 OK
{ "status": "telemetry written", "substation_id": "SUB001" }
Alias: POST /scada/write is an identical endpoint preserved for v4 compatibility. New integrations should use /ingest/telemetry.

POST /ingest/event Ingest

Write a discrete grid event — a state change, alarm, or operational transition at a device or feeder. Events populate the Alarms & Outages tab and trigger email alerts for FEEDER_TRIP conditions.

Request Body

FieldTypeDescription
device_id REQUIRED string Device or RTU originating the event. E.g. SUB001-FDR002.
severity REQUIRED string ALARM — triggers alerting pipeline. INFO — logged, no alert.
event_type REQUIRED string See event types below. E.g. FEEDER_TRIP, FEEDER_RESTORE.
feeder_id optional string Feeder affected. Used for trip/restore correlation.
old_state optional string State before transition. E.g. CLOSED.
new_state optional string State after transition. E.g. OPEN.
message optional string Free-text description included in alert emails.
latitude optional float Event location — for map overlay.
longitude optional float Event location — for map overlay.
timestamp optional string ISO 8601 UTC. Defaults to time of receipt.

Event Types

event_typeseverityEffect
FEEDER_TRIPALARMLogged + email alert sent (subject to rate limits). Marks feeder as tripped.
FEEDER_RESTOREINFOLogged + restore notification sent (only if a TRIP alert was previously sent for this feeder).
Any other valueINFO or ALARMLogged to the events table. Visible in dashboard Alarms & Outages tab.

Example — Feeder Trip

{
  "device_id":   "SUB001-FDR002",
  "feeder_id":   "FDR002",
  "severity":    "ALARM",
  "event_type":  "FEEDER_TRIP",
  "old_state":   "CLOSED",
  "new_state":   "OPEN",
  "message":     "Overcurrent protection operated — FDR002",
  "latitude":    45.5231,
  "longitude":   -122.6765,
  "timestamp":   "2026-06-11T14:35:00Z"
}
Response — 200 OK
{ "status": "event written", "device_id": "SUB001-FDR002" }

POST /ingest/outage Ingest

Write or update an outage record with customer impact data. Send an ACTIVE record when an outage begins and a RESTORED record when it is cleared. Outages appear on the Alarms & Outages tab and the Executive View grid health metric.

Request Body

FieldTypeDescription
outage_id REQUIRED string Unique identifier for this outage. Must match on the restore record. E.g. OUT-SUB001-FDR002-1749650000.
feeder_id REQUIRED string Feeder affected.
customers_out REQUIRED integer Customers without power. Send 0 on restore.
status REQUIRED string ACTIVE — outage in progress. RESTORED — outage cleared.
region optional string Region or substation label. E.g. SUB001.
mw_interrupted optional float Load interrupted in megawatts.
cause optional string Cause category. E.g. STORM, EQUIPMENT, ANIMAL, UNKNOWN.
etr_time optional string Estimated time to restore — ISO 8601 UTC.
latitude / longitude optional float Outage location for map overlay.
timestamp optional string ISO 8601 UTC. Defaults to time of receipt.

Example — Active Outage

{
  "outage_id":       "OUT-SUB001-FDR002-1749650000",
  "feeder_id":       "FDR002",
  "region":          "SUB001",
  "customers_out":   847,
  "mw_interrupted":  12.4,
  "status":          "ACTIVE",
  "cause":           "STORM",
  "etr_time":        "2026-06-11T16:00:00Z",
  "latitude":        45.5231,
  "longitude":       -122.6765,
  "timestamp":       "2026-06-11T14:35:00Z"
}

Example — Restore

{
  "outage_id":      "OUT-SUB001-FDR002-1749650000",
  "feeder_id":      "FDR002",
  "customers_out":  0,
  "status":         "RESTORED",
  "timestamp":      "2026-06-11T15:48:00Z"
}
Response — 200 OK
{ "status": "outage written", "outage_id": "OUT-SUB001-FDR002-1749650000" }

GET /query/telemetry/latest Query

Returns the most recent telemetry reading per substation from the last 1 minute. Used by the live telemetry panel in the dashboard.

GET /api/query/telemetry/latest
X-API-Key: your-api-key-here
Response — array of objects
[
  {
    "substation_id": "SUB001",
    "frequency_hz":  60.002,
    "voltage_kv":    229.8,
    "current_a":     1187.4,
    "power_mw":      274.2,
    "latitude":      45.5231,
    "longitude":     -122.6765,
    "time":          "2026-06-11 14:32:00+00:00"
  }
]

GET /query/telemetry/timeseries Query

Returns voltage, power, and frequency time series for a single substation. Drives the time-series charts on the Telemetry tab.

ParamTypeDefaultDescription
substation_idstringRequired. Substation to query.
minutesinteger30Lookback window. Accepts: 5, 15, 30, 60, 360, 1440, 10080.
GET /api/query/telemetry/timeseries?substation_id=SUB001&minutes=60

GET /query/ml/latest Query

Returns the most recent ML anomaly score per substation. Drives the score cards on the ML Anomaly tab.

GET /api/query/ml/latest
Response — array of objects
[
  {
    "substation_id":  "SUB001",
    "anomaly_score":  0.2341,  // 0.0 – 1.0. >= 0.7 = anomaly
    "is_anomaly":     false,
    "baseline_mean":  60.001,
    "baseline_std":   0.0082,
    "z_score":        0.585,
    "latest_value":   60.002,  // frequency_hz reading that was scored
    "time":           "2026-06-11 14:32:00+00:00"
  }
]

GET /query/ml/trend Query

Returns ML anomaly score history across all substations for trend visualization.

ParamTypeDefaultDescription
minutesinteger30Lookback window.
GET /api/query/ml/trend?minutes=60

GET /query/events Query

Returns recent grid events — feeder trips, restores, and any other logged events — ordered most-recent first.

ParamTypeDefaultDescription
minutesinteger30Lookback window. Max returned: 100 records.
GET /api/query/events?minutes=60

GET /query/outages/active Query

Returns currently active outages — records with status = ACTIVE written within the last 2 hours, deduplicated by outage ID.

GET /api/query/outages/active
Response — array of objects
[
  {
    "outage_id":      "OUT-SUB001-FDR002-1749650000",
    "feeder_id":      "FDR002",
    "region":         "SUB001",
    "customers_out":  847,
    "mw_interrupted": 12.4,
    "cause":          "STORM",
    "etr_time":       "2026-06-11T16:00:00Z",
    "time":           "2026-06-11 14:35:00+00:00"
  }
]

GET /query/frequency/thresholds Query

Returns frequency readings for all substations for the threshold plot on the Frequency tab.

ParamTypeDefaultDescription
minutesinteger30Lookback window.
GET /api/query/frequency/thresholds?minutes=30

GET /query/substations Query

Returns all distinct substation IDs that have sent telemetry in the last hour, with their coordinates. Useful for validating that your integration is sending data for the expected substations.

GET /api/query/substations
Response — array of objects
[
  { "substation_id": "SUB001", "latitude": 45.5231, "longitude": -122.6765 },
  { "substation_id": "SUB002", "latitude": 45.5152, "longitude": -122.6784 }
]

Data Model

All data is stored in InfluxDB 3 Core across three measurements. Understanding the schema helps when interpreting query responses.

scada_grid

Primary time-series measurement. Stores raw telemetry and ML anomaly scores together, time-aligned.

FieldTypeSource
substation_idtagIngest
feeder_id, device_id, site_nametagIngest
voltage_kv, current_a, power_mw, frequency_hzfieldIngest
anomaly_score, is_anomaly, z_scorefieldML service (written every 60s)
baseline_mean, baseline_std, latest_value, model_versionfieldML service

scada_events

Discrete event log. Every feeder trip, restore, and other state transition.

outages

Outage lifecycle records. Each outage has an ACTIVE record and a RESTORED record identified by the same outage_id.

GET /health

Returns API health status. No authentication required. Use for uptime monitoring and integration validation.

GET /api/health
Response — 200 OK
{
  "status":    "ok",
  "version":   "v5",
  "database":  "hopsensors_v5",
  "timestamp": "2026-06-11T14:32:00.000000+00:00"
}