Skip to main content

Redtail integration: technical reference

A complete reference for SurgeTK's Redtail CRM integration — authentication, endpoints called, sync mechanics, filter evaluation, and field-by-field mappings — for Redtail's integration-support team and technically inclined SurgeTK administrators.

This reference complements our end-user Syncing with Redtail guide. If you are a firm admin looking for how to connect, run, or troubleshoot a sync, start there. This document is intended for readers who need to know precisely what the integration does on the wire and how incoming data is mapped.

Authentication and environments

The integration authenticates against Redtail's public v1 API:

SurgeTK's connect flow performs a single GET /authentication call using HTTP Basic Auth with the raw credential triple apiKey:username:password. On success it stores the returned user_key. All subsequent API calls include:

  • Authorization: Basic base64(apiKey:username:password) — rebuilt per sync from the decrypted password.

  • userkey: <stored user_key> header.

Passwords are encrypted at rest with AES-256-GCM. All traffic to Redtail uses HTTPS. Credentials — including a fresh user_key — are re-validated whenever a firm re-enters their password via the Connect flow; SurgeTK does not proactively probe credential freshness between connects.

Connect-time error handling distinguishes:

Redtail response

SurgeTK HTTP

Displayed message

401 with body containing Gateway Time-out

503

"Redtail API is currently unavailable."

401 with body containing Account Locked

403

"Your Redtail account is locked."

401 (other)

401

"Invalid Redtail credentials."

axios ECONNABORTED (30 s timeout)

504

"Request to Redtail API timed out."

Any other HTTP response

passthrough

Redtail API error: <status> - <message>

No response

500

Could not connect to Redtail API: <message>

Permissions matrix

Operation

SurgeTK role required

View integration status, last-sync time

Any authenticated user

Connect, disconnect

Admin

Sync, cancel sync

Admin

View or edit Sync Filters

Admin

Refresh filter lists

Admin

Link Redtail advisors to SurgeTK users

Admin

Endpoints SurgeTK calls

Paginated endpoints use page_size=200 unless otherwise noted. Incremental syncs append &updated_since=<ISO 8601 UTC> where <ISO 8601 UTC> is the timestamp of the last successful sync.

Endpoint

Purpose

GET /authentication

Connect-time credential validation; returns user_key.

GET /lists/contact_categories

Populates the Contact Categories filter. Cached 24 h per firm.

GET /lists/contact_statuses

Populates the Contact Statuses filter. Cached 24 h per firm.

GET /lists/account_types

Informational; used for display only. Cached 24 h per firm.

GET /lists/servicing_advisors

Resolves Redtail servicing-advisor IDs to names during sync.

GET /lists/writing_advisors

Resolves Redtail writing-advisor IDs to names during sync.

GET /contacts?page={p}&page_size=200&include=phones,emails,addresses,family&updated_since=<ISO>

Primary contact enumeration.

GET /contacts/{contactId}/phones?page={p}&page_size=200

Fallback — called only when the &include=phones payload is missing or incomplete.

GET /contacts/{contactId}/emails?page={p}&page_size=200

Fallback — called only when the &include=emails payload is missing the primary.

GET /families?page={p}&page_size=200&family_members=true

Family (household) enumeration.

GET /contacts/{contactId}/accounts?assets=true

Per-contact account list. Falls back to /contacts/{contactId}/accounts without the assets=true query if the first form errors.

GET /accounts/{accountId}

Per-account detail enrichment.

GET /accounts/{accountId}/beneficiaries

Per-account beneficiaries. Errors are non-fatal.

GET /accounts/{accountId}/transactions?page={p}&page_size=200&date_from=<ISO>

Fallback for inferring systematic-withdrawal patterns when no direct fields are present.

GET /accounts/{accountId}/user_defined_fields and GET /accounts/{accountId}/udfs

Fallback UDF sources for systematic-withdrawal fields.

Endpoints SurgeTK does not call

The following Redtail endpoints are intentionally not used by the integration:

  • GET /contacts/{id}/notes

  • GET /contacts/{id}/activities

  • Any contact-level UDF endpoint (only account-level UDFs are consulted)

  • Any insurance-policy or membership endpoint

  • Any cost-basis, lot-level, or performance endpoint

  • masterv2, familyv2, accountsv2 endpoints

Sync mechanics

  • Trigger: manual. SurgeTK does not poll Redtail on a schedule and does not consume Redtail webhooks.

  • Concurrency guard: per firm, only one sync can be active at a time. A second sync request returns HTTP 409 with "A sync is already in progress."

  • Heartbeat: the running sync writes a heartbeat every 30 seconds. If a sync's heartbeat stops for more than 5 minutes (for example due to a server restart), the next server boot marks it failed and releases the sync lock.

  • Rate limits and retries against the Redtail API: GET and HEAD requests are retried on HTTP 429, 500, 502, 503, 504 with exponential backoff — min(1000 × 2^attempt, 15000) + random(0..500) milliseconds — for up to 5 attempts per request. The axios client timeout is 30 seconds.

  • Account-upsert concurrency: account upserts into SurgeTK's database run with a concurrency limit of 5.

  • Progress transport: Socket.IO events (syncStateChange and a legacy redtailSyncProgress) stream phase and percentage updates to the user's open tabs. A client-side stall watchdog warns after 20 minutes of no updates.

  • Partial failures: single-record errors (one contact, one account) are counted and the sync continues. Final status is completed (no errors), partial (errors but finished), or failed (unrecoverable).

  • Cancellation: cooperative. The sync checks a cancel flag at stage boundaries; in-flight work finishes before the sync ends.

  • Incremental logic: once the first sync completes, subsequent syncs pass updated_since=<ISO 8601 UTC> derived from the last successful sync's completion timestamp to the contact, family, and account enumeration calls.

Filter evaluation

Filters are stored at the firm level and evaluated during contact enumeration.

Contact Categories and Contact Statuses share the same mode/types structure:

{ mode: 'allowlist' | 'denylist', types: string[] }

Evaluation rules:

  • When types is non-empty, matching is exact, case-insensitive, trimmed.

    • allowlist syncs a record only when its trimmed lower-cased category (or status) equals one of the entries in types.

    • denylist syncs a record only when its trimmed lower-cased category (or status) does not equal any entry in types.

  • When types is empty for Contact Categories, the filter falls back to a built-in smart default: sync contacts whose category contains client (case-insensitive) and does not contain ex-client or trust client. Contacts with no category are excluded.

  • When types is empty for Contact Statuses, the status check is skipped entirely — every status passes.

  • Null/empty category on a contact: excluded under a non-empty allowlist; included under a non-empty denylist.

  • Null/empty status on a contact: excluded under a non-empty allowlist; included under a non-empty denylist.

  • Non-configurable hard filters always apply: contacts with type === 'Business' and soft-deleted contacts (deleted === true) are never synced, regardless of filter mode.

One important re-sync behavior: filter checks apply only to contacts that have no existing SurgeTK Client record. Once a contact has been imported on an earlier sync, the filter no longer gates its account data — the sync will continue to fetch and update that contact's accounts on subsequent runs even if the contact would now be excluded by a tightened filter. Contact-level fields (name, email, category, status, etc.) stop updating once the contact falls outside the filter, but the Client record and its Account records are not deleted.

Closed Account Filter:

{ enabled: boolean, thresholdMonths: number }

Options for thresholdMonths: 6, 12 (default), 24, 36, 60. When enabled, an account is skipped if its normalized accountStatus is Closed, Transferred, or Inactive (or its dateClosed is set) and its dateClosed is older than thresholdMonths × 30.44 days before the current time. Accounts without a dateClosed are not filtered out even when their status implies closure.

A spouse-inclusion pass imports contacts that were filtered out by the category or status filter when they share a Redtail family with a contact that was synced. This prevents filters like "Client only" from breaking household integrity for married couples.

Field mapping — Contact → Client

Redtail field

SurgeTK Client field

Transformation

Notes

id

redtailId

integer

Primary identity anchor; uniquely indexed per firm.

first_name

firstName

trim

Required. Defaults to Unknown only when last_name is also empty.

middle_name

middleName

trim

Optional; omitted if empty.

last_name

lastName

trim

Required. Defaults to Client only when first_name is also empty.

dob

dob

normalized to UTC midnight

Timezone-safe.

gender / gender_description / sex

gender

male / female / other / ''

First non-empty source wins.

marital_status

maritalStatus

enum match, case-insensitive

Empty string if no match.

job_title / occupation / title / profession

occupation

trim

First non-empty source wins.

employer / company / company_name

employer

trim

Skipped for Redtail type Business.

type

redtailContactType

as-is

Informational.

category / category_type / contact_record_type

redtailCategory

as-is

Subject to the Contact Categories filter.

status / status_name

redtailStatus

as-is

Subject to the Contact Statuses filter.

status_id

redtailStatusId

integer

Stable numeric ID for accurate filter re-matching.

(derived) status contains deceased / dead / death

deceasedLiving

enum Living / Deceased

Default Living.

servicing_advisor_id

contactLevelServicingAdvisorId

integer

Used as fallback when the contact's family has no servicing advisor.

writing_advisor_id

contactLevelWritingAdvisorId

integer

Used as fallback when the contact's family has no writing advisor.

embedded phones[*] with phone_type == 3 or type Mobile

mobileNumber

string

First match wins.

embedded phones[*] with phone_type == 1 or type Home

homePhone

string

First match wins.

embedded emails[*] with is_primary == true (else first)

email

string

embedded addresses[*] — first address only

homeAddress

concatenation of line_1, line_2, city, state, postal_code

Single address.

The internal clientId field on a Client record — distinct from redtailId — is generated by SurgeTK itself when the sync creates a new client. The integration does not set it explicitly; SurgeTK's Client schema assigns a unique identifier at insert time. This ID is the stable handle SurgeTK uses to link clients to households, accounts, and imported data.

Field mapping — Family → Household

Redtail field

SurgeTK Household field

Notes

id

redtailFamilyId

Primary identity anchor; uniquely indexed per firm.

members[*].contact_id

Back-reference from Client.household

Every synced family member is linked to the household.

members[*].hoh == true

headOfHousehold

First member with hoh: true wins.

servicing_advisor_id

redtailServicingAdvisorId

Falls back to contact-level advisor when empty.

writing_advisor_id

redtailWritingAdvisorId

Falls back to contact-level advisor when empty.

Orphan clients (contacts not in any Redtail family) receive a synthetic solo household with a redtailFamilyId of SOLO-<redtailId>.

Advisor resolution: during sync, each servicing or writing advisor ID seen on a family is looked up in /lists/servicing_advisors or /lists/writing_advisors, and a RedtailAdvisor record is created or updated. The RedtailAdvisor's link to a SurgeTK user (linkedUser) is not populated by the sync — it requires an explicit manual linking step by a firm admin. Until linked, households referencing that advisor have no SurgeTK lead-advisor assignment. When an admin links a servicing advisor in the admin UI, SurgeTK immediately runs Household.updateMany over every household referencing that redtailServicingAdvisorId and assigns the chosen user to servicingLeadAdvisor. Linking a writing advisor updates only the RedtailAdvisor record; household-level writing-advisor reassignment happens on the next sync.

Field mapping — Account → Account

Redtail field

SurgeTK Account field

Transformation

Notes

id

redtailAccountId

integer

Primary identity anchor; uniquely indexed per firm.

number

accountNumber

string, stored as-is

Defaults to Unknown Number if missing. Masked to last-4 in JSON responses. This string is the match key used by later CSV imports (e.g. billing imports) to link to synced accounts; matching is exact, case-sensitive, firm-scoped.

balance

accountValue

parseFloat

Defaults to 0.

balance_as_of

asOfDate

Date

Defaults to the sync timestamp if missing.

account_type (and taxqualified_type, preferred when present)

accountType plus accountTypeRaw

case-insensitive enum match over a registered set of ~140 account types; falls through to substring match; defaults to Other

Raw value preserved in accountTypeRaw for audit.

taxqualified_type

taxStatus

regex normalization: /roth/Tax-Free; /401|403|457|traditional|sep|simple|pension|profit|ira/Tax-Deferred; /529|hsa|fsa/Tax-Exempt; else Taxable

company

custodian plus custodianRaw

canonical mapping for Charles Schwab / Schwab Advisor Services → Schwab; Fidelity, Pershing, TD Ameritrade kept as-is; else raw

Defaults to UnknownCustodian if empty.

status / account_status / status_description

accountStatus plus accountStatusRaw

regex normalization: /active|funded|open/Active; /closed|terminated/Closed; /transfer/Transferred; /inactive|dormant|suspended/Inactive; /pending|new|application|in.?process/Pending; else Other

close_date / closed_date / date_closed / termination_date / status_date

dateClosed

Date

First non-null source wins. Used by the Closed Account Filter.

systematic_withdraw_amount

systematicWithdrawals[].amount

parseFloat

See systematic-withdrawal fallback chain below.

systematic_withdraw_frequency

systematicWithdrawals[].frequency

Monthly / Quarterly / Semi-annual / Annually / ''

federal_tax_withholding

federalTaxWithholding

Number (percentage)

Omitted if undefined.

state_tax_withholding

stateTaxWithholding

Number (percentage)

Omitted if undefined.

account_beneficiaries[*]

beneficiaries.primary[] / beneficiaries.contingent[]

upsert into Beneficiary collection; routed by beneficiary_type_description

See Beneficiary section below.

Systematic-withdrawal fallback chain

When determining each account's systematic withdrawals, SurgeTK consults the following sources in priority order. Values found at a higher-priority source are kept; lower-priority sources only fill gaps.

  1. Direct account fields: systematic_withdraw_amount and systematic_withdraw_frequency from GET /contacts/{id}/accounts or GET /accounts/{id}.

  2. Payment-info block on the account detail: payment.withdrawals_monthly, payment.withdrawal_monthly, payment.monthly_withdrawals, payment.monthly, or payment.withdraw_amount for amount; payment.premium_frequency (numeric enum: 1=Monthly, 2=Quarterly, 3=Semi-annual, 4=Annually) for frequency.

  3. Account UDFs, searched case-insensitively for field names systematic withdrawal amount, systematic withdrawals amount, systematic wd amount, systematic withdrawal frequency, systematic withdrawals frequency, systematic wd frequency.

  4. Transaction inference: if no direct source yielded a usable amount/frequency pair, the integration fetches up to 18 months of account transactions, identifies withdrawal transactions by keyword and sign, groups by rounded amount, and infers a frequency from the median gap between occurrences (≤ 45 days = Monthly; ≤ 120 = Quarterly; ≤ 220 = Semi-annual; else Annually). Requires at least three matching occurrences of the same amount to confirm a pattern.

Results are stored as an array on Account.systematicWithdrawals, each entry carrying amount, frequency, and optional per-stream federalTaxWithholdingAmount and stateTaxWithholdingAmount.

Field mapping — Beneficiary

Each account_beneficiaries[*] entry returned from GET /accounts/{accountId}/beneficiaries is upserted into SurgeTK's Beneficiary collection keyed by (firmId, firstName, lastName, dateOfBirth?):

Redtail field

SurgeTK field

Notes

name

firstName + lastName

Split on whitespace; first token → firstName, remainder → lastName.

dob

dateOfBirth

Participates in the upsert key when present.

relationship

relationship

Stored verbatim.

beneficiary_type_description

routes the beneficiary into Account.beneficiaries.primary[] or Account.beneficiaries.contingent[]

Values containing primary (case-insensitive) go to primary; everything else goes to contingent.

percentage

percentageAllocation

parseFloat; defaults to 0.

IRD flags, contingent triggers, and per-stirpes distribution detail are not captured.

Deduplication keys

On every sync SurgeTK matches incoming Redtail records to existing SurgeTK records by the following unique per-firm keys, then upserts:

SurgeTK entity

Key

Client

(firmId, redtailId)

Household

(firmId, redtailFamilyId)

Account

(firmId, redtailAccountId)

RedtailAdvisor

(firmId, redtailAdvisorId)

A disconnect followed by a reconnect using the same Redtail account will match existing records back to their Redtail source; duplicates are not created.

Cascade behavior on household deletion

When a firm admin deletes a Redtail-sourced household from SurgeTK's Households page (for example as part of a first-sync reset), SurgeTK removes every record tied to that household from the database: the household itself, its clients, accounts, liabilities, assets, and insurance records. The Redtail identity anchors (redtailFamilyId, redtailId, redtailAccountId) are removed along with their parent documents. A subsequent sync will see no local records for those Redtail contacts and will recreate clean new ones under the current filter settings. No unique-index residue blocks the re-import.

Beneficiary records in SurgeTK's firm-scoped Beneficiary collection are not purged by the household-delete cascade; they remain until explicitly removed.

Normalizer summary

Normalizer

Inputs

Output domain

Account type

account_type, taxqualified_type

Enum of ~140 account types or Other.

Tax status

taxqualified_type (preferred), account_type

Taxable / Tax-Deferred / Tax-Free / Tax-Exempt.

Custodian

company

Canonical mapping for Schwab / Fidelity / Pershing / TD Ameritrade; else raw string or UnknownCustodian.

Frequency

systematic_withdraw_frequency and related

Monthly / Quarterly / Semi-annual / Annually / ''.

Account status

status, account_status, status_description

Active / Closed / Transferred / Inactive / Pending / Other.

Gender

gender, gender_description, sex

male / female / other / ''.


Related articles

  • Syncing with Redtail — the end-user guide covering connection, sync operation, filter configuration, and troubleshooting for SurgeTK firm admins.

Did this answer your question?