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:

LayerScopeMarker
Layer 1: OpenADR 3.0 CoreStandard objects (Events, Reports, Resources, Subscriptions), auth, REST patterns — as defined by the OpenADR 3.0 specification(unmarked — this is the baseline)
Layer 2: Texture ExtensionCustom payload types, device schemas, enrollment, sites, richer telemetry — capabilities Texture adds on top of the OpenADR standard[Texture Extension]
Layer 3: Program-SpecificPer-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:

CredentialPurpose
client_idYour OAuth 2.0 client identifier
client_secretYour OAuth 2.0 client secret
API base URLhttps://direct.texturehq.com/v2
Token endpoint URLhttps://direct.texturehq.com/v2/auth/token
Integration account IDYour server-assigned account identifier on Texture's platform
Program IDThe 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

ScopeDescription
read_eventsRead dispatch events targeted at your account/devices
read_programsRead program definitions
write_reportsCreate and update telemetry reports
write_vensCreate and update your integration account and devices
write_subscriptionsCreate 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"
}
FieldRequiredDescription
nameYesHuman-readable site label
addressYesPhysical installation address (street, city, state, zip)
utilityAccountNumberNoThe 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 TypeRequired Attributes
BATTERYDEVICE_TYPE, SITE_ID, STORAGE_MAX_DISCHARGE_POWER (AC inverter rating, kW), STORAGE_USABLE_CAPACITY (usable storage, Wh)
THERMOSTATDEVICE_TYPE, SITE_ID
EV_CHARGERDEVICE_TYPE, SITE_ID, x-static:NAMEPLATE_KW
SOLAR_INVERTERDEVICE_TYPE, SITE_ID, x-static:NAMEPLATE_KW
SMART_METERDEVICE_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"] }
          ]
        }
      ]
    }
  ]
}
FieldDescription
eventIDIf 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.
clientNameYour integration account name or identifier.
reportNameOptional human-readable name for debugging.
payloadDescriptorsMetadata for the payload types in this Report. Provides units and reading types.
resourcesArray 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 TypeStandard?ValuesRequired
State and measurements:
Payload TypeStandard?ValuesRequired
STORAGE_CHARGE_LEVELOpenADR 3.00–100 (%, maps to chargePercentage)Yes
STORAGE_USABLE_CAPACITYOpenADR 3.0≥ 0 (Wh, maps to capacity)No
STORAGE_MAX_DISCHARGE_POWEROpenADR 3.0≥ 0 (W, maps to maxDischargeRate)No
STORAGE_MAX_CHARGE_POWEROpenADR 3.0≥ 0 (W, maps to maxChargeRate)No
x-telemetry:CHARGING_STATETexture"CHARGING", "DISCHARGING", "IDLE", "STANDBY", "SLEEP", "FAULT", "OFF", "UNKNOWN"Yes
x-telemetry:STORAGE_ENERGY_LEVELTexture≥ 0 (Wh, current stored energy)No
x-telemetry:CHARGE_RATETextureany (W, positive=charging, negative=discharging)No
x-telemetry:CHARGE_POWERTexture≥ 0 (W, charging power — use alongside DISCHARGE_POWER when reporting gross flows)No
x-telemetry:DISCHARGE_POWERTexture≥ 0 (W, discharging power — use alongside CHARGE_POWER when reporting gross flows)No
x-telemetry:CHARGE_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative energy charged since installation)No
x-telemetry:DISCHARGE_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative energy discharged since installation)No
x-telemetry:BACKUP_RESERVETexture0–100 (%, backup reserve setting)No
x-telemetry:STRATEGYTexture"UNKNOWN", "SELF_CONSUMPTION", "TIME_OF_USE", "BACKUP"No
x-telemetry:IS_STORM_MODE_ACTIVETexturetrue, falseNo
x-telemetry:IS_STORM_MODE_ENABLEDTexturetrue, falseNo
x-telemetry:GRID_STATUSTexture"EXPORTING", "IMPORTING", "IDLE", "GRID_OUTAGE", "UNKNOWN"No
x-telemetry:GRID_POWERTextureinteger (W, positive=importing, negative=exporting)No
x-telemetry:GRID_IMPORT_POWERTexture≥ 0 (W, grid import only — alternative to GRID_POWER)No
x-telemetry:GRID_EXPORT_POWERTexture≥ 0 (W, grid export only — alternative to GRID_POWER)No
x-telemetry:GRID_ENERGYTextureinteger (Wh, net grid energy for this interval, positive=imported)No
x-telemetry:GRID_IMPORT_ENERGYTexture≥ 0 (Wh, grid import energy for this interval)No
x-telemetry:GRID_EXPORT_ENERGYTexture≥ 0 (Wh, grid export energy for this interval)No
x-telemetry:GRID_IMPORT_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative grid import since installation)No
x-telemetry:GRID_EXPORT_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative grid export since installation)No
x-telemetry:LOAD_POWERTextureany (W, household load)No
x-telemetry:LOAD_ENERGYTextureany (Wh, load energy for this reporting interval)No
x-telemetry:LOAD_ENERGY_LIFETIMETextureany (Wh, cumulative load energy since installation)No
x-telemetry:GENERATION_POWERTextureany (W, total on-site generation)No
x-telemetry:GENERATION_ENERGYTexture≥ 0 (Wh, generation energy for this interval)No
x-telemetry:GENERATION_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative generation since installation)No
x-telemetry:NUMBER_OF_BATTERIESTextureinteger (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 TypeStandard?ValuesRequired
BASELINEOpenADR 3.0W, power the battery would draw/export without any dispatch commandNo
DOWN_REGULATION_AVAILABLEOpenADR 3.0W, available discharge power (BMS-aware)No
UP_REGULATION_AVAILABLEOpenADR 3.0W, available charge headroom (BMS-aware)No
x-capacity:DISCHARGE_ENERGY_AVAILABLETextureWh, available discharge energy (BMS-aware)No
x-capacity:CHARGE_ENERGY_AVAILABLETextureWh, available charge energy (BMS-aware)No

Thermostat

These fields align with the Texture ThermostatState and ThermostatStaticState data models.

Payload TypeStandard?ValuesRequired
READING (units: FAHRENHEIT)OpenADR 3.0-40–150 (°F, maps to ambientTemperature)Yes
x-telemetry:OPERATING_MODETexture"HEAT", "COOL", "AUTO", "ECO", "OFF", "UNKNOWN"Yes
x-telemetry:COOL_TARGETTexture(°F, cooling setpoint)No
x-telemetry:HEAT_TARGETTexture(°F, heating setpoint)No
x-telemetry:FAN_MODETexture"AUTO", "ON", "OFF", "CIRCULATE", "UNKNOWN"No
x-telemetry:ALLOWED_MODESTexturearray of supported operating modesNo
x-telemetry:ALLOWED_FAN_MODESTexturearray of supported fan modesNo
x-telemetry:MIN_HEAT_SETPOINTTextureinteger (°F)No
x-telemetry:MAX_HEAT_SETPOINTTextureinteger (°F)No
x-telemetry:MIN_COOL_SETPOINTTextureinteger (°F)No
x-telemetry:MAX_COOL_SETPOINTTextureinteger (°F)No

EV Charger

These fields align with the Texture ChargerState data model.

Payload TypeStandard?ValuesRequired
x-telemetry:CHARGING_STATETexture"CHARGING", "CONNECTED", "SUSPENDED_EVSE", "SUSPENDED_EV", "DISCHARGING", "IDLE", "FAULT", "UNKNOWN"Yes
x-telemetry:IS_PLUGGED_INTexturetrue, falseYes
DEMANDOpenADR 3.0kW, average power over reporting intervalYes
USAGEOpenADR 3.0kWh, energy delivered in this reporting intervalNo
x-telemetry:CHARGER_POWERTexture≥ 0 (W, instantaneous power — alternative to DEMAND)No
x-telemetry:CHARGER_ENERGYTexture≥ 0 (Wh, energy for this interval — alternative to USAGE)No
x-telemetry:CHARGER_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative energy delivered since installation)No
x-telemetry:CHARGER_VOLTAGETexture≥ 0 (V)No
x-telemetry:CHARGER_CURRENTTexture≥ 0 (A)No
x-telemetry:FREQUENCYTextureHz, grid frequencyNo

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 TypeStandard?ValuesRequired
x-telemetry:GENERATION_POWERTexture≥ 0 (W, current output)Yes
x-telemetry:GENERATION_ENERGYTexture≥ 0 (Wh, energy generated in this reporting interval)No
x-telemetry:GENERATION_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative lifetime generation)No
x-telemetry:GRID_STATUSTexture"EXPORTING", "IMPORTING", "IDLE", "GRID_OUTAGE", "UNKNOWN"No
x-telemetry:GRID_POWERTextureinteger (W, positive=importing, negative=exporting)No
x-telemetry:GRID_ENERGYTextureinteger (Wh, net grid energy for this interval, positive=imported)No
x-telemetry:LOAD_POWERTextureany (W, household load)No
x-telemetry:LOAD_ENERGYTextureany (Wh, load energy for this reporting interval)No
x-telemetry:FREQUENCYTextureHz, grid frequencyNo

Vehicle (EV)

These fields align with the Texture VehicleState data model.

Payload TypeStandard?ValuesRequired
x-telemetry:CHARGING_STATETexture"CHARGING", "CONNECTED", "SUSPENDED_EVSE", "SUSPENDED_EV", "DISCHARGING", "IDLE", "FAULT", "UNKNOWN"Yes
STORAGE_CHARGE_LEVELOpenADR 3.00–100 (%, battery/vehicle charge level)Yes
x-telemetry:STORAGE_ENERGY_LEVELTexture≥ 0 (Wh, current stored energy)No
x-telemetry:CHARGE_LIMITTexture0–100 (%, charge limit setting)No
x-telemetry:CHARGE_COMPLETED_ATTextureISO 8601 datetime (estimated completion)No
x-telemetry:IS_PLUGGED_INTexturetrue, falseNo
x-telemetry:CHARGER_POWERTexture≥ 0 (W, instantaneous charging power)No
x-telemetry:CHARGER_ENERGYTexture≥ 0 (Wh, energy delivered in this interval)No
x-telemetry:CHARGER_ENERGY_LIFETIMETexture≥ 0 (Wh, cumulative energy delivered since installation)No
x-telemetry:CHARGER_VOLTAGETexture≥ 0 (V)No
x-telemetry:CHARGER_CURRENTTexture≥ 0 (A)No
x-telemetry:RANGETexture≥ 0 (miles, estimated range)No

Smart Meter

Payload TypeStandard?ValuesRequired
DEMANDOpenADR 3.0kW, net grid power (positive=importing, negative=exporting)Yes
USAGEOpenADR 3.0kWh, net energy in this reporting intervalNo
x-telemetry:REACTIVE_POWER_KVARTextureany (kVAR)No
x-telemetry:VOLTAGETexture≥ 0 (V)No
x-telemetry:FREQUENCYTextureHz, grid frequencyNo

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
    }
  ]
}
FieldDescription
programIDLinks to the program this event belongs to.
eventNameHuman-readable description.
priorityLower = higher priority. 0 is highest.
targetsYour integration account ID and/or specific device IDs. Typically targets your whole account; in some cases, Texture may target specific devices for partial dispatch.
intervalPeriodWhen the event starts and the duration of each interval.
intervalsThe dispatch instructions. Each interval has payloads with the action.
reportDescriptorsWhat 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 TypeDescriptionExample
DISPATCH_SETPOINTAbsolute power setpoint (kW). Negative = discharge/export.[-5.0]
CHARGE_STATE_SETPOINTTarget state of charge (% or kWh).[20]
CONTROL_SETPOINTRelative adjustment (kW or °F delta).[-3.0]
EXPORT_CAPACITY_LIMITMax export power (kW).[10.0]
IMPORT_CAPACITY_LIMITMax import power (kW). 0 = stop charging.[0.0]
SIMPLELoad level (0=normal, 1=moderate, 2=high, 3=emergency).[2]

Texture extension dispatch types: [Texture Extension]

Payload TypeDescriptionExample
x-telemetry:DURATION_MINUTESEvent duration in minutes.[60]
x-telemetry:TARGET_SOC_PCTTarget state of charge (%). Prefer CHARGE_STATE_SETPOINT.[20]
x-config:BACKUP_RESERVE_PCTOverride backup reserve floor for event duration (%).[20]
x-telemetry:TEMPERATURE_SETPOINT_FTarget temperature setpoint (°F).[72]
x-telemetry:FAN_MODEFan 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:

StatusWhen to SendDescription
"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 respondingThe device is actively discharging/charging/adjusting per the command
"COMPLETED"When the event's interval period ends, or the device finishesThe dispatch action is done
"FAILED"If the device cannot execute the commandInclude 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:

MethodPathDescription
GET/v2/subscriptionsList your subscriptions
GET/v2/subscriptions/:idGet a specific subscription
PUT/v2/subscriptions/:idUpdate (e.g., change callbackUrl)
DELETE/v2/subscriptions/:idRemove 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

ParameterValue
Brokermqtts://direct.texturehq.com:8883
ProtocolMQTT v5.0 over TLS 1.2+
AuthenticationUsername = OAuth 2.0 access token (same token as REST API); Password = empty string
Keep-alive60 seconds recommended
Clean sessionfalse (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"] }
        ]
      }]
    }
  ]
}
SettingValue
QoS1 (at least once delivery)
Retainfalse

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.

SettingValue
QoS1 (at least once delivery)
Message formatSame 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

ScenarioRecommendation
Small fleet (<100 devices), simple integrationREST + webhooks
Large fleet (1,000+ devices), frequent telemetryMQTT for telemetry, REST or MQTT for dispatch
Corporate environment with strict firewall rules against inbound connectionsMQTT (outbound only)
Prototype / proof of conceptREST + 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:

OutcomeDescriptionNext Steps
MatchedUtility and program(s) identifiedDevice proceeds to eligible state
Partial matchUtility identified, no active programsDevice enters discovered state
AmbiguousMultiple possible utilities (territory boundary)Manual review required
No matchLocation outside supported territoriesEnrollment rejected with reason
Invalid locationAddress cannot be geocodedEnrollment 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.

StateDescription
discoveredDevice registered, not yet matched to a program
eligibleDevice matched to territory, awaiting opt-in or program assignment
opted_inCustomer consent received and validated
pending_verificationAdditional verification required (utility account, address, etc.)
enrolledDevice fully enrolled in one or more programs
activeDevice actively participating in grid services
suspendedTemporarily suspended (connectivity, compliance, etc.)
opted_outCustomer revoked consent
revokedEnrollment terminated (decommission, ownership change, etc.)

Key state transitions:

TransitionTriggerYour Action Required
discovered → eligibleTerritory match successfulNone
eligible → opted_inConsent validatedProvide consent
opted_in → enrolledProgram assignment completeNone
enrolled → activeDevice begins participationEnsure connectivity
active → suspendedConnectivity loss, compliance issueResolve issue
active → opted_outCustomer revokes consentSend revocation event
any → revokedDecommission, ownership changeSend 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.

EventWhen to SendRequired Information
Consent revokedCustomer opts outDevice ID, revocation timestamp, source
Device decommissionedDevice removed from serviceDevice ID, decommission timestamp, reason
Device relocatedDevice moved to new addressDevice ID, old site info, new site info, relocation date
Ownership transferredDevice ownership changedDevice ID, previous owner, new owner, transfer date
Connectivity lostDevice offline >72 hoursDevice 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:

EventProcessing Time
Consent revokedImmediate (within 1 minute)
Device decommissionedWithin 15 minutes
Device relocatedWithin 1 hour (triggers re-matching)
Ownership transferredWithin 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

CategoryLimit
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 requests30 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

OperationRequirement
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 availability99.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

CodeMeaning
200OK
201Created
400Bad Request — validation error
401Unauthorized — missing or invalid token
403Forbidden — valid token, insufficient scope
404Not Found
409Conflict — idempotency mismatch or state conflict
429Too Many Requests — see Retry-After header
5xxServer error — retry with exponential backoff

Error categories and response:

CategoryYour Response
Validation errorsFix payload and retry
Authentication errorsRe-authenticate
Territory errorsVerify location data or await manual review
Consent errorsObtain fresh consent and resubmit
Conflict errorsCheck existing enrollment status
Rate limit errorsRetry 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

MethodPathDescriptionScope
POST/v2/auth/tokenGet access token(client_id/secret)
GET/v2/programsList programsread_programs
GET/v2/programs/:idGet a programread_programs
GET/v2/vens/:idGet your integration accountwrite_vens
PUT/v2/vens/:idUpdate your integration accountwrite_vens
POST/v2/x-texture/sitesCreate a sitewrite_vens
GET/v2/x-texture/sitesList siteswrite_vens
POST/v2/resourcesRegister a devicewrite_vens
GET/v2/resources/:idGet a devicewrite_vens
GET/v2/resourcesList deviceswrite_vens
PUT/v2/resources/:idUpdate a devicewrite_vens
POST/v2/reportsPush telemetry reportwrite_reports
GET/v2/eventsList dispatch eventsread_events
GET/v2/events/:idGet a dispatch eventread_events
POST/v2/subscriptionsCreate webhook subscriptionwrite_subscriptions
GET/v2/subscriptionsList subscriptionswrite_subscriptions
PUT/v2/subscriptions/:idUpdate subscriptionwrite_subscriptions
DELETE/v2/subscriptions/:idDelete subscriptionwrite_subscriptions
POST/v2/x-texture/enrollmentsEnroll in a programwrite_vens
GET/v2/x-texture/enrollmentsList enrollmentswrite_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):

TypeUnitsDescription
x-telemetry:CHARGING_STATEcharging, discharging, idle, standby, sleep, fault, off, unknown
x-telemetry:STORAGE_ENERGY_LEVELWhCurrent stored energy
x-telemetry:CHARGE_RATEWCurrent charge/discharge rate
x-telemetry:BACKUP_RESERVEPERCENTBackup reserve setting (0–100)
x-telemetry:STRATEGYunknown, self_consumption, time_of_use, backup
x-telemetry:IS_STORM_MODE_ACTIVEboolean — storm mode currently active
x-telemetry:IS_STORM_MODE_ENABLEDboolean — storm mode enabled
x-telemetry:GRID_STATUSexporting, importing, idle, unknown
x-telemetry:GRID_POWERWGrid power (positive=importing, negative=exporting)
x-telemetry:GRID_ENERGYWhCumulative grid energy
x-telemetry:LOAD_POWERWHousehold load power
x-telemetry:LOAD_ENERGYWhCumulative load energy
x-telemetry:NUMBER_OF_BATTERIESCount of battery modules

Report (telemetry) types — Thermostat (aligns with ThermostatState):

TypeUnitsDescription
x-telemetry:OPERATING_MODEheat, cool, auto, eco, off, unknown
x-telemetry:COOL_TARGETFAHRENHEITCooling setpoint
x-telemetry:HEAT_TARGETFAHRENHEITHeating setpoint
x-telemetry:FAN_MODEauto, on, off, circulate, unknown
x-telemetry:ALLOWED_MODESArray of supported operating modes
x-telemetry:ALLOWED_FAN_MODESArray of supported fan modes
x-telemetry:MIN_HEAT_SETPOINTFAHRENHEITMinimum heat setpoint (°F)
x-telemetry:MAX_HEAT_SETPOINTFAHRENHEITMaximum heat setpoint (°F)
x-telemetry:MIN_COOL_SETPOINTFAHRENHEITMinimum cool setpoint (°F)
x-telemetry:MAX_COOL_SETPOINTFAHRENHEITMaximum cool setpoint (°F)

Report (telemetry) types — EV Charger (aligns with ChargerState):

TypeUnitsDescription
x-telemetry:CHARGING_STATEcharging, connected, suspended_evse, suspended_ev, discharging, idle, fault, unknown
x-telemetry:IS_PLUGGED_INboolean — plug state
x-telemetry:CHARGER_VOLTAGEVCharger voltage
x-telemetry:CHARGER_CURRENTACharger current
x-telemetry:CHARGER_POWERWCharger power

Report (telemetry) types — Solar Inverter (aligns with InverterState):

TypeUnitsDescription
x-telemetry:GENERATION_POWERWCurrent inverter output
x-telemetry:GENERATION_ENERGYWhSession/daily generation
x-telemetry:GENERATION_ENERGY_LIFETIMEWhCumulative lifetime generation
x-telemetry:GRID_STATUSexporting, importing, idle, unknown
x-telemetry:GRID_POWERWGrid power (positive=importing, negative=exporting)
x-telemetry:GRID_ENERGYWhCumulative grid energy
x-telemetry:LOAD_POWERWHousehold load power
x-telemetry:LOAD_ENERGYWhCumulative load energy

Report (telemetry) types — Vehicle (aligns with VehicleState):

TypeUnitsDescription
x-telemetry:CHARGING_STATEcharging, connected, suspended_evse, suspended_ev, discharging, idle, fault, unknown
STORAGE_CHARGE_LEVELPERCENTBattery/vehicle charge level (0–100) — use standard type instead of x-telemetry:CHARGE_PERCENTAGE
x-telemetry:STORAGE_ENERGY_LEVELWhCurrent stored energy
x-telemetry:CHARGE_LIMITPERCENTCharge limit setting
x-telemetry:CHARGE_COMPLETED_ATISO 8601 datetime — estimated completion
x-telemetry:IS_PLUGGED_INboolean — plug state
x-telemetry:CHARGER_VOLTAGEVCharger voltage
x-telemetry:CHARGER_CURRENTACharger current
x-telemetry:CHARGER_POWERWCharger power
x-telemetry:RANGEmilesEstimated range

Report (telemetry) types — Common:

TypeUnitsDescription
x-telemetry:COMMAND_STATUSaccepted, executing, completed, failed
x-telemetry:COMMAND_FAILURE_REASONHuman-readable failure description
x-telemetry:REACTIVE_POWER_KVARkVARReactive power (smart meters)
x-telemetry:VOLTAGEVVoltage reading (smart meters)

Event (dispatch) types:

TypeUnitsDescription
x-telemetry:DURATION_MINUTESMINUTESEvent duration
x-telemetry:TARGET_SOC_PCTPERCENTTarget state of charge
x-telemetry:RESERVE_PCTPERCENTMinimum backup reserve
x-telemetry:TEMPERATURE_SETPOINT_FFAHRENHEITTarget temperature setpoint
x-telemetry:FAN_MODEFan operating mode (AUTO, ON, OFF, CIRCULATE)
x-telemetry:IS_TEST_EVENTboolean — test events must be handled identically to real events

Device registration (static) types:

TypeUnitsDescription
x-static:DEVICE_TYPEDevice category (BATTERY, THERMOSTAT, EV_CHARGER, SOLAR_INVERTER, VEHICLE, SMART_METER)
x-static:MANUFACTURER_DEVICE_IDYour internal device identifier
x-static:DEVICE_MODELDevice model name/number
x-static:SITE_IDAssociated site identifier
x-static:SERIAL_NUMBERDevice serial number
x-static:NAMEPLATE_KWkWAC 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 ConceptOpenADR 3.0 TermDescription
Texture's platformVTN (Virtual Top Node)The server that hosts programs, events, and reports
Your platform / integration accountVEN (Virtual End Node)The client that reads events and writes reports
ProgramProgramA utility demand response offering
Dispatch eventEventA dispatch signal sent to your platform
Telemetry reportReportDevice data sent from your platform to Texture
Webhook subscriptionSubscriptionReal-time notification registration
DeviceResourceAn 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 TermPlain Language
VEN / venIDYour integration account
Resource / resourceNameA registered device
ReportTelemetry data
EventDispatch command
SubscriptionWebhook registration