Last reviewed: September 2026.
This reference describes what the SurgeTK Redtail integration does on the wire and how it maps incoming data. It is written for Redtail’s integration-support team and for SurgeTK administrators who need exact behavior. For how to connect, run, and troubleshoot a sync, start with Syncing with Redtail.
In short: SurgeTK only reads from Redtail. It never writes to Redtail. Every sync is started by hand. Records match by Redtail ID. Sync Filters gate new imports only. Holdings are synced. A Redtail sync can delete some SurgeTK records, and the cases are listed below.
Authentication and environments
SurgeTK uses Redtail’s public v1 API over HTTPS.
Environment | Base URL | Notes |
Production |
| The default. Chosen with Redtail Environment › Production in the Connect dialog. |
Development |
| Chosen with Development. Any stored environment value other than production uses this URL. |
Credentials
The Connect dialog asks for Redtail Username and Redtail Password. SurgeTK then sends one
GET /authenticationwith HTTP Basic authentication.The Basic credential is the triple
apiKey:username:password, base64-encoded.apiKeyis SurgeTK’s own Redtail API key for that environment. Firms never see or enter it. There is one key for Production and one for Development.On success SurgeTK stores the returned user key (
authenticated_user.user_key).Every later call sends
Authorization: Basic base64(apiKey:username:password)and auserkeyrequest header with the stored user key. The Basic value is rebuilt for each sync from the decrypted password.The password is encrypted at rest with AES-256-GCM. A random 16-byte IV and the authentication tag are stored with the ciphertext. The username, the user key, and the environment are stored unencrypted, per firm.
Timeout: 30 seconds per request, for
/authenticationand for every call made during a sync. The three filter option lists use a 15-second timeout and are not retried.Credentials are validated only when a firm enters them (Connect or Reconnect). SurgeTK does not probe credentials between syncs. A sync reuses the stored user key. If Redtail answers 401 on any call during a sync, the run is marked failed and the firm must reconnect. See Errors during a sync below.
Connect-time errors
For every connect failure SurgeTK answers the browser with HTTP 500 and a message. The browser shows "Error connecting to Redtail: " followed by the message. The message text is the only part that tells the causes apart.
Redtail response | Displayed message |
401 whose body message contains "Gateway Time-out" | "Redtail API is currently unavailable. Please try again later." |
401 whose body message contains "Account Locked" | "Your Redtail account is locked. Please contact Redtail support or try again later." |
Any other 401 | "Invalid Redtail credentials. Please verify your username/password." |
No answer within 30 seconds | "Request to Redtail API timed out. Please try again later." |
Any other HTTP status | "Redtail API error: <status> - <message>". The message is the |
No HTTP response (network error) | "Could not connect to Redtail API: <error>" |
Environment, username, or password missing from the request | "Missing fields" (HTTP 400) |
SurgeTK has no API key configured for that environment | "Redtail API key is not configured for this environment." |
On success the browser shows "Redtail connected successfully!" and reloads the page.
Errors during a sync
A running sync classifies its own outcome with different wording. The detail in parentheses is the HTTP status and message of the first failed call.
Condition | Outcome | Message |
Any call answered 401 | failed | "Redtail rejected the saved login (HTTP 401 — <message>). Reconnect Redtail with updated credentials to resume syncing." |
Every call failed and none succeeded | failed | "Could not reach Redtail (<detail>). No data was synced." |
Some records could not be read or saved | partial | "Sync finished, but some data could not be retrieved (<detail>)." |
A 403 on a single record does not count as a credential failure. Only 401 does.
After a credential failure the Integrations card shows Action needed and "Redtail rejected the saved login (<error>). Syncing is paused — reconnect with your current Redtail credentials to resume." The progress card offers Reconnect Redtail with the hint "Update your Redtail password if it changed, then reconnect below. No data was lost."
The server does not block a new sync in this state. The Sync button still starts one. A sync started with rejected credentials fails again with the same 401 message.
Reconnecting replaces the stored credentials, fetches a new user key, clears the credential error, and clears the sync watermark. The next sync is a full read. Sync Filters are kept.
Permissions matrix
"Admin-level" means the Admin role, or any role that was given the admin sub-permission.
Operation | Required |
View connection status ("Connected", "Not connected", "Action needed") and "Last successful sync" | Any signed-in user |
Connect, reconnect, disconnect | Admin-level |
Start a sync, cancel a sync | Admin-level |
Open or save Sync Filters | Admin-level |
Refresh the filter option lists from Redtail | Admin-level |
Link Redtail advisors to SurgeTK users (Settings › Team Members) | Admin-level |
The controls are visible to every role: the Redtail toggle, Connect, Sync Filters, Sync (on the card and in the header), and Cancel or Retry Sync on the progress card. The server refuses a non-admin click with HTTP 403 and the message "Forbidden: This action requires administrator access. You have <level> access." (for example "lead advisor access"). For Sync the browser shows that text raw, for example
Error: {"message":"Forbidden: …"}.Rate limit: one user can start at most 10 syncs in 5 minutes. Further attempts get HTTP 429 with "Too many requests. Please slow down and try again."
Every user in the firm sees the progress card and the sync toasts, whoever started the sync.
Endpoints SurgeTK calls
Every call is a GET. SurgeTK never writes to Redtail. Paths below are relative to the base URL.
Pagination and includes: list calls send pagesize: 200 as a request header, not as a query parameter, because Redtail ignores page_size and include as query parameters. The include list is also a header. The page number is the page query parameter. The phones and emails fallbacks are the exception: they send page_size=200 as a query parameter.
Endpoint | When it is called | Notes |
| Connect and Reconnect only. | Basic auth only, no |
| When the Sync Filters dialog opens and the cached lists are older than 24 hours. On Refresh (cache bypassed). At the start of every sync ("Fetching Redtail settings...") when the cache is older than 24 hours. | Cached 24 hours per firm. Items flagged |
| During a sync, the first time an advisor ID must be resolved to a name. | Cached for the rest of the run. |
| Step 2, contacts. | Headers |
| Fallback only. Phones: when the included phones list was empty. Emails: when the included emails list was empty, or no email was resolved. | Query-parameter pagination. |
| Step 3, on every sync. | Never incremental. Page 1 is also read once before the run to count families. |
| Start of step 4, on every sync: a full enumeration of every contact. | Only |
| Step 4, for each contact that has a SurgeTK client record. Also for a contact with no client record that passes the filters, but then only for a comparison log; nothing is saved. | Holdings arrive embedded in each account’s |
| Step 4, once per account. | Detail enrichment: number, balance, type, custodian, feed fields, |
| Step 4, inside the detail call, for systematic-withdrawal custom fields. | Both answer 404 on the current API. After the first account where both answer 404, SurgeTK skips them for the rest of the run. |
| Step 4, only when the account reads as closed, has no close date yet, and the firm is not in delete mode. | Close-date probe. The first 404 switches it off for the rest of the run. Three consecutive other failures also switch it off. |
| Step 4, only when no systematic-withdrawal amount and frequency were found and the account has no stored withdrawals. |
|
| Step 4, after each account is saved. | A failure is not fatal. The result replaces the account’s beneficiary tiers. See Beneficiary under Field mappings. |
Retries and backoff
GET and HEAD requests are retried on HTTP 429, 500, 502, 503, and 504.
Up to 5 retries per request. The wait before retry N is
min(1000 × 2^N, 15000)milliseconds plus a random 0 to 500 milliseconds: about 2 s, 4 s, 8 s, 15 s, 15 s.Each request times out after 30 seconds.
The three option-list calls (
/lists/contact_categories,/lists/contact_statuses,/lists/account_types) use a 15-second timeout and are not retried.
Order of calls in one sync
"Preparing your sync...", then "Counting contacts..." (one contacts page) and "Counting households..." (one families page), then "Fetching Redtail settings..." (the option lists, or the cache).
"Getting your contacts...": the contact pages, plus phone and email fallbacks when needed.
"Organizing households...": the family pages. Then the family pass, the repair of broken household links, solo households, and the household lifecycle pass. These need no new Redtail calls, except phone and email fallbacks for family members being imported.
"Pulling account data...": the closed-account sweep (database only, delete mode only), the full contact enumeration, then for each contact its account list, and for each account the detail call, the conditional calls, and the beneficiaries call. The card shows "Pulling account data... (N/M contacts)" during the enumeration.
"Wrapping things up...": outcome, watermark, history record, notifications.
Endpoints SurgeTK does not call
Contact notes, activities, tasks, or workflows.
Contact-level user-defined fields. Only the two account-level UDF endpoints above are tried.
Contact photos.
GET /contacts/{id}/photoexists in the integration but is disabled.Insurance or policy endpoints. The sync creates no insurance records. Everything Redtail returns as an account becomes a SurgeTK account.
Cost basis, tax lot, or performance endpoints.
Any POST, PUT, PATCH, or DELETE. Nothing is written back.
Webhooks. SurgeTK does not register or consume Redtail webhooks.
Sync mechanics
Trigger and queue
Every sync is manual. Sync in the header or on the Integrations card opens Confirm Redtail Sync. Sync Now starts it. There is no schedule, no auto-sync setting, and no webhook trigger.
The request claims the firm’s writer lock, stores the Re-read every contact flag when it was ticked, then queues a job for the background CRM sync workers. Each worker runs one sync at a time, so a queued sync can wait behind other firms’ syncs.
One lock per firm is shared by Redtail syncs, Zoho syncs, and spreadsheet imports. A second request while the lock is held gets HTTP 409 with "A Redtail sync is already in progress. Please wait for it to finish." The first words change to "A Zoho sync", "A data import", or "Another operation" depending on what holds the lock. The browser shows this JSON raw.
A spreadsheet import started during a sync gets HTTP 409 with "A Redtail sync is currently running for your firm. Please wait for it to finish before starting an import."
During a sync, edit forms in SurgeTK warn: "Heads up — a sync is running. Your changes might get overwritten when it finishes."
Heartbeat, stale locks, and crash recovery
The worker writes a heartbeat every 30 seconds while the sync runs.
A sweep runs every 60 seconds on each web server. It clears any running sync whose heartbeat is older than 2 minutes and records "Sync interrupted by server restart". A crashed sync therefore releases the lock about 2 to 3 minutes after the crash. The one-time sweep at server start uses a 5-minute cutoff.
A queued sync that no worker picks up is cleared after 60 minutes with "Sync never started — the sync queue appears to be stalled".
A job whose worker died is re-run automatically about 10 minutes later, when its queue lock expires. The retry re-acquires the firm lock and runs visibly.
Cancel on a sync whose heartbeat is older than 2 minutes force-clears it with "Sync cancelled — process was unresponsive".
In the browser, after 20 minutes without an update the card shows "Sync may have stalled" and "No updates received for 20 minutes", with Retry Sync and Dismiss. This is a display watchdog only. The server-side sync may still be running.
Progress events
The server sends the Socket.IO event
syncStateChangeto every user of the firm. It carries the sync state: the active flag, type, phase, percent, step, message, record counts, and the last result. It drives the progress card: "Redtail Sync in progress", "Running for Xm Ys", "Step X of 5", "N of M", a progress bar, and Cancel. No percentage number is displayed.Step messages: "Preparing your sync...", "Counting contacts...", "Counting households...", "Fetching Redtail settings...", "Getting your contacts...", "Organizing households...", "Pulling account data...", "Pulling account data... (N/M contacts)", "Wrapping things up...".
A second event,
redtailSyncProgresswithphaseandpercent, goes to the triggering user’s room. Nothing in the front end listens to it.Toasts for everyone in the firm: "Redtail sync started" with "Syncing data from Redtail. You can continue working."; then "Redtail sync complete", "Redtail sync finished with issues", "Redtail sync failed", or "Redtail sync cancelled".
Outcomes
Outcome | When | What users see |
completed | No 401, at least one successful call, and no record errors. | "Sync complete" or "Sync complete — no changes found". Toast "Redtail sync complete". The watermark advances. |
partial | At least one record error: a contact, family, or account that could not be read or saved. | "Sync finished with issues". Admins get the notification "Redtail sync completed with issues". The watermark does not advance. |
failed | Any 401, or no successful call at all, or an unexpected crash. | "Sync failed", or "Redtail needs to be reconnected" after a 401. Admins get the notification "Redtail sync failed". The watermark does not advance. |
cancelled | The user cancelled. | "Sync cancelled". Toast "Redtail sync cancelled" with "The sync was cancelled. No completion time was recorded." The watermark does not advance. |
One bad record never stops the run. Errors are counted per record and the sync continues.
After a completed sync a banner reads "New data is available from the latest sync." with a Refresh button.
The sync history is on the Imports page. A cancelled run is recorded there with the status failed.
Cancellation
Cancellation is cooperative. The dialog reads "Cancel sync?" and "The sync will stop after the current step finishes. Data already synced will be kept.", with Keep syncing and Cancel sync. The button then reads "Cancelling…".
The cancel flag is checked at these points: after the contacts step, after the families step, before the household lifecycle pass, before the account step, every 25 contacts during the account enumeration, and before each batch of 100 account saves. It is not checked inside the contacts or families page loops.
Records already written stay. There is no rollback.
Afterwards the card shows "Sync cancelled" with Retry Sync and Dismiss.
Concurrency inside a sync
Contacts and families are processed page by page, one record at a time.
In the account step, contacts are enumerated one at a time, and each contact’s account list is fetched in turn. Account saves (the detail call, the conditional calls, the save, and the beneficiaries call) run 5 at a time, in batches of 100, with a cancel check before each batch.
Full and incremental reads
Only the contacts step can be incremental. Families are re-read on every sync. Every contact’s accounts are re-read on every sync.
Incremental: when a watermark exists, contacts are read with
GET /contacts/search?updated_since=<watermark>. Full: no watermark (first sync, after Connect or Reconnect, after Disconnect), or Re-read every contact ticked.The watermark is the time the run finished. It is written only when the outcome is completed, at least one Redtail call succeeded, and no call answered 401. Partial, failed, and cancelled runs keep the old watermark, so the missed window is read again next time.
Because the watermark is the completion time, a contact edited in Redtail while the sync was running can be missed. It is read again when it changes again, or on a full read.
Re-read every contact is a one-shot flag stored on the firm. Only a cleanly completed run clears it. A partial, failed, or cancelled full run leaves it set, so the next sync is full too. The checkbox is cleared each time the dialog opens. Saving Sync Filters does not set the flag; the dialog only recommends it.
A full read re-creates clients and households the firm deleted. SurgeTK keeps no record of a deliberate deletion. An incremental run also re-creates a deleted client when that contact changes in Redtail.
Filter evaluation
Filters are stored per firm and applied while contacts are read. Contact Categories and Contact Statuses share one structure: { mode: "allowlist" | "denylist", types: string[] }. Defaults: categories in allowlist mode with no entries; statuses in denylist mode with no entries.
Category and status rules
With entries, matching is exact, case-insensitive, and trimmed on both sides. Allowlist: the value must equal one entry. Denylist: the value must equal no entry.
Categories with no entries: the smart default applies in both modes, and the allowlist/denylist switch is ignored. The category must contain the text
clientand must not containex-clientortrust client. This is a substring test: "A Client", "Personal Client", "Former Client", "Prospective Client", "Non-Client", and "Ex Client" (no hyphen) pass. "Ex-Client" and "Trust Client" do not. A blank category fails.Statuses with no entries: every status passes.
Blank values with entries: an allowlist excludes a blank category or status; a denylist includes it.
Both gates must pass. The category gate runs first.
Category source:
category, thencategory_type, thencontact_record_type(first non-empty). Status source:status, thenstatus_name.Hard filters, in every mode: a contact whose
typeis exactly "Business", and a contact withdeletedset to true, never sync. Accounts of Business contacts are never fetched.Filters gate new imports only. A contact that already has a SurgeTK client record (matched by Redtail ID) keeps updating when it is read, even when it now fails the filter. Its accounts are still fetched. A filter change never deletes anything.
Family pass
After the households step, SurgeTK re-examines the contacts the filters excluded in this run.
Candidates: contacts read in this run that failed the category or status gate. On an incremental run these are only contacts changed since the watermark.
The contact must belong to a Redtail family (from
GET /families?family_members=true) that already contains at least one SurgeTK client.The category filter is bypassed completely, even for a category the firm deny-listed.
The status filter is applied only in denylist mode. An allowlist is not applied to family members. A member that already has a client record is updated even if its status is deny-listed.
Every family member qualifies: spouses, children, grandchildren, parents. There is no relationship-type filter. A status denylist is the only way to keep a family member out. See Why do children or other family members appear in my households?.
Business and deleted contacts stay excluded.
Imported members are attached to the family’s household, and their accounts are fetched in the account step.
Closed Accounts
Setting shape: { enabled: boolean, action: "close" | "delete" }. A stored thresholdMonths value (default 12) exists for backward compatibility; nothing reads it. There is no age threshold.
Effective action: an explicit
actionwins. With no stored action,enabledtrue means delete andenabledfalse means close. The default is disabled, so mark closed. In the Sync Filters dialog the choices are Mark closed ("Closed accounts stay in SurgeTK, marked closed. They leave AUM and Value Adds but keep their history. Manual and imported statuses always win.") and Delete (legacy) ("Closed accounts are removed from SurgeTK on every sync. Accounts you closed by hand or by import are never removed.").Closed test: an account counts as closed when its normalized status is Closed, Transferred, or Inactive, or when it has any close date. A status-only closure with no date counts. Redtail statuses containing dormant or suspended normalize to Inactive, so they count too.
In both modes, an account that reads as closed is left out of AUM totals and Value Add reports. The Hide closed accounts switch is a display preference for household account lists only.
Mark closed mode: the account is saved with its status and close date. A change from open to closed, or from closed to open, stamps the status source as redtail and writes one audit entry. A status set by hand or by a spreadsheet import (status source manual or import) is never overwritten. SurgeTK still records what Redtail said in the raw status field, so the account page can show the disagreement.
Reopen rule: only a Redtail status that normalizes to Active clears a stored close date. Pending, Other, and blank statuses never reopen a closed account. A close date from Redtail always wins.
Delete mode, before accounts are fetched: every stored Redtail-linked account that reads as closed is permanently deleted in bulk, removed from its household, and the household totals are recalculated.
Delete mode, per account: each account Redtail reports as closed is deleted if it exists in SurgeTK, and it is not imported.
Delete mode exemptions: accounts whose status was set by hand or by import are kept and still updated (except their status and close date). Accounts with no Redtail account ID (created by hand or by spreadsheet) are never touched. A Redtail account later updated by a spreadsheet import keeps its Redtail ID and is not exempt.
Close date sources, in order:
close_date,closed_date,date_closed,termination_date, read from the account list item, then fromGET /accounts/{id}. If there is still no date, the account reads as closed, and the firm is not in delete mode, SurgeTK callsGET /accounts/{id}/detailsonce for that account. That probe stops for the rest of the run after the first 404, or after three consecutive other failures. Dates are parsed as standard dates first, then as 8-digitYYYYMMDDorMMDDYYYYstrings. A year outside 1900 to 2100 is dropped.
Household Lifecycle from Redtail
Modes: Off (the default), Suggest, Automatic. Dialog descriptions: "SurgeTK never changes household status from Redtail." / "Syncs suggest archiving households whose members are all deceased or no longer clients. You review each suggestion on the Households page." / "Syncs archive those households automatically, capturing final Value Add snapshots. A status you set by hand always wins, and restoring is one click."
Status map: each Redtail contact status can be set to Default, Active client, No longer a client, or Deceased. Entries match by status ID first, then by name (case-insensitive, trimmed).
Default reading for unmapped statuses: a status matching
/deceased|dead|death/ireads as deceased; a status matching/inactive|former|ex-|terminated|archived|closed|estate/ireads as no longer a client. The deceased test runs first. A blank status has no opinion.Client flag: each time a contact is read, its deceased/living flag is rewritten from this reading to "Deceased" or "Living". A flag set by hand flips back to "Living" unless the Redtail status reads as deceased.
The household pass runs after the households step, for households with at least one Redtail-synced member. All members deceased: archive, reason deceased. All members deceased or no longer a client, with at least one merely no longer a client: archive, reason terminated. Any other member, including a member added by hand, keeps the household active.
Suggest: writes a suggestion on the household. The Households page shows "Redtail suggests archiving this household" and the row menu offers "Apply Redtail suggestion…". A dismissed suggestion is not raised again while the reading is unchanged. Suggestions that no longer apply are removed.
Automatic: archives at most 25 households per sync, minting final Value Add snapshots, and restores a household it archived itself when the reading returns to active. A household status set by hand or by import always wins. Setting the mode to Off removes outstanding suggestions. See Archiving households and closing accounts.
Deletions the sync can make
Closed accounts in delete mode, as above.
Emptied households: when a client is moved into its Redtail family’s household, the household it leaves is permanently deleted if no clients remain in it, unless that household is archived. This runs every sync and applies to any household, not only solo households. Records still attached to the deleted household are not moved by that step. Redtail-linked accounts are re-pointed to the new household in the account step.
Household archiving in Automatic mode, as above.
Broken links: a client whose household no longer exists loses the link and gets a solo household.
The sync never deletes a client. Contacts deleted in Redtail are skipped, not purged. Accounts that no longer appear in Redtail are only logged, never deleted. A filter change never deletes anything.
Field mappings
"Overwritten" means the Redtail value replaces the SurgeTK value on every read, including a blank. "Only when present" means a blank Redtail value leaves the stored value alone.
Contact to Client
Redtail field | SurgeTK field | Rule |
| Redtail ID | Integer. Match key together with the firm. |
| First, middle, last name | Trimmed. Overwritten. A blank first or last name is stored as "(Unknown)". Both blank: "Unknown" "Client". A real name in Redtail replaces the placeholder on the next read. |
| Date of birth | Overwritten. Blank in Redtail clears the stored date. |
| Marital status | Contains married: Married. Contains widowed: Widowed. Contains divorced: Divorced. Any other non-blank value: Single. Blank: blank. Overwritten. |
| The entry with | |
| Mobile number, home phone |
|
| Home address | The first entry, not the one marked primary. |
| Gender | male, female, or other. See Normalizers. Only when present and recognized. |
| Occupation | First non-empty. Only when present. |
| Employer | First non-empty. Only when present. Never for a Business contact. |
| Redtail contact type | As sent (Individual, Business, and so on). Only when present. |
| Redtail category | First non-empty. Only when present. |
| Redtail status; status ID | A blank status sent explicitly clears the stored status. A missing field leaves it alone. |
Derived from the status | Deceased/Living flag | Rewritten on every read. See Household Lifecycle from Redtail. |
| Date of death | Only when present and valid. |
| Termination date | Only when present and valid. |
| Contact-level advisor IDs | Used as the household fallback when the family carries no advisor. |
Not stored from a contact: family name, notes, activities, photos, extra phones, extra emails, extra addresses, retirement date, and contact-level custom fields.
Family to Household
Redtail field | SurgeTK field | Rule |
| Redtail family ID | Match key together with the firm. A household is created only when at least one family member is already a SurgeTK client. |
| Household membership | Every member with a client record is pointed at this household on every sync. A member moved to another household by hand is moved back. |
| Head of household | Each flagged member is set in turn, so the last flagged member wins. |
| Household advisor IDs | Family-level IDs first. If the family carries none, the contact-level IDs of the first member that has one. When the Redtail advisor is linked to a SurgeTK user, that user becomes the servicing or writing lead advisor and is added to the household’s lead advisors (added, never removed). |
(none) | Owner | Set to the user who ran the sync, on every sync. |
(none) | Household name | Not stored from Redtail. SurgeTK derives the display name from the first two clients sorted by last name, then first name. The head of household is used only when the household has no clients. |
A contact in no Redtail family gets a solo household whose Redtail family ID is
SOLO-<contactId>.Household membership follows Redtail. Moving a Redtail family member, or a Redtail account, to another household by hand is undone on the next sync.
Account to Account
Redtail field | SurgeTK field | Rule |
| Redtail account ID | Match key together with the firm. |
| Account number | Stored as sent. The detail value wins over the list value. Blank: "Unknown Number". Overwritten. |
| Account value | Parsed as a number. Blank or unreadable: the stored value is cleared, not set to 0. Overwritten. |
| As-of date | List value, then detail value. Holdings price dates override it (the latest |
| Account type; raw account type | Normalized. See Normalizers. |
| Tax status | Normalized. See Normalizers. |
| Custodian; raw custodian | Normalized. The value as sent is kept in the raw field. |
| Feed source, feed account number, registration, product | Trimmed strings. Overwritten. |
| Held away, managed, discretionary | Only when Redtail sends a boolean. |
| Account status; raw account status | Normalized. The raw status is refreshed on every sync. The normalized status is written unless the account’s status is protected. See Closed Accounts. |
| Date closed | See Closed Accounts. |
| Systematic withdrawals | See Systematic-withdrawal resolution order. |
| Federal and state withholding | Only when present on the detail payload. |
| Holdings | See the holdings rules below. |
(the contact) | Account owners | The contact’s client is added to the owners. Owners are never removed by the sync. |
(the contact’s household) | Household | Set to the owning client’s household on every sync. The household’s account list and total value are recalculated on save, and its Value Adds are marked stale. |
Holdings, one per assets[] entry: id to Redtail asset ID; ticker to symbol; cusip to CUSIP; name to security name; asset_type to security type; shares and shares_asof; price and price_asof; balance to market value. The holdings list is replaced in full on every sync when the assets=true call succeeded. When the fallback list without assets was used, existing holdings are kept. The account’s holdings date is the latest price_asof that parses, else the sync time. Cost basis and tax lots are not read. Holdings are read-only in SurgeTK.
Allocation rollup (the Buckets split): each holding is classified in this order: a firm override row (Settings › Securities), a global seed row (a short list of common ETFs and index funds), then name and ticker rules (annuities first, then cash including money-market tickers ending in XX, then income, then growth). When 100 percent of the holdings value is classified and the account’s split is not marked manual, SurgeTK writes the cash, income, annuities, and growth percentages with the source holdings. A holding left unclassified appears on Settings › Securities (admins only). An account with no holdings, no allocation, and an account type containing annuit gets 100 percent annuities. Any manual edit of the split marks it manual and stops automatic updates.
Beneficiary
Redtail field | SurgeTK field | Rule |
| First name, last name | Split on whitespace: the first word is the first name; the rest is the last name. A one-word name gets a single space as the last name. A blank name becomes "Unnamed Beneficiary". |
| Date of birth | Only when valid. Part of the match key when present. |
| Relationship | Written only when the beneficiary record is first created. Later changes in Redtail do not update it. |
| Tier | Exactly "primary" (case-insensitive): the primary tier. Any other value, including blank: the contingent tier. |
| Percentage allocation | Parsed as a number. 0 when unreadable. |
Beneficiary records are firm-wide and shared across accounts. The upsert key is the firm, plus first name and last name (compared case-insensitively), plus date of birth when Redtail supplies one. The unique index is (firmId, firstName, lastName, dateOfBirth).
The account’s primary and contingent tiers are replaced on every sync. When Redtail returns no beneficiaries, or the beneficiaries call fails, both tiers are emptied. Beneficiaries added by hand on a Redtail-synced account are therefore replaced.
The per stirpes flag and the "intentionally none" flag on the account are never touched by the sync.
Advisor to RedtailAdvisor
Redtail field | SurgeTK field | Rule |
| Redtail advisor ID | Match key together with the firm. A record is created the first time an ID is seen. |
| Advisor name | Matched by |
(derived) | Type | servicing, writing, or both when the same ID is seen in both roles. |
(none) | Linked user | Empty until an admin links the advisor under Settings › Team Members › Unlinked Redtail Advisors. Linking a servicing advisor updates every household with that servicing ID immediately. Linking a writing advisor takes effect on the next sync, for the households that sync touches. |
Systematic-withdrawal resolution order
For each account SurgeTK resolves one amount and one frequency in this order. Later steps override earlier ones as stated.
Start with
systematic_withdraw_amountandsystematic_withdraw_frequencyfrom the account list item (GET /contacts/{id}/accounts).If the amount is still empty or 0 and
GET /accounts/{id}returned apaymentblock, take the monthly figure from the first present ofpayment.withdrawals_monthly,payment.withdrawal_monthly,payment.monthly_withdrawals,payment.monthly. If the frequency is still blank, set it to Monthly.Account custom fields fill whatever is still empty: the amount from a field named
systematic withdrawal amount,systematic withdrawals amount, orsystematic wd amount; the frequency fromsystematic withdrawal frequency,systematic withdrawals frequency, orsystematic wd frequency(names compared case-insensitively). This step runs only whenGET /accounts/{id}succeeded. It finds nothing while the UDF endpoints answer 404.systematic_withdraw_amountandsystematic_withdraw_frequencyfromGET /accounts/{id}then override steps 2 and 3 whenever the key exists in the detail payload, even when its value is empty.payment.premium_frequency(1 Monthly, 2 Quarterly, 3 Semi-annual, 4 Annually; other values are ignored) andpayment.withdraw_amountfromGET /accounts/{id}override everything when present.Only when no amount-and-frequency pair resulted, and the account has no stored withdrawals, SurgeTK infers from
GET /accounts/{id}/transactions. See the rules below.
A pair is stored only when both the amount and the frequency are present. The amount may be 0. The frequency then goes through the frequency normalizer.
A non-empty result replaces the stored withdrawals, including withdrawals entered by hand. An empty result never clears stored withdrawals.
Transaction inference rules
Look-back: transactions with
date_fromset to 540 days before the sync.A transaction counts as a withdrawal when its
type,action, ortransaction_type_descriptioncontains withdraw, distribution, dist, rmd, swd, or systematic; or itsdescriptioncontains withdraw or distribution; or its amount (amount,net_amount, ortotal) is negative. Any one condition is enough.Date:
date,trade_date,settlement_date,posted_on, orcreated_on, first present.At least 3 withdrawals are needed. They are grouped by absolute amount to the cent. A group needs at least 3 entries.
Frequency from the median gap between consecutive dates in the group: 45 days or less is Monthly; 120 or less is Quarterly; 220 or less is Semi-annual; otherwise Annually.
One stream per frequency is kept: the largest amount wins.
A stream whose annualized total exceeds the account value is dropped (when the value is above 0).
The transactions endpoint is switched off for the rest of the run after a 404 on page 1.
Normalizers
Normalizer | Input | Rule |
Account type |
| Trimmed. First an exact, case-insensitive match against the 166 registered type names (158 unique). Else the first registered name, in list order, that appears inside the Redtail text. Else Other. Blank: Other. |
Tax status |
| Contains roth: Tax-Free. Else contains 401, 403, 457, traditional, sep, simple, pension, profit, or ira: Tax-Deferred. Else contains 529, hsa, or fsa: Tax-Exempt. Else Taxable. |
Custodian |
| Trimmed. Blank: UnknownCustodian. Case-insensitive substring: contains Charles Schwab or Schwab Advisor Services: Schwab; Fidelity: Fidelity; Pershing: Pershing; TD Ameritrade: TD Ameritrade. Anything else is stored as sent. "Fidelity Investments" becomes "Fidelity"; "Altruist" stays "Altruist". |
Account status |
| Trimmed. Blank stays blank. Checked in this order: closed or terminated: Closed; transfer: Transferred; inactive, dormant, or suspended: Inactive; active, funded, or open: Active; pending, new, application, or in process: Pending; else Other. |
Balance |
| Parsed as a number. Not a finite number: left empty. |
As-of date |
| New account with no date: the sync time. Existing account with no date: unchanged. Holdings price dates override. |
Frequency |
| Lower-cased exact match on monthly, quarterly, semiannual, semi-annual, or annual. Output: Monthly, Quarterly, Semi-annual, Annually. Any other value is not mapped. |
Gender |
| m, male, man: male. f, female, woman: female. non-binary, nonbinary, nb, x, other, unknown, unspecified, decline, prefer not to say: other. Anything else: not written. |
Marital status |
| See Contact to Client. |
Dates on holdings and close dates |
| Standard date-time stamps parse directly. 8-digit strings are read as |
Deduplication keys
SurgeTK record | Key | Index rule |
Client | (firmId, redtailId) | Unique when |
Household | (firmId, redtailFamilyId) | Unique when present and not null. Solo households use |
Account | (firmId, redtailAccountId) | Unique when |
Beneficiary | (firmId, firstName, lastName, dateOfBirth) | Unique, sparse. The sync matches names case-insensitively before it inserts. |
RedtailAdvisor | (firmId, redtailAdvisorId) | Unique, sparse. |
Matching is by Redtail ID only. Clients, households, and accounts created by spreadsheet import before Redtail was connected are not merged. The sync creates a second record.
The keys do not include the environment or the Redtail database. Switching between Production and Development, or between Redtail databases, can attach records to the wrong Redtail IDs.
Reconnecting to the same Redtail database matches the existing records. No duplicates are created.
Disconnect and reconnect
Disconnect dialog: "Disconnect Redtail?" with "Disconnecting will remove all saved Redtail credentials from SurgeTK. All data imported from Redtail will remain. You can reconnect later by toggling Redtail back on and re-entering your credentials." Buttons Cancel and Disconnect. Toast: "Successfully disconnected Redtail."
What Disconnect clears: the API key, user key, username, encrypted password, environment, and the sync watermark. It also clears everything else in the firm’s Redtail settings: the Contact Categories and Contact Statuses filters, the Closed Accounts setting, Hide closed accounts, the status map, the Household Lifecycle mode, the cached option lists, the credential status, and the Re-read every contact flag.
What Disconnect keeps: every synced client, household, account, holding, and beneficiary, with their Redtail IDs; advisor records and advisor links.
Reconnect without disconnecting (Connect again, or Reconnect Redtail after a credential failure): merges into the existing settings. Filters are kept. A new user key is fetched, the credential error is cleared, and the watermark is cleared, so the next sync is a full read.
After Disconnect and then Connect: the filters are back to their defaults, the next sync is a full read, existing records match by Redtail ID, and households or clients the firm deleted come back.
Cascade behavior on household deletion
Purge guard: if the households hold Value Add snapshots or surge packets, the server refuses with HTTP 409. The user sees "Delete permanently?" with a message such as "2 Value Add snapshots and 1 surge packet become unreachable if this household is deleted. Archiving keeps everything and hides it from lists." and the buttons Archive instead and Delete anyway. For a household that is already archived, the dialog offers only Delete anyway.
Deleted with the household: its clients; every account in the household or owned by those clients, including accounts that sit in other households; liabilities in the household or owned by those clients; assets owned by those clients; insurance that belongs to the household or where a client is the owner or the insured; the household’s Value Adds and their snapshots; unlinked Redtail advisor directory rows referenced by the household or its clients (linked advisors stay).
Not deleted: Beneficiary records (they live in the firm-wide list); surge packets (left behind, no longer reachable from a household).
A later full read, or a change to one of those contacts in Redtail, re-creates the household, its clients, and its accounts. No index residue blocks the re-import.
Deleting a client is blocked while linked data exists: "Cannot delete client — linked financial data exists. Please reassign or delete their accounts, liabilities, assets, or insurance policies first." Deleting a household’s only client deletes the household.
Known limitations to tell Redtail
Frequency values: SurgeTK maps only monthly, quarterly, semiannual, semi-annual, and annual (case-insensitive).
annualis the only recognized yearly value. Other spellings are not mapped.systematic_withdraw_amount,systematic_withdraw_frequency, and thepaymentblock may be absent from account payloads on the current API. SurgeTK then depends on the transactions endpoint, which is switched off after a 404.GET /accounts/{id}/user_defined_fieldsandGET /accounts/{id}/udfsanswer 404 on the current API (verified July 2026). SurgeTK probes them once per run and then skips them.GET /accounts/{id}/detailsandGET /accounts/{id}/transactionsare called conditionally, each behind a circuit breaker. SurgeTK has not confirmed what they return on every tenant.includeandpagesizeare honored only as request headers.updated_sinceis honored only on/contacts/search. SurgeTK is built for this.Everything returned by
GET /contacts/{id}/accountsbecomes a SurgeTK account, whatever its type. Insurance products returned there become accounts.companyis usually empty on feed-linked accounts. SurgeTK usesfeed_aggregatoras the custodian.Only
beneficiary_type_descriptionexactly "primary" reaches the primary tier.Contacts flagged
deletedare skipped but never purged in SurgeTK.Redtail account statuses containing dormant or suspended read as Inactive, which counts as closed.
No webhooks. SurgeTK reads on demand only.
