Texture OEM / TPDO Integration Specification
Version: 1.0 Last Updated: March 2026 Contact: integrations@texturehq.com
Executive Summary
Texture is a real-time energy software platform that connects device manufacturers and fleet operators to utility grid services programs. When utilities need to reduce peak demand or balance the grid, Texture coordinates enrolled devices — batteries, thermostats, EV chargers — to respond. This specification enables your devices to participate in those programs. Program benefits vary — some offer performance-based compensation to device owners or fleet operators; others provide value through reduced energy costs or expanded device eligibility. Texture facilitates the technical integration (device enrollment, telemetry, dispatch); program terms, compensation, and enrollment are between you and the program operator.
This specification defines the technical requirements for OEMs (Original Equipment Manufacturers — device makers) and TPDOs (Third-Party Device Owners — companies that finance, install, or operate fleets of residential energy devices) integrating with the Texture platform.
This is the Texture Direct API — not the core Texture API. Texture operates two distinct APIs. The core Texture API (documented at docs.texturehq.com) is the comprehensive platform API used by utilities and energy providers to manage sites, devices, customers, enrollments, schedules, commands, and programs across connected energy fleets. This specification covers the Texture Direct API — the grid services API that OEMs and TPDOs use to push telemetry, receive dispatch signals, and enroll devices in utility programs. If you're a utility or energy provider looking to access device data, you want the core Texture API docs, not this specification.
Integrating with this API makes your platform eligible to participate in grid services programs that Texture manages on behalf of utilities and grid operators, subject to utility approval and program-specific requirements. Your devices get enrolled, receive dispatch signals, and report telemetry — Texture handles the utility-side complexity.
This specification covers:
- Authentication and authorization — how to securely connect to Texture's API
- Site management — how to register installation locations for territory matching
- Device registration — how to tell Texture about the devices you manage
- Telemetry delivery — how to push real-time device data to Texture
- Dispatch receipt — how Texture tells you when devices need to respond to grid events
- OEM-initiated enrollment — how to proactively register devices with customer consent
- Service territory matching — how Texture maps devices to eligible utility programs
- Device lifecycle management — enrollment states, revocation, and ownership changes
The API follows a standard REST pattern with JSON payloads over HTTPS. The API surface is built on top of the OpenADR 3.0 industry standard for demand response interoperability (see Specification Layers and Appendix C for details).
No platform fee for this integration. Texture does not charge OEMs or TPDOs for access to this API. Your cost is the engineering investment to implement and maintain this integration. (Note: Texture does charge platform fees for its core product used by utilities and energy providers. This specification covers the OEM/TPDO integration path, which has no platform fee.) Participation terms, compensation structures, and program specifics vary by program and utility — Texture facilitates the technical integration, not the financial arrangements between program operators and participants.
Table of Contents
- Architecture
- Getting Started
- Authentication
- Site Management
- Resource Management (Devices)
- Reports (Telemetry Push)
- Events (Dispatch Commands)
- Subscriptions (Webhooks)
- MQTT Transport (Optional)
- OEM-Initiated Enrollment
- Performance, Rate Limits, and Reliability
- Security & Privacy
- Error Handling
- Certification Process
- Appendix A — Endpoint Quick Reference
- Appendix B — Texture Extension Payload Type Registry
- Appendix C — OpenADR 3.0 Alignment
- Appendix D — Reference Links
1. Architecture
How It Works
There are two data flows in the integration — one that runs continuously and one that is triggered by grid events.
Flow 1: Continuous Telemetry (always on)
Your platform pushes device telemetry to Texture at all times — not just during dispatch events. Texture uses this continuous data to surface real-time fleet capacity to utilities, enabling them to see what's available before they dispatch.
Your Platform
↓ (pushes telemetry continuously — every 5 min or on state change)
Texture
↓ (aggregates fleet capacity, surfaces availability to utilities)
Utility / Grid Operator
(sees real-time fleet capacity for planning and dispatch decisions)
Flow 2: Dispatch Events (triggered by grid need)
When the grid needs a response, the utility sends a dispatch signal. Texture translates it into an Event and notifies your platform. You control the devices and push event-response telemetry back.
Utility / Grid Operator
↓ (dispatch signal — "we need 50 MW of load reduction")
Texture
↓ (creates Event → notifies your platform via webhook)
Your Platform
↓ (reads event details, controls enrolled devices)
Devices Respond
↓ (your platform pushes telemetry back to Texture every 30–60 sec)
Texture
↓ (forwards performance telemetry upstream for settlement)
Utility / Settlement
Both flows use the same API surfaces — Reports for telemetry push, Events for dispatch. The key difference is that telemetry is always on, while dispatch events are triggered by grid conditions.
You own two integration surfaces:
- Telemetry push — your platform sends device data to Texture as Report objects (continuously).
- Dispatch receipt — Texture creates Event objects when the grid needs a response; your platform is notified via webhook, reads the Event, and executes device commands.
Who Is This For?
This integration path is the right choice if you:
- Manufacture devices (OEM) or operate a fleet of enrolled devices (TPDO)
- Want your devices to participate in grid services programs — demand response, virtual power plants, frequency regulation, and other utility programs. Without an integration like this, your devices may be ineligible for programs that require real-time telemetry and dispatch capability.
- Want your devices to be competitive with other manufacturers that already support Texture — utilities see Texture-integrated fleets as dispatch-ready
- Have engineering capacity to build and maintain a REST API integration
- Want zero ongoing platform fees for this integration
OEMs vs. TPDOs: Both OEMs and TPDOs integrate via the same API. Whether you manufacture the devices or finance and operate them, your integration path is the same. Texture serves as the platform that connects your fleet to utility programs. For role-specific guidance, contact integrations@texturehq.com for FAQ documents.
Specification Layers
This API surface is built on top of the OpenADR 3.0 (Open Automated Demand Response) industry standard. OpenADR defines the core REST/JSON patterns for how utilities and aggregators communicate with distributed energy resources. Texture implements an OpenADR 3.0-aligned API and extends it with additional capabilities for device enrollment, site management, and richer telemetry.
The specification uses three layers:
| Layer | Scope | Marker |
|---|---|---|
| Layer 1: OpenADR 3.0 Core | Standard objects (Events, Reports, Resources, Subscriptions), auth, REST patterns — as defined by the OpenADR 3.0 specification | (unmarked — this is the baseline) |
| Layer 2: Texture Extension | Custom payload types, device schemas, enrollment, sites, richer telemetry — capabilities Texture adds on top of the OpenADR standard | [Texture Extension] |
| Layer 3: Program-Specific | Per-program rules, performance thresholds, enrollment windows — details that vary by utility program | [Program] |
Layer 1 is the industry standard foundation — if you've integrated with any OpenADR 3.0 platform, these concepts will be familiar. Layer 2 is where Texture adds the device-specific schemas, enrollment workflows, and telemetry types that the base standard doesn't cover. Layer 3 is specific to the utility program you're participating in.
If you're building for multiple programs, Layers 1 and 2 are stable across programs; Layer 3 details are provided per program during onboarding.
2. Getting Started
Step 1: Contact Texture
Email integrations@texturehq.com to begin. You'll need:
- Your organization name and contact information
- The device types you plan to enroll (battery, thermostat, EV charger, solar inverter, etc.)
- Estimated fleet size
- Whether you are an OEM (device manufacturer), a TPDO (fleet operator), or both
Texture will confirm your eligibility, provision your account, and create your integration identity on our platform.
Step 2: Accept Integration Terms
During onboarding, a designated user from your organization will accept Texture's Integration Terms. These cover API access, data handling, and program participation obligations. Terms are available at texturehq.com/legal/integration-terms.
Step 3: Get Your Credentials
Texture provisions the following during onboarding:
| Credential | Purpose |
|---|---|
client_id |
Your OAuth 2.0 client identifier |
client_secret |
Your OAuth 2.0 client secret |
| API base URL | https://direct.texturehq.com/v2 |
| Token endpoint URL | https://direct.texturehq.com/v2/auth/token |
| Integration account ID | Your server-assigned account identifier on Texture's platform |
| Program ID | The enrolled program object ID |
Your client_id is scoped to the integration account(s) assigned to your organization. If you manage multiple brands (e.g., two product lines), each may have its own account with separate credentials.
Step 4: Register Sites and Devices
- Create Sites for each physical installation address (used for territory matching and program eligibility)
- Register Devices under your integration account, each linked to a Site
- Create a Webhook Subscription for dispatch event notifications on your program
- Push Telemetry with device data continuously — this starts immediately, not just during events
- When dispatch events arrive, read the event details, execute commands on devices, and push event-response telemetry
3. Authentication
OAuth 2.0 Client Credentials Flow
All API access uses OAuth 2.0 client credentials.
Token endpoint: https://direct.texturehq.com/v2/auth/token
Obtain an access token:
POST /v2/auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=your_client_id&client_secret=your_client_secret&scope=read_events+write_reports+write_vens+write_subscriptions
Response — 200 OK:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read_events write_reports write_vens write_subscriptions"
}
Use the token on all API requests:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Token lifecycle:
- Tokens expire after
expires_inseconds (default: 3600 / 1 hour). - Request a new token before expiry. There is no refresh token flow — just re-authenticate.
- Texture may revoke tokens at any time if abuse is detected.
Note: The client credentials grant type does not use refresh tokens (per RFC 6749 §4.4). When your access token expires, simply request a new one using the same
client_idandclient_secret. This is standard for server-to-server integrations — no token refresh logic is needed.
Scopes
| Scope | Description |
|---|---|
read_events |
Read dispatch events targeted at your account/devices |
read_programs |
Read program definitions |
write_reports |
Create and update telemetry reports |
write_vens |
Create and update your integration account and devices |
write_subscriptions |
Create and manage webhooks |
Scopes are automatically restricted to objects associated with your client_id. You cannot read other accounts' data.
Security
- Treat credentials as secrets. Do not commit
client_id/client_secretto source control. - Store in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.).
- Contact integrations@texturehq.com to rotate credentials if compromised. Old credentials are invalidated immediately.
4. Site Management [Texture Extension]
Before registering devices, create a Site for each physical installation address. Texture uses the site address to determine utility service territory and program eligibility. Every device must be linked to a Site.
POST /v2/x-texture/sites
Create a new site.
POST /v2/x-texture/sites
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Smith Residence",
"address": {
"street": "123 Main St",
"city": "Denver",
"state": "CO",
"zip": "80202"
},
"utilityAccountNumber": "ACCT-123456789"
}
| Field | Required | Description |
|---|---|---|
name |
Yes | Human-readable site label |
address |
Yes | Physical installation address (street, city, state, zip) |
utilityAccountNumber |
No | The customer's utility account number (helps Texture match to metering data; not required for site creation but may be required for specific program enrollment) |
Response — 201 Created:
{
"id": "site_abc456",
"name": "Smith Residence",
"address": { "street": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202" },
"utilityAccountNumber": "ACCT-123456789",
"serviceTerritory": "XCEL_CO",
"programEligible": true
}
The response tells you immediately whether the site is eligible for the target program:
programEligible: true— address is in the correct service territoryprogramEligible: false— address is outside the service territory or does not meet program requirements. Contact integrations@texturehq.com for details.
Texture geocodes the address automatically to determine geographic coordinates, timezone, and utility service territory.
Other endpoints:
GET /v2/x-texture/sites?limit=50 # List your sites
5. Resource Management (Devices)
Resources are individual devices registered under your integration account — a battery, thermostat, EV charger, solar inverter, etc. Each device must be registered before you can push telemetry for it or receive dispatch events targeting it.
API terminology note: In the API, devices are called "Resources" and your integration account is represented as a "VEN" (Virtual End Node) — these are industry-standard terms from the OpenADR specification. When you see
resourceNameor/resources/in the API, it means a device record. When you seevenIDor/vens/, it refers to your integration account. See Appendix C for full terminology mapping.
POST /v2/resources
Register a new device. Each device must be linked to a Site (see Section 4).
POST /v2/resources
Authorization: Bearer <token>
Content-Type: application/json
{
"objectType": "VEN_RESOURCE_REQUEST",
"resourceName": "OEM-BAT-001-A3F2",
"venID": "ven_your_org_001",
"attributes": [
{ "type": "x-static:DEVICE_TYPE", "values": ["BATTERY"] },
{ "type": "x-static:MANUFACTURER_DEVICE_ID", "values": ["OEM-BAT-001-A3F2"] },
{ "type": "x-static:DEVICE_MODEL", "values": ["acme_powervault_10kwh"] },
{ "type": "x-static:SITE_ID", "values": ["site_abc456"] },
{ "type": "x-static:SERIAL_NUMBER", "values": ["SN-12345678"] },
{ "type": "STORAGE_MAX_DISCHARGE_POWER", "values": [10] },
{ "type": "STORAGE_USABLE_CAPACITY", "values": [10000] }
]
}
Response — 201 Created:
{
"id": "res_7f8a9b0c1d2e",
"objectType": "VEN_RESOURCE",
"createdDateTime": "2026-03-24T18:30:00Z",
"modificationDateTime": "2026-03-24T18:30:00Z",
"resourceName": "OEM-BAT-001-A3F2",
"venID": "ven_your_org_001",
"attributes": [
{ "type": "x-static:DEVICE_TYPE", "values": ["BATTERY"] },
{ "type": "x-static:MANUFACTURER_DEVICE_ID", "values": ["OEM-BAT-001-A3F2"] },
{ "type": "x-static:DEVICE_MODEL", "values": ["acme_powervault_10kwh"] },
{ "type": "x-static:SITE_ID", "values": ["site_abc456"] },
{ "type": "x-static:SERIAL_NUMBER", "values": ["SN-12345678"] },
{ "type": "STORAGE_MAX_DISCHARGE_POWER", "values": [10] },
{ "type": "STORAGE_USABLE_CAPACITY", "values": [10000] }
]
}
The server-assigned id field (res_7f8a9b0c1d2e) is how Texture references this device internally. You'll use your resourceName for most operations, but some webhook payloads and Event targets may reference the server id.
[Texture Extension]TheresourceNamefield is your stable, unique device identifier — it must not change over the device's lifetime. Device attributes usex-static:prefixed types to carry device metadata andx-telemetry:prefixed types for dynamic measurements. Where OpenADR 3.0 defines a standard attribute type (e.g.,STORAGE_MAX_DISCHARGE_POWER), use the standard type. Texture extensions (x-static:,x-telemetry:) are used only where no standard type exists.
[Texture Extension]SITE_IDassociates the device with a physical installation address (created via Site Management). Texture uses the site address to determine utility service territory and program eligibility.
Battery / Smart Inverter / BESS clarification: If your device is a battery energy storage system (BESS) — whether marketed as a "battery," "smart inverter," or "energy storage system" — use device type
BATTERY. TheBATTERYtype covers the complete system including the inverter. Specify the AC inverter rating asSTORAGE_MAX_DISCHARGE_POWER(in kW) and total usable storage asSTORAGE_USABLE_CAPACITY(in Wh). Many residential "batteries" are actually a dumb storage module paired with a smart inverter — register the whole system as a singleBATTERYdevice.
Idempotency: Device registration is idempotent by resourceName within your account. Submitting the same resourceName with identical attributes returns 200 OK with the existing record. Conflicting attributes return 409 Conflict.
Required attributes by device type: [Texture Extension]
| Device Type | Required Attributes |
|---|---|
BATTERY |
DEVICE_TYPE, SITE_ID, STORAGE_MAX_DISCHARGE_POWER (AC inverter rating, kW), STORAGE_USABLE_CAPACITY (usable storage, Wh) |
THERMOSTAT |
DEVICE_TYPE, SITE_ID |
EV_CHARGER |
DEVICE_TYPE, SITE_ID, x-static:NAMEPLATE_KW |
SOLAR_INVERTER |
DEVICE_TYPE, SITE_ID, x-static:NAMEPLATE_KW |
SMART_METER |
DEVICE_TYPE, SITE_ID |
Standard vs. extension attributes: For batteries,
STORAGE_MAX_DISCHARGE_POWERandSTORAGE_USABLE_CAPACITYare standard OpenADR 3.0 types and should be used instead of customx-static:equivalents. For device types where no standard attribute exists (EV charger nameplate capacity, solar inverter rating), use the Texture extensionx-static:NAMEPLATE_KW.
Other endpoints:
GET /v2/resources/:resourceID # Get a specific device
GET /v2/resources?venID=...&limit=50 # List devices (paginate with skip/limit)
PUT /v2/resources/:resourceID # Update device attributes
Note: resourceName and venID cannot be changed after creation. Only attributes can be updated. Historical telemetry associated with the device is not affected by attribute updates.
6. Reports (Telemetry Push)
A Report is how your platform sends device data to Texture. Reports carry telemetry — state of charge, power output, temperature, metering data, etc.
Texture requires fresh telemetry from all enrolled devices continuously — not just during dispatch events. Push Reports as state changes occur — at minimum every 5 minutes, ideally within seconds of state changes. During active dispatch events, push every 30–60 seconds. Stale or missing telemetry directly impacts program performance measurement and fleet capacity visibility to utilities.
POST /v2/reports
POST /v2/reports
Authorization: Bearer <token>
Content-Type: application/json
{
"eventID": null,
"clientName": "ven_your_org_001",
"reportName": "telemetry_20260324T201500Z",
"payloadDescriptors": [
{
"objectType": "REPORT_PAYLOAD_DESCRIPTOR",
"payloadType": "STORAGE_CHARGE_LEVEL",
"units": "PERCENT",
"readingType": "DIRECT_READ"
},
{
"objectType": "REPORT_PAYLOAD_DESCRIPTOR",
"payloadType": "x-telemetry:CHARGING_STATE",
"readingType": "DIRECT_READ"
}
],
"resources": [
{
"resourceName": "OEM-BAT-001-A3F2",
"intervalPeriod": {
"start": "2026-03-24T20:15:00Z",
"duration": "PT0S"
},
"intervals": [
{
"id": 0,
"payloads": [
{ "type": "STORAGE_CHARGE_LEVEL", "values": [85] },
{ "type": "x-telemetry:CHARGE_RATE", "values": [2400] },
{ "type": "x-telemetry:CHARGING_STATE", "values": ["CHARGING"] },
{ "type": "x-telemetry:BACKUP_RESERVE", "values": [20] },
{ "type": "x-telemetry:GRID_STATUS", "values": ["IMPORTING"] }
]
}
]
}
]
}
| Field | Description |
|---|---|
eventID |
If this Report is in response to a dispatch event, set to the Event's id. For routine telemetry (not tied to a specific dispatch event), set to null. |
clientName |
Your integration account name or identifier. |
reportName |
Optional human-readable name for debugging. |
payloadDescriptors |
Metadata for the payload types in this Report. Provides units and reading types. |
resources |
Array of device report data. Each entry has resourceName, optional intervalPeriod, and intervals containing payloads. |
ISO 8601 durations: This API uses ISO 8601 duration format for time periods:
PT0S= zero seconds (a point-in-time reading),PT15M= 15 minutes,PT1H= 1 hour,PT4H= 4 hours.
Batch reporting: Include multiple entries in the resources array to push telemetry for multiple devices in a single request (recommended for efficiency). A single POST /v2/reports call containing telemetry for 50 devices counts as 1 request against the per-account rate limit.
For large fleets (1,000+ devices), use batch telemetry reports. A single
POST /v2/reportscontaining data for 500 devices counts as 1 request against the per-account limit. This means a fleet of 50,000 devices can report every 5 minutes with just 100 requests/minute — well within the 10,000 requests/minute account limit.
Telemetry Payload Types by Device Type [Texture Extension]
All telemetry is carried as Report payloads using a key-value pattern: { "type": "...", "values": [...] }. Standard OpenADR 3.0 types are used where available; Texture extensions use the x-telemetry: prefix for dynamic data and x-static: for device metadata.
Battery
These fields align with the Texture BatteryState and BatteryStaticState data models.
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
| State and measurements: |
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
STORAGE_CHARGE_LEVEL |
OpenADR 3.0 | 0–100 (%, maps to chargePercentage) |
Yes |
STORAGE_USABLE_CAPACITY |
OpenADR 3.0 | ≥ 0 (Wh, maps to capacity) |
No |
STORAGE_MAX_DISCHARGE_POWER |
OpenADR 3.0 | ≥ 0 (W, maps to maxDischargeRate) |
No |
STORAGE_MAX_CHARGE_POWER |
OpenADR 3.0 | ≥ 0 (W, maps to maxChargeRate) |
No |
x-telemetry:CHARGING_STATE |
Texture | "CHARGING", "DISCHARGING", "IDLE", "STANDBY", "SLEEP", "FAULT", "OFF", "UNKNOWN" |
Yes |
x-telemetry:STORAGE_ENERGY_LEVEL |
Texture | ≥ 0 (Wh, current stored energy) | No |
x-telemetry:CHARGE_RATE |
Texture | any (W, positive=charging, negative=discharging) | No |
x-telemetry:CHARGE_POWER |
Texture | ≥ 0 (W, charging power — use alongside DISCHARGE_POWER when reporting gross flows) |
No |
x-telemetry:DISCHARGE_POWER |
Texture | ≥ 0 (W, discharging power — use alongside CHARGE_POWER when reporting gross flows) |
No |
x-telemetry:CHARGE_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative energy charged since installation) | No |
x-telemetry:DISCHARGE_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative energy discharged since installation) | No |
x-telemetry:BACKUP_RESERVE |
Texture | 0–100 (%, backup reserve setting) | No |
x-telemetry:STRATEGY |
Texture | "UNKNOWN", "SELF_CONSUMPTION", "TIME_OF_USE", "BACKUP" |
No |
x-telemetry:IS_STORM_MODE_ACTIVE |
Texture | true, false |
No |
x-telemetry:IS_STORM_MODE_ENABLED |
Texture | true, false |
No |
x-telemetry:GRID_STATUS |
Texture | "EXPORTING", "IMPORTING", "IDLE", "GRID_OUTAGE", "UNKNOWN" |
No |
x-telemetry:GRID_POWER |
Texture | integer (W, positive=importing, negative=exporting) | No |
x-telemetry:GRID_IMPORT_POWER |
Texture | ≥ 0 (W, grid import only — alternative to GRID_POWER) |
No |
x-telemetry:GRID_EXPORT_POWER |
Texture | ≥ 0 (W, grid export only — alternative to GRID_POWER) |
No |
x-telemetry:GRID_ENERGY |
Texture | integer (Wh, net grid energy for this interval, positive=imported) | No |
x-telemetry:GRID_IMPORT_ENERGY |
Texture | ≥ 0 (Wh, grid import energy for this interval) | No |
x-telemetry:GRID_EXPORT_ENERGY |
Texture | ≥ 0 (Wh, grid export energy for this interval) | No |
x-telemetry:GRID_IMPORT_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative grid import since installation) | No |
x-telemetry:GRID_EXPORT_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative grid export since installation) | No |
x-telemetry:LOAD_POWER |
Texture | any (W, household load) | No |
x-telemetry:LOAD_ENERGY |
Texture | any (Wh, load energy for this reporting interval) | No |
x-telemetry:LOAD_ENERGY_LIFETIME |
Texture | any (Wh, cumulative load energy since installation) | No |
x-telemetry:GENERATION_POWER |
Texture | any (W, total on-site generation) | No |
x-telemetry:GENERATION_ENERGY |
Texture | ≥ 0 (Wh, generation energy for this interval) | No |
x-telemetry:GENERATION_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative generation since installation) | No |
x-telemetry:NUMBER_OF_BATTERIES |
Texture | integer (count of battery modules) | No |
Why are most battery telemetry types Texture extensions? The OpenADR 3.0 standard defines a handful of storage-specific types:
STORAGE_CHARGE_LEVEL,STORAGE_USABLE_CAPACITY,STORAGE_MAX_DISCHARGE_POWER, andSTORAGE_MAX_CHARGE_POWER. These cover capacity and state of charge, but most practical battery telemetry — charge/discharge rate, charging state, grid connectivity, backup reserve, operating strategy, storm mode, and load/grid power — is not covered by the standard and requires Texture extensions. Extension field names match the Texture core API data model (BatteryState) for consistency.
Static vs. dynamic fields:
STORAGE_MAX_DISCHARGE_POWERandSTORAGE_MAX_CHARGE_POWERare static attributes — set at device registration to describe the device's rated capability (e.g., "this inverter can discharge up to 10 kW").x-telemetry:CHARGE_RATEis a dynamic telemetry field — reported in real-time to describe what the device is actually doing right now (e.g., "currently discharging at 4.8 kW"). Both are needed: static for capacity planning, dynamic for real-time fleet visibility. UseSTORAGE_CHARGE_LEVEL(standard) for state of charge rather than duplicating with a custom field.
Grid services / VPP capacity:
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
BASELINE |
OpenADR 3.0 | W, power the battery would draw/export without any dispatch command | No |
DOWN_REGULATION_AVAILABLE |
OpenADR 3.0 | W, available discharge power (BMS-aware) | No |
UP_REGULATION_AVAILABLE |
OpenADR 3.0 | W, available charge headroom (BMS-aware) | No |
x-capacity:DISCHARGE_ENERGY_AVAILABLE |
Texture | Wh, available discharge energy (BMS-aware) | No |
x-capacity:CHARGE_ENERGY_AVAILABLE |
Texture | Wh, available charge energy (BMS-aware) | No |
Thermostat
These fields align with the Texture ThermostatState and ThermostatStaticState data models.
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
READING (units: FAHRENHEIT) |
OpenADR 3.0 | -40–150 (°F, maps to ambientTemperature) |
Yes |
x-telemetry:OPERATING_MODE |
Texture | "HEAT", "COOL", "AUTO", "ECO", "OFF", "UNKNOWN" |
Yes |
x-telemetry:COOL_TARGET |
Texture | (°F, cooling setpoint) | No |
x-telemetry:HEAT_TARGET |
Texture | (°F, heating setpoint) | No |
x-telemetry:FAN_MODE |
Texture | "AUTO", "ON", "OFF", "CIRCULATE", "UNKNOWN" |
No |
x-telemetry:ALLOWED_MODES |
Texture | array of supported operating modes | No |
x-telemetry:ALLOWED_FAN_MODES |
Texture | array of supported fan modes | No |
x-telemetry:MIN_HEAT_SETPOINT |
Texture | integer (°F) | No |
x-telemetry:MAX_HEAT_SETPOINT |
Texture | integer (°F) | No |
x-telemetry:MIN_COOL_SETPOINT |
Texture | integer (°F) | No |
x-telemetry:MAX_COOL_SETPOINT |
Texture | integer (°F) | No |
EV Charger
These fields align with the Texture ChargerState data model.
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
x-telemetry:CHARGING_STATE |
Texture | "CHARGING", "CONNECTED", "SUSPENDED_EVSE", "SUSPENDED_EV", "DISCHARGING", "IDLE", "FAULT", "UNKNOWN" |
Yes |
x-telemetry:IS_PLUGGED_IN |
Texture | true, false |
Yes |
DEMAND |
OpenADR 3.0 | kW, average power over reporting interval | Yes |
USAGE |
OpenADR 3.0 | kWh, energy delivered in this reporting interval | No |
x-telemetry:CHARGER_POWER |
Texture | ≥ 0 (W, instantaneous power — alternative to DEMAND) |
No |
x-telemetry:CHARGER_ENERGY |
Texture | ≥ 0 (Wh, energy for this interval — alternative to USAGE) |
No |
x-telemetry:CHARGER_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative energy delivered since installation) | No |
x-telemetry:CHARGER_VOLTAGE |
Texture | ≥ 0 (V) | No |
x-telemetry:CHARGER_CURRENT |
Texture | ≥ 0 (A) | No |
x-telemetry:FREQUENCY |
Texture | Hz, grid frequency | No |
CHARGING_STATE values:
CHARGING— power actively flowing.CONNECTED— vehicle plugged in but not charging (reason unknown; use this when your API only provides plug state + charging boolean).SUSPENDED_EVSE— the charger paused charging (DR dispatch, schedule, or peak demand management).SUSPENDED_EV— the vehicle is refusing power (battery limit, temperature, charge limit reached).DISCHARGING— exporting power to grid or home (V2G/V2H).IDLE— nothing plugged in.FAULT— hardware or communication error.UNKNOWN— state cannot be determined. For APIs with onlyvehicle_connectedandvehicle_chargingbooleans: useCONNECTEDwhen connected but not charging.
Amps-based chargers: Many Level 2 chargers (including Emporia) control charge rate in amps, not watts. When Texture sends
IMPORT_CAPACITY_LIMIT(kW), convert:amps = (kW × 1000) / line_voltage. Reportx-telemetry:CHARGER_VOLTAGEto enable accurate conversion.
Solar Inverter
These fields align with the Texture InverterState data model.
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
x-telemetry:GENERATION_POWER |
Texture | ≥ 0 (W, current output) | Yes |
x-telemetry:GENERATION_ENERGY |
Texture | ≥ 0 (Wh, energy generated in this reporting interval) | No |
x-telemetry:GENERATION_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative lifetime generation) | No |
x-telemetry:GRID_STATUS |
Texture | "EXPORTING", "IMPORTING", "IDLE", "GRID_OUTAGE", "UNKNOWN" |
No |
x-telemetry:GRID_POWER |
Texture | integer (W, positive=importing, negative=exporting) | No |
x-telemetry:GRID_ENERGY |
Texture | integer (Wh, net grid energy for this interval, positive=imported) | No |
x-telemetry:LOAD_POWER |
Texture | any (W, household load) | No |
x-telemetry:LOAD_ENERGY |
Texture | any (Wh, load energy for this reporting interval) | No |
x-telemetry:FREQUENCY |
Texture | Hz, grid frequency | No |
Vehicle (EV)
These fields align with the Texture VehicleState data model.
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
x-telemetry:CHARGING_STATE |
Texture | "CHARGING", "CONNECTED", "SUSPENDED_EVSE", "SUSPENDED_EV", "DISCHARGING", "IDLE", "FAULT", "UNKNOWN" |
Yes |
STORAGE_CHARGE_LEVEL |
OpenADR 3.0 | 0–100 (%, battery/vehicle charge level) | Yes |
x-telemetry:STORAGE_ENERGY_LEVEL |
Texture | ≥ 0 (Wh, current stored energy) | No |
x-telemetry:CHARGE_LIMIT |
Texture | 0–100 (%, charge limit setting) | No |
x-telemetry:CHARGE_COMPLETED_AT |
Texture | ISO 8601 datetime (estimated completion) | No |
x-telemetry:IS_PLUGGED_IN |
Texture | true, false |
No |
x-telemetry:CHARGER_POWER |
Texture | ≥ 0 (W, instantaneous charging power) | No |
x-telemetry:CHARGER_ENERGY |
Texture | ≥ 0 (Wh, energy delivered in this interval) | No |
x-telemetry:CHARGER_ENERGY_LIFETIME |
Texture | ≥ 0 (Wh, cumulative energy delivered since installation) | No |
x-telemetry:CHARGER_VOLTAGE |
Texture | ≥ 0 (V) | No |
x-telemetry:CHARGER_CURRENT |
Texture | ≥ 0 (A) | No |
x-telemetry:RANGE |
Texture | ≥ 0 (miles, estimated range) | No |
Smart Meter
| Payload Type | Standard? | Values | Required |
|---|---|---|---|
DEMAND |
OpenADR 3.0 | kW, net grid power (positive=importing, negative=exporting) | Yes |
USAGE |
OpenADR 3.0 | kWh, net energy in this reporting interval | No |
x-telemetry:REACTIVE_POWER_KVAR |
Texture | any (kVAR) | No |
x-telemetry:VOLTAGE |
Texture | ≥ 0 (V) | No |
x-telemetry:FREQUENCY |
Texture | Hz, grid frequency | No |
7. Events (Dispatch Commands)
A dispatch event is how Texture communicates grid signals to your platform. When a utility dispatches a grid event, Texture creates an Event object. Your platform is notified via webhook, reads the Event, and executes device commands.
Events contain intervals with payloads that specify the dispatch instruction (e.g., "discharge at 5 kW"), and reportDescriptors that tell you what telemetry to send back.
How Event Delivery Works
- The utility or grid operator issues a dispatch signal
- Texture receives and translates it into an Event
- Texture creates the Event with
targetspointing to your integration account and/or specific devices - Your webhook fires (to register a webhook, see Webhooks / Subscriptions) — Texture POSTs a notification to your
callbackUrl - Your platform reads the Event, interprets the payloads, and controls devices
- Your platform pushes Reports with the Event's
idaseventID— this serves as both acknowledgment and telemetry delivery
Event Structure
{
"id": "evt_program_20260724_001",
"objectType": "EVENT",
"createdDateTime": "2026-07-24T14:00:00Z",
"programID": "prg_program_id",
"eventName": "Peak Reduction — July 24 2pm",
"priority": 1,
"targets": ["ven_your_org_001"],
"intervalPeriod": {
"start": "2026-07-24T14:00:00Z",
"duration": "PT1H"
},
"payloadDescriptors": [
{
"objectType": "EVENT_PAYLOAD_DESCRIPTOR",
"payloadType": "DISPATCH_SETPOINT",
"units": "KW"
}
],
"intervals": [
{
"id": 0,
"payloads": [
{ "type": "DISPATCH_SETPOINT", "values": [-5.0] },
{ "type": "x-telemetry:DURATION_MINUTES", "values": [60] },
{ "type": "x-telemetry:TARGET_SOC_PCT", "values": [20] }
]
}
],
"reportDescriptors": [
{
"payloadType": "STORAGE_CHARGE_LEVEL",
"readingType": "DIRECT_READ",
"units": "PERCENT",
"repeat": -1
},
{
"payloadType": "x-telemetry:CHARGE_RATE",
"readingType": "DIRECT_READ",
"units": "W",
"repeat": -1
},
{
"payloadType": "BASELINE",
"readingType": "DIRECT_READ",
"units": "W",
"repeat": -1
},
{
"payloadType": "DOWN_REGULATION_AVAILABLE",
"readingType": "DIRECT_READ",
"units": "W",
"repeat": -1
}
]
}
| Field | Description |
|---|---|
programID |
Links to the program this event belongs to. |
eventName |
Human-readable description. |
priority |
Lower = higher priority. 0 is highest. |
targets |
Your integration account ID and/or specific device IDs. Typically targets your whole account; in some cases, Texture may target specific devices for partial dispatch. |
intervalPeriod |
When the event starts and the duration of each interval. |
intervals |
The dispatch instructions. Each interval has payloads with the action. |
reportDescriptors |
What telemetry Texture wants back during this event. repeat: -1 = report continuously until the event ends. In practice, push reports every 30–60 seconds during events. |
Understanding the payloads:
⚠️ Sign convention for power setpoints: Negative values mean discharge/export (power flowing out of the device to the grid). Positive values mean charge/import (power flowing into the device from the grid). For example,
DISPATCH_SETPOINT: [-5.0]means "discharge at 5 kW."
Dispatch Command Types
Standard dispatch types used by Texture:
| Payload Type | Description | Example |
|---|---|---|
DISPATCH_SETPOINT |
Absolute power setpoint (kW). Negative = discharge/export. | [-5.0] |
CHARGE_STATE_SETPOINT |
Target state of charge (% or kWh). | [20] |
CONTROL_SETPOINT |
Relative adjustment (kW or °F delta). | [-3.0] |
EXPORT_CAPACITY_LIMIT |
Max export power (kW). | [10.0] |
IMPORT_CAPACITY_LIMIT |
Max import power (kW). 0 = stop charging. |
[0.0] |
SIMPLE |
Load level (0=normal, 1=moderate, 2=high, 3=emergency). | [2] |
Texture extension dispatch types: [Texture Extension]
| Payload Type | Description | Example |
|---|---|---|
x-telemetry:DURATION_MINUTES |
Event duration in minutes. | [60] |
x-telemetry:TARGET_SOC_PCT |
Target state of charge (%). Prefer CHARGE_STATE_SETPOINT. |
[20] |
x-config:BACKUP_RESERVE_PCT |
Override backup reserve floor for event duration (%). | [20] |
x-telemetry:TEMPERATURE_SETPOINT_F |
Target temperature setpoint (°F). | [72] |
x-telemetry:FAN_MODE |
Fan operating mode. | ["AUTO"] |
No custom command type needed. Dispatch intent is conveyed through standard OpenADR payload types (
DISPATCH_SETPOINT,CONTROL_SETPOINT,SIMPLE, etc.) combined with extension parameters above. For example, a battery discharge command usesDISPATCH_SETPOINTwith a negative value plusx-telemetry:DURATION_MINUTESand optionallyCHARGE_STATE_SETPOINT. A thermostat adjustment usesCONTROL_SETPOINTplusx-telemetry:TEMPERATURE_SETPOINT_F.
Responding to Events
Your platform responds to events by creating Reports with the Event's id as the eventID field. This simultaneously acknowledges receipt and delivers performance telemetry.
{
"eventID": "evt_program_20260724_001",
"clientName": "ven_your_org_001",
"resources": [
{
"resourceName": "OEM-BAT-001-A3F2",
"intervalPeriod": { "start": "2026-07-24T14:00:30Z", "duration": "PT0S" },
"intervals": [{
"id": 0,
"payloads": [
{ "type": "STORAGE_CHARGE_LEVEL", "values": [78] },
{ "type": "x-telemetry:CHARGE_RATE", "values": [-4800] },
{ "type": "x-telemetry:CHARGING_STATE", "values": ["DISCHARGING"] },
{ "type": "x-telemetry:COMMAND_STATUS", "values": ["EXECUTING"] }
]
}]
}
]
}
[Texture Extension]x-telemetry:COMMAND_STATUSreports command execution state. Includex-telemetry:COMMAND_FAILURE_REASONwhen status is"FAILED".
Command status lifecycle:
| Status | When to Send | Description |
|---|---|---|
"ACCEPTED" |
First report after receiving the event (within seconds) | You received the dispatch command and are initiating device control |
"EXECUTING" |
Once the device has begun responding | The device is actively discharging/charging/adjusting per the command |
"COMPLETED" |
When the event's interval period ends, or the device finishes | The dispatch action is done |
"FAILED" |
If the device cannot execute the command | Include COMMAND_FAILURE_REASON with a human-readable explanation |
Push event-response Reports every 30–60 seconds during the event. Continue until the event's interval period ends or you've reported "COMPLETED". Event-response Reports support batch reporting — include telemetry for multiple devices in a single Report (recommended for large fleets).
Reading Events
GET /v2/events?programID=prg_program_id&active=true
Authorization: Bearer <token>
GET /v2/events/evt_program_20260724_001
Authorization: Bearer <token>
8. Subscriptions (Webhooks)
Subscriptions are how your platform gets notified in real time when new dispatch events are created. Instead of polling, you register a webhook URL and Texture pushes notifications to you.
POST /v2/subscriptions
POST /v2/subscriptions
Authorization: Bearer <token>
Content-Type: application/json
{
"clientName": "ven_your_org_001",
"programID": "prg_program_id",
"objectOperations": [
{
"objects": ["EVENT"],
"operations": ["CREATE", "UPDATE"],
"callbackUrl": "https://your-api.example.com/texture/webhooks",
"bearerToken": "your_webhook_secret_token"
}
]
}
The bearerToken is a secret you provide. Texture includes it as Authorization: Bearer <token> on all callback requests so you can verify the caller is Texture.
Notification Payload
POST https://your-api.example.com/texture/webhooks
Authorization: Bearer your_webhook_secret_token
Content-Type: application/json
{
"objectType": "EVENT",
"operation": "CREATE",
"object": {
"id": "evt_program_20260724_001",
"objectType": "EVENT",
"programID": "prg_program_id",
"intervalPeriod": { "start": "2026-07-24T14:00:00Z", "duration": "PT1H" },
"intervals": [
{
"id": 0,
"payloads": [
{ "type": "DISPATCH_SETPOINT", "values": [-5.0] },
{ "type": "DISPATCH_SETPOINT", "values": [-5.0] },
{ "type": "x-telemetry:DURATION_MINUTES", "values": [60] }
]
}
]
}
}
Your endpoint must return HTTP 200 to confirm delivery. Process the event asynchronously — return 200 immediately, then dispatch to devices.
Retry policy: Texture retries failed deliveries up to 3 times with exponential backoff (1s, 2s, 4s). After 3 consecutive failures across multiple events, the subscription is marked as unhealthy. Texture sends a notification email to your integration contact and continues to attempt delivery with reduced frequency. Subscriptions are never automatically disabled — we won't silently stop sending you dispatch events.
Multiple subscriptions: You can create multiple subscriptions for the same program with different callbackUrls for redundancy. Texture delivers to all active subscriptions independently.
Subscription management endpoints:
| Method | Path | Description |
|---|---|---|
GET |
/v2/subscriptions |
List your subscriptions |
GET |
/v2/subscriptions/:id |
Get a specific subscription |
PUT |
/v2/subscriptions/:id |
Update (e.g., change callbackUrl) |
DELETE |
/v2/subscriptions/:id |
Remove a subscription |
9. MQTT Transport (Optional) [Texture Extension]
For high-frequency telemetry or large fleets, Texture supports MQTT as an alternative transport alongside the REST API. MQTT is purpose-built for IoT workloads — persistent connections, low overhead, and native pub/sub eliminate the per-request cost of HTTP for continuous telemetry streams. This aligns with OpenADR 3.1, which added MQTT as a supported notification transport.
MQTT is entirely optional. All functionality available via REST is also available via MQTT. You can use REST exclusively, MQTT exclusively, or mix them (e.g., MQTT for telemetry push, REST webhooks for dispatch).
9.1 Connection Details
| Parameter | Value |
|---|---|
| Broker | mqtts://direct.texturehq.com:8883 |
| Protocol | MQTT v5.0 over TLS 1.2+ |
| Authentication | Username = OAuth 2.0 access token (same token as REST API); Password = empty string |
| Keep-alive | 60 seconds recommended |
| Clean session | false (to receive messages queued while disconnected) |
Use the same OAuth 2.0 client credentials flow from Section 3 to obtain a token. Set the token as the MQTT username. When the token expires, disconnect and reconnect with a fresh token.
9.2 Telemetry via MQTT (Your Platform → Texture)
Publish device telemetry to device-specific topics:
texture/v2/{venID}/reports/{resourceName}
The message body is identical to the JSON payload you would POST to /v2/reports — same structure, same field names, same payload types. The only difference is the transport.
Example — publish battery telemetry:
Topic: texture/v2/ven_your_org_001/reports/OEM-BAT-001-A3F2
{
"eventID": null,
"clientName": "ven_your_org_001",
"reportName": "telemetry_20260324T201500Z",
"resources": [
{
"resourceName": "OEM-BAT-001-A3F2",
"intervalPeriod": { "start": "2026-03-24T20:15:00Z", "duration": "PT0S" },
"intervals": [{
"id": 0,
"payloads": [
{ "type": "STORAGE_CHARGE_LEVEL", "values": [85] },
{ "type": "x-telemetry:CHARGE_RATE", "values": [2400] },
{ "type": "x-telemetry:CHARGING_STATE", "values": ["CHARGING"] }
]
}]
}
]
}
| Setting | Value |
|---|---|
| QoS | 1 (at least once delivery) |
| Retain | false |
MQTT telemetry is ideal for fleets pushing data every few seconds — the persistent connection avoids the overhead of establishing a new HTTPS connection per report.
Batch telemetry: To report multiple devices in a single publish, use the account-level topic:
texture/v2/{venID}/reports
Include multiple entries in the resources array, same as batch POST /v2/reports.
9.3 Dispatch via MQTT (Texture → Your Platform)
Subscribe to receive dispatch events:
texture/v2/{venID}/events/#
When Texture creates or updates an Event targeting your account, it publishes the Event object to this topic. The JSON format is identical to the webhook notification payload from Section 8.
| Setting | Value |
|---|---|
| QoS | 1 (at least once delivery) |
| Message format | Same as webhook notification JSON |
Advantages over webhooks for dispatch:
- No inbound firewall rules needed — MQTT connections are outbound-only
- No callback URL to expose publicly
- Automatic reconnection and queued message delivery
- Lower latency for time-sensitive dispatch signals
You still respond to events by pushing Reports (via MQTT or REST) with the Event's id as eventID.
9.4 When to Use MQTT vs. REST
| Scenario | Recommendation |
|---|---|
| Small fleet (<100 devices), simple integration | REST + webhooks |
| Large fleet (1,000+ devices), frequent telemetry | MQTT for telemetry, REST or MQTT for dispatch |
| Corporate environment with strict firewall rules against inbound connections | MQTT (outbound only) |
| Prototype / proof of concept | REST + webhooks |
You can mix transports freely. For example:
- Use MQTT for high-frequency telemetry push and REST webhooks for dispatch notifications
- Use MQTT for everything (telemetry + dispatch)
- Start with REST + webhooks and migrate to MQTT as fleet scales
MQTT and REST share the same authentication, payload formats, and data model. Switching transports requires no changes to your payload logic.
10. OEM-Initiated Enrollment [Texture Extension]
This section defines OEM-initiated enrollment — a pattern where OEMs and TPDOs proactively notify Texture when devices are deployed and customers have opted in to grid programs. This enables automatic device discovery without requiring end-user-initiated authorization flows.
10.1 Overview
OEM-initiated enrollment allows device manufacturers and TPDOs to:
- Notify Texture when a new device is commissioned
- Provide customer consent signals directly
- Enable automatic utility/program matching
- Streamline fleet enrollment for grid services programs
This pattern is additive to other enrollment flows. OEMs may support multiple enrollment methods simultaneously.
10.2 Enrollment Data Requirements
When registering a device for program enrollment, you must provide the following categories of data via the Site and Resource objects:
Device Information (Required):
- Stable unique device identifier (
resourceName) — must not change over time - Serial number
- Device model and manufacturer
- Device type (battery, inverter, EV charger, thermostat, etc.)
- Device capabilities (supported commands, nameplate capacity)
Site/Location Information (Required):
- Full street address (street, city, state, postal code)
Consent Information (Required for Enrollment):
- Opt-in status
- Consent timestamp
- Consent source (device app, web portal, installer app, purchase flow)
- Consent version (version of terms/conditions accepted)
- Scope of consent (which program types the customer authorized)
10.3 Service-Territory Matching
Upon receiving a site creation request, Texture performs automatic service-territory matching.
Matching Process:
- Geocoding: Texture geocodes the provided address to determine geographic coordinates.
- Geographic lookup: Coordinates are matched against utility service territory boundaries.
- Utility identification: Texture identifies the IOU (Investor-Owned Utility), municipal utility, co-op, or CCA (Community Choice Aggregator) serving the location.
- Program matching: Texture checks active programs in the identified territory against the device type and capabilities.
- Site record: Texture creates an internal site record linking device → site → utility → program(s).
Matching Outcomes:
| Outcome | Description | Next Steps |
|---|---|---|
| Matched | Utility and program(s) identified | Device proceeds to eligible state |
| Partial match | Utility identified, no active programs | Device enters discovered state |
| Ambiguous | Multiple possible utilities (territory boundary) | Manual review required |
| No match | Location outside supported territories | Enrollment rejected with reason |
| Invalid location | Address cannot be geocoded | Enrollment rejected, correction required |
When territory matching is ambiguous, Texture accepts the request as pending and notifies the OEM of resolution within 48 business hours.
10.4 Device Lifecycle States
Devices progress through a defined lifecycle of enrollment states.
| State | Description |
|---|---|
discovered |
Device registered, not yet matched to a program |
eligible |
Device matched to territory, awaiting opt-in or program assignment |
opted_in |
Customer consent received and validated |
pending_verification |
Additional verification required (utility account, address, etc.) |
enrolled |
Device fully enrolled in one or more programs |
active |
Device actively participating in grid services |
suspended |
Temporarily suspended (connectivity, compliance, etc.) |
opted_out |
Customer revoked consent |
revoked |
Enrollment terminated (decommission, ownership change, etc.) |
Key state transitions:
| Transition | Trigger | Your Action Required |
|---|---|---|
| discovered → eligible | Territory match successful | None |
| eligible → opted_in | Consent validated | Provide consent |
| opted_in → enrolled | Program assignment complete | None |
| enrolled → active | Device begins participation | Ensure connectivity |
| active → suspended | Connectivity loss, compliance issue | Resolve issue |
| active → opted_out | Customer revokes consent | Send revocation event |
| any → revoked | Decommission, ownership change | Send revocation event |
10.5 Consent Requirements
Customer consent provided by OEMs/TPDOs must meet the following requirements.
Required consent data:
- Opt-in status (must be
truefor enrollment) - Consent timestamp (must be within last 90 days of enrollment request)
- Consent source (where consent was captured)
- Consent version (version of terms/conditions accepted)
- Consent scope (which program types were authorized)
Validity rules:
- Freshness: Consent timestamp must be within 90 days. Stale consent (>90 days) requires re-consent.
- Specificity: Consent scope must include at least one valid program type.
- Non-repudiation: You must retain original consent records for minimum 7 years.
- Revocability: Customer must be able to revoke consent at any time.
By submitting an enrollment request, you certify that:
- Consent was obtained directly from the device owner or authorized representative.
- Consent was informed — the customer understood what they were agreeing to.
- Consent was voluntary — not coerced or bundled deceptively.
- You have records to substantiate consent if audited.
- You will honor revocation requests within 24 hours.
10.6 Revocation and Change Events
You must notify Texture of material changes to enrollment status.
| Event | When to Send | Required Information |
|---|---|---|
| Consent revoked | Customer opts out | Device ID, revocation timestamp, source |
| Device decommissioned | Device removed from service | Device ID, decommission timestamp, reason |
| Device relocated | Device moved to new address | Device ID, old site info, new site info, relocation date |
| Ownership transferred | Device ownership changed | Device ID, previous owner, new owner, transfer date |
| Connectivity lost | Device offline >72 hours | Device ID, last seen timestamp |
Events must be delivered via authenticated API calls, include unique identifiers for idempotency, and use timestamps for sequencing.
Texture processing times:
| Event | Processing Time |
|---|---|
| Consent revoked | Immediate (within 1 minute) |
| Device decommissioned | Within 15 minutes |
| Device relocated | Within 1 hour (triggers re-matching) |
| Ownership transferred | Within 24 hours (requires verification) |
10.7 Coexistence with Other Enrollment Methods
OEM-initiated enrollment is additive to other enrollment flows (including OAuth-based flows). Devices may be enrolled via either method. If a device is enrolled via OEM-initiated flow and the user later completes an alternative authorization, the enrollments merge — both consent signals strengthen the audit trail. Revocation via either method triggers full revocation.
11. Performance, Rate Limits, and Reliability
Rate Limits
| Category | Limit |
|---|---|
| Telemetry reports (per integration account) | 10,000 requests/minute |
| Telemetry reports (per device) | 60 requests/minute |
| Events (read) | 1,000 requests/minute |
| Device management (CRUD) | 500 requests/minute |
| Subscriptions (CRUD) | 200 requests/minute |
| Token requests | 30 requests/minute |
Rate limit errors return HTTP 429 with a Retry-After header. If your fleet requires higher limits, contact integrations@texturehq.com.
Batch telemetry for large fleets: A single
POST /v2/reportscontaining data for 500 devices counts as 1 request against the per-account limit. For a fleet of 50,000 devices reporting every 5 minutes, that's just 100 requests/minute — well within the 10,000/minute account limit. Always use batch reports for fleets over 1,000 devices.
Performance Targets
| Operation | Requirement |
|---|---|
| Report submission (your side) | p95 < 2 seconds |
| Webhook callback response (your side) | < 5 seconds (Texture timeout) |
| Event notification delivery (Texture side) | < 5 seconds from Event creation |
| Token endpoint (Texture side) | p99 < 500ms |
| Texture API availability | 99.9% |
Historical Data
Texture supports retrieval of historical telemetry for reconciliation, settlement, and reporting.
- Up to 90 days of history required where available (newly installed devices may have less)
- Granularity: 5-minute, 15-minute, or hourly depending on device type and program
- Historical queries must support pagination
12. Security & Privacy
Transport & Access Control
- All APIs served over HTTPS. TLS 1.2+ required.
- Client secrets and tokens must be rotatable without downtime.
- No anonymous endpoints for device data or control.
Data Handling
- You must comply with applicable privacy regulations (GDPR, CCPA, etc.).
- Documented process for de-provisioning access when a device or account is removed.
- Documented process for handling data deletion requests.
- Control commands must operate within manufacturer-specified safe operating limits. APIs must reject commands that could damage equipment. You are responsible for implementing appropriate safeguards at the device or cloud level regardless of commands received via API.
13. Error Handling
All errors use the RFC 7807 Problem Details format:
{
"type": "https://direct.texturehq.com/errors/validation-error",
"title": "Bad Request",
"status": 400,
"detail": "Report validation failed: STORAGE_CHARGE_LEVEL must be between 0 and 100",
"instance": "/v2/reports"
}
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | OK |
| 201 | Created |
| 400 | Bad Request — validation error |
| 401 | Unauthorized — missing or invalid token |
| 403 | Forbidden — valid token, insufficient scope |
| 404 | Not Found |
| 409 | Conflict — idempotency mismatch or state conflict |
| 429 | Too Many Requests — see Retry-After header |
| 5xx | Server error — retry with exponential backoff |
Error categories and response:
| Category | Your Response |
|---|---|
| Validation errors | Fix payload and retry |
| Authentication errors | Re-authenticate |
| Territory errors | Verify location data or await manual review |
| Consent errors | Obtain fresh consent and resubmit |
| Conflict errors | Check existing enrollment status |
| Rate limit errors | Retry with backoff |
| Server errors (5xx) | Retry with exponential backoff |
14. Certification Process
OEMs and TPDOs must complete a certification review before going live. Contact integrations@texturehq.com to begin.
Implementation checklist:
- Contact integrations@texturehq.com to begin the integration process
- Register for credentials (
client_id,client_secret) - Implement OAuth 2.0 client credentials token acquisition
- Create Sites for installation addresses
- Register Devices under your integration account
- Implement telemetry push via
POST /v2/reports - Create a Subscription and implement webhook callback endpoint
- Implement Event reading and device dispatch
- Push event-response Reports with
eventIDset - Implement consent capture and revocation event notifications
- Test in Texture sandbox environment
- Complete certification review with Texture team
Certification covers:
- Token acquisition functioning correctly
- Webhook notification handling (webhook receives and processes Events)
- Report validation passing for your device types
- Event response flow completing (Event notification → Reports with
eventID) - Latency meeting requirements under simulated load
- Program-specific test event (per program requirements)
[Program]
Appendix A — Endpoint Quick Reference
| Method | Path | Description | Scope |
|---|---|---|---|
POST |
/v2/auth/token |
Get access token | (client_id/secret) |
GET |
/v2/programs |
List programs | read_programs |
GET |
/v2/programs/:id |
Get a program | read_programs |
GET |
/v2/vens/:id |
Get your integration account | write_vens |
PUT |
/v2/vens/:id |
Update your integration account | write_vens |
POST |
/v2/x-texture/sites |
Create a site | write_vens |
GET |
/v2/x-texture/sites |
List sites | write_vens |
POST |
/v2/resources |
Register a device | write_vens |
GET |
/v2/resources/:id |
Get a device | write_vens |
GET |
/v2/resources |
List devices | write_vens |
PUT |
/v2/resources/:id |
Update a device | write_vens |
POST |
/v2/reports |
Push telemetry report | write_reports |
GET |
/v2/events |
List dispatch events | read_events |
GET |
/v2/events/:id |
Get a dispatch event | read_events |
POST |
/v2/subscriptions |
Create webhook subscription | write_subscriptions |
GET |
/v2/subscriptions |
List subscriptions | write_subscriptions |
PUT |
/v2/subscriptions/:id |
Update subscription | write_subscriptions |
DELETE |
/v2/subscriptions/:id |
Delete subscription | write_subscriptions |
POST |
/v2/x-texture/enrollments |
Enroll in a program | write_vens |
GET |
/v2/x-texture/enrollments |
List enrollments | write_vens |
Base URL: https://direct.texturehq.com/v2
Appendix B — Texture Extension Payload Type Registry [Texture Extension]
All extension types used in this specification (x-telemetry: for dynamic payloads, x-static: for device metadata):
Report (telemetry) types — Battery (aligns with BatteryState):
| Type | Units | Description |
|---|---|---|
x-telemetry:CHARGING_STATE |
— | charging, discharging, idle, standby, sleep, fault, off, unknown |
x-telemetry:STORAGE_ENERGY_LEVEL |
Wh | Current stored energy |
x-telemetry:CHARGE_RATE |
W | Current charge/discharge rate |
x-telemetry:BACKUP_RESERVE |
PERCENT | Backup reserve setting (0–100) |
x-telemetry:STRATEGY |
— | unknown, self_consumption, time_of_use, backup |
x-telemetry:IS_STORM_MODE_ACTIVE |
— | boolean — storm mode currently active |
x-telemetry:IS_STORM_MODE_ENABLED |
— | boolean — storm mode enabled |
x-telemetry:GRID_STATUS |
— | exporting, importing, idle, unknown |
x-telemetry:GRID_POWER |
W | Grid power (positive=importing, negative=exporting) |
x-telemetry:GRID_ENERGY |
Wh | Cumulative grid energy |
x-telemetry:LOAD_POWER |
W | Household load power |
x-telemetry:LOAD_ENERGY |
Wh | Cumulative load energy |
x-telemetry:NUMBER_OF_BATTERIES |
— | Count of battery modules |
Report (telemetry) types — Thermostat (aligns with ThermostatState):
| Type | Units | Description |
|---|---|---|
x-telemetry:OPERATING_MODE |
— | heat, cool, auto, eco, off, unknown |
x-telemetry:COOL_TARGET |
FAHRENHEIT | Cooling setpoint |
x-telemetry:HEAT_TARGET |
FAHRENHEIT | Heating setpoint |
x-telemetry:FAN_MODE |
— | auto, on, off, circulate, unknown |
x-telemetry:ALLOWED_MODES |
— | Array of supported operating modes |
x-telemetry:ALLOWED_FAN_MODES |
— | Array of supported fan modes |
x-telemetry:MIN_HEAT_SETPOINT |
FAHRENHEIT | Minimum heat setpoint (°F) |
x-telemetry:MAX_HEAT_SETPOINT |
FAHRENHEIT | Maximum heat setpoint (°F) |
x-telemetry:MIN_COOL_SETPOINT |
FAHRENHEIT | Minimum cool setpoint (°F) |
x-telemetry:MAX_COOL_SETPOINT |
FAHRENHEIT | Maximum cool setpoint (°F) |
Report (telemetry) types — EV Charger (aligns with ChargerState):
| Type | Units | Description |
|---|---|---|
x-telemetry:CHARGING_STATE |
— | charging, connected, suspended_evse, suspended_ev, discharging, idle, fault, unknown |
x-telemetry:IS_PLUGGED_IN |
— | boolean — plug state |
x-telemetry:CHARGER_VOLTAGE |
V | Charger voltage |
x-telemetry:CHARGER_CURRENT |
A | Charger current |
x-telemetry:CHARGER_POWER |
W | Charger power |
Report (telemetry) types — Solar Inverter (aligns with InverterState):
| Type | Units | Description |
|---|---|---|
x-telemetry:GENERATION_POWER |
W | Current inverter output |
x-telemetry:GENERATION_ENERGY |
Wh | Session/daily generation |
x-telemetry:GENERATION_ENERGY_LIFETIME |
Wh | Cumulative lifetime generation |
x-telemetry:GRID_STATUS |
— | exporting, importing, idle, unknown |
x-telemetry:GRID_POWER |
W | Grid power (positive=importing, negative=exporting) |
x-telemetry:GRID_ENERGY |
Wh | Cumulative grid energy |
x-telemetry:LOAD_POWER |
W | Household load power |
x-telemetry:LOAD_ENERGY |
Wh | Cumulative load energy |
Report (telemetry) types — Vehicle (aligns with VehicleState):
| Type | Units | Description |
|---|---|---|
x-telemetry:CHARGING_STATE |
— | charging, connected, suspended_evse, suspended_ev, discharging, idle, fault, unknown |
STORAGE_CHARGE_LEVEL |
PERCENT | Battery/vehicle charge level (0–100) — use standard type instead of x-telemetry:CHARGE_PERCENTAGE |
x-telemetry:STORAGE_ENERGY_LEVEL |
Wh | Current stored energy |
x-telemetry:CHARGE_LIMIT |
PERCENT | Charge limit setting |
x-telemetry:CHARGE_COMPLETED_AT |
— | ISO 8601 datetime — estimated completion |
x-telemetry:IS_PLUGGED_IN |
— | boolean — plug state |
x-telemetry:CHARGER_VOLTAGE |
V | Charger voltage |
x-telemetry:CHARGER_CURRENT |
A | Charger current |
x-telemetry:CHARGER_POWER |
W | Charger power |
x-telemetry:RANGE |
miles | Estimated range |
Report (telemetry) types — Common:
| Type | Units | Description |
|---|---|---|
x-telemetry:COMMAND_STATUS |
— | accepted, executing, completed, failed |
x-telemetry:COMMAND_FAILURE_REASON |
— | Human-readable failure description |
x-telemetry:REACTIVE_POWER_KVAR |
kVAR | Reactive power (smart meters) |
x-telemetry:VOLTAGE |
V | Voltage reading (smart meters) |
Event (dispatch) types:
| Type | Units | Description |
|---|---|---|
x-telemetry:DURATION_MINUTES |
MINUTES | Event duration |
x-telemetry:TARGET_SOC_PCT |
PERCENT | Target state of charge |
x-telemetry:RESERVE_PCT |
PERCENT | Minimum backup reserve |
x-telemetry:TEMPERATURE_SETPOINT_F |
FAHRENHEIT | Target temperature setpoint |
x-telemetry:FAN_MODE |
— | Fan operating mode (AUTO, ON, OFF, CIRCULATE) |
x-telemetry:IS_TEST_EVENT |
— | boolean — test events must be handled identically to real events |
Device registration (static) types:
| Type | Units | Description |
|---|---|---|
x-static:DEVICE_TYPE |
— | Device category (BATTERY, THERMOSTAT, EV_CHARGER, SOLAR_INVERTER, VEHICLE, SMART_METER) |
x-static:MANUFACTURER_DEVICE_ID |
— | Your internal device identifier |
x-static:DEVICE_MODEL |
— | Device model name/number |
x-static:SITE_ID |
— | Associated site identifier |
x-static:SERIAL_NUMBER |
— | Device serial number |
x-static:NAMEPLATE_KW |
kW | AC power rating (used for EV chargers, solar inverters where no standard type exists) |
Note: For battery devices, prefer the standard OpenADR 3.0 types
STORAGE_MAX_DISCHARGE_POWERandSTORAGE_USABLE_CAPACITYoverx-static:NAMEPLATE_KWandx-static:CAPACITY_KWH.
Appendix C — OpenADR 3.0 Alignment
This API is built on top of the OpenADR 3.0 (Open Automated Demand Response) industry standard for demand response interoperability. If you're familiar with OpenADR, this section maps Texture concepts to OpenADR terminology. If you've never worked with OpenADR, you can safely skip this section — the spec above contains everything you need.
What Is OpenADR?
OpenADR is an industry standard that defines how utilities and aggregators communicate with distributed energy resources (DERs). Version 3.0 uses a REST/JSON pattern (replacing the older SOAP/XML-based 2.0b). Texture implements an OpenADR 3.0-aligned API so that your integration is portable and standards-compliant.
The core OpenADR 3.0 objects — Programs, Events, Reports, Resources, Subscriptions — form the foundation of this API (Layer 1). Texture extends these with additional payload types, enrollment workflows, and site management capabilities (Layer 2). See Specification Layers for the full breakdown.
Concept Mapping
| Texture Concept | OpenADR 3.0 Term | Description |
|---|---|---|
| Texture's platform | VTN (Virtual Top Node) | The server that hosts programs, events, and reports |
| Your platform / integration account | VEN (Virtual End Node) | The client that reads events and writes reports |
| Program | Program | A utility demand response offering |
| Dispatch event | Event | A dispatch signal sent to your platform |
| Telemetry report | Report | Device data sent from your platform to Texture |
| Webhook subscription | Subscription | Real-time notification registration |
| Device | Resource | An individual energy device (battery, thermostat, etc.) |
Why This Matters
- Interoperability: If you build to this spec, your integration is compatible with other OpenADR 3.0-based platforms.
- Standards compliance: Payload types, object structures, and REST patterns follow the OpenADR 3.0 specification. Texture extensions are clearly marked with
[Texture Extension].
API Object Names
You'll notice that API endpoints and JSON fields use OpenADR terms (e.g., /v2/vens/, venID, resourceName). This is intentional — it keeps the API standards-compliant. Throughout this spec, we use plain-language equivalents in descriptions:
| API Term | Plain Language |
|---|---|
VEN / venID |
Your integration account |
Resource / resourceName |
A registered device |
Report |
Telemetry data |
Event |
Dispatch command |
Subscription |
Webhook registration |
Appendix D — Reference Links
- Texture Core API Documentation — the device connectivity API for utilities and energy providers (separate from this spec)
- OpenADR 3.0 Specification
- Integration Terms
- For OEM and TPDO FAQ documents, contact integrations@texturehq.com