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

  1. Architecture
  2. Getting Started
  3. Authentication
  4. Site Management
  5. Resource Management (Devices)
  6. Reports (Telemetry Push)
  7. Events (Dispatch Commands)
  8. Subscriptions (Webhooks)
  9. MQTT Transport (Optional)
  10. OEM-Initiated Enrollment
  11. Performance, Rate Limits, and Reliability
  12. Security & Privacy
  13. Error Handling
  14. Certification Process
  15. Appendix A — Endpoint Quick Reference
  16. Appendix B — Texture Extension Payload Type Registry
  17. Appendix C — OpenADR 3.0 Alignment
  18. 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:

  1. Telemetry push — your platform sends device data to Texture as Report objects (continuously).
  2. 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

  1. Create Sites for each physical installation address (used for territory matching and program eligibility)
  2. Register Devices under your integration account, each linked to a Site
  3. Create a Webhook Subscription for dispatch event notifications on your program
  4. Push Telemetry with device data continuously — this starts immediately, not just during events
  5. 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_in seconds (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_id and client_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_secret to 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 territory
  • programEligible: 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 resourceName or /resources/ in the API, it means a device record. When you see venID or /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] The resourceName field is your stable, unique device identifier — it must not change over the device's lifetime. Device attributes use x-static: prefixed types to carry device metadata and x-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_ID associates 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. The BATTERY type covers the complete system including the inverter. Specify the AC inverter rating as STORAGE_MAX_DISCHARGE_POWER (in kW) and total usable storage as STORAGE_USABLE_CAPACITY (in Wh). Many residential "batteries" are actually a dumb storage module paired with a smart inverter — register the whole system as a single BATTERY device.

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_POWER and STORAGE_USABLE_CAPACITY are standard OpenADR 3.0 types and should be used instead of custom x-static: equivalents. For device types where no standard attribute exists (EV charger nameplate capacity, solar inverter rating), use the Texture extension x-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/reports containing 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, and STORAGE_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_POWER and STORAGE_MAX_CHARGE_POWER are 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_RATE is 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. Use STORAGE_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 only vehicle_connected and vehicle_charging booleans: use CONNECTED when 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. Report x-telemetry:CHARGER_VOLTAGE to 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

  1. The utility or grid operator issues a dispatch signal
  2. Texture receives and translates it into an Event
  3. Texture creates the Event with targets pointing to your integration account and/or specific devices
  4. Your webhook fires (to register a webhook, see Webhooks / Subscriptions) — Texture POSTs a notification to your callbackUrl
  5. Your platform reads the Event, interprets the payloads, and controls devices
  6. Your platform pushes Reports with the Event's id as eventID — 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 uses DISPATCH_SETPOINT with a negative value plus x-telemetry:DURATION_MINUTES and optionally CHARGE_STATE_SETPOINT. A thermostat adjustment uses CONTROL_SETPOINT plus x-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_STATUS reports command execution state. Include x-telemetry:COMMAND_FAILURE_REASON when 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:

  1. Geocoding: Texture geocodes the provided address to determine geographic coordinates.
  2. Geographic lookup: Coordinates are matched against utility service territory boundaries.
  3. Utility identification: Texture identifies the IOU (Investor-Owned Utility), municipal utility, co-op, or CCA (Community Choice Aggregator) serving the location.
  4. Program matching: Texture checks active programs in the identified territory against the device type and capabilities.
  5. 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

Customer consent provided by OEMs/TPDOs must meet the following requirements.

Required consent data:

  • Opt-in status (must be true for 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:

  1. Consent was obtained directly from the device owner or authorized representative.
  2. Consent was informed — the customer understood what they were agreeing to.
  3. Consent was voluntary — not coerced or bundled deceptively.
  4. You have records to substantiate consent if audited.
  5. 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/reports containing 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 eventID set
  • 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_POWER and STORAGE_USABLE_CAPACITY over x-static:NAMEPLATE_KW and x-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

You've already got the data. Let's make it work.

Book a demo to explore how leading energy teams monitor usage, automate workflows, and collaborate in real time.