Skip to content

Backfilling Measurement Data

Tenovi delivers measurements in real time via webhooks. There are scenarios where you need to retrieve measurements on demand instead:

  • Your webhook endpoint was unreachable during a window of device activity
  • You are seeding a new system with historical data
  • You need to audit or verify that your records are complete

This recipe covers pulling measurements directly from the HWI API and reconciling them against what you already have.

You will need an active API key and your Client Domain. All requests are made against https://api2.tenovi.com/clients/{CLIENT_DOMAIN}/hwi/ over HTTPS. See API URL Config and Client Domain for details.

Each API key is limited to 1 request per second. Backfilling is a loop, so this is the constraint that shapes your implementation. See Scoping the backfill below.

EndpointReturns
GET /clients/{CLIENT_DOMAIN}/hwi/patients/{PATIENT_EXTERNAL_ID}/measurements/Measurements across all devices assigned to a patient
GET /clients/{CLIENT_DOMAIN}/hwi/hwi-devices/{HWI_DEVICE_ID}/measurements/Measurements for a single device

Both endpoints identify their subject in the URL path rather than as a query parameter. Query parameters are used only for the time window and pagination.

If a patient has multiple devices, the patient endpoint returns data for all of them in one response. The device endpoint requires a separate request per device based upon the HWI Device ID.

For most backfills, the patient endpoint is the right choice. It produces fewer requests against the same rate limit. Use the device endpoint when you are investigating one specific piece of hardware.

Assemble the list of identifiers you intend to backfill, either patient external IDs or HWI Device IDs.

Scope deliberately - A backfill covering every patient on your account for a year is almost never what you need, and at 1 request per second it is an overnight job. Narrow by the window where your webhook endpoint was actually unavailable, or by the subset of patients you are migrating.

Use timestamp__gte and timestamp__lt to define the window.

GET /clients/{CLIENT_DOMAIN}/hwi/patients/{PATIENT_EXTERNAL_ID}/measurements/?timestamp__gte={ISO8601}&timestamp__lt={ISO8601}&page_size=100

timestamp__gte is inclusive. timestamp__lt is exclusive. Set timestamp__lt to the end of the period after the one you want, not to the last second of the period you want.

GET /clients/{CLIENT_DOMAIN}/hwi/patients/abc123/measurements/?timestamp__gte=2026-01-01T00:00:00Z&timestamp__lt=2026-02-01T00:00:00Z&page_size=100

This returns all of January 2026. Ending the window at 2026-01-31T23:59:59Z would silently drop any measurement recorded in the final second of the month.

The half open interval also makes chunked backfills clean. Each window’s timestamp__lt becomes the next window’s timestamp__gte, which produces no gaps and no overlapping records.

{
"count": 247,
"next": "https://api2.tenovi.com/clients/{CLIENT_DOMAIN}/hwi/patients/abc123/measurements/?page=2",
"previous": null,
"results": [
{
"metric": "blood_pressure",
"device_name": "Tenovi BPM - Wide Range",
"hwi_device_id": "7f9c2e14-3b8a-4d51-9e22-6c1a0f5b8d43",
"patient_id": "abc123",
"hardware_uuid": "AABBCC001122",
"sensor_code": "10",
"mac_address": "1A2B3C4D5E6F",
"value_1": "128",
"value_2": "82",
"created": "2026-01-14T09:24:11Z",
"timestamp": "2026-01-14T09:22:00Z",
"timezone_offset": -5,
"estimated_timestamp": false,
"filter_params": {}
}
]
}
FieldNotes
metricThe measurement type. Determines how value_1 and value_2 should be interpreted.
value_1Primary value. Returned as a string.
value_2Secondary value, used by metrics that report a pair such as blood pressure. Not populated for single value metrics.
hwi_device_idThe device the reading came from. Returned hyphenated and lowercase. Accepted in the URL path with or without hyphens. Part of the reconciliation key described in Step 3.
hardware_uuidThe Gateway that relayed the reading. Useful for correlating a data gap with a Gateway connectivity problem.
timestampWhen the reading was taken. UTC, ISO 8601. Part of the reconciliation key.
createdWhen Tenovi ingested the record. Not the time of the reading. Do not use for reconciliation.
timezone_offsetWhole number offset from UTC in hours, for example 5 or -5. Reflects the patient’s local time.
estimated_timestampBoolean. If true, timestamp came from the device clock rather than a verified source. See Step 3.
sensor_codeIdentifies the device type. Each device page lists its Sensor Code. See Available Devices.

Set page_size to 100. The maximum is 1000, but larger pages mean slower individual responses and more data to hold in memory per iteration. Walk the result set using the next link in the root of the response. See Pagination.

Truncation is silent. To confirm you retrieved everything, compare count in the response against the number of records you actually processed. If you processed fewer than count, you stopped early.

Step 3: Reconcile against your own records

Section titled “Step 3: Reconcile against your own records”

This is the part that determines whether a backfill helps you or corrupts your data.

The HWI API does not return a unique identifier per measurement, and webhook payloads do not carry one either. To determine whether a retrieved record already exists in your system, match on the combination of:

  • hwi_device_id
  • metric
  • timestamp

Match on the raw UTC string exactly as returned. If you store timestamps converted to local time, or truncate fractional seconds, your key will not match on the way back in.

When estimated_timestamp is true, the timestamp came from the device’s own clock because the normal timestamp sources were unavailable. Device clocks drift and can reset after a power loss. See Estimated Timestamps for how the fallback chain works.

This matters because one third of your composite key is unreliable in exactly these records.

Branch your reconciliation on the flag:

estimated_timestampApproach
falseExact match on hwi_device_id + metric + timestamp. Skip if present, insert if not.
trueExact match first. If no match, do not insert blindly. Compare against records for the same device and metric within a tolerance window, and flag close matches for review.

Applying exact matching to estimated records produces duplicates. Applying a tolerance window to all records drops legitimate readings taken close together. The flag is what tells you which risk you are managing.

Consider leaning on the created field for instances where you encounter estimated timestamps.

At 1 request per second, runtime is a function of how many pages you request, not how many measurements you retrieve.

ScopeApproximate pagesApproximate runtime
50 patients, 1 month, low volume50 to 100Under 2 minutes
500 patients, 1 month500 to 1,5008 to 25 minutes
2,000 patients, 6 months12,000 or more3 hours or more

Two ways to reduce the work:

  • Narrow the window to the period where data is actually missing rather than backfilling defensively
  • Use patient-measurements rather than looping device-measurements per device

If a backfill will run for hours, make it resumable. Record which patient and which window completed successfully so an interrupted run does not start over.

Both endpoints are available in the Tenovi Postman collection with pre-configured requests. Confirm your window boundaries and response shape there before writing the loop.

Issues specific to this recipe. For platform wide behavior see Common Gotchas.

Duplicate records after backfill One component of your composite key did not match. Likely causes, in order of frequency: hwi_device_id stored in a different case or hyphenation than the API returns, timestamps normalized to a different format or timezone, or records where estimated_timestamp is true and the device clock had drifted. Compare a duplicated pair field by field to identify which component diverged.

No data returned for a device you expect to be active The device may not be assigned to the patient you queried, or the Gateway may not have connected during the window. See Gateway Connectivity Report to confirm the Gateway was reachable.