Skip to main content

Redtail integration: technical reference

What the SurgeTK Redtail integration does on the wire: authentication, endpoints, sync mechanics, filters, field mappings, normalizers, match keys, and known limitations.

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

https://crm.redtailtechnology.com/api/public/v1

The default. Chosen with Redtail Environment › Production in the Connect dialog.

Development

https://review.crm.redtailtechnology.com/api/public/v1

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 /authentication with HTTP Basic authentication.

  • The Basic credential is the triple apiKey:username:password, base64-encoded. apiKey is 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 a userkey request 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 /authentication and 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 message field of Redtail’s body, else "Unknown error".

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

GET /authentication

Connect and Reconnect only.

Basic auth only, no userkey header. Returns the user key.

GET /lists/contact_categories, GET /lists/contact_statuses, GET /lists/account_types

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 deleted are dropped. account_types is fetched and counted only; it is never used for mapping. If a list comes back empty, the previous cached list is kept.

GET /lists/servicing_advisors, GET /lists/writing_advisors

During a sync, the first time an advisor ID must be resolved to a name.

Cached for the rest of the run.

GET /contacts?page=N (full read) or GET /contacts/search?updated_since=<ISO 8601 UTC>&page=N (incremental read)

Step 2, contacts.

Headers include: phones,emails,addresses,family and pagesize: 200. updated_since is honored only on /contacts/search. Page 1 is also read once before the run to count contacts from meta.total_records.

GET /contacts/{id}/phones?page=N&page_size=200 and GET /contacts/{id}/emails?page=N&page_size=200

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.

GET /families?page=N&family_members=true

Step 3, on every sync.

Never incremental. Page 1 is also read once before the run to count families.

GET /contacts?page=N

Start of step 4, on every sync: a full enumeration of every contact.

Only id, type, category, and status are used. Custodian feeds change balances and holdings without changing the contact, so updated_since is not a valid signal for account data.

GET /contacts/{id}/accounts?assets=true&page=N

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 assets array. If this call fails, the same call without assets=true is used, and existing holdings are kept.

GET /accounts/{id}

Step 4, once per account.

Detail enrichment: number, balance, type, custodian, feed fields, payment block, systematic fields, withholding, status, close date.

GET /accounts/{id}/user_defined_fields, then GET /accounts/{id}/udfs

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.

GET /accounts/{id}/details

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.

GET /accounts/{id}/transactions?date_from=<YYYY-MM-DD>&page=N

Step 4, only when no systematic-withdrawal amount and frequency were found and the account has no stored withdrawals.

date_from is 540 days before the sync. A 404 on page 1 switches it off for the rest of the run.

GET /accounts/{id}/beneficiaries

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

  1. "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).

  2. "Getting your contacts...": the contact pages, plus phone and email fallbacks when needed.

  3. "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.

  4. "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.

  5. "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}/photo exists 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 syncStateChange to 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, redtailSyncProgress with phase and percent, 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 client and must not contain ex-client or trust 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, then category_type, then contact_record_type (first non-empty). Status source: status, then status_name.

  • Hard filters, in every mode: a contact whose type is exactly "Business", and a contact with deleted set 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 action wins. With no stored action, enabled true means delete and enabled false 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 from GET /accounts/{id}. If there is still no date, the account reads as closed, and the firm is not in delete mode, SurgeTK calls GET /accounts/{id}/details once 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-digit YYYYMMDD or MMDDYYYY strings. 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/i reads as deceased; a status matching /inactive|former|ex-|terminated|archived|closed|estate/i reads 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

id

Redtail ID

Integer. Match key together with the firm.

first_name, middle_name, last_name

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.

dob

Date of birth

Overwritten. Blank in Redtail clears the stored date.

marital_status

Marital status

Contains married: Married. Contains widowed: Widowed. Contains divorced: Divorced. Any other non-blank value: Single. Blank: blank. Overwritten.

emails[]

Email

The entry with is_primary, else the first entry. Overwritten. Fallback: GET /contacts/{id}/emails.

phones[]

Mobile number, home phone

type "Mobile" or phone_type_description mobile; type "Home" or description home. Overwritten. The fallback endpoint uses phone_type 3 (mobile) and 1 (home). Other phone types are not stored.

addresses[0]

Home address

The first entry, not the one marked primary. line_1, line_2, city, state, postal_code joined with commas. Overwritten. Other addresses are not stored.

gender, gender_description, sex

Gender

male, female, or other. See Normalizers. Only when present and recognized.

job_title, occupation, title, profession

Occupation

First non-empty. Only when present.

employer, company, company_name

Employer

First non-empty. Only when present. Never for a Business contact.

type

Redtail contact type

As sent (Individual, Business, and so on). Only when present.

category, category_type, contact_record_type

Redtail category

First non-empty. Only when present.

status, status_name; status_id

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.

death_date, date_of_death

Date of death

Only when present and valid.

client_termination_date

Termination date

Only when present and valid.

servicing_advisor_id, writing_advisor_id

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

id

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.

members[].contact_id

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.

members[].hoh

Head of household

Each flagged member is set in turn, so the last flagged member wins.

servicing_advisor_id, writing_advisor_id

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

id

Redtail account ID

Match key together with the firm.

number

Account number

Stored as sent. The detail value wins over the list value. Blank: "Unknown Number". Overwritten.

balance

Account value

Parsed as a number. Blank or unreadable: the stored value is cleared, not set to 0. Overwritten.

balance_as_of

As-of date

List value, then detail value. Holdings price dates override it (the latest price_asof). New account with no date: the sync time. Existing account with no date: unchanged.

taxqualified_type, else account_type

Account type; raw account type

Normalized. See Normalizers. account_type is kept as sent in the raw field.

taxqualified_type, else account_type

Tax status

Normalized. See Normalizers.

company, else feed_aggregator

Custodian; raw custodian

Normalized. The value as sent is kept in the raw field. company is usually empty on feed-linked accounts, so feed_aggregator is the usual source.

feed_aggregator, feed_account_number, registration, product

Feed source, feed account number, registration, product

Trimmed strings. Overwritten.

held_away, managed, discretionary

Held away, managed, discretionary

Only when Redtail sends a boolean.

status, account_status (on the detail also status_description)

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.

close_date, closed_date, date_closed, termination_date

Date closed

See Closed Accounts.

systematic_withdraw_amount, systematic_withdraw_frequency, the payment block, custom fields, transactions

Systematic withdrawals

See Systematic-withdrawal resolution order.

federal_tax_withholding, state_tax_withholding

Federal and state withholding

Only when present on the detail payload.

assets[] (with assets=true)

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

name

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".

dob

Date of birth

Only when valid. Part of the match key when present.

relationship

Relationship

Written only when the beneficiary record is first created. Later changes in Redtail do not update it.

beneficiary_type_description

Tier

Exactly "primary" (case-insensitive): the primary tier. Any other value, including blank: the contingent tier.

percentage

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

servicing_advisor_id or writing_advisor_id on a contact or family

Redtail advisor ID

Match key together with the firm. A record is created the first time an ID is seen.

name from GET /lists/servicing_advisors or GET /lists/writing_advisors

Advisor name

Matched by id. Not found, or the list unavailable: "Unknown Advisor #<id>". The name is refreshed on every sync.

(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.

  1. Start with systematic_withdraw_amount and systematic_withdraw_frequency from the account list item (GET /contacts/{id}/accounts).

  2. If the amount is still empty or 0 and GET /accounts/{id} returned a payment block, take the monthly figure from the first present of payment.withdrawals_monthly, payment.withdrawal_monthly, payment.monthly_withdrawals, payment.monthly. If the frequency is still blank, set it to Monthly.

  3. Account custom fields fill whatever is still empty: the amount from a field named systematic withdrawal amount, systematic withdrawals amount, or systematic wd amount; the frequency from systematic withdrawal frequency, systematic withdrawals frequency, or systematic wd frequency (names compared case-insensitively). This step runs only when GET /accounts/{id} succeeded. It finds nothing while the UDF endpoints answer 404.

  4. systematic_withdraw_amount and systematic_withdraw_frequency from GET /accounts/{id} then override steps 2 and 3 whenever the key exists in the detail payload, even when its value is empty.

  5. payment.premium_frequency (1 Monthly, 2 Quarterly, 3 Semi-annual, 4 Annually; other values are ignored) and payment.withdraw_amount from GET /accounts/{id} override everything when present.

  6. 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_from set to 540 days before the sync.

  • A transaction counts as a withdrawal when its type, action, or transaction_type_description contains withdraw, distribution, dist, rmd, swd, or systematic; or its description contains withdraw or distribution; or its amount (amount, net_amount, or total) is negative. Any one condition is enough.

  • Date: date, trade_date, settlement_date, posted_on, or created_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

taxqualified_type, else 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

taxqualified_type, else account_type, lower-cased

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

company, else feed_aggregator

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

status, account_status, status_description

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

balance

Parsed as a number. Not a finite number: left empty.

As-of date

balance_as_of, price_asof

New account with no date: the sync time. Existing account with no date: unchanged. Holdings price dates override.

Frequency

systematic_withdraw_frequency and the payment block

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

gender, gender_description, sex

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

marital_status

See Contact to Client.

Dates on holdings and close dates

price_asof, shares_asof, the close date fields

Standard date-time stamps parse directly. 8-digit strings are read as YYYYMMDD when the first four digits look like a year, else as MMDDYYYY. Impossible dates and implausible years are dropped.

Deduplication keys

SurgeTK record

Key

Index rule

Client

(firmId, redtailId)

Unique when redtailId is a number.

Household

(firmId, redtailFamilyId)

Unique when present and not null. Solo households use SOLO-<contactId>.

Account

(firmId, redtailAccountId)

Unique when redtailAccountId is a number.

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). annual is the only recognized yearly value. Other spellings are not mapped.

  • systematic_withdraw_amount, systematic_withdraw_frequency, and the payment block 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_fields and GET /accounts/{id}/udfs answer 404 on the current API (verified July 2026). SurgeTK probes them once per run and then skips them.

  • GET /accounts/{id}/details and GET /accounts/{id}/transactions are called conditionally, each behind a circuit breaker. SurgeTK has not confirmed what they return on every tenant.

  • include and pagesize are honored only as request headers. updated_since is honored only on /contacts/search. SurgeTK is built for this.

  • Everything returned by GET /contacts/{id}/accounts becomes a SurgeTK account, whatever its type. Insurance products returned there become accounts.

  • company is usually empty on feed-linked accounts. SurgeTK uses feed_aggregator as the custodian.

  • Only beneficiary_type_description exactly "primary" reaches the primary tier.

  • Contacts flagged deleted are 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.


Related articles

Did this answer your question?