Core Concepts
Snapshot
A snapshot is a complete download of all currently published records for a dataset. It represents the state of the dataset at the moment of the most recently published version. When you call the snapshot endpoint, you receive:- Every record in the current version of the dataset
- A
versionobject with the version number, publish timestamp, total row count, and quote-safe count - A
syncobject containing anetag, acursorstring, and achanges_urlpointing to the delta endpoint pre-seeded with your new cursor
Delta / Changes
A delta (or changes response) contains only the records that changed after a given cursor. Rather than returning full records for unchanged rows, it returns a list ofDatasetChange objects — each carrying the record ID, the operation type, and the new payload (for upserts) or just the ID (for deletes).
Delta responses also return a latest_cursor you should store to use as the since parameter on your next poll. If has_changes is false, no records changed since your cursor — store the latest_cursor anyway because it advances through empty version windows.
Cursor
A cursor is an opaque string that represents a specific point in dataset version history. You should treat it as a token, not a timestamp or integer — its internal format may change between API versions. Cursors are stable: if you store a cursor from six months ago and pass it assince, the API returns all changes that occurred after that version. There is no cursor expiry window for standard polling intervals.
Never construct or parse a cursor value. Always store and replay the exact string returned by the API. Cursors from a
snapshot response and from a changes response are interchangeable — they reference the same version namespace.Supported Datasets
You can sync any of the following dataset keys using the snapshot and changes endpoints:| Dataset Key | Trade | Description |
|---|---|---|
finance_fees | solar, hvac, roofing, plumbing, electrical | Approved dealer-fee and finance-program rows |
finance_products | solar, hvac, roofing, plumbing, electrical | Lender and program quick-fact context |
finance_eligibility | solar, hvac, roofing, plumbing, electrical | Scoped lender/PACE eligible-product records |
equipment_pricing | solar, hvac, roofing, plumbing, electrical | Equipment and service price observations |
utility_territory | (all) | Utility providers and service territory context |
electricity_rates | (all) | Regional EIA/context electricity averages |
hvac_incentives | hvac | Program-level HVAC incentive records |
hvac_climate | hvac | County climate and weather-normal context |
hvac_labor | hvac | Labor and material adder catalog |
hvac_ahri_matches | hvac | AHRI/ENERGY STAR matched-system records |
Change Operation Types
Every entry in achanges array carries an operation field with one of three values:
| Operation | Meaning | What to do |
|---|---|---|
upsert | The record was created or updated. The payload contains the full, current record. | Insert if new, replace if existing — keyed on record_id. |
delete | The record has been removed from the published dataset. The payload is null. | Remove the record from your local store by record_id. |
supersede | The record has been replaced by a newer version. The payload contains the replacement record. | Treat identically to upsert — replace by record_id. The old record is no longer valid. |
The Sync Flow
Follow these steps to keep a local dataset store current:Fetch the snapshot
Call the snapshot endpoint for your dataset. Store all returned records in your local store (database, cache, or in-memory map).
Store the cursor
Persist
sync.cursor alongside the records. You will pass this as the since parameter on your next poll. Associate the cursor with the dataset key and trade filter you used so you can look it up correctly.Apply upserts and deletes
Iterate
changes in order. For upsert and supersede, replace the record in your local store using record_id as the key. For delete, remove the record.Using the TypeScript SDK
Thehsd-client-sdk handles snapshot fetching, cursor storage, cache management, and change application for you. You supply a cache adapter; the SDK updates it automatically on every sync call.
ETag Headers and Conditional GET
The snapshot endpoint returns anETag response header whose value matches sync.etag in the response body. On subsequent requests, you can pass this value as If-None-Match to receive a 304 Not Modified response when the dataset version has not changed since your last fetch — saving bandwidth and processing time.
304, your local store is already current. No body is returned and no cursor update is needed. Always pass the ETag value exactly as returned by the API — including the W/ prefix and the surrounding double-quotes.
ETags and the delta endpoint serve different use cases. Use ETags for snapshot polling when you want to avoid re-processing an unchanged full dataset. Use the delta endpoint when you need to know exactly which records changed and want to apply targeted updates.
Pagination on Large Change Sets
If more changes exist than the default page limit, thesync.next_url field in the changes response contains a pre-built URL for the next page. Continue following next_url until it is null, then store the final latest_cursor.
For full API parameter documentation, see the Sync Snapshot reference and Sync Changes reference.