Historical Backfill
Load past energy data for connected devices so your history doesn't start at connection time
When you connect a device to Texture, live polling begins immediately and metrics start accumulating from that moment forward. Historical backfill fills in the data that existed before the connection — pulling past production, consumption, storage, and grid energy directly from the manufacturer's API and writing it into Texture as if it had always been there.
This is what lets a newly onboarded fleet show a full year of generation history on day one, or lets a program operator reconstruct a baseline for devices that were enrolled retroactively.
Why Historical Backfill?#
Live polling answers "what is this device doing now?" Backfill answers "what was it doing last month, last quarter, last year?" Common reasons to use it:
- Program baselines — Reconstruct device behavior for the period a program requires (e.g. a full calendar month) for devices enrolled after the fact.
- Retroactive onboarding — A customer connects an existing fleet and expects to see history, not an empty chart.
- Analytics and reporting — Trend analysis, performance baselines, and savings calculations that need data predating the connection.
- Gap filling — Recover periods where live polling was interrupted.
Once backfilled, historical data is queryable through the same Metrics API as live data — there is no separate "historical" endpoint to read from.
How It Works#
Backfill is an explicit, range-bound operation. You choose which devices to backfill and for what time window — there is no automatic "backfill everything" behavior. This keeps the operation safe: it never silently floods a manufacturer's API or competes with live polling.
At a high level:
- A backfill is requested for a set of devices and a start/end time range.
- Texture resolves each device to its underlying manufacturer connection and fans out one job per connection.
- A manufacturer-specific coordinator splits the requested range into chunks the OEM API allows (for example 7-day or 1-day windows), respecting that OEM's rate limits.
- Each chunk is fetched, the raw response is archived, and the normalized data is written into Texture's time-series store with an
ingestTypeofbackfill. - The data becomes available through the standard Metrics API.
Backfill is isolated from live polling
Historical backfill runs on a completely separate path from real-time polling. It never advances a device's live-polling cursor and never interferes with incoming live data — the two can run at the same time without conflict. Backfilled data and live data sit side by side in the same metrics.
Idempotency#
Backfills are safe to re-run. Each finished backfill is recorded per device and time window, so re-requesting a window that has already completed is skipped rather than duplicated. A previously failed window is not skipped — re-requesting it simply retries.
Manufacturer Support & Quirks#
Historical backfill is available for the manufacturers below. Because each OEM exposes historical data differently, the available lookback, data granularity, and chunking differ per manufacturer. Texture handles the chunking and rate limiting automatically — the table is here so you know what to expect from the resulting data.
| Manufacturer | Lookback (retention) | Native granularity | System identifier | Notes |
|---|---|---|---|---|
| SolarEdge (Monitoring API) | Multi-year | 15-minute | Site ID | Available date range is discovered per site, so backfill never requests data from before the system was commissioned. |
| Enphase (Monitoring API v4) | Up to 2 years | 15-minute | System ID | Hard 2-year cap enforced regardless of requested range. Uses per-connection OAuth; very old, long backfills may require the connection to be re-authorized. |
| Enphase (Grid Services) | Program-dependent | 15-minute | Site ID | Separate path for grid-services program sites (e.g. CA programs) that are not OAuth-connected. |
| FranklinWH | ~1 year (conservative) | Daily | Serial number | Daily-resolution API: a one-year backfill is many more jobs than a 15-minute OEM, so large ranges take longer to complete. |
| Tesla (Grid Services) | 12 months minimum | Interval | Site ID | Prefers revenue-grade metering (RGM) signals where available, falling back to standard signals. Shorter retention window — backfill promptly. |
| EG4 | Up to 12 months | Daily | Serial number | 12-month lookback cap; ranges older than 12 months are silently clamped to the cap. Daily chunking. |
Lookback limits are enforced
Requests are clamped to each manufacturer's retention window. If you request three years of Enphase data, you will receive at most two years (the rest is silently dropped, not errored). Likewise EG4 and Tesla clamp to roughly 12 months. Data simply does not exist on the OEM side before these windows.
Granularity affects completion time, not correctness
Manufacturers with daily-resolution APIs (FranklinWH, EG4) require one job per day of the requested range, while 15-minute OEMs use much larger chunks. A long backfill on a daily-granularity OEM is still correct — it just takes more passes to complete and produces coarser data points.
Unsupported manufacturers#
Manufacturers without historical backfill support (for example Generac, Emporia, SMA, and thermostat/EV-charger integrations) are skipped, not errored. If a backfill request includes a mix of supported and unsupported devices, the supported ones proceed and the unsupported ones are reported as skipped.
Requesting a Backfill#
A backfill is triggered by specifying a batch of devices and a time window. There are two ways to do this.
Option 1 — Request through Texture#
During the current rollout, Texture runs backfills on your behalf. Send your Texture contact (or support):
- the manufacturer,
- the device or system identifiers (Texture device IDs, or the OEM site IDs / serial numbers),
- the start and end dates of the window you need.
Texture validates the request, clamps it to the manufacturer's retention limit, and runs it. This is the recommended path today and requires no code.
Option 2 — Trigger via the API#
Backfills are triggered with the triggerHistoricalBackfill GraphQL mutation on the Texture supergraph. You pass a batch of Texture device IDs (not OEM-side identifiers — Texture resolves each device to its manufacturer connection and system identifier for you) and an explicit time window. The mutation returns immediately with an operationId; the actual fetching happens asynchronously.
mutation BackfillApril($input: TriggerHistoricalBackfillInput!) {
triggerHistoricalBackfill(input: $input) {
success
message
operationId
connectionsQueued
connectionsSkipped {
id
reason
}
}
}{
"input": {
"workspaceId": "cmcxv8qu8000zxkikt3g7esnu",
"deviceIds": [
"cmd8fgy611akwjv1qoupv6ujk",
"cmo35zul60720gm1s6kxoh248",
"cmgo4pmme1hwoi01qcng1j2yr"
],
"startTime": "2026-04-01T00:00:00Z",
"endTime": "2026-05-01T00:00:00Z"
}
}Input parameters
| Field | Type | Description |
|---|---|---|
workspaceId | String! | Workspace that owns the devices. The caller must have workspace-update permission on it. |
deviceIds | [String!]! | Texture device IDs (Device.id) to backfill — not OEM identifiers. Between 1 and 1000 per call; duplicates are de-duplicated server-side. |
startTime | DateTime! | Inclusive ISO-8601 start of the window. |
endTime | DateTime! | Exclusive ISO-8601 end of the window. Must be greater than startTime, not in the future, and within 5 years of startTime. |
A successful response looks like this — note that backfill uses partial-success semantics: devices that can't be queued are returned in connectionsSkipped rather than failing the whole call.
{
"data": {
"triggerHistoricalBackfill": {
"success": true,
"message": "Queued 2 of 3 devices",
"operationId": "op_2x9c1f3b8a",
"connectionsQueued": 2,
"connectionsSkipped": [
{ "id": "cmo35zul60720gm1s6kxoh248", "reason": "COORDINATOR_NOT_AVAILABLE" }
]
}
}
}Skip reason codes (connectionsSkipped[].reason):
| Code | Meaning |
|---|---|
DEVICE_NOT_FOUND | The device ID does not exist in Texture. |
WRONG_WORKSPACE | The device exists but isn't connected to the workspaceId supplied. |
CONNECTION_NOT_FOUND | The device has no live OEM connection to backfill against. |
MISSING_MANUFACTURER_USER_ID | The connection is missing the OEM-side user ID needed to resolve credentials. |
COORDINATOR_NOT_AVAILABLE | The device's manufacturer does not support historical backfill (see the support table above). |
NO_SYSTEM_IDENTIFIER | The device has neither a site ID nor a serial number to address the OEM API with. |
Batch and pace large fleets
You can pass up to 1000 device IDs per call, but backfills compete for each OEM's API rate limits. For large fleets, send devices in small batches (for example ramp 1 → 10 → 50 per call) with a short pause between calls, and chunk very long date ranges on your side. Requesting only the devices and window you actually need keeps backfills fast and avoids contending with live polling.
Checking Backfill Status#
The mutation returns an operationId that ties together every device queued by a single call. Because fetching is asynchronous, completion is tracked separately. The simplest confirmation is to query the Metrics API for the backfilled window and watch the data appear as chunks finish.
Texture also records the outcome of every finished backfill (per device and window, SUCCESS or FAILURE) keyed by that operationId, so your Texture contact can report on progress, per-system coverage, and any failures for a given operation.
Retrieving Backfilled Data#
Backfilled data is not read from a special endpoint — it lands in the same time-series store as live data and is queried through the standard Metrics API. Once a backfill completes, query the metric you need over the historical range. You can request multiple devices in one call by repeating the filterDeviceId[] parameter.
GET /v1/metrics/generationcurl -X GET "https://api.texturehq.com/v1/metrics/generation?filterDeviceId[]=cmd8fgy611akwjv1qoupv6ujk&filterDeviceId[]=cmgo4pmme1hwoi01qcng1j2yr&filterRangeAfter=2026-04-01T00:00:00Z&filterRangeBefore=2026-05-01T00:00:00Z&windowSize=DAY" \
-H "Texture-Api-Key: your_api_key"The response shape, window sizes (FIFTEEN_MINUTES, HOUR, DAY, WEEK, MONTH, YEAR), and filtering options are identical to those documented in the Metrics reference. Note that the resolution you can meaningfully request is bounded by the manufacturer's native granularity — a daily-granularity OEM (FranklinWH, EG4) will not produce 15-minute detail for backfilled periods, so request DAY windows for those.
Give backfills time to complete
Large backfills are processed in chunks over time. If a historical range looks incomplete right after a request, it may still be filling in. Querying again later will show the additional data as chunks finish.
Best Practices#
- Pass Texture device IDs, not OEM identifiers. The mutation takes
Device.idvalues and resolves each to its manufacturer connection automatically. - Remember
endTimeis exclusive. To backfill all of April, use2026-04-01T00:00:00Z→2026-05-01T00:00:00Z. - Batch and pace large fleets. Up to 1000 devices per call, but ramp batch size (1 → 10 → 50) and space calls out so you don't exhaust OEM rate limits or contend with live polling.
- Know the retention cap before you ask. Don't expect data older than the manufacturer's window (2 years for Enphase, ~12 months for Tesla and EG4).
- Match query granularity to the source. Use larger
windowSizevalues (DAY,WEEK) for daily-granularity OEMs like FranklinWH and EG4. - Re-running is safe. Backfills are idempotent; completed windows are skipped, failed windows retry.
- Combine with live data freely. Backfilled and live metrics share the same store and query path — no special handling needed in your application.
Next Steps#
- Read the Metrics API reference to query backfilled data
- Explore the full API
- Review per-manufacturer integration details
- Contact Texture to request a backfill for your devices
Historical backfill closes the gap between when a device started reporting to its manufacturer and when it connected to Texture — giving you complete, queryable history across your fleet.