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:
Production base URL:
https://crm.redtailtechnology.com/api/public/v1Review/Development base URL:
https://review.crm.redtailtechnology.com/api/public/v1
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 | 503 | "Redtail API is currently unavailable." |
401 with body containing | 403 | "Your Redtail account is locked." |
401 (other) | 401 | "Invalid Redtail credentials." |
axios | 504 | "Request to Redtail API timed out." |
Any other HTTP response | passthrough |
|
No response | 500 |
|
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 |
| Connect-time credential validation; returns |
| Populates the Contact Categories filter. Cached 24 h per firm. |
| Populates the Contact Statuses filter. Cached 24 h per firm. |
| Informational; used for display only. Cached 24 h per firm. |
| Resolves Redtail servicing-advisor IDs to names during sync. |
| Resolves Redtail writing-advisor IDs to names during sync. |
| Primary contact enumeration. |
| Fallback — called only when the |
| Fallback — called only when the |
| Family (household) enumeration. |
| Per-contact account list. Falls back to |
| Per-account detail enrichment. |
| Per-account beneficiaries. Errors are non-fatal. |
| Fallback for inferring systematic-withdrawal patterns when no direct fields are present. |
| 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}/notesGET /contacts/{id}/activitiesAny 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,accountsv2endpoints
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:
GETandHEADrequests 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 (
syncStateChangeand a legacyredtailSyncProgress) 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), orfailed(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
typesis non-empty, matching is exact, case-insensitive, trimmed.allowlistsyncs a record only when its trimmed lower-cased category (or status) equals one of the entries intypes.denylistsyncs a record only when its trimmed lower-cased category (or status) does not equal any entry intypes.
When
typesis empty for Contact Categories, the filter falls back to a built-in smart default: sync contacts whose category containsclient(case-insensitive) and does not containex-clientortrust client. Contacts with no category are excluded.When
typesis 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 |
|
| integer | Primary identity anchor; uniquely indexed per firm. |
|
| trim | Required. Defaults to |
|
| trim | Optional; omitted if empty. |
|
| trim | Required. Defaults to |
|
| normalized to UTC midnight | Timezone-safe. |
|
|
| First non-empty source wins. |
|
| enum match, case-insensitive | Empty string if no match. |
|
| trim | First non-empty source wins. |
|
| trim | Skipped for Redtail type |
|
| as-is | Informational. |
|
| as-is | Subject to the Contact Categories filter. |
|
| as-is | Subject to the Contact Statuses filter. |
|
| integer | Stable numeric ID for accurate filter re-matching. |
(derived) status contains |
| enum | Default |
|
| integer | Used as fallback when the contact's family has no servicing advisor. |
|
| integer | Used as fallback when the contact's family has no writing advisor. |
embedded |
| string | First match wins. |
embedded |
| string | First match wins. |
embedded |
| string |
|
embedded |
| concatenation of | 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 |
|
| Primary identity anchor; uniquely indexed per firm. |
| Back-reference from | Every synced family member is linked to the household. |
|
| First member with |
|
| Falls back to contact-level advisor when empty. |
|
| 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 |
|
| integer | Primary identity anchor; uniquely indexed per firm. |
|
| string, stored as-is | Defaults to |
|
|
| Defaults to 0. |
|
| Date | Defaults to the sync timestamp if missing. |
|
| case-insensitive enum match over a registered set of ~140 account types; falls through to substring match; defaults to | Raw value preserved in |
|
| regex normalization: |
|
|
| canonical mapping for Charles Schwab / Schwab Advisor Services → | Defaults to |
|
| regex normalization: |
|
|
| Date | First non-null source wins. Used by the Closed Account Filter. |
|
|
| See systematic-withdrawal fallback chain below. |
|
|
|
|
|
| Number (percentage) | Omitted if undefined. |
|
| Number (percentage) | Omitted if undefined. |
|
| upsert into Beneficiary collection; routed by | 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.
Direct account fields:
systematic_withdraw_amountandsystematic_withdraw_frequencyfromGET /contacts/{id}/accountsorGET /accounts/{id}.Payment-info block on the account detail:
payment.withdrawals_monthly,payment.withdrawal_monthly,payment.monthly_withdrawals,payment.monthly, orpayment.withdraw_amountfor amount;payment.premium_frequency(numeric enum:1=Monthly,2=Quarterly,3=Semi-annual,4=Annually) for frequency.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.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 |
|
| Split on whitespace; first token → |
|
| Participates in the upsert key when present. |
|
| Stored verbatim. |
| routes the beneficiary into | Values containing |
|
|
|
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 |
|
Household |
|
Account |
|
RedtailAdvisor |
|
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 |
| Enum of ~140 account types or |
Tax status |
|
|
Custodian |
| Canonical mapping for Schwab / Fidelity / Pershing / TD Ameritrade; else raw string or |
Frequency |
|
|
Account status |
|
|
Gender |
|
|
Related articles
Syncing with Redtail — the end-user guide covering connection, sync operation, filter configuration, and troubleshooting for SurgeTK firm admins.