1. OEM Integration Overview
Energy and smart-device ecosystems are fragmented. A residential battery, a smart thermostat, and a smart water heater each expose telemetry and control through completely different OEM APIs - different authentication schemes, different data models, different units, different event semantics. Any company building an application on top of multiple device categories (energy management, demand response, home automation, insurance, utility programs) ends up re-solving the same integration problem for every OEM it connects to.
The platform is designed as a standardized integration and normalization layer that sits between OEM device ecosystems and downstream applications. Instead of every application team building and maintaining bespoke connectors to each OEM (e.g., a battery manufacturer's cloud API, a thermostat manufacturer's cloud API, a water heater manufacturer's cloud API), the platform provides:
- A single integration point for onboarding new OEMs
- A canonical, cross-OEM data model for devices, telemetry, and events
- A consistent way to enroll devices and issue control commands, regardless of the underlying OEM protocol
What problems this solves:
- For downstream applications: one API and one data model instead of N different OEM schemas
- For the ecosystem: faster time-to-integration when a new OEM or device category is added, since only the OEM-specific mapping layer changes - not the applications built on top
Architecture
Battery / Thermostat / Water Heater / etc."] --> B["OEM Cloud APIs & Event Streams"] B --> C["Integration & Ingestion Layer"] C --> D["Normalization & Data Quality"] D --> E["Unified Device Data Model
Telemetry · Events · Enrollment · Control State"] E --> F["APIs / Events"] F --> G["Downstream Applications"] G -.->|Control Commands| E E -.->|Translated Commands| C C -.->|OEM-specific Command Format| B B -.-> A
Telemetry and enrollment data flow left to right (device → application). Control commands flow right to left (application → device), passing back through the same normalization layer so a command issued in canonical form is translated into the correct OEM-specific call.
2. Integration Methods
| Method | When it's appropriate | Typical data | Key considerations |
|---|---|---|---|
| REST APIs | On-demand reads, enrollment actions, issuing control commands, low-to-moderate call volume | Device metadata, current state, historical telemetry queries, control commands | Auth (OAuth2/API key), rate limits, request idempotency for write operations |
| Event streams / Pub-Sub | Near-real-time telemetry and state-change events at scale | Telemetry readings, device online/offline events, alarms/diagnostics | Consumer offset management, at-least-once delivery, ordering not guaranteed across partitions - see Reliability |
| Webhooks | OEM-initiated push notifications for discrete events (enrollment status change, firmware update, alert) | Event payloads, status changes | Signature verification, retry/backoff on non-2xx responses, idempotency keys to handle re-delivery |
| Batch ingestion | Historical backfill, OEMs that only expose bulk export (CSV/file-based) rather than live APIs | Historical telemetry, device inventories | File format validation, deduplication against already-ingested data, late-arriving batch handling |
Most production OEM integrations combine at least two of these: a REST API for enrollment/control and either an event stream or webhooks for ongoing telemetry.
3. Data Model
OEM-specific schemas are mapped into a consistent canonical data model. Applications built on the platform interact only with this canonical model and are not exposed to OEM-specific field names, units, or structures.
Core entities:
| Entity | Description |
|---|---|
| Device | A single connected unit (battery, thermostat, water heater, etc.), identified by a canonical device ID mapped to the OEM's native device ID |
| Device Metadata | Make, model, category, firmware version, install location, capabilities (e.g., supports control vs. telemetry-only) |
| Telemetry | Time-series readings emitted by the device (e.g., state of charge, temperature, power draw) |
| Enrollment / Connection State | Whether a device is linked, its authorization status, and connection health |
| Events | Discrete occurrences - alerts, faults, connectivity changes, firmware updates |
| Control Command / Command State | A control action issued to a device and its execution status (pending, acknowledged, completed, failed) |
| Diagnostics / Alerts | Fault codes, warnings, and health indicators normalized across OEMs where the OEM exposes them |
Example: Device Metadata
{
"device_id": "dev_8f2c1a",
"canonical_type": "battery",
"oem": "oem_a",
"oem_device_id": "PWX-993211",
"model": "Battery Gen2",
"firmware_version": "3.4.1",
"capabilities": ["telemetry", "control"],
"install_location": {
"site_id": "site_44a1",
"timezone": "America/Los_Angeles"
},
"status": "active"
}
Example: Telemetry Event
{
"device_id": "dev_8f2c1a",
"timestamp": "2026-08-31T09:12:04Z",
"metric": "state_of_charge",
"value": 62.5,
"unit": "percent",
"source": "event_stream"
}
Example: Device Event
{
"device_id": "dev_9a01f7",
"event_type": "connectivity_lost",
"severity": "warning",
"timestamp": "2026-08-31T09:15:41Z",
"details": {
"last_seen": "2026-08-31T08:52:10Z"
}
}
4. OEM Data Mapping
Each OEM exposes its own field names, units, and structures. The mapping layer translates each OEM's native schema into the canonical model above, so downstream applications never handle OEM-specific formats directly.
| OEM field (example) | Canonical field |
|---|---|
battery_soc_pct (OEM A) | telemetry.state_of_charge (unit: percent) |
soc (OEM B, 0–1 scale) | telemetry.state_of_charge (converted to percent) |
tstat_setpoint_f (OEM C) | telemetry.target_temperature (unit: celsius, converted) |
dev_uid (OEM A) | device.oem_device_id |
serial (OEM B) | device.oem_device_id |
Mapping also normalizes:
- Units - all canonical values use a fixed unit per metric; OEM-native units are converted at ingestion
- Timestamps - all timestamps normalized to UTC ISO 8601, regardless of the timezone or format the OEM provides
- Enumerations - OEM-specific status codes are mapped to a fixed canonical enum (e.g., varying "online/active/connected" states become a single
connectedvalue) - Missing values - represented explicitly as
nullrather than omitted, so applications can distinguish "not reported by OEM" from "zero" - Optional fields - canonical schema marks fields as optional where not all OEMs/device categories provide them (e.g., firmware version may be unavailable for some water heater OEMs)
- OEM-specific extensions - fields with no canonical equivalent are preserved under a namespaced
oem_extensionsobject rather than dropped, so no OEM data is silently lost
{
"device_id": "dev_8f2c1a",
"target_temperature": 51.7,
"unit": "celsius",
"oem_extensions": {
"oem_a": {
"eco_mode": true
}
}
}
5. Device Enrollment
Enrollment is the process by which a device account (typically an end customer's OEM account or a specific device) is linked into the platform so telemetry and control become available.
Typical enrollment flow:
- Authorization - customer authorizes access via OEM OAuth flow (or OEM-provided linking mechanism)
- Device discovery - platform queries the OEM API to enumerate devices available under the authorized account
- Mapping to canonical model - each discovered device is assigned a canonical
device_idand its metadata is normalized - Capability detection - platform records which capabilities (telemetry read, control write) the device/OEM actually supports, since this varies by OEM and device tier
- Activation - device begins ingesting telemetry and becomes eligible for control commands, if supported
Exact enrollment UX and OAuth flow details are OEM-specific and defined per integration.
6. Device Control
Where an OEM supports it, the platform allows control commands to be issued to a device using the canonical model rather than the OEM's native command format.
- Commands are submitted against a canonical
device_idwith a canonical command type (e.g.,set_target_temperature,set_charge_mode) - The platform translates the command into the OEM-specific API call or message format
- Command execution is tracked as asynchronous state:
pending → acknowledged → completed | failed, since most OEM devices do not confirm control actions synchronously - Not all devices or OEMs support control for all parameters - supported control actions are exposed per device via its capability metadata (see Data Model)
Which specific control actions are supported per OEM/device category is defined during that OEM's integration and documented separately.
7. Integration Lifecycle
| Stage | What happens |
|---|---|
| 1. Authentication / access setup | Credentials or OAuth client configured for the OEM's API; scopes and permissions confirmed |
| 2. Schema discovery | OEM's data model, available endpoints/streams, and supported device categories are catalogued |
| 3. Field mapping | OEM fields are mapped to the canonical model, including units, enums, and any OEM-specific extensions |
| 4. Initial / backfill ingestion | Historical or existing device data is ingested to populate the canonical model before real-time ingestion begins |
| 5. Real-time ingestion | Live telemetry and events begin flowing via the appropriate integration method (stream, webhook, or polling) |
| 6. Validation & reconciliation | Ingested data is checked against OEM source data for completeness and correctness before rollout |
| 7. Production rollout | Integration is enabled for live customer devices under that OEM |
| 8. Monitoring | Ongoing tracking of ingestion health, data freshness, and error rates for the integration |
8. Reliability & Data Quality
Production integrations across multiple OEM data sources need to handle inconsistency at the source. The platform's normalization layer is designed around the following:
- Duplicate messages - ingestion is idempotent per event using a deterministic key (device ID + timestamp + metric), so re-delivered messages don't produce duplicate records
- Out-of-order events - events are ordered by their canonical timestamp rather than arrival order; late-arriving data is inserted into its correct time position rather than appended
- Missing telemetry - gaps are represented explicitly rather than interpolated, so applications can distinguish "device didn't report" from "value was zero"
- Delayed data - ingestion timestamp and event timestamp are tracked separately so downstream consumers can identify and handle lag
- Invalid values - values outside expected physical ranges (e.g., negative percentage) are flagged and quarantined rather than passed through silently
- Schema changes - OEM schema/version changes are handled at the mapping layer so canonical output remains stable even if an OEM changes its native API
- Retries - failed ingestion or command delivery is retried with backoff; retry behavior is method-specific (see Integration Methods)
- Timestamp normalization - all timestamps are converted to UTC at ingestion, regardless of source format or timezone
9. API Reference - Minimal Example
The following illustrates the shape of the canonical API. It is a representative example, not a complete or currently implemented specification.
GET /devices
Returns devices enrolled under the authenticated account.
GET /devices
{
"devices": [
{ "device_id": "dev_8f2c1a", "canonical_type": "battery", "status": "active" },
{ "device_id": "dev_9a01f7", "canonical_type": "thermostat", "status": "active" }
]
}
GET /devices/{device_id}
Returns canonical metadata for a specific device.
GET /devices/dev_8f2c1a
{
"device_id": "dev_8f2c1a",
"canonical_type": "battery",
"model": "Battery Gen2",
"capabilities": ["telemetry", "control"],
"status": "active"
}
GET /devices/{device_id}/telemetry
Returns telemetry readings for a device over a time range.
GET /devices/dev_8f2c1a/telemetry?metric=state_of_charge&since=2026-08-30T00:00:00Z
{
"device_id": "dev_8f2c1a",
"metric": "state_of_charge",
"readings": [
{ "timestamp": "2026-08-30T00:00:00Z", "value": 58.2 },
{ "timestamp": "2026-08-30T01:00:00Z", "value": 59.0 }
]
}
POST /devices/{device_id}/commands
Issues a control command to a device that supports it.
POST /devices/dev_8f2c1a/commands
{
"command_type": "set_charge_mode",
"parameters": { "mode": "backup_reserve" }
}
{
"command_id": "cmd_3391a",
"device_id": "dev_8f2c1a",
"status": "pending"
}
10. OEM Integration FAQ
How do we authenticate to the platform?
Access is scoped per OEM integration using OAuth2 or API-key based credentials. Exact flow details are finalized per OEM during onboarding.
How are OEM-specific fields handled?
Fields with no canonical equivalent are preserved under an oem_extensions namespace rather than discarded, so no data is lost even if it isn't part of the standard model.
How is real-time data delivered?
Via event streams or webhooks, depending on what the OEM supports. Both are consumed into the same canonical model on the platform side.
What happens when messages arrive out of order?
Events are re-ordered by canonical timestamp on ingestion, not by arrival order.
How are duplicate events handled?
Ingestion is idempotent per event using a deterministic key, so re-delivered messages don't create duplicate records.
How are schema changes on the OEM side managed?
Schema changes are absorbed at the OEM-specific mapping layer, so the canonical model and downstream applications remain unaffected.
Can historical data be ingested?
Yes - batch/backfill ingestion is part of the standard integration lifecycle, run before real-time ingestion is enabled.
How is device identity maintained across systems?
Each device receives a canonical device_id that is mapped to the OEM's native device identifier and remains stable even if OEM-side identifiers change.
How are OEM-specific extensions supported without breaking the canonical model?
They're additive - stored alongside the canonical fields rather than replacing or altering the core schema.
Is device control supported, or only telemetry?
Both, where the OEM and device support it. Control commands are issued in canonical form and translated to the OEM-specific format; supported actions vary by device and are exposed via device capability metadata.
How is an integration monitored once in production?
Through ingestion health, data freshness, and error-rate tracking established during the monitoring stage of the integration lifecycle.