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.
Before you start
Section titled “Before you start”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.
Two endpoints, two different questions
Section titled “Two endpoints, two different questions”| Endpoint | Returns |
|---|---|
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.
Step 1: Choose your scope
Section titled “Step 1: Choose your scope”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.
Step 2: Request the window
Section titled “Step 2: Request the window”Use timestamp__gte and timestamp__lt to define the window.
GET /clients/{CLIENT_DOMAIN}/hwi/patients/{PATIENT_EXTERNAL_ID}/measurements/?timestamp__gte={ISO8601}×tamp__lt={ISO8601}&page_size=100timestamp__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×tamp__lt=2026-02-01T00:00:00Z&page_size=100This 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.
Example response
Section titled “Example response”{ "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": {} } ]}Key measurement fields
Section titled “Key measurement fields”| Field | Notes |
|---|---|
metric | The measurement type. Determines how value_1 and value_2 should be interpreted. |
value_1 | Primary value. Returned as a string. |
value_2 | Secondary value, used by metrics that report a pair such as blood pressure. Not populated for single value metrics. |
hwi_device_id | The 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_uuid | The Gateway that relayed the reading. Useful for correlating a data gap with a Gateway connectivity problem. |
timestamp | When the reading was taken. UTC, ISO 8601. Part of the reconciliation key. |
created | When Tenovi ingested the record. Not the time of the reading. Do not use for reconciliation. |
timezone_offset | Whole number offset from UTC in hours, for example 5 or -5. Reflects the patient’s local time. |
estimated_timestamp | Boolean. If true, timestamp came from the device clock rather than a verified source. See Step 3. |
sensor_code | Identifies the device type. Each device page lists its Sensor Code. See Available Devices. |
Pagination and truncation
Section titled “Pagination and truncation”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.
There is no measurement ID
Section titled “There is no measurement ID”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_idmetrictimestamp
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.
Estimated timestamps break exact matching
Section titled “Estimated timestamps break exact matching”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_timestamp | Approach |
|---|---|
false | Exact match on hwi_device_id + metric + timestamp. Skip if present, insert if not. |
true | Exact 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.
Scoping the backfill
Section titled “Scoping the backfill”At 1 request per second, runtime is a function of how many pages you request, not how many measurements you retrieve.
| Scope | Approximate pages | Approximate runtime |
|---|---|---|
| 50 patients, 1 month, low volume | 50 to 100 | Under 2 minutes |
| 500 patients, 1 month | 500 to 1,500 | 8 to 25 minutes |
| 2,000 patients, 6 months | 12,000 or more | 3 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-measurementsrather than loopingdevice-measurementsper 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.
Testing with Postman
Section titled “Testing with Postman”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.
Common Issues
Section titled “Common Issues”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.