Incremental API Syncs: Loading Only Changed Holistiplan Data
Use this guide when your integration regularly retrieves Holistiplan data and you want to avoid loading an entire firm on every run.
Why use incremental loads?
As the amount of data in a firm grows, repeatedly retrieving the entire dataset becomes increasingly inefficient. A full load requires your integration to transfer, process, and compare records that have not changed since the previous run.
An incremental load instead retrieves only the records that are new or have changed since the last successful run. This can:
- Reduce API requests and data transfer by avoiding records that have not changed.
- Improve sync performance by giving your integration fewer records to process.
- Reduce processing and storage overhead in your own systems.
- Scale more efficiently as the amount of data in a firm grows.
- Keep data synchronized more frequently because each run has less data to process.
Incremental loading is a common data integration pattern. For additional background, see Microsoft's documentation on incremental data loading or AWS's guidance on incremental data pipelines. Microsoft describes the pattern as copying only new or changed data rather than copying everything on every run, while AWS notes that incremental loading reduces the volume of data moved and processed during each load.
Recommended approach: Use a full load for your initial synchronization, then use incremental loads for subsequent runs whenever the endpoint supports an appropriate timestamp filter.
How incremental syncs workAPI version: All examples in this guide use API v2 under
/v2/. Version 1 is deprecated. Build new integrations on API v2.
For most resources, an incremental sync follows this pattern:
- Store a timestamp from your last successful sync.
- Request records using the appropriate timestamp filter and
__gte. - Retrieve all pages until a page is empty.
- Process each record using its
idas the unique identifier. - Save the newest timestamp only after the entire sync completes successfully.
- Use that timestamp as the cursor for the next run.
Important: Use __gte
Use __gte rather than __gt when filtering by your cursor.
__gte means "this timestamp or later," so the record at the exact cursor may be returned again on the next run. Your integration should safely process the same record more than once by updating the existing record using its id.
This approach helps prevent records from being missed when multiple records share the same timestamp.
Pagination
Lists are paginated using limit and offset.
- Default page size:
10 - Maximum page size:
100
Continue requesting pages until the API returns an empty page.
A delta reduces the number of records you need to retrieve, but you should still paginate through the complete result set for each run.
Saving your cursor
On each successful run:
- Keep the newest timestamp returned by the API.
- Use the timestamp returned by Holistiplan rather than the clock on your server.
- Use that timestamp as the cursor for your next run.
- Save the new cursor only after all pages have been processed successfully.
If a sync fails partway through, retry using the previous successful cursor.
Sort by the same timestamp you filter on. When the endpoint supports it, add id as a second sort field so page boundaries remain stable.
Use ISO 8601 timestamps, for example:
2026-10-01T15:30:00Z
Example
GET /api/public/v2/households/?last_updated__gte=2026-10-01T15:30:00Z&ordering=last_updated&limit=100Choosing the right timestamp
The timestamp you use depends on what you are trying to synchronize.
last_updated: The record was saved or edited.created: The record was first created.uploaded_at: A document was uploaded.action_at: An activity event occurred.created_at: A resource was created or saved, depending on the endpoint.
Choose the filter that matches the question your integration is asking.
For example, use last_updated__gte when you need both newly created and subsequently edited records. Use created__gte when you only need records that were first created during the sync window.
Incremental sync reference
| Resource | Recommended filter | Use it for | Important note |
|---|---|---|---|
| Households | last_updated__gte |
Household changes | Deletions and status changes use Household Activity |
| Clients | last_updated__gte |
Client changes | Deletions use Household Activity |
| Income Tax | last_updated__gte |
Parsed tax data | Report layout changes are separate |
| Scenario Analyses | last_updated__gte |
New and edited scenarios | created only represents the original upload |
| Tax Returns | uploaded_at__gte |
New document uploads | Poll processing IDs separately |
| Tax Reports | No delta filter | Formatted reports | Use income tax/scenario changes plus periodic full loads |
| Roth Projections | No timestamp filter | Current projections | Use a full list or filter by household |
| Tax Letter Status | No timestamp filter | Current letter status | Request by tax year |
| PDF Snapshots | No date filter | Snapshot jobs | Poll using the snapshot job ID |
| Usage | created__gte |
New usage events | Refunds require a slower full comparison |
| Household Activity | action_at__gte |
Household/client events | Event rows do not change after creation |
| User Activity Report | date__gte |
Daily advisor activity | Always request the current day again |
| Client Upload Links | created_at__gte |
New/refreshed upload links | Deleted links require ID comparison |
| Users | None | Firm setup | Full read |
| Units | None | Firm setup | Full read |
| Permission Groups | None | Firm setup | Full read |
| Subsidiaries | None | Firm setup | Full read |
| Affiliation Firms | created_at__gte for new firms |
Current affiliation data | Counts/status are calculated at request time |
Households
Endpoint: GET /households/
Filter: last_updated__gte
Use this endpoint when you maintain a CRM or warehouse copy of household information such as:
- Name
- Display name
- State
- CRM key
- External reference
- Subsidiary
- Billed status
- Primary advisor
- Tax preparer
last_updated changes when the household record is saved, including updates made through the API.
created__gte answers a different question: which households were first created during the specified window. An older household that was edited later appears in a last_updated delta but not a created delta.
Pair with
Use Households alongside Clients, Income Tax, and Tax Reports. A change to a client, tax return, or report does not update the household timestamp.
Use Household Activity to identify:
- Deletes
- Deactivations
- Reactivations
- Merges
Clients
Endpoint: GET /clients/
Filter: last_updated__gte
Use this endpoint when you synchronize client information such as:
- Name
- Birth date
- Primary contact
- Email addresses
- CRM key
- Active status
- Household association
last_updated changes when the client record is saved.
created__gte only identifies clients that were added during the specified window.
Pair with Household Activity
Use Household Activity to identify client removals.
client_created: A client was added.client_edited: A client changed.client_deleted: A client was removed.
The activity event does not contain the client's fields. For client_created and client_edited, read GET /clients/{id}/ to retrieve the current values.
A deleted client returns 404.
Income Tax
Endpoint: GET /income_tax/
Filter: last_updated__gte
Use this endpoint when you need parsed tax return data that is new or has been edited.
This is the primary feed for tax numbers.
A new return and an edited return both update last_updated.
You can also filter by:
householdyeartax_payer_1tax_payer_2
The response includes created, which represents when the source document was uploaded. This endpoint does not accept created__gte.
You do not need created__gte because last_updated__gte includes new returns.
Note: This endpoint contains filed return data. Scenarios are available through Scenario Analyses.
Pair with
Use Tax Returns when you need upload status.
Use Tax Reports when you need the client-facing formatted report.
Changes to report layout, such as showing or hiding a section, changing the font, or changing the display name, do not update last_updated on the Income Tax record.
Scenario Analyses
Endpoint: GET /scenario_analyses/
Filters:
last_updated__gtefor anything that changedcreated__gtefor scenarios whose source document was uploaded during the specified window
Use this endpoint when you store scenario results and want each sync to pick up new and edited scenarios.
last_updated changes when the scenario is saved.
created represents the original document upload time and does not change when someone later edits the scenario.
Filtering only on created can therefore cause your integration to miss scenario edits.
You can also filter by:
householdyeartax_payer_1tax_payer_2
Pair with
Use Tax Reports and the scenario's Income Tax ID when you need the formatted report.
As with Income Tax, layout-only report changes are not represented by the scenario timestamp.
Tax Returns
Endpoint: GET /tax_returns/
Filter: uploaded_at__gte
Use this endpoint to discover newly uploaded tax documents.
After identifying new uploads, read each tax return by ID until its processing status is complete.
Tax return status can include:
- Uploaded
- Processing
- Ready
- Needs review
uploaded_at represents when the document was created. It does not change when processing finishes, or when the household or tax year associated with the document is changed.
Therefore:
- Use
uploaded_at__gteto discover new uploads. - Do not use it to determine whether an existing upload finished processing.
Processing workflow
- Use
uploaded_at__gteto find new uploads. - Store the IDs that are still processing.
- Request each ID using
GET /tax_returns/{id}/. - Continue polling until the status settles.
- Once processing succeeds, retrieve parsed field values from Income Tax.
Tax Reports
Endpoint: GET /tax_reports/
Tax Reports do not support a created or updated timestamp filter.
Use the following pattern instead:
- Run an incremental sync on Income Tax or Scenario Analyses.
- Identify the Income Tax IDs that changed.
- Request the corresponding Tax Report using
GET /tax_reports/{income_tax_id}/. - Run a full Tax Reports list on a slower schedule to capture layout-only changes.
Use Tax Reports when you need the formatted report, including:
- Display name
- Font
- Layout
- Sections shown
Changing the report layout does not update the Income Tax timestamp.
The report ID is the Income Tax ID.
Roth Projections
Endpoint: GET /roth_projections/
This endpoint does not provide a created or updated timestamp filter.
Use either:
- A full list, or
- The
householdfilter when you already know which households need to be refreshed.
Use this endpoint when you need the projection name, household, and year.
The projection's detailed assumptions are not available on this endpoint.
Tax Letter Status
Endpoint: GET /tax_letter_status/
Request the list for a specific tax year:
GET /tax_letter_status/?year=2025
Use this endpoint when you need current tax letter status.
Each row includes:
tax_letter_status_last_modifiedtax_letter_sent_datetime
These timestamps are not filters. Request the relevant tax year and keep rows whose timestamps are newer than your previous run.
PDF Snapshots
Endpoints:
GET /pdf_snapshots/ GET /pdf_snapshots/{id}/
PDF snapshots use a job-based workflow rather than a timestamp-based delta.
After you queue a snapshot, use the returned snapshot_job_id to poll the job.
The workflow is:
- Queue a snapshot from the Tax Report or Roth Projection using
POST .../queue_pdf_snapshot/. - Store the returned
snapshot_job_id. - Request
GET /pdf_snapshots/{id}/. - Continue polling until
processing_statusis successful. - Download the completed PDF using
GET /pdf_snapshots/{id}/download/.
The list includes created_at and last_updated, but it does not provide a date filter.
The job ID is the reliable handle for tracking a snapshot.
Usage
Endpoint: GET /usage/
Filter: created__gte
Use this endpoint when recording upload consumption for billing or reporting.
Each row represents a usage event and is written once.
You can also filter by:
firmsourceadvisor
The advisor filter uses the advisor's username.
Update your local store using the usage row's id.
Important: Refunds
If an upload is later refunded, the original usage row is removed from the list. No new event is created to indicate the refund.
A created__gte delta therefore does not tell you about refunds.
If your integration needs to remove refunded usage, compare IDs during a slower full load.
Household Activity
Endpoint: GET /household_activity/
Filter: action_at__gte
Use Household Activity when you need an event feed for changes involving households or clients, including deletions and deactivations.
Each row represents a single event and does not change after it is created.
action_at represents when the event occurred.
You can also filter by action when you only want specific event types.
Useful sync actions
| Action | Meaning |
|---|---|
create |
Household created |
delete |
Household deleted |
deactivate |
Household deactivated |
reactivate |
Household reactivated |
merge_out |
Household moved to another firm |
details_updated |
Household details changed |
client_created |
Client added |
client_edited |
Client changed |
client_deleted |
Client removed |
tax_return_uploaded |
Tax return uploaded |
irs_transcript_uploaded |
IRS transcript uploaded |
View events such as tax_return_viewed and scenario_analysis_viewed are also included in this feed. Exclude those events from a data sync unless you are building an activity report.
Important: Household Activity tells you that a record changed. It does not contain the updated record fields. Read the corresponding Household or Client endpoint to retrieve the current values.
User Activity Report
Endpoint: GET /user_activity_report/
Filter: date__gte
Dates use the format:
YYYY-MM-DD
Use this endpoint when you want daily totals of advisor activity, including scenarios, tax reports, tax letters, and similar activity.
These rows represent totals for a date rather than individual events.
Important: Always request the current day again
Today's totals can continue increasing throughout the day.
Yesterday's totals are stable.
For this reason, even if your cursor is the last date you processed, request the current day again on every run.
The firm's User Activity Report must be enabled.
You can also filter by:
activityadvisor_id
Client Upload Links
Endpoint: GET /client_upload_links/
Filter: created_at__gte
Use this endpoint when tracking client upload links that were newly created, refreshed, or completed.
created_at changes when the link is saved. This includes:
- Issuing the link
- Refreshing the link
- The client completing the upload (
uploadedbecomestrue)
You can also filter by:
clientyear
Deleted links are removed from the list. If your integration needs to detect deleted links, compare IDs during a slower full load.
Users, Units, Permission Groups, and Subsidiaries
These endpoints represent firm configuration and do not provide a created or updated cursor.
Load the full list when your integration starts and again when firm configuration changes.
| Endpoint | What it provides | Helpful filter |
|---|---|---|
GET /users/ |
Advisors who can log in | external_key |
GET /units/ |
Units in the firm | None |
GET /firm_permission_groups/ |
Permission groups that can be assigned to users | None |
GET /subsidiaries/ |
Subsidiaries, including disclaimer text | name or firm |
Affiliation Firms
Endpoint: GET /affiliation_firms/
Use a full list when you need current:
- Household counts
- Plan
- Renewal date
- Subscription status
Use created_at__gte only when you need firms that were added during the specified window.
Counts, plan information, renewal dates, and subscription status are calculated at request time. Changes to those values do not update created_at.
This endpoint supports a page size of up to 3000, so a full refresh can usually be completed in relatively few requests.
Handling deleted and removed records
Not every resource reports deletions through the same mechanism.
Households and clients
Use Household Activity to identify deletions, deactivations, and other relevant events.
Other resources
Some resources simply disappear from their list after deletion. If your integration needs to detect those removals, periodically compare the IDs from a full load against the IDs stored in your system.
Usage refunds
Refunded usage rows disappear from the Usage endpoint without generating a new event. Use a periodic full comparison if your integration needs to detect refunds.
Recommended sync schedule for a tax integration
A typical tax-data sync might look like this:
| Data you store | Call on each incremental run |
|---|---|
| Households | GET /households/?last_updated__gte={cursor} |
| People | GET /clients/?last_updated__gte={cursor} |
| Removals and deactivations | GET /household_activity/?action_at__gte={cursor} |
| Parsed returns | GET /income_tax/?last_updated__gte={cursor} |
| Scenarios | GET /scenario_analyses/?last_updated__gte={cursor} |
| New PDF uploads | GET /tax_returns/?uploaded_at__gte={cursor}, then re-read in-progress IDs |
| Tax reports whose numbers changed | GET /tax_reports/{income_tax_id}/ for IDs from the Income Tax or Scenario Analysis run |
| Tax reports whose layout changed | Full GET /tax_reports/ on a slower schedule |
| Upload usage | GET /usage/?created__gte={cursor} |
Common mismatches to avoid
Household last_updated is not a catch-all
Household last_updated covers changes to the household record itself. Client edits, tax uploads, and report layout changes are represented separately.
Tax Return uploaded_at is not processing status
uploaded_at identifies the upload. To determine whether processing has finished, re-read the Tax Return by ID.
Parsed tax values are available through Income Tax after processing succeeds.
Scenario created is not the scenario's last change
created represents the original document upload. Use last_updated to capture later scenario edits.
Income Tax last_updated does not cover report layout
Income Tax timestamps track tax-number changes. Tax Report layout changes are separate and cannot be captured with an Income Tax delta.
For that reason, integrations that need the complete Tax Report should combine incremental Income Tax or Scenario Analysis updates with periodic full Tax Report loads.
In summary
For most resources, use the resource's update timestamp with __gte, paginate until the response is empty, and save your newest timestamp only after the sync completes successfully.
Use specialized workflows for resources that do not support incremental filters, and use Household Activity when you need to detect household or client removals.
The most important principles are:
- Use
__gtefor cursor-based syncs. - Process records idempotently using their IDs.
- Paginate through the entire result set.
- Do not advance your cursor until the run succeeds.
- Use the timestamp returned by Holistiplan, not your server's clock.
- Use the appropriate endpoint for deletions and other events.
- Use periodic full loads for resources that do not expose a reliable delta.