# Importing Agent Skills Source: https://docs.salesfinity.ai/ai-companion/agent-skills Bring skills written in the open Agent Skills format into Salesfinity from a GitHub link or an uploaded file. Salesfinity skills use the same shape as the open **Agent Skills** format: a `SKILL.md` file with YAML frontmatter carrying a `name` and `description`, followed by a Markdown body. That means a skill written for another agent can be imported directly, and a skill written in Salesfinity can be shared the other way. ## What gets imported Only the `SKILL.md` file itself. Supporting files that some skill folders carry, such as `references/`, `scripts/`, or `assets/`, are ignored. Salesfinity stores each skill as a single editable document so it stays visible and searchable in the Skills tab, and it does not mirror folder structures. If a skill depends on a script or reference file, inline what matters into the body after importing. The frontmatter must contain `name` and `description`; an import without them is rejected with a message saying which is missing. The Markdown body becomes the skill content. ## Import from GitHub Open the file on github.com and copy the URL from the address bar. It must be a link to the file itself in the form `https://github.com/owner/repo/blob/branch/path/SKILL.md`. Links to a folder, a repository root, a gist, or a non-GitHub host are rejected with a message explaining the expected shape. Open the Skills tab, click **Import**, choose GitHub, and paste the link. The skill appears in the Skills tab marked as imported. Open it to review the body and adjust the name or description. The repository must be public. Salesfinity fetches the raw file from GitHub on your behalf; it does not use your GitHub credentials. ## Import from a file Choose **File** in the import dialog and upload either: * a `.md` file that is the `SKILL.md`, or * a `.zip` of a skill folder. Salesfinity extracts only the `SKILL.md` from it and ignores everything else. ## Limits and truncation | Item | Limit | What happens over the limit | | ------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------- | | Skill content | 50,000 characters | The import is rejected. A skill body is never silently truncated, because that would drop instructions. | | Name | 200 characters | Truncated. Edit it in the Skills tab afterwards. | | Description | 500 characters | Truncated. Third-party skills often ship long trigger lists in the description; the import still succeeds. | | Upload size | Sized for a zipped skill folder | Larger uploads are rejected. | Imports are rate limited per user, so a burst of imports may be asked to wait a moment. ## Provenance and safety An imported skill runs as instructions for the companion, exactly like one you wrote. Treat a skill from an unknown source the way you would treat a script: read it before you rely on it. Salesfinity records the source on every imported skill, shows the full body in the Skills tab so nothing is hidden, and keeps every write the companion makes behind [approval](/ai-companion/overview#every-write-is-approval-gated) regardless of what a skill says. ## Exporting a Salesfinity skill There is no export button, but a skill is already in the format. Copy the name, description, and content from the Skills tab into a `SKILL.md`: ```markdown theme={null} --- name: Pre-call brief description: One-screen briefing on a person and their company before a call. --- ## Goal ... ``` Commit it to a repository and any teammate, or any team, can import it from the link. # Creating Skills Source: https://docs.salesfinity.ai/ai-companion/creating-skills Three ways to create a skill, the structure that makes one reliable, and the guided Skill Creator flow the AI Companion follows. There are three ways to create a skill. Pick by how much you already know about the workflow. | You want to | Do this | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | Save something you just did with the companion | Say **"save this as a skill"** in the conversation. | | Build a skill for a workflow you can describe | Say **"help me create a skill that…"**, or click "Help me create a skill together" in the Skills tab. | | Write it yourself | Open the Skills tab and click **New Skill**. | | Reuse one someone else wrote | [Import it](/ai-companion/agent-skills). | ## The guided flow When you ask the companion to create or improve a skill, it loads the **Skill Creator** guide, a platform skill that scripts the whole authoring process, before asking you anything. The flow is the same whether you start from a conversation or from scratch. From the conversation and your request, the companion works out the trigger (when should a future run reach for this?), the inputs that vary per run, the tools and integrations involved, what "done" looks like, and any guardrails. Starting from a conversation, most of this is already in the transcript, and it does not re-interview you about it. At most three or four questions, in one round, each with a suggested default. It always confirms the trigger and the output, because those decide whether the skill ever gets used correctly. Goal, inputs, steps, output, guardrails. See [structure](#the-structure-of-a-good-skill) below. Would a fresh run with no memory of this conversation succeed? Is every step a concrete tool call or decision? Does it reference only tools this team can use? Did today's dates or a specific contact leak in where a placeholder belongs? You see the name, description, and full body, with **Yes, create it** and **Cancel** suggestions. Nothing is saved until you confirm. The skill appears in the Skills tab, marked as generated. The companion offers to run it on a real example right away, and suggests a routine if the workflow is recurring. ## The structure of a good skill A skill is instructions for a future run that has none of today's context. Write it so that run succeeds first time. **Name** says what the skill does, not how. "Monday pipeline review", not "HubSpot query plus summary v2". No dates, no instance data. **Description** is the one line the companion matches on. Say when to use it and what it produces: "Weekly pipeline summary from HubSpot deals, posted as a table. Use for 'pipeline review' or Monday check-ins." **Content** follows this shape: ```markdown theme={null} ## Goal One or two sentences: what a successful run produces. ## Inputs What to ask the user (or infer) at run time, with defaults. - time range — default: last 7 days ## Steps Numbered, concrete, in order. Name the actual tools. Put decision points inline ("if no deals match, say so and stop — don't broaden the filter silently"). ## Output The exact shape of the result — format, columns, links to include, where it gets delivered. ## Guardrails What this skill must not do; when to stop and ask the user. ``` Leave a section out only when it is genuinely empty. Keep the body tight; a skill is instructions, not documentation. ## Example A skill a manager might save after doing this once by hand: ```markdown theme={null} --- name: Monday pipeline review description: Weekly summary of open HubSpot deals by stage with the week's call activity per deal, posted as a table. Use for "pipeline review", "Monday check-in", or "what moved this week". --- ## Goal A table of open deals showing stage, amount, owner, days in stage, and the number of dials and connects to the deal's contacts in the last 7 days. ## Inputs - time range — default: last 7 days - owner — default: the whole team ## Steps 1. Ask the HubSpot specialist for open deals, with stage, amount, owner, and the date the stage last changed. 2. For each deal, get the associated contacts. 3. Pull call history for those contacts in the time range and count dials and connects per deal. 4. If there are no open deals, say so and stop. ## Output One table sorted by amount descending. Columns: Deal, Stage, Amount, Owner, Days in stage, Dials, Connects. Link each deal to HubSpot. End with three deals that had zero dials this week. ## Guardrails Read-only. Never change a deal stage or amount. ``` ## Checklist before you save * The trigger is in the description, in the words a user would actually say. * Every step names a real capability the team has connected. A skill that says "check Salesloft" on a team without Salesloft will fail on step one. * Inputs that vary per run are inputs, not hardcoded values. * The output section says exactly what to deliver and where. * Anything the skill must never do is in guardrails, so an unattended routine run honors it. # Memory Source: https://docs.salesfinity.ai/ai-companion/memory How the AI Companion remembers durable facts about you across conversations, what it will and will not save, and how to see and control what it knows. Every conversation with the AI Companion starts fresh, except for memory. Memory is a small set of durable facts about you and your work that the companion carries from one conversation to the next, so you do not have to re-explain your role, your territory, or how you like things done. Memory is **per user, per team**. The same person on a different team has a separate memory, so nothing about one team's work leaks into another. ## What a memory looks like A memory has a type, a short name, and a one-sentence body written in the third person. | Type | Label in the app | What goes here | Example | | --------- | ---------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | User | About you | Stable facts about you: role, team, territory, working style, tools you rely on | "The user is an enterprise SDR covering manufacturing accounts in the Midwest." | | Project | Projects | A named deal, account, or goal you track over weeks | "The user is working the Acme renewal, due end of quarter." | | Feedback | Feedback | A standing rule or correction that should shape every future reply | "The user wants every call summary to end with next steps as a checklist." | | Reference | Reference | An external system you depend on and what you use it for | "The user's primary CRM is HubSpot; Salesloft is used for sequencing." | The **name** describes what the memory covers, not the fact itself: "Role and team", not "User is an enterprise SDR". That is what lets a later change update the memory in place instead of creating a duplicate beside it. ## How memories get saved ### Automatically After each turn, a background extractor reads the exchange and decides whether it revealed anything worth keeping. Most turns yield nothing; that is the expected outcome. A fact is saved only if all three hold: 1. It will still be true and useful next week, in a conversation about something else. 2. It is about you or your standing world, not about what you happen to be doing right now. 3. It is not already known from your profile, the platform, or the current conversation. Some things are never saved automatically, because they are the most common false positives: * **Task requests.** "Source ten SDR managers in San Francisco" is a job to do now, not a fact about you, however detailed it is. * **Rules inferred from one incident.** One failed tool call or one odd result is not a standing preference. * **Transient state.** An expired connection, a schedule that came back a certain way, something you are about to do. * **Platform knowledge.** What an integration can or cannot do goes stale and is not about you. * **Anything already on your profile** or visible in the conversation. Before saving, the extractor compares the candidate against your existing memories. A fact that is already there, even worded differently, is skipped or merged, never duplicated. ### Explicitly Say it. "Remember that I only call the West Coast after 11am" saves a feedback memory immediately. "From now on…" and "always…" work the same way. You can also ask the companion to update a memory that is now wrong, to forget one, or to tell you what it remembers. ### In the Memory tab Open the companion and switch to **Memory**. Memories are grouped by type, and each one shows whether you saved it or the companion inferred it. You can add a memory with the form, edit any memory's type, name, or body, and delete any memory. ## How memories are used At the start of every conversation, your memories are placed in the companion's context under "About you", and the companion treats them as things it already knows. When you have many memories, the ones not pre-loaded are still reachable: the companion searches memory when a question touches something it does not have in front of it. ## Turning memory off The Memory tab has a switch. When memory is off: * Nothing new is saved, automatically or explicitly. The companion's memory tools are removed from its toolset entirely. * Existing memories are not shown to the companion and the tab reads "Memory is off". * Your memories are kept, not deleted, so turning it back on restores them. ## Limits and housekeeping | Item | Limit | | -------------------------- | ---------------- | | Memories per user per team | 100 | | Name | 200 characters | | Body | 1,000 characters | When the cap is reached, the least valuable memory is retired to make room. Value is weighted mostly by importance, with explicit saves and standing rules ranked above one-off projects, and then by how recently and how often a memory has been used. A retired memory is archived rather than deleted immediately, and is recoverable for 90 days; saving a memory with the same name during that window brings it back. ## Related Memory is what the companion knows about you. Skills are what it knows how to do. How the companion plans, acts, and asks for approval. # AI Companion Source: https://docs.salesfinity.ai/ai-companion/overview What the AI Companion is, what it can do across Salesfinity and your connected platforms, and how it plans, acts, and asks for approval. The AI Companion is a sales assistant that lives inside Salesfinity. You talk to it in plain language and it answers from your real data: call history, analytics, follow-ups, contact lists, and every platform your team has connected. It can also act, from drafting a follow-up to enrolling a contact in a sequence, and it asks before it changes anything. The companion is available to members of a team with an active plan. Open it from the companion panel anywhere in the dashboard. ## What you can ask it to do Connect rate by time of day, list performance, SDR leaderboards, call scoring trends. Answers come back as charts and metric cards, not paragraphs. Review today's activity, find calls with a given disposition, create and complete follow-up tasks, build a call list from call history. Look up a contact, account, or deal in HubSpot, Salesforce, Outreach, Salesloft, Apollo, Amplemarket, SmartLead, or Gong, and update records with your approval. Source new accounts and people onto a table, load CRM records by stage or owner, check which rows already exist in your CRM, and enrich the rest. Write follow-up emails from a call, send on-brand HTML email, post to Slack, and book meetings through connected apps. Pull together the CRM record, call history, logged emails, open deals, and battle cards for a person or company into one briefing. ## How it works A conversation with the companion runs as a loop: it reads your message, decides which tools it needs, calls them, reads the results, and either calls more tools or writes its reply. A single request can involve many tool calls. Several things shape that loop. ### It plans first For anything that takes more than a couple of steps, the companion writes a short, visible plan and ticks items off as it works. If a step reveals new work, it updates the plan rather than silently drifting. ### It loads tools on demand The companion has well over a hundred tools, so it keeps only a few in view and loads the rest by name or category when a task needs them. You do not have to name a tool. Ask for the outcome and it finds the capability. ### It dispatches specialists for connected platforms Each connected platform has its own specialist. When you mention HubSpot, the companion hands the lookup to the HubSpot specialist, which knows that platform's objects and query language and returns a clean result. Specialists run in parallel when a question spans platforms, for example checking the same person in HubSpot and Salesforce at once. Specialists **read**. Writes come back to the companion, which performs them itself so every change passes through approval. ### It uses external apps through connected accounts Beyond the platforms with specialists, the companion can reach apps your team has connected for it, such as Gmail, Google Calendar, Slack, Notion, Calendly, Cal, Pipedrive, and ActiveCampaign. If an app is not connected, the companion tells you and gives you a link to connect it. ### Every write is approval-gated Any tool that changes something outside Salesfinity's own scratch space, whether it is a CRM update, an email send, a sequence enrollment, or a paid enrichment, pauses the conversation and shows an **Approve / Decline** card with the exact input. Nothing runs until you approve. A declined or ignored card resolves as declined after ten minutes, and the companion asks what you would prefer instead of retrying. Paid enrichment has one more rule: the companion never enriches phones or emails on its own initiative. It only does so when you ask, and it states the row count before the card appears. ### It answers with visuals The companion renders results as rich blocks: metric cards, charts, tables, single-record cards, org charts, and clickable suggestions. Every call, list, or score it mentions links to the matching page in Salesfinity. ## What makes it yours Eight step-by-step tutorials with prompts to paste: build accounts, use signals, score, check the CRM, re-engage closed-lost, track job changes, find contacts, and build look-alikes. Saved workflows the companion follows when a request matches. Author them, generate them from a conversation, or import them. Durable facts about you and your work that the companion carries across conversations. Companion workflows that run on a schedule or when a call is logged, with their own approval policy and an inbox for results. Per-conversation working files the companion uses for multi-step research, so long tasks do not lose intermediate results. ## Routines A routine is a saved companion task with a trigger. It runs hourly, daily, weekly, monthly, on a custom cron schedule in your timezone, or when a call is logged that matches a filter, and it can also be run manually or test-run before you save it. Each routine has a write policy: **read-only** strips every mutating tool before the run, **require approval** pauses the run and sends each write to your approvals inbox, and **auto-approve** lets the run execute writes on its own. Runs, their results, and any questions the routine needs answered land in the routine inbox. ## Companion and the REST API or MCP server The companion is the assistant inside Salesfinity. If you want to bring Salesfinity data into a different assistant, such as Claude, use the [MCP server](/mcp/overview). If you want to build your own automation, use the [REST API](/api-reference/introduction). All three read the same data. # Skills Source: https://docs.salesfinity.ai/ai-companion/skills What a skill is, where skills come from, how the AI Companion decides to use one, and the limits that apply. A skill is a saved set of instructions the AI Companion follows when a request matches. It is plain Markdown: a name, a one-line description, and a body of steps. There is no separate model, runtime, or tool behind a skill. The companion reads the body inside its own turn and does what it says with the tools it already has. Skills are how a team turns a workflow that worked once into one that works every time, for everyone. "Monday pipeline review", "pre-call brief for enterprise accounts", "log a referral the way we do it" are all skills. ## Where skills come from | Source | How it is created | Editable | | --------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------- | | Authored | Written in the Skills tab with the **New Skill** form. | Yes | | Generated | The companion drafts it from a conversation when you say "save this as a skill". | Yes | | Imported | Pulled from a GitHub link or an uploaded file in the [Agent Skills format](/ai-companion/agent-skills). | Yes | | Built-in | Seeded from Salesfinity's starter catalog when a team first activates. | Yes | | Platform | Shipped with the product for the companion's own use, such as the Skill Creator guide. | No, and not shown in the Skills tab | Every skill except platform skills belongs to the team. Any member can see, edit, deactivate, or delete it in the Skills tab. ## How the companion uses a skill At the start of every conversation the companion is given the name and description of every active skill on the team. That is all it sees at this point, which is why the description matters so much. When your request aligns with a skill's description, the companion loads the skill's full body. It prefers a matching skill over improvising a workflow from scratch. The steps run inside the companion's normal loop: plan, call tools, ask for approval on writes, reply. If the body introduces steps the companion did not anticipate, it re-plans around them. If a skill is missing or fails to load, the companion says so and handles the request from first principles rather than pretending. You can also invoke a skill directly by naming it: "run the Monday pipeline review". ## The Skills tab Open the companion and switch to **Skills**. From there you can: * **Create** a skill with the form: name, description, and content. * **Ask the companion** to create one with you. The tab offers "Help me create a skill together", which starts the guided flow described in [Creating skills](/ai-companion/creating-skills). * **Import** a skill from GitHub or a file. * **Edit**, **deactivate**, or **delete** any skill. A deactivated skill is kept but hidden from the companion; use it to pause a skill without losing it. ## Limits | Field | Limit | | ----------- | ----------------- | | Name | 200 characters | | Description | 500 characters | | Content | 50,000 characters | The content limit is generous on purpose, but a skill that approaches it is usually two skills. Around 1,500 words is a good ceiling for a body the companion can follow reliably. ## Skills and routines A skill is instructions; a routine is a trigger. When a skill describes something that should run on a schedule, "every Monday at 9am", wire it into a [routine](/ai-companion/overview#routines) whose instruction invokes the skill. The routine's write policy controls what the skill may change while running unattended. ## Related The structure of a good skill and the guided creation flow. Bring in skills written in the open Agent Skills format. # Add a Contact to a List Source: https://docs.salesfinity.ai/api-reference/endpoint/add-contact POST /v1/contact-lists/{id} Add a contact to an existing list Adds a new contact to an existing contact list. The contact is added to both the source list and the dialing queue immediately. ### Path Parameters * **id** (*required*, string): Contact list ID returned from [Create a List](/api-reference/endpoint/create-list) or [Get All Contact Lists](/api-reference/endpoint/get-contact-lists-csv). ### Request Body | Field | Type | Required | Max Length | Description | | -------------------- | ------ | -------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `first_name` | string | No | 200 | Contact's first name | | `last_name` | string | No | 200 | Contact's last name | | `email` | string | No | 320 | Contact's email address | | `phone_numbers` | array | No | 10 items | Array of phone number objects | | `company` | string | No | 300 | Company name | | `title` | string | No | 300 | Job title | | `linkedin` | string | No | 500 | LinkedIn profile URL | | `website` | string | No | 500 | Website URL | | `account` | string | No | 500 | Account or organization identifier | | `notes` | string | No | 2,000 | Free-text notes about the contact | | `priority` | number | No | — | Contact priority (lower is higher priority) | | `timezone` | string | No | 100 | IANA timezone identifier (e.g. `America/New_York`) | | `external_relations` | object | No | — | External system references (e.g. CRM IDs) | | `custom_fields` | array | No | 10 items | Array of custom field objects. See [Get Custom Fields](/api-reference/endpoint/get-custom-fields) to retrieve available fields for your team. | #### Phone Number Object | Field | Type | Required | Description | | -------------- | ------ | -------- | -------------------------------------------------- | | `type` | string | Yes | Phone number type: `mobile`, `direct`, or `office` | | `number` | string | Yes | Phone number in E.164 format (e.g. `+14155552671`) | | `country_code` | string | No | ISO 3166-1 alpha-2 country code (e.g. `US`) | | `extension` | string | No | Phone extension | #### Custom Field Object | Field | Type | Required | Max Length | Description | | ------- | ------ | -------- | ---------- | ------------------------------------------------------ | | `type` | string | Yes | — | Field data type: `string`, `number`, or `boolean` | | `label` | string | Yes | 100 | Display label for the field | | `value` | any | No | — | Field value (string, number, boolean, or string array) | ### Example Request ```json theme={null} { "first_name": "John", "last_name": "Doe", "email": "john@acme.com", "company": "Acme Corp", "title": "VP of Sales", "phone_numbers": [ { "type": "direct", "number": "+14155552671" } ], "notes": "Interested in enterprise plan. Follow up after Q2.", "custom_fields": [ { "type": "string", "label": "Industry", "value": "SaaS" }, { "type": "number", "label": "Employee Count", "value": 250 }, { "type": "boolean", "label": "Decision Maker", "value": true } ] } ``` ### Response (201) Returns the contact list ID. # Get List Performance Source: https://docs.salesfinity.ai/api-reference/endpoint/analytics-list-performance GET /v1/analytics/list-performance Returns call metrics grouped by contact list with pagination Returns call metrics grouped by contact list with pagination. Use this endpoint to analyze performance across different contact lists. ### Query Parameters * **start\_date** (*optional*, date): Start date for the analytics period. Accepts ISO 8601 format. If omitted, defaults to 7 days before `end_date`. * Examples: * `2024-01-01T00:00:00.000Z` - ISO 8601 with UTC timezone * `2024-01-01` - Simple date format (YYYY-MM-DD) * `2024-01-01T00:00:00-05:00` - ISO 8601 with timezone offset * **end\_date** (*optional*, date): End date for the analytics period. Accepts ISO 8601 format. If omitted, defaults to the current date. * Examples: * `2024-01-31T23:59:59.999Z` - ISO 8601 with UTC timezone * `2024-01-31` - Simple date format (YYYY-MM-DD) * `2024-01-31T23:59:59-05:00` - ISO 8601 with timezone offset * **user\_ids** (*optional*, array of strings): Filter by specific user IDs. * Example: `["64d2b3f2c4e3a6b8f2d9e1a7"]` * **disposition\_ids** (*optional*, array of numbers): Filter by disposition IDs (1=Meeting Set, 2=No Longer With Company, 3=Not Interested, etc.). * Example: `[1, 2, 3]` * **timezone** (*optional*, string): Timezone for date calculations. * Example: `America/New_York` * **page** (*optional*, number): Page number (default: 1). * Example: `1` * **limit** (*optional*, number): Items per page (default: 10, max: 100). * Example: `10` * **search** (*optional*, string): Search by list name. * Example: `"sales"` ### Response Returns a paginated list of contact list performance metrics. **Metrics per list:** * `_id` - Contact list ID * `name` - Contact list name * `source` - List source (e.g., "csv", "crm") * `total_calls` - Total calls made to contacts in this list * `connected_calls` - Connected calls * `conversations` - Meaningful conversations * `meetings_set` - Meetings set from this list * `good_quality_contacts` - Contacts with valid phone numbers * `data_quality` - Ratio of good quality contacts * `owner` - List owner information (id, name, email) # Get Analytics Overview Source: https://docs.salesfinity.ai/api-reference/endpoint/analytics-overview GET /v1/analytics/overview Returns aggregated analytics metrics with growth rates compared to the previous period Returns aggregated analytics metrics with growth rates compared to the previous period. Use this endpoint to get a high-level summary of call performance including total calls, connected calls, conversations, meetings set, and follow-up tasks. ### Query Parameters * **start\_date** (*optional*, date): Start date for the analytics period. Accepts ISO 8601 format. If omitted, defaults to 7 days before `end_date`. * Examples: * `2024-01-01T00:00:00.000Z` - ISO 8601 with UTC timezone * `2024-01-01` - Simple date format (YYYY-MM-DD) * `2024-01-01T00:00:00-05:00` - ISO 8601 with timezone offset * **end\_date** (*optional*, date): End date for the analytics period. Accepts ISO 8601 format. If omitted, defaults to the current date. * Examples: * `2024-01-31T23:59:59.999Z` - ISO 8601 with UTC timezone * `2024-01-31` - Simple date format (YYYY-MM-DD) * `2024-01-31T23:59:59-05:00` - ISO 8601 with timezone offset * **user\_ids** (*optional*, array of strings): Filter by specific user IDs. * Example: `["64d2b3f2c4e3a6b8f2d9e1a7"]` * **disposition\_ids** (*optional*, array of numbers): Filter by disposition IDs (1=Meeting Set, 2=No Longer With Company, 3=Not Interested, etc.). * Example: `[1, 2, 3]` * **timezone** (*optional*, string): Timezone for date calculations. * Example: `America/New_York` ### Response Each metric is returned as an object of the form `{ "value": number, "growth_rate": number }`, where `growth_rate` is the percentage change compared to the previous period of equal length. **Metrics included:** * `total_calls` - Total number of calls made * `total_inbound_calls` - Total number of inbound calls * `connected_calls` - Calls that connected (answered by human, disposition \< 10) * `conversations` - Connected calls with a meeting set or duration >= 60s * `connection_rate` - `connected_calls / total_calls` (percentage) * `conversation_rate` - `conversations / connected_calls` (percentage) * `avg_calls_per_day` - Average calls per active day * `total_call_duration` - Total call duration in seconds * `total_meetings_set` - Calls with disposition ID = 1 (Meeting Set) * `total_follow_up_tasks` - Number of follow-up tasks created * `unique_contacts` - Number of distinct contacts called * `unique_companies` - Number of distinct companies called Example: ```json theme={null} { "total_calls": { "value": 1240, "growth_rate": 12.5 }, "total_inbound_calls": { "value": 85, "growth_rate": -3.1 }, "connected_calls": { "value": 430, "growth_rate": 8.0 }, "conversations": { "value": 96, "growth_rate": 5.2 }, "connection_rate": { "value": 34.7, "growth_rate": 1.4 }, "conversation_rate": { "value": 22.3, "growth_rate": -0.8 }, "avg_calls_per_day": { "value": 62, "growth_rate": 4.0 }, "total_call_duration": { "value": 51840, "growth_rate": 9.7 }, "total_meetings_set": { "value": 41, "growth_rate": 10.0 }, "total_follow_up_tasks": { "value": 58, "growth_rate": 6.3 }, "unique_contacts": { "value": 512, "growth_rate": 7.1 }, "unique_companies": { "value": 233, "growth_rate": 3.9 } } ``` # Get SDR Performance Source: https://docs.salesfinity.ai/api-reference/endpoint/analytics-sdr-performance GET /v1/analytics/sdr-performance Returns call metrics grouped by SDR (user) with pagination Returns call metrics grouped by SDR (Sales Development Representative) with pagination. Use this endpoint to analyze individual user performance. ### Query Parameters * **start\_date** (*optional*, date): Start date for the analytics period. Accepts ISO 8601 format. If omitted, defaults to 7 days before `end_date`. * Examples: * `2024-01-01T00:00:00.000Z` - ISO 8601 with UTC timezone * `2024-01-01` - Simple date format (YYYY-MM-DD) * `2024-01-01T00:00:00-05:00` - ISO 8601 with timezone offset * **end\_date** (*optional*, date): End date for the analytics period. Accepts ISO 8601 format. If omitted, defaults to the current date. * Examples: * `2024-01-31T23:59:59.999Z` - ISO 8601 with UTC timezone * `2024-01-31` - Simple date format (YYYY-MM-DD) * `2024-01-31T23:59:59-05:00` - ISO 8601 with timezone offset * **user\_ids** (*optional*, array of strings): Filter by specific user IDs. * Example: `["64d2b3f2c4e3a6b8f2d9e1a7"]` * **disposition\_ids** (*optional*, array of numbers): Filter by disposition IDs (1=Meeting Set, 2=No Longer With Company, 3=Not Interested, etc.). * Example: `[1, 2, 3]` * **timezone** (*optional*, string): Timezone for date calculations. * Example: `America/New_York` * **page** (*optional*, number): Page number (default: 1). * Example: `1` * **limit** (*optional*, number): Items per page (default: 10, max: 100). * Example: `10` * **search** (*optional*, string): Search by SDR name or email. * Example: `"john@example.com"` ### Response Returns a paginated list of SDR performance metrics. **Metrics per SDR:** * `_id` - User ID * `first_name` - SDR first name * `last_name` - SDR last name * `email` - SDR email * `image` - Profile image URL * `total_calls` - Total calls made * `connected_calls` - Connected calls * `connection_rate` - Ratio of connected to total calls (0-1) * `conversations` - Meaningful conversations * `conversation_rate` - Ratio of conversations to total calls (0-1) * `avg_dials_day` - Average dials per active day * `total_duration` - Total call duration in seconds * `meetings_set` - Number of meetings set * `data_quality` - Data quality ratio (0-1) # Retrieve Call Logs Source: https://docs.salesfinity.ai/api-reference/endpoint/call-log GET /v1/call-log Retrieves a paginated list of call logs with filtering and sorting support. Retrieves a paginated list of call logs from the system. Use this endpoint to access call logs with metadata, such as timestamps, dispositions, and contact information. ### Query Parameters #### Pagination & Sorting * **limit** (*optional*, number): Number of items per page (default: 10, max: 100) * **page** (*optional*, number): The current page number to retrieve (default: 1) * **sort** (*optional*, string): Sort field with optional `-` prefix for descending order (default: `-createdAt`) #### Filters All filters are passed as query parameters with the `filters[field]` format. ##### Date Filters * **filters\[start\_date]** (*optional*, ISO 8601 date): Start of date range * **filters\[end\_date]** (*optional*, ISO 8601 date): End of date range ##### Call Properties * **filters\[outcome]** (*optional*, string): Filter by call outcome * `answered` - Call was answered * `no-answer` - Call was not answered * `cancelled` - Call was cancelled * **filters\[direction]** (*optional*, string): Filter by call direction * `inbound` - Incoming calls * `outbound` - Outgoing calls * **filters\[min\_duration]** (*optional*, number): Minimum call duration in seconds * **filters\[max\_duration]** (*optional*, number): Maximum call duration in seconds * **filters\[has\_recording]** (*optional*, boolean): Filter by recording availability * `true` - Only calls with recordings * `false` - Only calls without recordings * **filters\[answered\_by]** (*optional*, string): Filter by who answered * `human` - Answered by a person * `machine_start` - Answered by voicemail/machine * **filters\[is\_completed]** (*optional*, boolean): Filter by completion status ##### Disposition Filters * **filters\[disposition\_ids]** (*optional*, number\[]): Filter by disposition IDs. See [Get Dispositions](/api-reference/endpoint/get-dispositions) for available IDs. * **filters\[exclude\_negative\_dispositions]** (*optional*, boolean): When `true`, only returns calls with positive dispositions (Meeting Set, Referral, Callback Later, etc.) ##### Entity Filters * **filters\[user\_ids]** (*optional*, string\[]): Filter by user IDs * **filters\[contact\_list\_ids]** (*optional*, string\[]): Filter by contact list IDs * **filters\[contact\_ids]** (*optional*, string\[]): Filter by contact IDs (the contact's `_id`) * **filters\[crm\_ids]** (*optional*, string\[]): Filter by CRM IDs (the contact's `crm_id`) * **filters\[sequences]** (*optional*, string\[]): Filter by sequence IDs. See [Get Sequences](/api-reference/endpoint/get-sequences) to discover available sequences. ##### Phone Number Filters * **filters\[from]** (*optional*, string\[]): Filter by caller phone numbers * **filters\[to]** (*optional*, string\[]): Filter by called phone numbers ##### Search * **filters\[search]** (*optional*, string): Full-text search across the contact's name, company, account, title and email, the to/from and contact phone numbers, and the call notes, summary and disposition/call purpose/call sentiment names. Call transcripts are not searched. ### Example Requests **Basic request with date filter:** ``` GET /v1/call-log?filters[start_date]=2024-01-01&filters[end_date]=2024-01-31 ``` **Filter by outcome and minimum duration:** ``` GET /v1/call-log?filters[outcome]=answered&filters[min_duration]=60 ``` **Filter by disposition and recording:** ``` GET /v1/call-log?filters[disposition_ids][]=1&filters[disposition_ids][]=4&filters[has_recording]=true ``` **Search with multiple filters:** ``` GET /v1/call-log?filters[search]=Acme&filters[direction]=outbound&page=1&limit=50 ``` **Filter by sequence:** ``` GET /v1/call-log?filters[sequences][]=seq_12345&filters[sequences][]=seq_67890 ``` **Filter by CRM ID:** ``` GET /v1/call-log?filters[crm_ids][]=00Q5f000001abcXYZ ``` ### Response Returns a JSON object containing the list of call logs with pagination metadata. Each call log includes both `contact_list` and `source`. `contact_list` is the **dialing-queue** list the call was placed from (its `_id` is the queue id). `source` identifies the **original source list** the call came from — for CSV lists, `source.id` is the CSV list id (which differs from the queue id). `source.id`/`source.name` are `null` for non-CSV lists. `contact_list` is unchanged and remains fully backward compatible. ```json theme={null} { "data": [ { "_id": "507f1f77bcf86cd799439011", "call_id": "call_abc123", "outcome": "answered", "direction": "outbound", "answered_by": "human", "duration": 180, "to": "+1234567890", "from": "+1987654321", "disposition": { "internal_id": 1, "external_id": "meeting_set", "external_name": "Meeting Set" }, "contact": { "first_name": "John", "last_name": "Doe", "company": "Acme Corp", "email": "john@acme.com", "title": "VP of Sales" }, "contact_list": { "_id": "507f1f77bcf86cd799439022", "name": "Q1 Prospects" }, "source": { "type": "csv", "id": "507f1f77bcf86cd799439099", "name": "Q1 Prospects" }, "user": { "_id": "507f1f77bcf86cd799439033", "first_name": "Jane", "last_name": "Smith", "email": "jane@company.com" }, "recording_url": "https://recordings.example.com/call_abc123.mp3", "notes": "Great conversation, follow up next week", "is_completed": true, "started_at": "2024-01-15T14:30:00.000Z", "ended_at": "2024-01-15T14:33:00.000Z", "createdAt": "2024-01-15T14:33:00.000Z", "updatedAt": "2024-01-15T14:33:00.000Z" } ], "pagination": { "total": 150, "page": 1, "limit": 10, "pages": 15 } } ``` ### Response Fields | Field | Type | Description | | --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `_id` | string | Unique identifier | | `call_id` | string | Unique call identifier | | `outcome` | string | Call outcome (answered, no-answer, cancelled) | | `direction` | string | Call direction (inbound, outbound) | | `answered_by` | string | Who answered (human, machine\_start) | | `duration` | number | Call duration in seconds | | `to` | string | Called phone number | | `from` | string | Caller phone number | | `disposition` | object | Disposition details with internal\_id | | `contact` | object | Contact information | | `contact_list` | object | The dialing-queue list (`ContactList`) the call was placed from; its `_id` is the queue id. Unchanged for backward compatibility. | | `source` | object | The original source list the call came from. Additive; leaves `contact_list` untouched. Fields: `type` (integration source, e.g. `csv`/`hubspot`), `id` (the original source list id — the CSV list id for CSV lists, `null` for non-CSV), `name` (source list name). `id`/`name` are `null` when unresolved. | | `user` | object | User who made/received the call | | `recording_url` | string | URL to call recording (if available) | | `notes` | string | Call notes | | `transcription` | string | Call transcription (if available) | | `summary` | string | AI-generated call summary (if available) | | `is_completed` | boolean | Whether the call is completed | | `started_at` | date | Call start time | | `ended_at` | date | Call end time | | `createdAt` | date | Record creation time | | `updatedAt` | date | Record last update time | # Create a List Source: https://docs.salesfinity.ai/api-reference/endpoint/create-list POST /v1/contact-lists Creates a new contact list Creates a new contact list for the team. Contacts are optional and can be added later via the [Add Contact](/api-reference/endpoint/add-contact) endpoint. ### Limits | Resource | Limit | | ------------------------- | ---------------- | | Contacts per list | 2,000 | | List name | 1–100 characters | | Notes per contact | 2,000 characters | | Phone numbers per contact | 10 | | Custom fields per contact | 10 | | Request payload size | 10MB | ### Request Body | Field | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Yes | Name of the contact list (1–100 characters) | | `user_id` | string | Yes | ID of the team member who owns this list. Must be a valid member of the team. Get this from [Get Team](/api-reference/endpoint/get-team). | | `contacts` | array | No | Array of contact objects (max 2,000). See [Add Contact](/api-reference/endpoint/add-contact) for the contact object schema. | ### Example Request ```json theme={null} { "name": "Q2 Outbound Prospects", "user_id": "680edc0d1504192884a148e0", "contacts": [ { "first_name": "John", "last_name": "Doe", "email": "john@acme.com", "company": "Acme Corp", "title": "VP of Sales", "phone_numbers": [ { "type": "direct", "number": "+14155552671" } ] } ] } ``` ### Example Request (empty list) ```json theme={null} { "name": "Q2 Outbound Prospects", "user_id": "680edc0d1504192884a148e0" } ``` ### Response (201) Returns the created contact list object with its `_id`. Use this ID for all subsequent operations (adding contacts, merging, deleting, etc.). # Create a Note Source: https://docs.salesfinity.ai/api-reference/endpoint/create-note POST /v2/notes Creates a note attached to a person (Contact) or company (Company), identified by domain identifiers rather than by list/contact IDs. For a person, supply at least one of linkedin_url, email, crm_id, or phone. For a company, supply at least one of website_url or name. If no matching record exists yet, a minimal Contact or Company is created on the fly and the note is attached to its stable ID. Creates a note attached to a **person** (Contact) or a **company** (Company). Unlike the older list-scoped endpoints, v2 notes are addressed by **domain identifiers** rather than by contact-list and contact IDs — so you can attach a note without first looking the person up in a list. The target is resolved from the identifiers you provide. If no matching record exists yet, a minimal Contact or Company is created on the fly and the note is attached to its stable ID. ### Request Body | Field | Type | Required | Max Length | Description | | --------- | ------ | -------- | ---------- | --------------------------------------------------------------------------------------------- | | `type` | string | Yes | — | `person` or `company`. Determines which identifier fields are required. | | `user_id` | string | Yes | — | ID of the team member authoring the note. Must be a member of the team that owns the API key. | | `content` | string | Yes | 10,000 | Plain text content of the note. | #### Person identifiers (`type: "person"`) Supply **at least one** of: | Field | Type | Max Length | Description | | -------------- | ------ | ---------- | ---------------------------------------------------------- | | `linkedin_url` | string | 500 | LinkedIn profile URL. Username is extracted automatically. | | `email` | string | 320 | Email address. | | `crm_id` | string | 200 | External CRM identifier (e.g. `salesforce:003XXX`). | | `phone` | string | 50 | Phone number in E.164 format. | Optionally, link a company when a **new** Contact has to be created (ignored if the person already exists): | Field | Type | Max Length | Description | | ----------------- | ------ | ---------- | -------------------------------------- | | `company_name` | string | 300 | Company name to link to the person. | | `company_website` | string | 500 | Company website to link to the person. | #### Company identifiers (`type: "company"`) Supply **at least one** of: | Field | Type | Max Length | Description | | ------------- | ------ | ---------- | ------------------------------------------------------------------- | | `website_url` | string | 500 | Company website URL. Normalized to hostname for matching. | | `name` | string | 300 | Company name. Case-insensitive match against the company name list. | ### Example Request — person ```json theme={null} { "type": "person", "user_id": "507f1f77bcf86cd799439033", "content": "Mentioned moving back from Lisbon next quarter.", "email": "john@example.com", "company_name": "Acme Corp" } ``` ### Example Request — company ```json theme={null} { "type": "company", "user_id": "507f1f77bcf86cd799439033", "content": "Renewal owner is the new VP of Ops — loop in before Q3.", "website_url": "https://acme.com" } ``` ### Response (201) Returns the created [note object](/api-reference/endpoint/list-notes#note-object). For a person note, `contact` is set; for a company note, `company` is set. ### Errors | Status | Description | | ------ | --------------------------------------------------------------------------------------------------------------- | | 400 | Validation failed (e.g. no identifier supplied for the chosen `type`), or `user_id` is not a member of the team | # Delete a List Source: https://docs.salesfinity.ai/api-reference/endpoint/delete-contact-list DELETE /v1/contact-lists/csv/{id} Delete a contact list Permanently deletes a contact list and all its contacts from both the source and the dialing queue. **Warning:** This action is irreversible — all contacts and data in the list will be lost. ### Path Parameters * **id** (*required*, string): The contact list ID returned from [Create a List](/api-reference/endpoint/create-list) or [Get All Contact Lists](/api-reference/endpoint/get-contact-lists-csv). ### Response (200) ```json theme={null} { "success": true } ``` ### Errors | Status | Description | | ------ | ---------------------- | | 404 | Contact list not found | # Delete a Note Source: https://docs.salesfinity.ai/api-reference/endpoint/delete-note DELETE /v2/notes/{id} Deletes an existing note. Only the original author may delete a note. Deletes an existing note. Only the **original author** may delete a note. ### Path Parameters * **id** (*required*, string): Note ID, from [List Notes](/api-reference/endpoint/list-notes). ### Request Body | Field | Type | Required | Description | | --------- | ------ | -------- | ---------------------------------------------------------------------------------------- | | `user_id` | string | Yes | ID of the team member performing the delete. Must match the original author of the note. | ### Example Request ```json theme={null} { "user_id": "507f1f77bcf86cd799439033" } ``` ### Response (200) ```json theme={null} { "success": true } ``` ### Errors | Status | Description | | ------ | ------------------------------------ | | 403 | Only the author can delete this note | | 404 | Note not found | # Delete Snoozed Contact Source: https://docs.salesfinity.ai/api-reference/endpoint/delete-snoozed-contact DELETE /v1/snoozed-contacts/{id} Delete a snoozed contact Removes a contact from the snoozed list, allowing them to appear in call lists again immediately. ### Path Parameters * **id** (*required*, string): Snoozed contact ID ### Response Returns a success message if deleted, or 404 if not found. ```json theme={null} { "message": "Snoozed contact deleted successfully" } ``` # Request an Email Enrichment Source: https://docs.salesfinity.ai/api-reference/endpoint/enrich-email POST /v1/api/enrichment/email Starts an asynchronous lookup of a work or personal email address for a LinkedIn profile. Returns immediately with a request `_id` and a `status` of `pending`; the result is delivered later either by polling `GET /v1/api/enrichment/email/{id}` or via the optional `callback_url` webhook. Each completed lookup costs 1 enrichment credit (charged only when an email is found). Requires a positive credit balance. Starts an **asynchronous** lookup of a work or personal email address for a LinkedIn profile. The call returns immediately with a request `_id` and a `status` of `pending`. The actual lookup runs in the background — collect the result either by [polling](/api-reference/endpoint/get-email-enrichment) the returned `_id`, or by providing a `callback_url` that we POST to when the lookup finishes. Each **completed** lookup costs **1 enrichment credit**. Credits are charged only when an email is found (`status: "completed"`), never for `not-found`. The request is rejected with `402` if the team has no remaining credits — check your balance with [Get Enrichment Credits](/api-reference/endpoint/get-enrichment-credits). ### Request Body | Field | Type | Required | Description | | -------------- | ------------- | -------- | -------------------------------------------------------------------------------- | | `linkedin_url` | string | Yes | LinkedIn profile URL. Must be a `linkedin.com/in/` URL. | | `type` | string | Yes | Email type to find — `work` or `personal`. | | `callback_url` | string (URL) | No | Webhook POSTed when the enrichment finishes. Retried up to 3 times with backoff. | | `external_id` | string (≤256) | No | Opaque value echoed back in the callback for client-side correlation. | ### Example Request ```json theme={null} { "linkedin_url": "https://www.linkedin.com/in/janedoe", "type": "work", "callback_url": "https://example.com/webhooks/enrichment", "external_id": "lead-42" } ``` ### Response (201) ```json theme={null} { "_id": "507f1f77bcf86cd799439011", "status": "pending", "linkedin_url": "https://www.linkedin.com/in/janedoe" } ``` Persist the `_id` — it is the handle for polling and the identifier referenced in the callback. ### Callback payload If you supplied a `callback_url`, we POST a JSON body to it once the lookup resolves: ```json theme={null} { "request_id": "507f1f77bcf86cd799439011", "status": "completed", "enrichment_type": "work_email", "email": { "email": "jane@acme.com", "type": "work" }, "linkedin_url": "https://www.linkedin.com/in/janedoe", "external_id": "lead-42" } ``` `status` is `completed` (with `email`) or `not-found` (`email` is `null`). Delivery is attempted up to 3 times; if every attempt fails, fall back to polling. ### Errors | Status | Description | | ------ | ---------------------------------------------------------------------------------------------------------------- | | 400 | Validation failed — `linkedin_url` is not a `linkedin.com/in/` URL, or `type` is not `work`/`personal` | | 402 | Insufficient enrichment credits | # Request a Phone Enrichment Source: https://docs.salesfinity.ai/api-reference/endpoint/enrich-phone POST /v1/api/enrichment/phone Starts an asynchronous lookup of mobile and direct-dial phone numbers for a LinkedIn profile. Returns immediately with a request `_id` and a `status` of `pending`; a provider waterfall then runs in the background, validating each candidate number and completing on the first one that clears validation. The result is delivered either by polling `GET /v1/api/enrichment/phone/{id}` or via the optional `callback_url` webhook. A completed lookup that returns at least one number costs 1 enrichment credit; a completed lookup with an empty list, and every not-found result, are free. Phone and email enrichment share one credit pool, and a positive balance is required. Starts an **asynchronous** lookup of mobile and direct-dial phone numbers for a LinkedIn profile. The call returns immediately with a request `_id` and a `status` of `pending`. Behind it runs a provider waterfall: each data provider in turn is asked for a number, every candidate is validated, and the request completes on the first number that clears validation. Collect the result either by [polling](/api-reference/endpoint/get-phone-enrichment) the returned `_id`, or by providing a `callback_url` that we POST to when the lookup finishes. Each **completed** lookup that returns at least one number costs **1 enrichment credit**. A `completed` result with an empty `phone_numbers` list, and every `not-found` result, are free. Phone and email enrichment draw on the same credit pool — check the balance with [Get Enrichment Credits](/api-reference/endpoint/get-enrichment-credits). The request is rejected with `402` if the team has no remaining credits. ### Request Body | Field | Type | Required | Description | | -------------- | ------------- | -------- | -------------------------------------------------------------------------------- | | `linkedin_url` | string | Yes | LinkedIn profile URL. Must be a `linkedin.com/in/` URL. | | `callback_url` | string (URL) | No | Webhook POSTed when the enrichment finishes. Retried up to 3 times with backoff. | | `external_id` | string (≤256) | No | Opaque value echoed back in the callback for client-side correlation. | ### Example Request ```json theme={null} { "linkedin_url": "https://www.linkedin.com/in/janedoe", "callback_url": "https://example.com/webhooks/enrichment", "external_id": "lead-42" } ``` ### Response (201) ```json theme={null} { "_id": "507f1f77bcf86cd799439011", "linkedin": "https://www.linkedin.com/in/janedoe", "linkedin_username": "janedoe", "status": "pending", "enrichment_type": "contact", "createdAt": "2026-08-27T10:15:04.221Z", "updatedAt": "2026-08-27T10:15:04.221Z" } ``` Persist the `_id` — it is the handle for polling and the identifier referenced in the callback as `request_id`. `external_id` is held against your request and returned in the **callback** only. It is not echoed in this response, nor in the polling response. ### Callback payload If you supplied a `callback_url`, we POST a JSON body to it once the lookup resolves: ```json theme={null} { "request_id": "507f1f77bcf86cd799439011", "status": "completed", "enrichment_type": "contact", "contact": { "linkedin_username": "janedoe", "phone_numbers": [ { "number": "+14155550123", "country_code": "US", "type": "mobile", "extension": "", "source": "prospeo", "validation": { "phone_number": "+14155550123", "is_valid": true, "carrier": "AT&T", "line_type": "mobile", "likely_to_answer": "P1", "score": 92, "status": "completed" } } ] }, "linkedin_url": "https://www.linkedin.com/in/janedoe", "external_id": "lead-42" } ``` Note the shape difference from polling: the callback nests the numbers under `contact`, while [the poll response](/api-reference/endpoint/get-phone-enrichment) returns `phone_numbers` at the top level. `status` is either `completed` or `not-found` — a phone enrichment never calls back with `failed`. On `not-found`, `contact` is usually `null`; when the profile is known but no provider was eligible to be queried, `contact` is present with the numbers already on file. Delivery is attempted up to 3 times with backoff; if every attempt fails, fall back to polling. ### Errors | Status | Description | | ------ | ---------------------------------------------------------------------------- | | 400 | Validation failed — `linkedin_url` is not a `linkedin.com/in/` URL | | 402 | Insufficient enrichment credits | # Get Call Log by ID Source: https://docs.salesfinity.ai/api-reference/endpoint/get-call-log-by-id GET /v1/call-log/{id} Get a call log by ID Retrieves a single call log entry by its ID. ### Path Parameters * **id** (*required*, string): Call log ID ### Response Returns the call log object matching the given ID, or 404 if not found. ```json theme={null} { "data": { "_id": "507f1f77bcf86cd799439011", "call_id": "call_abc123", "outcome": "answered", "direction": "outbound", "answered_by": "human", "duration": 180, "to": "+1234567890", "from": "+1987654321", "disposition": { "internal_id": 1, "external_id": "meeting_set", "external_name": "Meeting Set" }, "contact": { "first_name": "John", "last_name": "Doe", "company": "Acme Corp", "email": "john@acme.com", "title": "VP of Sales" }, "contact_list": { "_id": "507f1f77bcf86cd799439022", "name": "Q1 Prospects" }, "user": { "_id": "507f1f77bcf86cd799439033", "first_name": "Jane", "last_name": "Smith", "email": "jane@company.com" }, "recording_url": "https://recordings.example.com/call_abc123.mp3", "notes": "Great conversation, follow up next week", "is_completed": true, "started_at": "2024-01-15T14:30:00.000Z", "ended_at": "2024-01-15T14:33:00.000Z", "createdAt": "2024-01-15T14:33:00.000Z", "updatedAt": "2024-01-15T14:33:00.000Z" } } ``` # Get a List by ID Source: https://docs.salesfinity.ai/api-reference/endpoint/get-contact-list-by-id GET /v1/contact-lists/csv/{id} Returns a single CSV contact list with all its contacts. Supports searching, sorting, and paginating contacts. Returns a single CSV contact list with all its contacts. Supports searching, sorting, and paginating the contacts within the list. ### Path Parameters * **id** (*required*, string): CSV contact list ID ### Query Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------ | | `search` | string | Search contacts by first name, last name, email, or company (case-insensitive) | | `sort` | string | Sort contacts by field. Prefix with `-` for descending. Examples: `first_name`, `-company`, `priority` | | `page` | number | Page number for contacts. Default: 1 | | `limit` | number | Contacts per page. Default: 50 | ### Response Returns the list metadata and a paginated array of contacts: | Field | Type | Description | | ---------- | ------ | ------------------------------------- | | `_id` | string | Unique identifier of the contact list | | `name` | string | Name of the list | | `user` | string | ID of the user who owns the list | | `contacts` | array | Array of contact objects | Each contact object includes: | Field | Type | Description | | --------------- | ------ | -------------------- | | `_id` | string | Contact ID | | `first_name` | string | First name | | `last_name` | string | Last name | | `email` | string | Email address | | `company` | string | Company name | | `title` | string | Job title | | `phone_numbers` | array | Phone numbers | | `linkedin` | string | LinkedIn profile URL | | `website` | string | Website URL | | `notes` | string | Notes | | `priority` | number | Priority | | `timezone` | string | Timezone | | `custom_fields` | array | Custom fields | ### Example Requests ```bash theme={null} # Get a list with all contacts GET /v1/contact-lists/csv/507f1f77bcf86cd799439011 # Search contacts by name GET /v1/contact-lists/csv/507f1f77bcf86cd799439011?search=john # Sort contacts by company, page 2 GET /v1/contact-lists/csv/507f1f77bcf86cd799439011?sort=company&page=2&limit=20 ``` ### Errors | Status | Description | | ------ | -------------------------- | | 404 | CSV contact list not found | # Get All Lists Source: https://docs.salesfinity.ai/api-reference/endpoint/get-contact-lists-csv GET /v1/contact-lists/csv Returns all CSV contact lists for the team with filtering, sorting, and pagination. Returns all CSV contact lists for the team. This is the primary endpoint for listing your contact lists. Returns metadata only — use [Get List by ID](/api-reference/endpoint/get-contact-list-by-id) to see the contacts inside a list. ### Query Parameters | Parameter | Type | Description | | --------------- | ------ | ---------------------------------------------------------------------------------------------------- | | `search` | string | Search lists by name (case-insensitive) | | `filters[user]` | string | Filter by user ID | | `sort` | string | Sort by field. Prefix with `-` for descending. Default: `-createdAt`. Examples: `name`, `-updatedAt` | | `page` | number | Page number. Default: 1 | | `limit` | number | Items per page. Default: 10 | ### Response Returns a paginated array of contact lists: | Field | Type | Description | | ---------------- | ------ | ------------------------------------- | | `_id` | string | Unique identifier of the contact list | | `name` | string | Name of the list | | `user` | string | ID of the user who owns the list | | `total_contacts` | number | Number of contacts in the list | | `createdAt` | string | Date the list was created | | `updatedAt` | string | Date the list was last updated | ### Example Requests ```bash theme={null} # Get all lists GET /v1/contact-lists/csv # Search by name GET /v1/contact-lists/csv?search=outbound # Filter by user GET /v1/contact-lists/csv?filters[user]=680edc0d1504192884a148e0 # Sort by name ascending GET /v1/contact-lists/csv?sort=name # Combine filters GET /v1/contact-lists/csv?search=q2&filters[user]=680edc0d1504192884a148e0&sort=-createdAt&page=1&limit=20 ``` # Get Custom Fields Source: https://docs.salesfinity.ai/api-reference/endpoint/get-custom-fields GET /v1/custom-fields Returns all custom field mappings configured for the team. Custom fields represent field mappings between Salesfinity and integrated CRM platforms. Returns all custom field mappings configured for your team. Custom fields represent field mappings between Salesfinity and your integrated CRM platforms (Salesforce, HubSpot, etc.). Each record contains the integration source and the mapped fields for contacts and leads. ### Response Returns an array of custom field mapping records. * `_id` - Unique identifier * `source` - Integration source (e.g. `salesforce`, `hubspot`) * `team` - Team ID * `user` - User ID who configured the mapping * `fields` - Array of contact field mappings * `type` - Field category * `field_type` - Data type of the field * `internal_name` - Salesfinity field name * `external_key` - CRM field key * `external_name` - CRM field display name * `lead_fields` - Array of lead field mappings (same structure as `fields`) * `createdAt` - Creation timestamp * `updatedAt` - Last update timestamp # Get Disposition by ID Source: https://docs.salesfinity.ai/api-reference/endpoint/get-disposition-by-id GET /v1/dispositions/{id} Get a specific disposition by its internal ID Retrieves a specific disposition by its ID. ### Path Parameters * **id** (*required*, number): The internal disposition ID (1-14 for default dispositions, higher for custom) ### Response Returns a JSON object containing the disposition details. ```json theme={null} { "data": { "_id": "507f1f77bcf86cd799439011", "id": 1, "name": "Answered - Meeting Set", "is_custom": false, "is_required": true, "category": "positive" } } ``` ### Error Responses * **404 Not Found**: Disposition with the specified ID does not exist # Get Dispositions Source: https://docs.salesfinity.ai/api-reference/endpoint/get-dispositions GET /v1/dispositions Returns all dispositions available for the team, including default and custom dispositions Retrieves all dispositions available for your team. Dispositions are call outcome categories used to classify the result of each call. This endpoint returns both default dispositions (built-in) and any custom dispositions your team has created. ### Query Parameters * **is\_custom** (*optional*, boolean): Filter by custom or default dispositions * `true` - Only return custom dispositions * `false` - Only return default dispositions * **category** (*optional*, string): Filter by disposition category * `positive` - Meeting set, referral, callback later, reach out in 6 months, send an email * `negative` - Not interested, do not call again * `neutral` - No longer with company, wrong contact * `not_answered` - No answer, left voicemail, gatekeeper, bad number * `cancelled` - Cancelled calls ### Response Returns a JSON object containing an array of dispositions. ```json theme={null} { "data": [ { "_id": "507f1f77bcf86cd799439011", "id": 1, "name": "Answered - Meeting Set", "is_custom": false, "is_required": true, "category": "positive" }, { "_id": "507f1f77bcf86cd799439012", "id": 2, "name": "Answered - No Longer with Company", "is_custom": false, "is_required": false, "category": "neutral" } ] } ``` ### Response Fields | Field | Type | Description | | ------------- | ------- | -------------------------------------------- | | `_id` | string | Unique identifier | | `id` | number | Disposition ID (use for filtering call logs) | | `name` | string | Human-readable disposition name | | `is_custom` | boolean | Whether this is a custom disposition | | `is_required` | boolean | Whether this disposition is required | | `category` | string | Disposition category | ### Default Dispositions | ID | Name | Category | | -- | --------------------------------- | ------------- | | 1 | Answered - Meeting Set | positive | | 2 | Answered - No Longer with Company | neutral | | 3 | Answered - Not Interested | negative | | 4 | Answered - Referral | positive | | 5 | Answered - Wrong Contact | neutral | | 6 | Answered - Call back later | positive | | 7 | Answered - Reach out in 6 months | positive | | 8 | Answered - Send an email | positive | | 9 | Answered - Do not call again | negative | | 10 | No Answer | not\_answered | | 11 | Left Voicemail | not\_answered | | 12 | Gatekeeper | not\_answered | | 13 | Bad Number | not\_answered | | 14 | Cancelled | cancelled | # Poll an Email Enrichment Source: https://docs.salesfinity.ai/api-reference/endpoint/get-email-enrichment GET /v1/api/enrichment/email/{id} Returns the current state of an email enrichment request. While the lookup is in progress `status` is `pending` or `processing`. When finished `status` is `completed` (with the found `email`) or `not-found` (no email available). Polling is a fallback for the callback and is safe to call repeatedly — a request is never charged twice. Returns the current state of an email enrichment request created with [Request an Email Enrichment](/api-reference/endpoint/enrich-email). Polling is the fallback for the `callback_url` webhook — it is safe to call repeatedly, and a request is **never charged twice** regardless of how often you poll. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------- | | `id` | string | The enrichment request `_id` returned when the request was created. | ### Status lifecycle | Status | Meaning | | ------------ | -------------------------------------------------------------------------- | | `pending` | Accepted, not yet started. | | `processing` | Lookup in progress. | | `completed` | An email was found — see the `email` field. (1 credit charged.) | | `not-found` | Lookup finished, no email available. `email` is `null`. No credit charged. | | `failed` | The lookup could not be completed. | ### Response (200) — completed ```json theme={null} { "_id": "507f1f77bcf86cd799439011", "status": "completed", "enrichment_type": "work_email", "email": { "email": "jane@acme.com", "type": "work" }, "linkedin_url": "https://www.linkedin.com/in/janedoe", "external_id": "lead-42" } ``` ### Response (200) — still running ```json theme={null} { "_id": "507f1f77bcf86cd799439011", "status": "processing", "linkedin_url": "https://www.linkedin.com/in/janedoe" } ``` ### Errors | Status | Description | | ------ | ---------------------------------------------- | | 404 | No enrichment request found for the given `id` | # Get Enrichment Credits Source: https://docs.salesfinity.ai/api-reference/endpoint/get-enrichment-credits GET /v1/api/enrichment/credits Returns the current enrichment credit balance for the team that owns the API key. Phone and email enrichment draw on the same pool: each completed lookup costs 1 credit. Returns the current enrichment credit balance for the team that owns the API key. Each **completed** email enrichment costs **1 credit**. Use this endpoint to check available credits before kicking off enrichment requests — a request with a zero balance is rejected with `402 Payment Required`. ### Response (200) ```json theme={null} { "balance": 250 } ``` | Field | Type | Description | | --------- | ------- | ---------------------------------------------------- | | `balance` | integer | Number of enrichment credits remaining for the team. | # Get Follow-up Tasks Source: https://docs.salesfinity.ai/api-reference/endpoint/get-follow-ups GET /v1/follow-up Returns all follow-up tasks for the team with pagination Returns all follow-up tasks for the team with pagination support. Follow-up tasks are created after calls to track pending actions and reminders. ### Query Parameters * **page** (*optional*, number): Page number for pagination. Default: 1 * **limit** (*optional*, number): Number of items per page. Min: 1, Max: 100 * **sort** (*optional*, string): Sort field(s). Prefix with "-" for descending order. Default: `-createdAt` * Example: `follow_up_date,-priority` ### Response Returns a paginated array of follow-up tasks with the following fields: * `_id` - Unique identifier * `team` - Team ID * `user` - Assigned user * `call_log` - Associated call log ID * `contact` - Contact ID * `company` - Company ID * `priority` - Task priority: `high`, `medium`, or `low` * `status` - Task status: `not_overdue`, `overdue`, or `completed` * `follow_up_date` - Scheduled follow-up date * `first_name` - Contact's first name * `last_name` - Contact's last name * `tags` - Array of tags: `timing`, `budget`, `competitor` * `touches` - Number of contact attempts * `context` - Additional context or notes * `is_archived` - Whether the task is archived * `createdAt` - Creation timestamp * `updatedAt` - Last update timestamp # Poll a Phone Enrichment Source: https://docs.salesfinity.ai/api-reference/endpoint/get-phone-enrichment GET /v1/api/enrichment/phone/{id} Returns the current state of a phone enrichment request. While the waterfall is running `status` is `pending` or `processing`. When finished `status` is `completed` (with the numbers in `phone_numbers`) or `not-found` (every eligible provider was exhausted without a number that passed validation). `phone_numbers` is populated only on a `completed` request and is an empty array otherwise. Polling is a fallback for the callback and is safe to call repeatedly — a request is never charged twice. Returns the current state of a phone enrichment request created with [Request a Phone Enrichment](/api-reference/endpoint/enrich-phone). Polling is the fallback for the `callback_url` webhook — it is safe to call repeatedly, and a request is **never charged twice** regardless of how often you poll. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------- | | `id` | string | The enrichment request `_id` returned when the request was created. | ### Status lifecycle | Status | Meaning | | ------------ | ------------------------------------------------------------------------------------------------- | | `pending` | Accepted, not yet started. | | `processing` | The provider waterfall is running. | | `completed` | The lookup finished — see `phone_numbers`. (1 credit charged, unless the list came back empty.) | | `not-found` | Every eligible provider was exhausted without a number that passed validation. No credit charged. | `phone_numbers` is returned **only** on a `completed` request. Until then the field is an empty array, even if we already hold numbers for that profile from an earlier lookup. ### Response (200) — completed ```json theme={null} { "_id": "507f1f77bcf86cd799439011", "linkedin": "https://www.linkedin.com/in/janedoe", "linkedin_username": "janedoe", "status": "completed", "enrichment_type": "contact", "phone_numbers": [ { "number": "+14155550123", "country_code": "US", "type": "mobile", "extension": "", "source": "prospeo", "validation": { "phone_number": "+14155550123", "is_valid": true, "carrier": "AT&T", "line_type": "mobile", "likely_to_answer": "P1", "score": 92, "status": "completed" } } ], "createdAt": "2026-08-27T10:15:04.221Z", "updatedAt": "2026-08-27T10:16:38.904Z" } ``` `phone_numbers` is everything we hold for that LinkedIn profile, not only what this request discovered — a profile enriched twice returns the accumulated list both times. ### Phone number object | Field | Type | Description | | -------------- | ------ | -------------------------------------------------------------------------------- | | `number` | string | The number in E.164 format. | | `country_code` | string | ISO 3166-1 alpha-2 country of the number (`US`, `GB`, …) — not a dialing prefix. | | `type` | string | `mobile`, `direct`, or `office`. | | `extension` | string | Dial extension, empty when there is none. | | `source` | string | The data provider the number came from. | | `validation` | object | Result of the phone validation pass — see below. | ### Validation object | Field | Type | Description | | ------------------ | ------- | -------------------------------------------------------------------------- | | `phone_number` | string | The number that was validated. | | `is_valid` | boolean | Whether the number passed validation. Numbers that fail are never stored. | | `carrier` | string | Carrier reported by the validator. | | `line_type` | string | Line classification, e.g. `mobile` or `landline`. | | `likely_to_answer` | string | Answer-likelihood tier — `P1` (best) through `P3`. | | `score` | number | Validator confidence score. | | `status` | string | `completed` once validation resolved; `pending` while it is still running. | A request only completes on a number that is valid, is not a `landline`, and is not tier `P3`; anything else sends the waterfall on to the next provider. Numbers that were rejected on the way may still appear in the array from earlier passes, so filter on `validation` rather than assuming every entry is dial-worthy. ### Response (200) — still running ```json theme={null} { "_id": "507f1f77bcf86cd799439011", "linkedin": "https://www.linkedin.com/in/janedoe", "linkedin_username": "janedoe", "status": "processing", "enrichment_type": "contact", "phone_numbers": [], "createdAt": "2026-08-27T10:15:04.221Z", "updatedAt": "2026-08-27T10:15:09.117Z" } ``` ### Errors | Status | Description | | ------ | ---------------------------------------------- | | 404 | No enrichment request found for the given `id` | # Get Scored Call by ID Source: https://docs.salesfinity.ai/api-reference/endpoint/get-scored-call-by-id GET /v1/scored-calls/{id} Retrieves a single scored call by its ID with full AI-generated insight. Retrieves a single scored call by its ID, including the full AI-generated insight with scoring facets, lead qualification, and coaching recommendations. ### Path Parameters * **id** (*required*, string): Scored call ID ### Response Returns the scored call object matching the given ID, or 404 if not found. ```json theme={null} { "data": { "_id": "507f1f77bcf86cd799439011", "insight": { "total_score": 78, "lead": { "function": "Sales", "tech_stack_mentioned": ["Salesforce", "Outreach"], "current_vendor": "Competitor Inc", "fit_score": 85, "is_decision_maker": true, "is_correct_persona": true, "pain_point_resonated": true, "is_qualified_meeting": true, "is_likely_to_buy": false, "prospect_lifecycle_stage": ["solution_aware"] }, "messaging": { "value_prop_resonated": ["efficiency", "time_savings"], "objection_outcome": ["handled"], "emotional_triggers": ["frustration_with_current_tool"], "recommendation_to_nail_messaging": "Lead with ROI data specific to their industry" }, "targeting_feedback": { "persona_fit": "excellent", "industry_fit": "good", "persona_seniority": "vp", "title_relevance": "good", "data_quality_issue": [], "future_follow_up_needed": true, "follow_up_reason": ["renewal"] }, "metadata": { "duration_sec": 245, "asr_confidence_avg": 0.92, "audio_features_available": true, "talk_listen_ratio_rep": 0.45, "total_questions": 8, "open_question_ratio": 0.625, "interruptions_by_rep": 1, "core_pitch_duration_sec": 35, "estimated_wpm_rep": 155, "objections_detected": ["budget"] }, "facets": { "intro": { "score": 85, "weight_pct": 10, "explanation": "Strong permission-based opener", "insufficient_evidence": false, "checks": { "permission_opener_used": true, "opener_length_sec": 12, "time_to_opener_sec": 3, "improvement_recommendation": "Consider a more personalized opener" } }, "discovery": { "score": 72, "weight_pct": 25, "explanation": "Good question quality but missed timing topic", "insufficient_evidence": false, "metrics": { "total_questions": 8, "open_question_ratio": 0.625, "improvement_recommendation": "Ask about timeline and decision process", "topic_coverage": ["pain", "current_tools"] } }, "pitch": { "score": 80, "weight_pct": 20, "explanation": "Well-tailored pitch to discovery findings", "insufficient_evidence": false, "metrics": { "core_pitch_duration_sec": 35, "tailored_to_discovery": true, "improvement_recommendation": "Include a customer success story", "outcome_keywords": ["efficiency", "save_time"] } }, "tonality": { "score": 75, "weight_pct": 15, "explanation": "Good pace, minor filler word usage", "insufficient_evidence": false, "metrics": { "estimated_wpm": 155, "interruptions_by_rep": 1, "filler_density_per_min": 2.1, "improvement_recommendation": "Reduce filler words" } }, "objection_handling": { "score": 70, "weight_pct": 15, "explanation": "Addressed budget objection but no follow-up probe", "insufficient_evidence": false, "metrics": { "objections_detected": ["budget"], "followup_probe_present": false, "resolution_check_present": true, "improvement_recommendation": "Add a follow-up question after handling objections" } }, "cta": { "score": 82, "weight_pct": 15, "explanation": "Clear CTA with specific time offered", "insufficient_evidence": false, "metrics": { "cta_attempted": true, "cta_type": "meeting", "specific_time_offered": true, "improvement_recommendation": "Confirm next steps via email", "outcome": "accepted" } } }, "gaps": { "skill": 25, "playbook": 15, "rationale": "Discovery needs more depth on timing and decision process" }, "coaching": { "top_wins": ["Strong opener", "Good pitch tailoring"], "top_opportunities": ["Deeper discovery", "Objection follow-up probes"], "suggested_drills": [ { "facet": "discovery", "assignment": "Practice SPIN questions for uncovering timeline" } ] } }, "call_log": { "_id": "507f1f77bcf86cd799439022", "contact": { "first_name": "John", "last_name": "Doe", "company": "Acme Corp" }, "recording_url": "https://recordings.example.com/call_abc123.mp3", "duration": 245 }, "user": { "_id": "507f1f77bcf86cd799439033", "first_name": "Jane", "last_name": "Smith", "email": "jane@company.com" }, "type": "call", "createdAt": "2024-01-15T14:33:00.000Z", "updatedAt": "2024-01-15T14:33:00.000Z" } } ``` # Get Sequence by ID Source: https://docs.salesfinity.ai/api-reference/endpoint/get-sequence-by-id GET /v1/sequences/{id} Get a specific sequence by ID Retrieves a specific sequence by its ID with detailed statistics. ### Path Parameters * **id** (*required*, string): The sequence ID ### Response Returns a JSON object containing the sequence details. ```json theme={null} { "data": { "id": "seq_12345", "name": "Q1 Outbound Campaign", "last_used_at": "2024-02-01T15:30:00.000Z", "first_used_at": "2024-01-05T09:00:00.000Z", "total_calls": 1250 } } ``` ### Error Responses * **404 Not Found**: Sequence with the specified ID does not exist # Get Sequences Source: https://docs.salesfinity.ai/api-reference/endpoint/get-sequences GET /v1/sequences Returns all sequences used in call logs for the team Retrieves all sequences that have been used in call logs for your team. Sequences represent marketing/sales automation campaigns from integrated platforms like Outreach, Salesloft, etc. Use this endpoint to discover sequence IDs for filtering call logs. ### Query Parameters * **search** (*optional*, string): Search sequences by name * **sort** (*optional*, string): Sort field and order. Default: `-last_used_at` * `-last_used_at` - Most recently used first * `last_used_at` - Oldest used first * `-total_calls` - Most calls first * `total_calls` - Fewest calls first * `-name` - Name descending (Z-A) * `name` - Name ascending (A-Z) * **limit** (*optional*, number): Results per page (default: 100, max: 100) * **page** (*optional*, number): Page number (default: 1) ### Response Returns a JSON object containing an array of sequences with pagination. ```json theme={null} { "data": [ { "id": "seq_12345", "name": "Q1 Outbound Campaign", "last_used_at": "2024-02-01T15:30:00.000Z", "first_used_at": "2024-01-05T09:00:00.000Z", "total_calls": 1250 }, { "id": "seq_67890", "name": "Enterprise Follow-up", "last_used_at": "2024-01-28T10:15:00.000Z", "first_used_at": "2024-01-10T14:00:00.000Z", "total_calls": 430 } ], "pagination": { "total": 15, "page": 1, "limit": 100 } } ``` ### Response Fields | Field | Type | Description | | --------------- | ------ | --------------------------------------- | | `id` | string | Sequence ID (from external integration) | | `name` | string | Sequence name | | `last_used_at` | date | Most recent call date in this sequence | | `first_used_at` | date | First call date in this sequence | | `total_calls` | number | Total calls made within this sequence | # Get Snoozed Contact by ID Source: https://docs.salesfinity.ai/api-reference/endpoint/get-snoozed-contact-by-id GET /v1/snoozed-contacts/{id} Get a snoozed contact by ID Retrieves a specific snoozed contact by its ID. ### Path Parameters * **id** (*required*, string): Snoozed contact ID ### Response Returns the snoozed contact if found, or 404 if not found. **Response fields:** * `_id` - Unique identifier * `team` - Team ID * `user` - User who snoozed the contact * `contact_list` - Associated contact list ID * `linkedin_username` - LinkedIn username * `external_contact_id` - External CRM contact ID * `email` - Email address * `phone_number` - Phone number * `snooze_until` - Date when snooze expires * `createdAt` - Creation timestamp * `updatedAt` - Last update timestamp # Get Snoozed Contact by LinkedIn Source: https://docs.salesfinity.ai/api-reference/endpoint/get-snoozed-contact-by-linkedin GET /v1/snoozed-contacts/by-linkedin/{username} Get a snoozed contact by LinkedIn username Retrieves a snoozed contact by their LinkedIn username. Useful for checking if a specific LinkedIn profile is currently snoozed. ### Path Parameters * **username** (*required*, string): LinkedIn username to look up ### Response Returns the snoozed contact if found, or 404 if not found. **Response fields:** * `_id` - Unique identifier * `team` - Team ID * `user` - User who snoozed the contact * `linkedin_username` - LinkedIn username * `external_contact_id` - External CRM contact ID * `email` - Email address * `phone_number` - Phone number * `snooze_until` - Date when snooze expires * `createdAt` - Creation timestamp * `updatedAt` - Last update timestamp # Get Snoozed Contacts Source: https://docs.salesfinity.ai/api-reference/endpoint/get-snoozed-contacts GET /v1/snoozed-contacts Returns all snoozed contacts for the team with pagination and filtering Returns all snoozed contacts for the team with pagination and filtering support. Snoozed contacts are temporarily suppressed from call lists until their snooze period expires. ### Query Parameters * **page** (*optional*, number): Page number for pagination. Default: 1 * **limit** (*optional*, number): Number of items per page. Min: 1, Max: 100 * **sort** (*optional*, string): Sort field(s). Prefix with "-" for descending order. Default: `-createdAt` * **linkedin\_username** (*optional*, string): Filter by LinkedIn username * **external\_contact\_id** (*optional*, string): Filter by external contact ID (e.g., CRM ID) * **email** (*optional*, string): Filter by email address * **phone\_number** (*optional*, string): Filter by phone number ### Response Returns a paginated array of snoozed contacts with the following fields: * `_id` - Unique identifier * `team` - Team ID * `user` - User who snoozed the contact * `contact_list` - Associated contact list ID * `linkedin_username` - LinkedIn username * `external_contact_id` - External CRM contact ID * `email` - Email address * `phone_number` - Phone number * `snooze_until` - Date when snooze expires * `createdAt` - Creation timestamp * `updatedAt` - Last update timestamp # Get All Users Source: https://docs.salesfinity.ai/api-reference/endpoint/get-team GET /v1/team Returns team information Retrieves details about the current team, including each team member's status, license, and relevant user data. * **Tags**: `teams` * **Response**: Team information and associated members. # List Notes Source: https://docs.salesfinity.ai/api-reference/endpoint/list-notes GET /v2/notes Looks up the person or company by the provided identifiers and returns the notes attached to it. Resolve-only: returns 404 if no matching record exists (nothing is created on read). Notes are sorted pinned first, then by createdAt descending. For a person, supply at least one of linkedin_url, email, crm_id, or phone. For a company, supply at least one of website_url or name. Returns the notes attached to a **person** (Contact) or a **company** (Company), resolved from the identifiers you provide. This is **resolve-only**: unlike [Create a Note](/api-reference/endpoint/create-note), it never creates a record. If no matching person or company exists, it returns `404`. Notes come back sorted **pinned first**, then by `createdAt` descending. ### Query Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------------------------------------------------------------------- | | `type` | string | Yes | `person` or `company`. Determines which identifier params are required. | #### Person identifiers (`type=person`) Supply **at least one** of `linkedin_url`, `email`, `crm_id`, or `phone`. | Parameter | Type | Description | | -------------- | ------ | ---------------------------- | | `linkedin_url` | string | LinkedIn profile URL | | `email` | string | Email address | | `crm_id` | string | External CRM identifier | | `phone` | string | Phone number in E.164 format | #### Company identifiers (`type=company`) Supply **at least one** of `website_url` or `name`. | Parameter | Type | Description | | ------------- | ------ | ------------------------------- | | `website_url` | string | Company website URL | | `name` | string | Company name (case-insensitive) | ### Response | Field | Type | Description | | ------------ | -------------- | --------------------------------------------------------------------------------------------------- | | `type` | string | Target type echoed from the request (`person` or `company`). | | `contact_id` | string \| null | Resolved unified Contact ID. Present when `type=person`. Stable across reimports and dialer drains. | | `company_id` | string \| null | Resolved unified Company ID. Present when `type=company`. | | `notes` | array | Array of [note objects](#note-object), pinned first then by creation date descending. | #### Note Object | Field | Type | Description | | ----------- | -------------- | ------------------------------------------------------------------------------------------------------------ | | `_id` | string | Note ID | | `team` | string | Team that owns the note | | `author` | object | Author (`_id`, `first_name`, `last_name`, `email`, `image`). Populated when available; otherwise just `_id`. | | `content` | string | Plain text content of the note | | `is_pinned` | boolean | Whether the note is pinned to the top | | `pinned_at` | string \| null | When the note was pinned (ISO 8601) | | `pinned_by` | string \| null | ID of the member who pinned it | | `contact` | string \| null | Contact this note is attached to (mutually exclusive with `company`) | | `company` | string \| null | Company this note is attached to (mutually exclusive with `contact`) | | `createdAt` | string | Creation timestamp (ISO 8601) | | `updatedAt` | string | Last update timestamp (ISO 8601) | ### Example Request ```bash theme={null} GET /v2/notes?type=person&email=john@example.com ``` ### Errors | Status | Description | | ------ | ----------------------------------- | | 404 | No matching person or company found | # Merge Lists Source: https://docs.salesfinity.ai/api-reference/endpoint/merge-lists POST /v1/contact-lists/{id}/merge Merges contacts from one or more source lists into the target list. Optionally deletes the source lists after merging. Merges contacts from one or more source lists into a target list. Optionally deletes the source lists after merging. This is useful when payload limits require creating multiple sub-lists that should logically be one list. Instead of leaving SDRs with fragmented lists in the dialer, merge them server-side into a single clean list. ### Path Parameters * **id** (*required*, string): Target contact list ID to merge into. ### Request Body | Field | Type | Required | Description | | ----------------- | --------- | -------- | ------------------------------------------------------------------ | | `source_list_ids` | string\[] | Yes | IDs of the source lists to merge into the target (1–20 lists) | | `delete_sources` | boolean | No | Whether to delete the source lists after merging. Default: `false` | ### Limits * Max **20** source lists per merge request * The target list cannot appear in `source_list_ids` (it will be ignored) ### Example Request ```json theme={null} { "source_list_ids": [ "507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012", "507f1f77bcf86cd799439013" ], "delete_sources": true } ``` ### Response (201) Returns the updated target contact list. ### Errors | Status | Description | | ------ | ----------------------------- | | 404 | Target contact list not found | # Pin or Unpin a Note Source: https://docs.salesfinity.ai/api-reference/endpoint/pin-note POST /v2/notes/{id}/pin Toggles whether a note is pinned. Any team member may toggle a pin (not just the author). Pinned notes sort to the top. Toggles whether a note is pinned. Pinned notes sort to the top of the list. Unlike editing and deleting, **any team member** may toggle a pin — not just the author. ### Path Parameters * **id** (*required*, string): Note ID, from [List Notes](/api-reference/endpoint/list-notes). ### Request Body | Field | Type | Required | Description | | --------- | ------ | -------- | ------------------------------------------------------------------------------------------- | | `user_id` | string | Yes | ID of the team member toggling the pin. Must be a member of the team that owns the API key. | ### Example Request ```json theme={null} { "user_id": "507f1f77bcf86cd799439033" } ``` ### Response (201) Returns the updated [note object](/api-reference/endpoint/list-notes#note-object) with the new `is_pinned`, `pinned_at`, and `pinned_by` values. ### Errors | Status | Description | | ------ | -------------- | | 404 | Note not found | # Add the List to Dialing Queue Source: https://docs.salesfinity.ai/api-reference/endpoint/reimport-contacts POST /v1/contact-lists/csv/{id}/reimport Reimport contacts from CSV Reimports contacts from the source list back into the dialing queue. This replaces the current queue contents with the full source list. Use this to reset a partially-dialed list back to its original state, or to push source changes into the active queue. ### Path Parameters * **id** (*required*, string): Contact list ID. ### Response (201) Returns the updated contact list. ### Errors | Status | Description | | ------ | -------------------------- | | 404 | CSV contact list not found | # Remove a Contact from a List Source: https://docs.salesfinity.ai/api-reference/endpoint/remove-contact DELETE /v1/contact-lists/{id}/contacts/{contactId} Remove a contact from a contact list Removes a contact from an existing contact list. The contact is removed from both the source list and the dialing queue. ### Path Parameters * **id** (*required*, string): Contact list ID. * **contactId** (*required*, string): ID of the contact to remove. ### Response (200) ```json theme={null} { "success": true } ``` ### Errors | Status | Description | | ------ | ---------------------- | | 404 | Contact list not found | # Retrieve Scored Calls Source: https://docs.salesfinity.ai/api-reference/endpoint/scored-calls GET /v1/scored-calls Retrieves a paginated list of AI-scored calls with detailed insights, scoring facets, lead qualification, and coaching recommendations. Retrieves a paginated list of AI-scored calls. Each scored call includes detailed insights across six scoring facets (intro, discovery, pitch, tonality, objection handling, CTA), lead qualification data, coaching recommendations, and an overall score. ### Query Parameters #### Pagination & Sorting * **limit** (*optional*, number): Number of items per page (default: 10, max: 100) * **page** (*optional*, number): The current page number to retrieve (default: 1) * **sort** (*optional*, string): Sort field with optional `-` prefix for descending order (default: `-createdAt`) #### Filters * **start\_date** (*optional*, ISO 8601 date): Start of date range * **end\_date** (*optional*, ISO 8601 date): End of date range * **min\_score** (*optional*, number): Minimum total score (0-100) * **max\_score** (*optional*, number): Maximum total score (0-100) * **user\_id** (*optional*, string): Filter by user ID ### Example Requests **Basic request:** ``` GET /v1/scored-calls?page=1&limit=10 ``` **Filter by date range:** ``` GET /v1/scored-calls?start_date=2024-01-01&end_date=2024-01-31 ``` **Filter by score range:** ``` GET /v1/scored-calls?min_score=70&max_score=100 ``` **Filter by user and date:** ``` GET /v1/scored-calls?user_id=507f1f77bcf86cd799439033&start_date=2024-01-01&end_date=2024-01-31 ``` ### Response Returns a JSON object containing the list of scored calls with pagination metadata. ```json theme={null} { "data": [ { "_id": "507f1f77bcf86cd799439011", "insight": { "total_score": 78, "lead": { "function": "Sales", "tech_stack_mentioned": ["Salesforce", "Outreach"], "current_vendor": "Competitor Inc", "fit_score": 85, "is_decision_maker": true, "is_correct_persona": true, "pain_point_resonated": true, "is_qualified_meeting": true, "is_likely_to_buy": false, "prospect_lifecycle_stage": ["solution_aware"] }, "messaging": { "value_prop_resonated": ["efficiency", "time_savings"], "objection_outcome": ["handled"], "emotional_triggers": ["frustration_with_current_tool"], "recommendation_to_nail_messaging": "Lead with ROI data specific to their industry" }, "targeting_feedback": { "persona_fit": "excellent", "industry_fit": "good", "persona_seniority": "vp", "title_relevance": "good", "data_quality_issue": [], "future_follow_up_needed": true, "follow_up_reason": ["renewal"] }, "metadata": { "duration_sec": 245, "asr_confidence_avg": 0.92, "audio_features_available": true, "talk_listen_ratio_rep": 0.45, "total_questions": 8, "open_question_ratio": 0.625, "interruptions_by_rep": 1, "core_pitch_duration_sec": 35, "estimated_wpm_rep": 155, "objections_detected": ["budget"] }, "facets": { "intro": { "score": 85, "weight_pct": 10, "explanation": "Strong permission-based opener", "insufficient_evidence": false, "checks": { "permission_opener_used": true, "opener_length_sec": 12, "time_to_opener_sec": 3, "improvement_recommendation": "Consider a more personalized opener" } }, "discovery": { "score": 72, "weight_pct": 25, "explanation": "Good question quality but missed timing topic", "insufficient_evidence": false, "metrics": { "total_questions": 8, "open_question_ratio": 0.625, "improvement_recommendation": "Ask about timeline and decision process", "topic_coverage": ["pain", "current_tools"] } }, "pitch": { "score": 80, "weight_pct": 20, "explanation": "Well-tailored pitch to discovery findings", "insufficient_evidence": false, "metrics": { "core_pitch_duration_sec": 35, "tailored_to_discovery": true, "improvement_recommendation": "Include a customer success story", "outcome_keywords": ["efficiency", "save_time"] } }, "tonality": { "score": 75, "weight_pct": 15, "explanation": "Good pace, minor filler word usage", "insufficient_evidence": false, "metrics": { "estimated_wpm": 155, "interruptions_by_rep": 1, "filler_density_per_min": 2.1, "improvement_recommendation": "Reduce filler words" } }, "objection_handling": { "score": 70, "weight_pct": 15, "explanation": "Addressed budget objection but no follow-up probe", "insufficient_evidence": false, "metrics": { "objections_detected": ["budget"], "followup_probe_present": false, "resolution_check_present": true, "improvement_recommendation": "Add a follow-up question after handling objections" } }, "cta": { "score": 82, "weight_pct": 15, "explanation": "Clear CTA with specific time offered", "insufficient_evidence": false, "metrics": { "cta_attempted": true, "cta_type": "meeting", "specific_time_offered": true, "improvement_recommendation": "Confirm next steps via email", "outcome": "accepted" } } }, "gaps": { "skill": 25, "playbook": 15, "rationale": "Discovery needs more depth on timing and decision process" }, "coaching": { "top_wins": ["Strong opener", "Good pitch tailoring"], "top_opportunities": ["Deeper discovery", "Objection follow-up probes"], "suggested_drills": [ { "facet": "discovery", "assignment": "Practice SPIN questions for uncovering timeline" } ] } }, "call_log": { "_id": "507f1f77bcf86cd799439022", "contact": { "first_name": "John", "last_name": "Doe", "company": "Acme Corp" }, "recording_url": "https://recordings.example.com/call_abc123.mp3", "duration": 245 }, "user": { "_id": "507f1f77bcf86cd799439033", "first_name": "Jane", "last_name": "Smith", "email": "jane@company.com" }, "type": "call", "createdAt": "2024-01-15T14:33:00.000Z", "updatedAt": "2024-01-15T14:33:00.000Z" } ], "pagination": { "total": 42, "page": 1, "limit": 10, "pages": 5 } } ``` ### Response Fields | Field | Type | Description | | ----------- | ------ | -------------------------------------------------------------- | | `_id` | string | Unique identifier for the scored call | | `insight` | object | Full scoring insight (see below) | | `call_log` | object | Associated call log with contact, recording\_url, and duration | | `user` | object | User who made the call | | `type` | string | Scoring type (`call`) | | `createdAt` | date | Record creation time | | `updatedAt` | date | Record last update time | #### Insight Object | Field | Type | Description | | -------------------- | ------ | ----------------------------------------------------------------------------- | | `total_score` | number | Overall call score (0-100) | | `lead` | object | Lead qualification data (fit\_score, decision\_maker, lifecycle stage) | | `messaging` | object | Messaging effectiveness (value props, objection outcomes, emotional triggers) | | `targeting_feedback` | object | Persona and industry fit assessment | | `metadata` | object | Call characteristics (duration, talk ratio, questions, speech rate) | | `facets` | object | Six scoring categories with individual scores and evidence | | `gaps` | object | Skill and playbook gap scores with rationale | | `coaching` | object | Top wins, opportunities, and suggested drills | #### Scoring Facets | Facet | Weight | Description | | -------------------- | ------ | -------------------------------------------------------- | | `intro` | 10% | Permission opener usage and effectiveness | | `discovery` | 25% | Question quality, open question ratio, topic coverage | | `pitch` | 20% | Pitch duration, tailoring to discovery, outcome keywords | | `tonality` | 15% | Speech rate, interruptions, filler word density | | `objection_handling` | 15% | Objection detection, follow-up probes, resolution | | `cta` | 15% | CTA attempt, type, specificity, and outcome | # Update a Note Source: https://docs.salesfinity.ai/api-reference/endpoint/update-note PATCH /v2/notes/{id} Updates the content of an existing note. Only the original author may edit a note. Updates the content of an existing note. Only the **original author** may edit a note. ### Path Parameters * **id** (*required*, string): Note ID, from [List Notes](/api-reference/endpoint/list-notes). ### Request Body | Field | Type | Required | Max Length | Description | | --------- | ------ | -------- | ---------- | ---------------------------------------------------------------------------------------- | | `content` | string | Yes | 10,000 | Updated plain text content of the note | | `user_id` | string | Yes | — | ID of the team member performing the update. Must match the original author of the note. | ### Example Request ```json theme={null} { "content": "Updated: confirmed budget approved for Q3.", "user_id": "507f1f77bcf86cd799439033" } ``` ### Response (200) Returns the updated [note object](/api-reference/endpoint/list-notes#note-object). ### Errors | Status | Description | | ------ | ---------------------------------- | | 403 | Only the author can edit this note | | 404 | Note not found | # Update Snoozed Contact Source: https://docs.salesfinity.ai/api-reference/endpoint/update-snoozed-contact PUT /v1/snoozed-contacts/{id} Update a snoozed contact Updates a snoozed contact's information, including extending or modifying the snooze period. ### Path Parameters * **id** (*required*, string): Snoozed contact ID ### Request Body All fields are optional. Only provided fields will be updated. * **linkedin\_username** (*optional*, string): LinkedIn username * **external\_contact\_id** (*optional*, string): External CRM contact ID * **email** (*optional*, string): Email address * **phone\_number** (*optional*, string): Phone number * **snooze\_until** (*optional*, date): New snooze expiration date (ISO 8601 format) * Example: `2024-06-15T00:00:00.000Z` ### Response Returns the updated snoozed contact, or 404 if not found. # Errors Source: https://docs.salesfinity.ai/api-reference/errors The JSON error envelope returned by every Salesfinity API endpoint, what each status code means, and how to recover from it. Every Salesfinity API error is JSON. There is no HTML error page, no plain-text fallback, and no endpoint that answers a failure differently — a client can parse a failure the same way on every route and every status code. ## The error envelope ```json theme={null} { "message": "Forbidden resource", "error": "Forbidden", "statusCode": 403 } ``` | Field | Type | Description | | ------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `message` | string or string array | Human-readable description of what went wrong. Becomes an **array of strings** when request validation fails, with one entry per field that did not validate. | | `error` | string | The HTTP reason phrase for the status, for example `Forbidden`, `Not Found`, `Bad Request`. Stable across releases — branch on this. | | `statusCode` | integer | The HTTP status code, repeated in the body so it survives proxies and transports that drop it. | Branch on `error` or `statusCode`, never on `message`. Message text is written for humans and may be reworded; `error` and `statusCode` are contractual. ## Status codes ### 400 Bad Request The request body or query parameters failed validation. `message` is an array with one entry per invalid field. ```json theme={null} { "message": [ "limit must not be greater than 100", "type must be one of the following values: work, personal" ], "error": "Bad Request", "statusCode": 400 } ``` **Recover by** reading each entry in `message` and correcting the named field. Check the request against the schema shown on the operation's reference page. Retrying an identical request will fail identically. ### 402 Payment Required The team has run out of enrichment credits. Only the enrichment endpoints return this. ```json theme={null} { "message": "Insufficient enrichment credits", "error": "Payment Required", "statusCode": 402 } ``` **Recover by** topping up credits in the dashboard, then retrying. Call [Get Enrichment Credits](/api-reference/endpoint/get-enrichment-credits) before a bulk run to avoid hitting this mid-batch. ### 403 Forbidden The `x-api-key` header is missing, malformed, revoked, or belongs to a different team. ```json theme={null} { "message": "Forbidden resource", "error": "Forbidden", "statusCode": 403 } ``` Salesfinity returns **403 for authentication failures, not 401**. A client written around the usual "re-authenticate on 401" convention will never trigger its auth-recovery path against this API. Handle 403 explicitly. **Recover by** checking that the `x-api-key` header is present and spelled correctly, that the key has not been revoked in **Settings → Connections & API**, and that it belongs to the team whose data you are requesting. ### 404 Not Found Either the route does not exist, or the resource does exist but belongs to another team. ```json theme={null} { "message": "Cannot GET /v1/unknown-route", "error": "Not Found", "statusCode": 404 } ``` A `message` of the form `Cannot ` means the route itself is wrong — check the method and path against the reference. Any other message means the route was correct but the record was not found for this team. **Recover by** verifying the path, the HTTP method, and any ID in the URL. Because API keys are team-scoped, the API returns 404 rather than 403 for records owned by another team; it does not confirm that they exist. ### 429 Too Many Requests The client is being throttled. ```json theme={null} { "message": "Too many requests", "error": "Too Many Requests", "statusCode": 429 } ``` **Recover by** backing off and retrying with exponential backoff and jitter. Salesfinity does not currently publish a fixed request quota, so treat 429 as a signal to reduce concurrency rather than as a fixed budget to compute against. ### 500 Internal Server Error Something failed on the Salesfinity side. ```json theme={null} { "message": "Internal server error", "error": "Internal Server Error", "statusCode": 500 } ``` **Recover by** retrying with exponential backoff. If it persists, email [hello@salesfinity.co](mailto:hello@salesfinity.co) with the request path and the timestamp. ## Which errors are retryable | Status | Retryable | Notes | | ------ | --------- | -------------------------------------------------------------- | | 400 | No | The request is malformed; an identical retry fails identically | | 402 | No | Requires topping up credits first | | 403 | No | Requires a valid key | | 404 | No | Requires a correct path or ID | | 429 | **Yes** | Back off with exponential jitter | | 500 | **Yes** | Back off with exponential jitter | `POST` endpoints are not idempotent. Cap retries on writes, and confirm state with a `GET` before retrying a create so you do not duplicate a record. ## Handling errors in code ```js Node.js theme={null} async function salesfinity(path, init = {}) { const res = await fetch(`https://client-api.salesfinity.co${path}`, { ...init, headers: { "x-api-key": process.env.SALESFINITY_API_KEY, ...init.headers }, }); if (res.ok) return res.json(); // Every failure is JSON with the same three fields. const { message, error, statusCode } = await res.json(); const detail = Array.isArray(message) ? message.join("; ") : message; const err = new Error(`Salesfinity ${statusCode} ${error}: ${detail}`); err.statusCode = statusCode; err.retryable = statusCode === 429 || statusCode >= 500; throw err; } ``` ```python Python theme={null} import os, requests class SalesfinityError(Exception): def __init__(self, body): self.status_code = body["statusCode"] self.error = body["error"] message = body["message"] self.detail = "; ".join(message) if isinstance(message, list) else message self.retryable = self.status_code == 429 or self.status_code >= 500 super().__init__(f"Salesfinity {self.status_code} {self.error}: {self.detail}") def salesfinity(path, **kwargs): res = requests.get( f"https://client-api.salesfinity.co{path}", headers={"x-api-key": os.environ["SALESFINITY_API_KEY"]}, timeout=30, **kwargs, ) if not res.ok: raise SalesfinityError(res.json()) return res.json() ``` ## Reporting a problem Email [hello@salesfinity.co](mailto:hello@salesfinity.co) or use the [help center](https://support.salesfinity.ai). Include: * The request method and path, for example `GET /v1/call-log`. * The timestamp of the request, with its timezone. * The `error` and `statusCode` from the response body. Never include your API key in a support message. # API Reference Source: https://docs.salesfinity.ai/api-reference/introduction The Salesfinity REST API: authentication, conventions, errors, and a complete index of every operation, generated from the OpenAPI description. The Salesfinity API is a JSON-over-HTTPS REST API for the Salesfinity parallel dialer. Use it to manage contact lists, read call logs and AI call scores, pull team and SDR analytics, work with notes, and enrich phone numbers and email addresses. Everything on this page is plain text, so a person skimming, a search crawler, and an autonomous agent all get the same answer without running any JavaScript. ## At a glance | | | | ----------------------- | -------------------------------------------------------------------------------------------------------------- | | **Base URL** | `https://client-api.salesfinity.co` | | **Authentication** | `x-api-key` request header | | **Media type** | `application/json` on requests and responses | | **OpenAPI description** | [`/api-reference/openapi.json`](/api-reference/openapi.json) (OpenAPI 3.0.1) | | **Developer portal** | [Keys, quickstarts, testing, client patterns](/developers) | | **Support** | [hello@salesfinity.co](mailto:hello@salesfinity.co) · [support.salesfinity.ai](https://support.salesfinity.ai) | ## Quickstart Generate a key in the Salesfinity dashboard under **Settings → Connections & API**, then confirm it works by reading the team it belongs to. Every key is scoped to exactly one team, and every response is filtered to that team. ```bash theme={null} curl https://client-api.salesfinity.co/v1/team \ --header 'x-api-key: YOUR_API_KEY' ``` A successful call returns the team name and its members, each with a user record, a status, and a license. If the key is missing or wrong you get an HTTP 403 with a JSON body rather than an HTML error page — see [Errors](/api-reference/errors). Once the key works, the usual next step is to list the team's contact lists and then read one: ```bash theme={null} # Page 1 of the team's CSV contact lists curl 'https://client-api.salesfinity.co/v1/contact-lists/csv?page=1&limit=10' \ --header 'x-api-key: YOUR_API_KEY' # One list, including the contacts inside it curl 'https://client-api.salesfinity.co/v1/contact-lists/csv/CONTACT_LIST_ID' \ --header 'x-api-key: YOUR_API_KEY' ``` ## Authentication Every endpoint requires an API key in the `x-api-key` header. There is no OAuth flow, no bearer token, and no unauthenticated endpoint. Keys are long-lived until you revoke them in the dashboard. A missing, malformed, revoked, or wrong-team key returns **HTTP 403 `Forbidden`**, not 401. That is worth encoding in client code, because the conventional "retry auth on 401" branch will never fire against this API. ```json theme={null} { "message": "Forbidden resource", "error": "Forbidden", "statusCode": 403 } ``` Because a key is team-scoped, requesting a resource that belongs to a different team returns **404 Not Found** rather than 403 — the API does not confirm that records outside your team exist. There is no separate sandbox host. See the [developer portal](/developers) for how to test against live data safely. ## Conventions | Concern | Convention | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Media type | `application/json` on requests and responses | | Pagination | `page` (1-based) and `limit`. The `limit` default varies by endpoint — 10 on most lists, 50 for contacts inside a list, 100 for sequences — and is capped at 100 where a cap applies | | Sorting | `sort=field` ascending, `sort=-field` descending, for example `sort=-createdAt` | | Filtering | Bracketed query parameters such as `filters[start_date]`, `filters[user_ids]`, `filters[disposition_ids]` | | IDs | 24-character hexadecimal strings | | Errors | Always JSON, always `{ message, error, statusCode }` | ## Errors Every error response, on every endpoint and every status code, uses the same JSON envelope: | Field | Type | Description | | ------------ | ---------------------- | ----------------------------------------------------------------------------------------------------------- | | `message` | string or string array | Human-readable description. An array with one entry per invalid field when request validation fails. | | `error` | string | Stable HTTP reason phrase, for example `Forbidden` or `Not Found`. Branch on this rather than on `message`. | | `statusCode` | integer | HTTP status code, repeated in the body so it survives transports that drop it. | | Status | `error` | When it happens | What to do | | ------ | ----------------------- | -------------------------------------------------------- | -------------------------------------------------- | | 400 | `Bad Request` | Body or query parameters failed validation | Read the `message` array; each entry names a field | | 402 | `Payment Required` | Team has no enrichment credits left | Top up credits, then retry | | 403 | `Forbidden` | API key missing, malformed, revoked, or for another team | Fix the `x-api-key` header | | 404 | `Not Found` | Unknown route, or a resource another team owns | Check the path and the ID | | 429 | `Too Many Requests` | Client is being throttled | Retry with exponential backoff and jitter | | 500 | `Internal Server Error` | Fault on the Salesfinity side | Retry with backoff, then contact support | Branch on `error` or `statusCode` rather than on `message`. Retry **429** and **5xx** with exponential backoff; **400**, **402**, **403**, and **404** will not succeed on an identical retry. The full envelope, per-status examples, and recovery steps are on the [Errors](/api-reference/errors) page. ## Webhooks Webhook subscriptions are created and managed inside the Salesfinity application, not through this API. Configure them in the dashboard under **Settings → Connections & API**; there are no webhook endpoints to call. The enrichment endpoints are the exception: they accept a per-request `callback_url` rather than a standing subscription — see [Enrich Email](/api-reference/endpoint/enrich-email) and [Enrich Phone](/api-reference/endpoint/enrich-phone). ## What you can do with the API The REST API exposes 37 operations across 12 groups. Every operation has a unique `operationId`, a description, typed parameters, and a JSON response schema in the OpenAPI description. | Group | Operations | What it covers | | ---------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------ | | [Contact Lists](/api-reference/endpoint/get-contact-lists-csv) | 8 | Create, read, merge, and delete CSV contact lists; add and remove the contacts inside them; push a list into the dialing queue | | [Notes](/api-reference/endpoint/create-note) | 5 | Attach freeform notes to a person or company, list them, edit or delete your own, and toggle pins | | [Snoozed Contacts](/api-reference/endpoint/get-snoozed-contacts) | 5 | Read and manage contacts held out of the dialing queue, by ID or LinkedIn username | | [Enrichment](/api-reference/endpoint/enrich-email) | 5 | Start an asynchronous phone or email lookup, poll its result, and check credit balance | | [Analytics](/api-reference/endpoint/analytics-overview) | 3 | Aggregated call metrics with growth rates, plus breakdowns by contact list and by SDR | | [Call Logs](/api-reference/endpoint/call-log) | 2 | Paginated call history with filtering and sorting, and single-call lookup | | [Scored Calls](/api-reference/endpoint/scored-calls) | 2 | AI-scored calls with per-facet scoring and coaching recommendations | | [Dispositions](/api-reference/endpoint/get-dispositions) | 2 | Default and custom call outcomes configured for the team | | [Sequences](/api-reference/endpoint/get-sequences) | 2 | Sequences referenced by the team's call logs | | [Teams](/api-reference/endpoint/get-team) | 1 | The team that owns the API key, with members, statuses, and licenses | | [Follow-up Tasks](/api-reference/endpoint/get-follow-ups) | 1 | Follow-up tasks created from call outcomes | | [Custom Fields](/api-reference/endpoint/get-custom-fields) | 1 | Custom field mappings configured for the team | ## Complete endpoint index Every operation in the API, with its HTTP method, path, and reference page. This table is generated from the OpenAPI description, so it is always in sync with the spec. ### Contact Lists | Method | Path | Operation | Description | | -------- | --------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | `POST` | `/v1/contact-lists` | [Create a contact list](/api-reference/endpoint/create-list) | Creates a new contact list | | `POST` | `/v1/contact-lists/{id}` | [Add a contact to a list](/api-reference/endpoint/add-contact) | Add a contact to an existing list | | `DELETE` | `/v1/contact-lists/{id}/contacts/{contactId}` | [Remove a contact from a list](/api-reference/endpoint/remove-contact) | Remove a contact from a contact list | | `POST` | `/v1/contact-lists/{id}/merge` | [Merge contact lists](/api-reference/endpoint/merge-lists) | Merges contacts from one or more source lists into the target list | | `GET` | `/v1/contact-lists/csv` | [List contact lists](/api-reference/endpoint/get-contact-lists-csv) | Returns all CSV contact lists for the team with filtering, sorting, and pagination | | `DELETE` | `/v1/contact-lists/csv/{id}` | [Delete a contact list](/api-reference/endpoint/delete-contact-list) | Delete a contact list | | `GET` | `/v1/contact-lists/csv/{id}` | [Get a contact list](/api-reference/endpoint/get-contact-list-by-id) | Returns a single CSV contact list with all its contacts | | `POST` | `/v1/contact-lists/csv/{id}/reimport` | [Reimport a contact list into the dialing queue](/api-reference/endpoint/reimport-contacts) | Reimport contacts from CSV | ### Teams | Method | Path | Operation | Description | | ------ | ---------- | -------------------------------------------------------- | ------------------------ | | `GET` | `/v1/team` | [Get the current team](/api-reference/endpoint/get-team) | Returns team information | ### Dispositions | Method | Path | Operation | Description | | ------ | ----------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | | `GET` | `/v1/dispositions` | [List dispositions](/api-reference/endpoint/get-dispositions) | Returns all dispositions available for the team, including default and custom dispositions | | `GET` | `/v1/dispositions/{id}` | [Get a disposition](/api-reference/endpoint/get-disposition-by-id) | Get a specific disposition by its internal ID | ### Sequences | Method | Path | Operation | Description | | ------ | -------------------- | ------------------------------------------------------------ | ---------------------------------------------------- | | `GET` | `/v1/sequences` | [List sequences](/api-reference/endpoint/get-sequences) | Returns all sequences used in call logs for the team | | `GET` | `/v1/sequences/{id}` | [Get a sequence](/api-reference/endpoint/get-sequence-by-id) | Get a specific sequence by ID | ### Call Logs | Method | Path | Operation | Description | | ------ | ------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- | | `GET` | `/v1/call-log` | [List call logs](/api-reference/endpoint/call-log) | Retrieves a paginated list of call logs with filtering and sorting support | | `GET` | `/v1/call-log/{id}` | [Get a call log](/api-reference/endpoint/get-call-log-by-id) | Get a call log by ID | ### Analytics | Method | Path | Operation | Description | | ------ | -------------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | `GET` | `/v1/analytics/list-performance` | [List call performance by contact list](/api-reference/endpoint/analytics-list-performance) | Returns call metrics grouped by contact list with pagination | | `GET` | `/v1/analytics/overview` | [Get analytics overview](/api-reference/endpoint/analytics-overview) | Returns aggregated analytics metrics with growth rates compared to the previous period | | `GET` | `/v1/analytics/sdr-performance` | [List call performance by SDR](/api-reference/endpoint/analytics-sdr-performance) | Returns call metrics grouped by SDR (user) with pagination | ### Snoozed Contacts | Method | Path | Operation | Description | | -------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `GET` | `/v1/snoozed-contacts` | [List snoozed contacts](/api-reference/endpoint/get-snoozed-contacts) | Returns all snoozed contacts for the team with pagination and filtering | | `DELETE` | `/v1/snoozed-contacts/{id}` | [Delete a snoozed contact](/api-reference/endpoint/delete-snoozed-contact) | Delete a snoozed contact | | `GET` | `/v1/snoozed-contacts/{id}` | [Get a snoozed contact](/api-reference/endpoint/get-snoozed-contact-by-id) | Get a snoozed contact by ID | | `PUT` | `/v1/snoozed-contacts/{id}` | [Update a snoozed contact](/api-reference/endpoint/update-snoozed-contact) | Update a snoozed contact | | `GET` | `/v1/snoozed-contacts/by-linkedin/{username}` | [Get a snoozed contact by LinkedIn username](/api-reference/endpoint/get-snoozed-contact-by-linkedin) | Get a snoozed contact by LinkedIn username | ### Follow-up Tasks | Method | Path | Operation | Description | | ------ | --------------- | -------------------------------------------------------------- | -------------------------------------------------------- | | `GET` | `/v1/follow-up` | [List follow-up tasks](/api-reference/endpoint/get-follow-ups) | Returns all follow-up tasks for the team with pagination | ### Custom Fields | Method | Path | Operation | Description | | ------ | ------------------- | --------------------------------------------------------------- | --------------------------------------------------------- | | `GET` | `/v1/custom-fields` | [List custom fields](/api-reference/endpoint/get-custom-fields) | Returns all custom field mappings configured for the team | ### Scored Calls | Method | Path | Operation | Description | | ------ | ----------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | | `GET` | `/v1/scored-calls` | [List scored calls](/api-reference/endpoint/scored-calls) | Retrieves a paginated list of AI-scored calls with detailed insights, scoring facets, lead qualification, and… | | `GET` | `/v1/scored-calls/{id}` | [Get a scored call](/api-reference/endpoint/get-scored-call-by-id) | Retrieves a single scored call by its ID with full AI-generated insight | ### Notes | Method | Path | Operation | Description | | -------- | -------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `GET` | `/v2/notes` | [List notes](/api-reference/endpoint/list-notes) | Looks up the person or company by the provided identifiers and returns the notes attached to it | | `POST` | `/v2/notes` | [Create a note](/api-reference/endpoint/create-note) | Creates a note attached to a person (Contact) or company (Company), identified by domain identifiers rather… | | `DELETE` | `/v2/notes/{id}` | [Delete a note](/api-reference/endpoint/delete-note) | Deletes an existing note | | `PATCH` | `/v2/notes/{id}` | [Update a note](/api-reference/endpoint/update-note) | Updates the content of an existing note | | `POST` | `/v2/notes/{id}/pin` | [Toggle a note pin](/api-reference/endpoint/pin-note) | Toggles whether a note is pinned | ### Enrichment | Method | Path | Operation | Description | | ------ | ------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `GET` | `/v1/api/enrichment/credits` | [Get enrichment credit balance](/api-reference/endpoint/get-enrichment-credits) | Returns the current enrichment credit balance for the team that owns the API key | | `POST` | `/v1/api/enrichment/email` | [Start an email enrichment](/api-reference/endpoint/enrich-email) | Starts an asynchronous lookup of a work or personal email address for a LinkedIn profile | | `GET` | `/v1/api/enrichment/email/{id}` | [Get an email enrichment result](/api-reference/endpoint/get-email-enrichment) | Returns the current state of an email enrichment request | | `POST` | `/v1/api/enrichment/phone` | [Start a phone enrichment](/api-reference/endpoint/enrich-phone) | Starts an asynchronous lookup of mobile and direct-dial phone numbers for a LinkedIn profile | | `GET` | `/v1/api/enrichment/phone/{id}` | [Get a phone enrichment result](/api-reference/endpoint/get-phone-enrichment) | Returns the current state of a phone enrichment request | ## Resources for agents and crawlers * [`/llms.txt`](/llms.txt) — a compact index of every page on this site. * [`/llms-full.txt`](/llms-full.txt) — the full documentation as a single plain-text file. * [`/api-reference/openapi.json`](/api-reference/openapi.json) — the complete OpenAPI 3.0.1 description, also served at [`/openapi.json`](/openapi.json). * [`/sitemap.xml`](/sitemap.xml) — every canonical URL. * Any page also serves Markdown. Append `.md` to a page URL, or send `Accept: text/markdown`, to get the source instead of the rendered page. ```bash theme={null} curl -H 'Accept: text/markdown' https://docs.salesfinity.ai/api-reference/introduction curl https://docs.salesfinity.ai/api-reference/introduction.md ``` ## Getting help Email [hello@salesfinity.co](mailto:hello@salesfinity.co) or browse the [help center](https://support.salesfinity.ai). When reporting an API problem, include the request path, the timestamp, and the `error` and `statusCode` from the response body — that is usually enough to find the request in Salesfinity's logs. # Developer Portal Source: https://docs.salesfinity.ai/developers API keys, quickstarts, testing guidance, SDK-free client patterns, and machine-readable resources for building on Salesfinity. Everything you need to go from zero to a working Salesfinity integration: getting a key, making your first authenticated call, testing without disturbing live dialing, handling errors and retries, and the machine-readable files your tools can consume directly. Dashboard → Settings → Connections & API OpenAPI 3.0.1, 35 operations, typed responses The JSON error envelope and what to do about each status Hosted MCP endpoint for AI assistants ## 1. Get an API key 1. Open the Salesfinity dashboard and go to **Settings → Connections & API**. 2. Generate a new API key. 3. Copy it immediately and store it in your secret manager. Treat it like a password. A key is scoped to a **single team**. Every request made with it sees only that team's contact lists, call logs, and analytics. There is no account-wide or cross-team key, so an integration serving several teams needs one key per team. Keys are long-lived and do not expire on a schedule. Rotate them from the same settings screen when someone with access leaves, or if a key is ever committed to source control. ## 2. Make your first call Every endpoint authenticates with the `x-api-key` request header. The cheapest way to verify a key is to read the team it belongs to. ```bash cURL theme={null} curl https://client-api.salesfinity.co/v1/team \ --header 'x-api-key: YOUR_API_KEY' ``` ```js Node.js theme={null} const res = await fetch("https://client-api.salesfinity.co/v1/team", { headers: { "x-api-key": process.env.SALESFINITY_API_KEY }, }); if (!res.ok) { // Errors are always JSON, never an HTML page. const { message, error, statusCode } = await res.json(); throw new Error(`Salesfinity ${statusCode} ${error}: ${message}`); } const team = await res.json(); ``` ```python Python theme={null} import os, requests res = requests.get( "https://client-api.salesfinity.co/v1/team", headers={"x-api-key": os.environ["SALESFINITY_API_KEY"]}, timeout=30, ) if not res.ok: body = res.json() raise RuntimeError( f"Salesfinity {body['statusCode']} {body['error']}: {body['message']}" ) team = res.json() ``` If the key is missing or invalid you get HTTP 403 and a JSON body, not an HTML error page: ```json theme={null} { "message": "Forbidden resource", "error": "Forbidden", "statusCode": 403 } ``` ## 3. Build the client There is no official Salesfinity SDK. The API is small, uniform, and fully described by an OpenAPI document, so the two supported paths are a thin hand-written client or a generated one. ### Generate a client from the OpenAPI description The description at [`/api-reference/openapi.json`](/api-reference/openapi.json) is also served at [`/openapi.json`](/openapi.json). Every operation has a unique `operationId`, which becomes the method name in most generators. ```bash theme={null} # TypeScript, using openapi-typescript npx openapi-typescript https://docs.salesfinity.ai/openapi.json -o salesfinity.d.ts # Any language, using openapi-generator openapi-generator-cli generate \ -i https://docs.salesfinity.ai/openapi.json \ -g python \ -o ./salesfinity-client ``` ### Conventions worth encoding once | Concern | Convention | | ------------ | ------------------------------------------------------------------------------------------------------------------------ | | Base URL | `https://client-api.salesfinity.co` | | Auth | `x-api-key` header on every request | | Auth failure | **403**, never 401 — a "refresh on 401" branch will never fire | | Pagination | `page` (1-based) and `limit`; the `limit` default varies by endpoint (10, 50, or 100), capped at 100 where a cap applies | | Sorting | `sort=field` ascending, `sort=-field` descending | | Filtering | Bracketed parameters such as `filters[start_date]`, `filters[user_ids]` | | Errors | Always JSON, always `{ message, error, statusCode }` | | IDs | 24-character hexadecimal strings | ### Retries Retry on **429** and **5xx** with exponential backoff and jitter. Do not retry **400**, **402**, **403**, or **404** — none of them succeed on a second identical attempt. `POST` endpoints are not idempotent, so cap retries and confirm state with a `GET` before retrying a create. Salesfinity does not currently publish a fixed request quota. Build the 429 path anyway and keep concurrency modest. ## 4. Test safely There is no separate sandbox host. `https://client-api.salesfinity.co` is the only public API host, and it operates on live data, so build your test plan around that: * **Start read-only.** `GET /v1/team`, `GET /v1/dispositions`, `GET /v1/custom-fields`, and the analytics endpoints have no side effects and are safe to hammer during development. * **Use a dedicated test team.** Generate a key for a team that is not actively dialing, so writes cannot disturb a live queue. * **Use a throwaway contact list.** [Create a List](/api-reference/endpoint/create-list) gives you an isolated target for add, remove, and merge calls that you can [delete](/api-reference/endpoint/delete-contact-list) afterwards. * **Know which writes reach the dialer.** [Add a Contact to a List](/api-reference/endpoint/add-contact) puts the contact into the dialing queue immediately, and [Reimport Contacts](/api-reference/endpoint/reimport-contacts) pushes a whole list into it. Everything else is safe to exercise against a test list. * **Point the enrichment `callback_url` somewhere disposable** while developing, so test results are not POSTed at a production handler. Enrichment endpoints spend real credits. `POST /v1/api/enrichment/email` and `POST /v1/api/enrichment/phone` each consume a credit per completed lookup, drawn from the same pool, and return **402 Payment Required** when the team's balance reaches zero. Check [Get Enrichment Credits](/api-reference/endpoint/get-enrichment-credits) before a bulk run. ## 5. Pick an integration surface 35 operations for full programmatic control. Best for backend integrations and data sync. Hosted at `https://mcp.salesfinity.ai/mcp`. Best for AI assistants and internal tooling. Worked examples for managers, reps, and shared workflows. ## Machine-readable resources | Resource | URL | Format | | ------------------- | ----------------------------------------------------------------------------------------------- | ------------------ | | OpenAPI description | [`/openapi.json`](/openapi.json) · [`/api-reference/openapi.json`](/api-reference/openapi.json) | OpenAPI 3.0.1 JSON | | Documentation index | [`/llms.txt`](/llms.txt) | Plain text | | Full documentation | [`/llms-full.txt`](/llms-full.txt) | Plain text | | Sitemap | [`/sitemap.xml`](/sitemap.xml) | XML | | Agent card | [`/.well-known/agent-card.json`](/.well-known/agent-card.json) | JSON | | MCP server card | [`/.well-known/mcp/server-card.json`](/.well-known/mcp/server-card.json) | JSON | | Agent skills index | [`/.well-known/agent-skills/index.json`](/.well-known/agent-skills/index.json) | JSON | Every documentation page is also available as Markdown — append `.md` to the URL, or send an `Accept: text/markdown` request header: ```bash theme={null} curl -H 'Accept: text/markdown' https://docs.salesfinity.ai/developers curl https://docs.salesfinity.ai/developers.md ``` ## Support * **Email** — [hello@salesfinity.co](mailto:hello@salesfinity.co) * **Help center** — [support.salesfinity.ai](https://support.salesfinity.ai) Include the request path, the timestamp, and the `error` and `statusCode` from the response body when reporting an API problem. Never include your API key. # Boss Mode Source: https://docs.salesfinity.ai/dialer/boss-mode What Boss Mode is, how phone validation scores every mobile number on a list, and how the dialer uses those scores to reach more people. Boss Mode is the dialer's phone validation layer. Before you dial a list, it checks every mobile number against live carrier data and tells you which ones are real, which carrier they belong to, and how likely each one is to be answered. With Boss Mode on, the dialer skips the numbers that will not pick up and dials the best number for each person first. Boss Mode is enabled per team. If you do not see the Boss Mode button above your contact list, ask your account manager to turn it on. ## How it works Open a contact list in the dialer and click **Boss mode**. The first time on a list, a dialog explains what is about to happen and offers **Start Cleanup**. That creates a validation request for the list. Salesfinity collects each phone number on the list that is typed as mobile and checks it against carrier records. Numbers already validated in the last six months are reused instantly, so a list that shares numbers with an earlier cleanup finishes faster. The rest are checked in the background, and a progress panel shows how many contacts are done. For every number, Salesfinity stores whether it is valid, its carrier, its line type, and, for US numbers, a likely-to-answer score. See [what the scores mean](#what-the-scores-mean). When validation finishes, the list is marked validated and the Boss Mode toggle turns on. Subsequent visits to the list toggle Boss Mode on and off without re-validating. Only one cleanup runs per user at a time. Starting a second one while the first is in progress is rejected. A request that has not finished after ten minutes is no longer considered active, so a stuck request will not block you. ## What the scores mean Every US mobile number receives a likely-to-answer grade from its activity score. Line type comes from the carrier, so a number saved as mobile that turns out to be a landline or VoIP line is graded on the stricter non-mobile bands. | Grade | Label in the dialer | Mobile lines | Other line types | | -------- | ------------------------------- | -------------------------- | ------------------ | | P1 | Highest likely-to-answer | Activity score 90 or above | Score above 79 | | P2 | Medium likely-to-answer | Score 61 to 89 | Score 59 to 79 | | P3 | Lowest likely-to-answer | Score below 61 | Score below 59 | | Unscored | Non-mobile or not yet validated | No score available | No score available | International numbers are validated for line type, carrier, and whether the number is live, but do not receive an activity score, so they show as unscored. Results are cached across Salesfinity for six months. Validating the same number on another list, or another team, reuses the stored result. ## What changes when Boss Mode is on * **P3 numbers are skipped.** The dialer removes numbers graded P3 from each contact's dialing set, so a rep does not spend a line on a number that is unlikely to answer. A number that the dialer has already screened as a live line is kept regardless of grade. * **Best number first.** Each contact's remaining numbers are ordered P1, then P2, then unscored, so the parallel dialer tries the strongest number first. * **Badges and details.** Every validated number shows a grade badge, and hovering it reveals line type, carrier, and status. * **Filter, sort, and group by grade.** The contact table gains a "Likeliness to pickup" filter and lets you sort or group contacts by their best grade. * **Contact counts reflect it.** The list header and the enrichment dialog count only the contacts that still have a dialable number under Boss Mode. * **Calls are tagged.** Each call logged while Boss Mode is on records that fact, which feeds the analytics below. Turning Boss Mode off restores every number and the original order. Nothing is deleted by validation; a P3 number is hidden, not removed. ## Boss Mode in analytics Two views under **Analytics → Connect rate** use validation data: * **Boss Mode on vs off** compares the connect rate of calls dialed with verified numbers against calls dialed with all numbers, so a manager can see what validation is worth on their lists. * **Validation score** breaks connect rate down by grade, P1 through P3 and unscored. The AI Companion can pull the same comparison when you ask it about connect rate by Boss Mode. ## Boss Mode and enrichment Boss Mode grades the numbers you already have. [Contact enrichment](/enrichment/contacts) finds numbers you do not have. They work well together: run a cleanup first, then enrich the contacts left without a P1 or P2 number. Numbers returned by enrichment arrive already screened, since enrichment discards landlines and lowest-grade numbers before adding a number to a contact. ## Related Find mobile numbers for contacts that have a LinkedIn profile. Read connect rate metrics through the API. # How Contact Enrichment Works Source: https://docs.salesfinity.ai/enrichment/contacts How Salesfinity finds mobile numbers for contacts, what it does with the result, and what to expect while a run is in progress. Contact enrichment finds a **mobile number** for a person from their LinkedIn profile. It is built for the dialer: the goal is a number that rings on a phone the person actually carries, not a switchboard. ## Requirements * Each contact needs a **LinkedIn profile URL**. It is the key Salesfinity uses to identify the person. Contacts without one are skipped, and a run with no eligible contacts is refused with a message saying so. * The team needs enough [credits](/enrichment/overview#credits) to cover one per selected contact. * Only one enrichment run per user at a time. Start the next one when the current one finishes or after cancelling it. ## Running it from the dialer In a contact list, select the contacts you want to enrich. With [Boss Mode](/dialer/boss-mode) on, the count reflects contacts that still lack a strong number. The dialog shows your available credits and the cost, one credit per contact. If your team has connected its own data provider, choose it here; otherwise the run uses Salesfinity's data network. The run is queued and you can keep working. A banner on the list tracks progress, and you are notified when it completes. You can cancel a run in progress; contacts already processed keep their results. ## What happens during a run Salesfinity keeps the results of past lookups. If a selected profile was enriched before, its number is applied immediately without a new lookup. Each remaining contact is sent for a lookup, one contact per job so a slow lookup never holds up the rest of the list. The lookup runs asynchronously and reports back when it has a result. Returned numbers are screened before they reach the contact. Landlines are dropped, and any number graded lowest likely-to-answer is dropped, using the same grading as Boss Mode. If nothing survives, the contact is marked as not found and is not charged. The surviving mobile number is added to the contact as a new phone number flagged as enriched. Numbers the contact already had are never duplicated or replaced, so nothing you imported is lost. When every contact has reported back, the run is marked complete, credits are charged for the contacts where a number was found, and you are notified. The enrichment banner on the list shows when it last ran and offers **Enrich again**. ## After enrichment * **Dial it.** The new number is part of the contact's dialing set immediately. * **Push it to your CRM.** For contacts imported from a CRM, the enriched number can be written back to the record so the next import already has it. See [how data moves](/integrations/data-flow#other-things-that-flow-between-systems). * **Validate it.** Enriched numbers are screened at lookup time, but running a Boss Mode cleanup after a large enrichment grades them alongside the rest of the list. ## Enrichment from a sourcing table When the AI Companion builds a list on a sourcing table, it can enrich the rows the same way. Because it costs credits, the companion states how many rows will be enriched and shows an approval card first; it never enriches a table on its own initiative. Each row that receives a number is charged one credit, exactly as in the dialer. ## Enrichment lists If you have a CSV you want enriched without adding it to the dialer yet, upload it as an enrichment list. Salesfinity enriches every row that has a LinkedIn URL, then lets you export the enriched rows to a dialer contact list or download the CSV. ## Enrichment through the API The [Enrich Phone](/api-reference/endpoint/enrich-phone) endpoint runs the same lookup for a single LinkedIn URL and returns a request ID to poll, or calls a webhook of yours when the result is ready. It is the right choice when your own system owns the contact and only needs the number. # How Email Enrichment Works Source: https://docs.salesfinity.ai/enrichment/emails How Salesfinity finds work and personal email addresses, where it runs, and how results and credits are handled. Email enrichment finds an email address for a person from their LinkedIn profile. You choose the kind of address you want: * **Work email** — the address at the person's current employer. Use it for outbound sequences and CRM records. * **Personal email** — a non-corporate address. Useful for recruiting and for reaching people who have changed jobs. Each is a separate lookup with its own result and its own credit. ## Where it runs | Surface | How it works | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Sourcing tables (AI Companion) | Ask the companion for work or personal emails on a table. It shows the row count and an approval card, then fills the email column as results arrive. Each row tracks its own status: enriching, enriched, or not found. | | Referrals captured on a call | When a rep captures a referral during a call, Salesfinity looks for the referred person's work email so the referral can be followed up. | | REST API | [Enrich Email](/api-reference/endpoint/enrich-email) with `type` set to `work` or `personal`, then poll or receive a callback. | | MCP server | The `enrich_email` and `get_email_enrichment` [tools](/mcp/tools). | ## What happens during a lookup You get back a request ID with a status of pending. The lookup runs in the background; nothing waits on it. Salesfinity's data network searches for an address of the requested type. If none can be found for that person, the lookup resolves as not found rather than returning a guess. The status becomes completed with the address, or not found. In the app the row or contact updates in place. Through the API you poll the request ID, or Salesfinity POSTs the result to the callback URL you supplied, retrying up to three times if your endpoint does not answer. ## Credits An email lookup costs **one credit only when an address is found**. Not-found results are free. Work and personal lookups on the same person are two lookups and can cost two credits. Email and phone enrichment share one credit pool; see [credits](/enrichment/overview#credits). ## Tips * Give the companion the **type** you want. "Find work emails for this table" is unambiguous; "find emails" defaults to work. * For a large table, enrich once and reuse. Results are stored on the row, so the table keeps its addresses when you come back to it. * If you own the contact record elsewhere, use the API with a callback so your system is updated the moment the address is found. # Enrichment Overview Source: https://docs.salesfinity.ai/enrichment/overview How Salesfinity fills in missing phone numbers and email addresses, where enrichment runs, and how credits work. Enrichment fills the gaps in a contact record so it can be dialed or emailed. Give Salesfinity a person's LinkedIn profile and it finds their mobile number, their work email, or their personal email through Salesfinity's data network. You pay only for what is found. Find mobile numbers for contacts on a dialer list or a sourcing table. Find work and personal email addresses, in the app or through the API. ## Where enrichment runs | Surface | What it enriches | How to start it | | ------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | Dialer contact list | Mobile numbers for selected contacts | Select contacts, then **SmartEnrich Contacts** | | Sourcing tables (AI Companion) | Phone numbers and emails for rows on a table | Ask the companion to enrich; it shows the row count and an approval card | | Enrichment lists | A CSV uploaded only to be enriched, then exported to the dialer or downloaded | Upload a CSV under enrichment lists | | REST API | One LinkedIn profile per request, phone or email | [Enrich Phone](/api-reference/endpoint/enrich-phone), [Enrich Email](/api-reference/endpoint/enrich-email) | | MCP server | The same lookups from an AI assistant | The `enrich_phone` and `enrich_email` [tools](/mcp/tools) | Every surface uses the same pipeline and the same credit pool. ## Credits Enrichment is metered in **enrichment credits**. Each team has a monthly allowance from its plan plus any credits it has purchased; the dialer, the AI Companion, and the API all draw from the same balance. * **1 credit per contact where something was found.** A phone lookup that returns a mobile number costs one credit. An email lookup that returns an address costs one credit. Lookups that find nothing are free. * **Before a run starts**, Salesfinity checks that the team has enough credits for the worst case (one credit per selected contact) and refuses the run if not. Only the found contacts are charged when it finishes. * **Purchased credits** top up the monthly allowance. You can buy more from the enrichment dialog when the balance runs low. Check the balance at any time with [Get Enrichment Credits](/api-reference/endpoint/get-enrichment-credits) or the `get_enrichment_credits` MCP tool. Enrichment always requires an explicit action. Salesfinity never enriches contacts automatically, and the AI Companion asks for approval with the row count before any paid enrichment runs. ## Your own data provider By default, lookups run through Salesfinity's data network. If your team has connected its own contact data provider under **Settings → Connections & API → Data Providers**, the enrichment dialog lets you choose it instead. Lookups through your own provider use that provider's credits, not Salesfinity's, and the results land on the contact the same way. # Salesfinity Documentation Source: https://docs.salesfinity.ai/index Product guides, REST API reference, and MCP server docs for the Salesfinity parallel dialer: integrations, Boss Mode, enrichment, and the AI Companion. Salesfinity is a parallel dialing platform for outbound sales teams. This site explains how the product works under the hood and documents every programmatic way into it. Pick the path that matches what you are trying to do. Import lists and tasks from HubSpot, Salesforce, or Outreach and log every call back. Validate every mobile number on a list and dial the ones that will answer. Find mobile numbers and email addresses, one credit per hit. Ask questions of your data and your CRM, act with approval, save workflows as skills. A JSON REST API for contact lists, call logs, scores, analytics, and enrichment. A hosted MCP server that gives Claude and other clients the same data. ## Choose where to start How integrations, Boss Mode, enrichment, skills, and memory actually work. Start here if you run or administer a Salesfinity team. Step-by-step list building with the AI Companion, with prompts to paste. Start here if you want a list to call by lunch. Authentication, conventions, errors, and every operation, generated from the OpenAPI description. Start here if you are writing code. One-click setup for Claude.ai, Claude Code, and other MCP clients, plus a tool reference and use cases for managers and reps. ## What can Salesfinity help you do? Every logged call becomes an activity on the right record, the task is completed, and the sequence advances. See exactly what moves where. Boss Mode grades every number by likelihood to answer and skips the ones that will not. Turn a LinkedIn profile into a mobile number that rings, screened before it reaches the contact. The companion pulls the CRM record, call history, emails, and deals into one briefing. Say "save this as a skill" and the companion turns what you just did into instructions the whole team can run. Ask Claude for connect rates, leaderboards, and scored calls through the MCP server. ## At a glance | | | | ------------------------- | -------------------------------------------------------------------------------------------------------------- | | **App** | [web.salesfinity.co](https://web.salesfinity.co/dashboard/dialer) | | **API base URL** | `https://client-api.salesfinity.co` | | **Authentication** | `x-api-key` request header, generated under Settings → Connections & API | | **OpenAPI description** | [`/api-reference/openapi.json`](/api-reference/openapi.json) (OpenAPI 3.0.1) | | **MCP endpoint** | `https://mcp.salesfinity.ai/mcp` | | **Docs index for agents** | [`/llms.txt`](/llms.txt), [`/llms-full.txt`](/llms-full.txt) | | **Sitemap** | [`/sitemap.xml`](/sitemap.xml) | | **Support** | [hello@salesfinity.co](mailto:hello@salesfinity.co) · [support.salesfinity.ai](https://support.salesfinity.ai) | ## How the pieces fit Contacts come in from a CSV, a CRM list, a sequence, or a sourcing table built by the AI Companion. Boss Mode validates their numbers and enrichment finds the ones that are missing. The parallel dialer works the list. Every logged call is written back to the platform the contact came from, scored by AI, and made available to your own systems through webhooks, the REST API, and the MCP server. The AI Companion sits across all of it, answering questions from the same data and taking actions with your approval. Each guide on this site describes one of those pieces the way it behaves in production, so the behavior you read about here is the behavior you will see in the app. ## Resources for agents and crawlers This documentation publishes machine-readable entry points so an agent can orient itself without scraping rendered HTML: * [`/llms.txt`](/llms.txt) — a compact index of every page on this site. * [`/llms-full.txt`](/llms-full.txt) — the full documentation as a single plain-text file. * [`/api-reference/openapi.json`](/api-reference/openapi.json) — the complete OpenAPI 3.0.1 description, also served at [`/openapi.json`](/openapi.json). * [`/sitemap.xml`](/sitemap.xml) — every canonical URL. * Any page also serves Markdown. Append `.md` to a page URL, or send `Accept: text/markdown`, to get the source instead of the rendered page. The complete, plain-text index of every API operation lives on the [API Reference](/api-reference/introduction#complete-endpoint-index) page. ## Getting help Email [hello@salesfinity.co](mailto:hello@salesfinity.co) or browse the [help center](https://support.salesfinity.ai). When reporting a problem, include the page or request path, the timestamp, and any error shown, which is usually enough to find it in Salesfinity's logs. # How Data Moves Between Platforms Source: https://docs.salesfinity.ai/integrations/data-flow The path a contact takes into Salesfinity and the path a call takes back out, including sync states, retries, and what else flows between systems. Salesfinity never becomes a second system of record. Contacts are copied in so they can be dialed fast, and everything that happens on the call is written back to wherever the contact came from. This page traces both directions. ```mermaid theme={null} flowchart TB subgraph In["Inbound"] direction LR CRM[(CRM or sequencer)] -- "list, report, sequence, or due tasks" --> Import[Import] Import --> List[Salesfinity contact list with record IDs] end List --> Dial[Parallel dialer] Dial -- "disposition, notes, recording, transcript" --> Log[Call log] subgraph Out["Outbound"] direction LR Log --> Queue[Sync queue] Queue -- "recording and transcript" --> Gong[(Gong)] Log --> Webhooks[Webhooks, API, MCP] end Queue -- "activity on the record, task completed" --> CRM ``` ## Inbound: from your platform into Salesfinity An import is a **snapshot**. Salesfinity reads the list, report, sequence, or due-task queue you chose and copies each person into a Salesfinity contact list. For every contact it stores: * The mapped fields from your [field mapping](/integrations/overview#how-to-integrate) — name, title, company, email, phone numbers, plus any custom fields you configured. * The **source** (HubSpot, Salesforce, Outreach, and so on) and the **record ID** in that source. For task-based imports it also stores the task ID and, where the platform has them, the sequence and step. * The list name, taken from the platform's list, report, or sequence name. Because it is a snapshot, edits made in the CRM after the import do not appear in Salesfinity until you re-import. Re-importing the same list refreshes the contacts in place; call history and dispositions already recorded in Salesfinity are kept. Contacts uploaded from a **CSV**, created manually, or created from follow-ups have no source record. They are still linked to your CRM at call time, described below. ## During the call Everything on the call stays in Salesfinity until you log the outcome: the live transcript, recording, AI notes, and the disposition you choose. Nothing is written to the CRM mid-call. ## Outbound: from the call log to your platform When you log a call, Salesfinity saves the call log immediately and marks it **queued** for sync. A background worker then does the rest, so logging never waits on a CRM. For calls longer than five seconds that were recorded, the worker waits until the recording URL and transcript are available before syncing, so the activity written to your CRM carries the recording link. It retries a few times; if the recording still has not arrived, the call is synced without it rather than being dropped. A contact that came from HubSpot, Salesforce, Outreach, or another integration is logged back to **that** platform using the stored record ID. See each platform's guide for exactly what is written: [HubSpot](/integrations/hubspot#what-salesfinity-writes-back), [Salesforce](/integrations/salesforce#what-salesfinity-writes-back), [Outreach](/integrations/outreach#what-salesfinity-writes-back). A contact from a CSV, a custom list, or a follow-up has nowhere obvious to go, so the call is logged to **every CRM the team has connected** among Salesforce, HubSpot, and ActiveCampaign. Salesfinity matches the person in each CRM by email, phone, or name, and creates the record if nobody matches. A CSV contact that carries a Salesforce or HubSpot ID (for example, exported from that CRM) is logged only to that CRM. If the contact was imported from a task, the task is completed in the source platform. In Outreach the sequence step advances; in Salesforce an active cadence step is marked complete. The call log stores the ID and link of the activity created in each platform, and its sync state is updated. Call history shows the state and, on failure, the reason. ### Sync states | State | Meaning | What to do | | ----------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | Queued | Logged, waiting for the worker. | Nothing. Usually clears within seconds, longer if a recording is still being processed. | | In progress | Being written to the platform now. | Nothing. | | Success | Written to every target. | Nothing. The activity link is on the call log. | | Warning | The call fanned out to several CRMs and at least one accepted it while another rejected it. | Fix the failing CRM (usually a reconnect) and retry. The CRMs that already have the call are not written again. | | Error | No platform accepted the call. The reason is stored on the call log. | See [Troubleshooting](/integrations/troubleshooting#a-call-failed-to-sync). | | Skipped | The call was intentionally not synced, for example because no integration applied. | Nothing. | Retries are safe. A retry only writes to the platforms that are still missing the call, so a partial failure never produces duplicate activities. ## Other things that flow between systems * **Phone numbers.** A number found by [enrichment](/enrichment/contacts) or edited in the dialer can be pushed onto the CRM record, so the next import already has it. * **Sequences.** Adding, moving, or removing a person in a sequence from the dialer writes straight to the sequencer. See the sequence section on each platform guide. * **Gong.** When Gong is connected, recorded calls of at least ten seconds that were not voicemails are uploaded to Gong after the CRM sync, under the rep's mapped Gong user. Contacts imported from Gong Engage sync to Gong directly and have their task marked done. See [Gong](/integrations/gong). * **Follow-up sync.** Calls made in a connected platform's own dialer can be pulled into Salesfinity, transcribed, and turned into follow-up tasks, so a rep's follow-up pipeline covers calls made anywhere. * **Outbound to your own systems.** Every logged call is also available to your systems through [webhooks configured in the dashboard](/developers), the [REST API](/api-reference/introduction), and the [MCP server](/mcp/overview). Those read from Salesfinity's call log and do not depend on the CRM sync. ## Where credentials live Each integration stores an OAuth session or API key per Salesfinity user and team. Tokens are refreshed automatically in the background; see the sessions section on each platform guide for when a reconnect is needed. Disconnecting an integration deletes the stored credentials first and then the integration configuration, so a failed disconnect can be retried safely. # Gong Source: https://docs.salesfinity.ai/integrations/gong How Salesfinity sends calls to Gong, works Gong Engage call tasks, what permissions the Gong app needs, and how to configure user and outcome mapping. The Gong integration does two things. It **uploads dialed calls to Gong**, recording and all, so they are transcribed, tracked, and scored alongside the rest of your team's conversations. And for teams on Gong Engage, it **imports your open call tasks** into the dialer and marks them done when you call. Unlike the CRM integrations, Gong is connected **once per team**, and only a team admin can change its configuration. Connect it from **Settings → Connections & API → Gong**. ## Architecture ```mermaid theme={null} sequenceDiagram autonumber actor Admin as Team admin actor Rep participant SF as Salesfinity participant Gong Admin->>SF: Connect Gong (once per team) SF->>Gong: OAuth (technical administrator) Gong-->>SF: Access + refresh token Admin->>SF: Map users and dispositions Rep->>SF: Import Engage call tasks (optional) SF->>Gong: Open call tasks for the mapped user, due today Gong-->>SF: Tasks with contact, flow, and step Rep->>SF: Dial, then log the call SF->>SF: CRM sync first (HubSpot, Salesforce, ...) SF->>Gong: Upload call + recording URL under the mapped user Gong-->>SF: Call ID (duplicates rejected, treated as done) SF->>Gong: Mark the Engage task done Gong->>Gong: Fetch recording, transcribe, run trackers ``` ## Permissions required to connect Salesfinity authorizes with Gong through OAuth. Gong restricts app authorization to users with **technical administrator** rights, so have your Gong technical admin do the connection. The consent screen asks for these scopes: | Scope | Why Salesfinity needs it | | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `api:calls:create` | Upload each dialed call, with its recording, to Gong. | | `api:calls:read:basic`, `api:calls:read:extensive` | Show a contact's past Gong calls on the contact card and answer AI Companion questions about calls and transcripts. | | `api:users:read` | List Gong users for the user mapping. | | `api:call-outcomes:read` | List Gong call outcomes for the disposition mapping. | | `api:tasks:read`, `api:tasks:write` | Import open Engage call tasks and mark them done after the call. | | `api:flows:read`, `api:flows:write` | Read the Engage flow and step a task belongs to. | | `api:crm:upload` | Attach uploaded calls to the right CRM records inside Gong. | | `api:data-privacy:read` | Find a contact's Gong calls by phone number or email for the contact card. | The connection is stored for the team. Gong access tokens last about a day and Salesfinity refreshes them automatically; Gong rotates the refresh token on every use, and Salesfinity keeps the newest one so the two never drift. ## Configuration Open **Configure** on the Gong card. There are two tabs, and both are admin-only. Pairs each Salesfinity user with a Gong user. The mapping decides which Gong user an uploaded call belongs to and whose Engage tasks a rep imports. Map **every rep who dials**. A call from a rep with no mapping is still uploaded, but under one of the other mapped users, so it lands on the wrong person's Gong profile. Maps each Salesfinity disposition to a Gong call outcome. The outcome is sent with every uploaded call. Leave a disposition unmapped and the call is uploaded without an outcome. ## What Salesfinity sends to Gong Every call is uploaded through Gong's calls API as an **outbound call** titled "Salesfinity Call" with: | Field | Value | | ----------------------- | ----------------------------------------------------------------------------------------------------- | | Primary user | The Gong user mapped to the rep | | Start time and duration | From the call log | | Recording | A download URL for the call recording; Gong fetches it and transcribes it | | Disposition | The Gong outcome mapped from the Salesfinity disposition | | Purpose | The call purpose, when one was set | | Parties | The rep, identified by email and Gong user, and the contact, identified by name and the number dialed | | Client unique ID | The Salesfinity call log ID, so the same call can never be uploaded twice | Gong rejects a second upload carrying an ID it has already seen. Salesfinity treats that as success, which makes retries safe: a call is never duplicated in Gong. ### Which calls are uploaded Calls are uploaded on two paths, depending on where the contact came from. * **Contacts imported from Gong Engage.** Every logged call is uploaded, together with the task it belonged to, and the task is marked done when you finished the call. This runs as the contact's primary sync, the same slot a CRM sync occupies for a HubSpot contact. * **Contacts from anywhere else** (CSV, HubSpot, Salesforce, Outreach, and so on). The call is uploaded after its CRM sync, and only if it is a real conversation: **at least ten seconds long, recorded, and not a left-voicemail disposition**. Short dials, unrecorded calls, and voicemails are skipped so they do not clutter Gong. Uploaded calls are marked on the Salesfinity call log, so a call is never sent twice even if its sync is retried. ## Gong Engage call tasks If your team uses Gong Engage, import with Gong as the list source and Salesfinity pulls your **open call tasks** into a list named "All tasks". A task qualifies when it is: * a manual task or a flow step, whose action is a **call**, * **open**, assigned to the Gong user mapped to you, * **due today or earlier**. Each imported contact carries the task ID, the flow and step it came from, and the contact's CRM ID and URL from Gong. Gong Engage tasks can only be imported this way; there is no list picker, because Engage has no lists to pick from. After you log the call, Salesfinity marks the task **done** in Gong as the mapped user. If Gong reports that no task changed, for example because the task was already closed or reassigned, the call log records that so the rep can see the task still needs attention. ## Gong data inside Salesfinity * **Contact card.** The Gong Calls card lists a contact's past Gong calls, found by their phone numbers and email, with direction, disposition, participants, and a link to the call in Gong. * **AI Companion.** The Gong specialist can pull calls, transcripts, activity and interaction stats, scorecards, trackers, library folders, tasks, flows, users, workspaces, and audit logs to answer questions such as "summarize my last three calls with Acme" or "which trackers fired on yesterday's demo". ## Troubleshooting | Symptom | Cause | Fix | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | A call is in Salesfinity but not in Gong | It was under ten seconds, had no recording, or was a voicemail. | Expected. Only real conversations are uploaded. | | Calls appear under the wrong Gong user | The rep has no user mapping. | Map the rep in the User mapping tab. | | An Engage task stays open after the call | Gong matched no task for the mapped user, usually because the task was reassigned or closed in Gong. | Close it in Gong. Future calls on that contact will not reopen it. | | The Gong card reads disconnected | The refresh token was revoked, or the authorizing user lost technical admin rights. | Have a Gong technical admin reconnect from the settings card. | | "User mapping not found" on import | Your Salesfinity user is not mapped to a Gong user. | Ask a team admin to add the mapping. | # HubSpot Source: https://docs.salesfinity.ai/integrations/hubspot Permissions Salesfinity needs in HubSpot, what it imports, what it writes back, and how to configure the integration. The HubSpot integration imports contacts from HubSpot lists or from your due call tasks, and logs every dialed call back to HubSpot as a call engagement on the contact. Connect it from **Settings → Connections & API → HubSpot**. ## Architecture ```mermaid theme={null} sequenceDiagram autonumber actor Rep participant SF as Salesfinity participant HS as HubSpot portal Rep->>SF: Connect HubSpot SF->>HS: OAuth (public app, required + optional scopes) HS-->>SF: Access + refresh token, portal ID, owner ID Rep->>SF: Import a list or "All due tasks" SF->>HS: Read list members / search open tasks by owner HS-->>SF: Contacts with mapped properties SF-->>Rep: Contact list with HubSpot contact IDs Rep->>SF: Dial, then log the call SF->>HS: Create call engagement, associate to contact SF->>HS: Mark the contact's open call tasks completed HS-->>SF: Engagement ID SF-->>Rep: Call log shows sync state and link ``` Salesfinity is a public HubSpot app installed through OAuth. It uses HubSpot's CRM and engagements APIs only; nothing is added to your portal beyond the app itself, which appears under **Settings → Integrations → Connected Apps** once a user has connected. ## Permissions required to connect Salesfinity authorizes with HubSpot through OAuth. When you click Connect, HubSpot shows the permissions below. The first group is required: HubSpot will refuse the install if your portal cannot grant one of them. The second group is requested but optional: HubSpot grants each one only if your portal has the feature, and silently drops it otherwise, so a portal without deals or sequences can still connect. ### Required | Permission | Why Salesfinity needs it | | ---------------------------- | ------------------------------------------------------------------------------------------------ | | `crm.lists.read` | List your HubSpot lists and read their members so you can import one. | | `crm.objects.contacts.read` | Read contact properties during import and when matching a dialed contact. | | `crm.objects.contacts.write` | Create a contact when you log a call to someone not yet in HubSpot, and sync phone numbers back. | | `crm.objects.owners.read` | Resolve owners so calls and imports are attributed to the right HubSpot user. | | `sales-email-read` | Read a contact's logged email history for the contact activity card. | | `crm.objects.companies.read` | Read company properties for the account view and custom field mapping. | | `crm.schemas.companies.read` | Discover which company properties exist so you can map them. | | `automation.sequences.read` | List sequences so you can enroll contacts from the dialer. | ### Optional | Permission | What it unlocks | | -------------------------------------------------- | -------------------------------------------------------------------------------------- | | `crm.objects.deals.read`, `crm.schemas.deals.read` | Deal fields on the contact card, deal custom fields, and deal-based lists in sourcing. | | `crm.objects.deals.write` | Updating deals from the AI Companion. | | `crm.objects.companies.write` | Updating companies from the AI Companion. | | `automation.sequences.enrollments.write` | Enrolling a contact in a sequence from the dialer. | | `crm.objects.leads.read` | Reading HubSpot leads. | A permission that was optional at connect time cannot be added later without reconnecting. If a HubSpot feature returns "missing the Companies read permission" or "missing the Deals read permission", disconnect and reconnect HubSpot and approve the full list. See [Troubleshooting](/integrations/troubleshooting#hubspot-says-a-permission-is-missing). The person connecting needs a HubSpot seat that can grant these permissions. Super admins always can. A user with a restricted permission set may see the install refused. ## For HubSpot admins: setup checklist Every rep can connect with their own HubSpot user, in which case calls are logged as that user and tasks are imported from that user's queue. Or a team admin connects once and turns on **Share with team**, and reps who never connect use the admin's connection while calls are still attributed to the owner the admin assigns them. Most teams have each rep connect. Installing requires a HubSpot user who can grant every [required permission](#required). A super admin always can. A user with a restricted permission set may be refused at install time, and a portal whose subscription lacks a feature will silently drop the matching optional permission. From **Settings → Connections & API → HubSpot** click Connect, choose the portal, and approve. Approve everything shown. A permission skipped now can only be added by reconnecting. In the integration's configuration, pick the HubSpot user whose tasks "All due tasks" should import. It defaults to the connecting user. In a shared integration, set it per member under "Call task source per member"; a member without one cannot import tasks. Use the automatic suggestion to map every Salesfinity disposition to a HubSpot call outcome, then map contact properties to Salesfinity fields and pick any custom properties you want on the contact card. Import a small list, dial one contact, and open the call in HubSpot. The engagement should sit on the contact's timeline under the rep's owner, and any open call task on that contact should now be completed. ## Objects and access | HubSpot object | Access | What Salesfinity does with it | | ------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Lists | Read | List your lists, read their members for import | | Contacts | Read, Write | Import mapped properties; find a contact by email or phone when logging a CSV call; create the contact when there is no match; write phone numbers back | | Tasks | Read, Write | Import open tasks for "All due tasks"; mark a contact's open call tasks completed after a call | | Calls (engagements) | Write | Create one call engagement per logged call and associate it with the contact | | Owners | Read | Attribute engagements and imports to the right HubSpot user | | Companies | Read (write optional) | Company fields on the contact card, company custom properties, account lists in sourcing | | Deals | Read (optional), write optional | Deal fields on the contact card, deal custom properties, deal-based lists in sourcing | | Sequences | Read, enrollment write optional | List sequences and enroll a contact from the dialer | | Emails (sales email read) | Read | Show logged email history on the contact card | | Property schemas | Read | Discover contact, company, and deal properties for mapping | The dialer integration never deletes anything in HubSpot and never archives a contact. ## How task completion works When a call is logged on a contact, Salesfinity reads the engagements associated with that contact and finds the tasks that are **call type** and still **not started**. Each one is set to completed, keeps its original owner, and gets the body "Call was made." Tasks of other types (email, to-do) and tasks already in progress are left alone. This is why a task imported through "All due tasks" disappears from the rep's HubSpot queue after the call without anyone touching HubSpot. ## Owner attribution HubSpot separates the **user** who logs in from the **owner** record used on CRM objects. When a rep connects, Salesfinity resolves their owner ID from the owner list and stores both. Every engagement is created with that owner, and "All due tasks" filters on it. If a rep's calls show up unowned in HubSpot, the rep has no owner record in the portal; create one for them in HubSpot and reconnect. ## What Salesfinity imports You can import from HubSpot in two ways. * **A list.** Pick any HubSpot list, active or static. Salesfinity reads its members and their mapped properties into a Salesfinity contact list named after the HubSpot list. * **All due tasks.** Import with no list selected and Salesfinity pulls the open tasks assigned to your configured call task source user whose due date has passed or is today, along with the contact each task is associated with. The list is named "All due tasks". Tasks that are already completed are skipped. Each imported contact keeps its HubSpot contact ID, which is how the call is routed back to the right record later. Contact fields are filled from your contact field mapping; unmapped properties are not imported. ## What Salesfinity writes back When you log a call on a HubSpot-sourced contact, Salesfinity creates a **call engagement** in HubSpot and associates it with the contact. The engagement carries: | HubSpot property | Value | | ------------------- | ------------------------------------------------------------------- | | Title | "Salesfinity Call: First Last" | | Body | Your call notes | | From and to numbers | The number you dialed from and the number you reached | | Recording URL | A link to the call recording, when the call was recorded | | Status | Completed, busy, no answer, or failed, derived from the disposition | | Direction | Outbound | | Duration | Talk time in milliseconds | | Disposition | The HubSpot call outcome mapped from your Salesfinity disposition | | Owner | The HubSpot user connected to Salesfinity | After creating the engagement, Salesfinity marks the contact's open **tasks** as completed, so a task you imported disappears from your HubSpot queue once you have called it. Two more write paths run outside of call logging: * **Contacts you dial from a CSV or custom list** are logged to HubSpot too, if HubSpot is connected. Salesfinity searches HubSpot for the person by email or phone number. If nobody matches, it creates the contact first and then logs the call. * **Phone numbers** found by enrichment or edited in the dialer can be pushed back onto the HubSpot contact. The call log in Salesfinity keeps a link to the created engagement so you can open it in HubSpot from call history. ## Sequences From the dialer you can **enroll** a contact in a HubSpot sequence. Enrollment needs the optional sequence enrollment permission and a sender email, which is the connected HubSpot user's inbox. HubSpot has no public API to unenroll a contact, so **remove** and **move between sequences** are not available for HubSpot. Do those in HubSpot itself. ## Configuration Open the integration after connecting to reach these settings. The HubSpot user whose call tasks are imported by "All due tasks". Defaults to the user who connected. Change it if a manager connects on behalf of a rep, or if tasks are assigned to a shared owner. Maps each Salesfinity disposition to a HubSpot call outcome. Salesfinity's built-in dispositions fall into answered, no answer, left voicemail, gatekeeper, bad number, and cancelled. Every one must be mapped; when HubSpot has fewer outcomes than Salesfinity, several Salesfinity dispositions map to the same outcome. Use the automatic suggestion to fill the whole table, then adjust. Which HubSpot contact property fills each Salesfinity field. A field can pull from more than one property, which is useful when a phone number may live in either "Phone" or "Mobile". Additional contact, company, and deal properties to bring across. Supported property types are text, textarea, number, select, radio, checkbox, and phone number. These show as custom fields on the contact in the dialer and can be used in filters. Available to a team admin or owner on their own HubSpot connection. Shares the field and disposition mappings with every member, and lets members who have not connected HubSpot use this connection for import and call logging. See [sharing an integration](/integrations/overview#sharing-an-integration-with-the-team). ## Rate limits HubSpot enforces a per-portal limit on search requests that is shared by every user and app in the portal. Salesfinity throttles its own searches to stay under it, and retries automatically when HubSpot asks it to wait. A very large import or a busy portal can therefore take longer than usual without anything being wrong. ## Questions admins ask Reps need whatever HubSpot seat lets them create calls and tasks on contacts. Sequences, deals, and sales email history depend on Sales Hub features; without them the matching optional permissions are dropped and those features are simply absent in Salesfinity. A call engagement carries duration, outcome, direction, recording URL, and both phone numbers as structured properties, so HubSpot reports and workflows can use them. A note would lose all of that. Reps see every list their HubSpot user can see. Restrict list visibility in HubSpot and the picker follows. HubSpot limits search calls per second per portal, shared by every app and user in the portal. Salesfinity throttles its own searches and retries when HubSpot asks it to wait, so large imports can take longer at busy times but do not fail. Salesfinity stores the OAuth access and refresh token for each connected user in its credential store; they are never shown in the UI or returned by the API. Disconnecting deletes them; uninstalling the app in HubSpot invalidates them. ## Related Where a call goes after you log it, and what each sync state means. Missing permissions, empty imports, and failed syncs. # Outreach Source: https://docs.salesfinity.ai/integrations/outreach Permissions Salesfinity needs in Outreach, what it imports, what it writes back, and how to configure call purposes and dispositions. The Outreach integration imports prospects from a sequence or from your due call tasks, and logs every dialed call back to Outreach as a call on the prospect, completing the task and advancing the sequence step. Connect it from **Settings → Connections & API → Outreach**. ## Permissions required to connect Salesfinity authorizes with Outreach through OAuth. When you connect, Outreach asks you to approve these scopes: | Scope | Why Salesfinity needs it | | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `prospects.read`, `prospects.write` | Import prospects, match a dialed contact, create a prospect when logging a call to someone new, and pause or resume their sequence. | | `tasks.read`, `tasks.write`, `taskPriorities.read` | Import your due call tasks and mark them complete when the call is logged. | | `calls.read`, `calls.write` | Create the call record for every dialed call and read past call activity. | | `callDispositions.read` | List call dispositions for the disposition mapping. | | `callPurposes.read`, `callPurposes.write`, `callPurposes.all` | List call purposes and attach the chosen purpose to each logged call. | | `sequences.read`, `sequenceSteps.read`, `sequenceStates.write` | List sequences to import from, read the step a task belongs to, and add or remove prospects in sequences. | | `stages.read` | Read prospect stages for the contact card. | | `accounts.read`, `accounts.write` | Read the prospect's account and its fields. | | `phoneNumbers.read`, `phoneNumbers.write` | Read prospect phone numbers, drop numbers Outreach has flagged invalid, and sync enriched numbers back. | | `users.read`, `teams.read` | Attribute calls and tasks to the connected user. | | `mailboxes.read`, `mailings.read`, `mailings.write` | Show the prospect's email history on the contact card. | The connecting user needs an Outreach profile that allows API access. Outreach admins can restrict which apps a role may authorize; if the connect page fails before reaching Outreach's consent screen, ask your Outreach admin to allow Salesfinity. ## What Salesfinity imports * **A sequence.** Pick a sequence and Salesfinity imports the prospects in it. The Salesfinity list is named after the sequence. * **All tasks.** Import with no sequence selected and Salesfinity pulls your incomplete call tasks that are due today or later, together with the prospect, sequence, and sequence step each task belongs to. The list is named "All tasks". Before the import finishes, Salesfinity fetches the phone numbers your Outreach org has marked **invalid** and removes them from the imported contacts, so you do not dial numbers Outreach already knows are dead. Every imported contact keeps its Outreach prospect ID and, when it came from a task, the task ID and sequence step. Those are what let the call complete the right task later. ## What Salesfinity writes back When you log a call on an Outreach-sourced contact, Salesfinity creates a **call** in Outreach on the prospect: | Call attribute | Value | | ------------------------- | ---------------------------------------------------------------------- | | Direction | Outbound | | Outcome | Completed | | Note | Your call notes | | Answered at, completed at | The call's start and end time | | To, from | The number reached and the number dialed from | | Recording URL | A link to the recording, when the call was recorded | | Sequence action | The step action the task belonged to, so the sequence advances | | Tags | "Salesfinity" | | Disposition | The Outreach call disposition mapped from your Salesfinity disposition | | Call purpose | The purpose chosen when logging, or your default call purpose | | Task | The imported task, marked complete when you finished the call | If the task no longer exists in Outreach, for example because the sequence was edited after import, the call is logged without the task link rather than failing. When the call was recorded, Salesfinity also submits the recording to **Kaia** so Outreach can transcribe and analyze it. This runs after the call is logged and does not block it. Two more write paths run outside of call logging: * **Contacts you dial from a CSV or custom list** with Outreach as their sequencer are matched by email; if nobody matches, a prospect is created before the call is logged. * **Phone numbers** found by enrichment or edited in the dialer can be written back to the prospect. ## Sequences From the dialer's sequence controls you can **add** a prospect to a sequence, **remove** them, and **move** them to another sequence. Sequence membership also drives the contact card, which shows the prospect's current sequence, step, and stage. ## Configuration The Outreach call purpose attached to every logged call unless you pick a different one while logging. Set this first; Outreach requires a purpose on calls in many orgs. Maps each Salesfinity disposition to an Outreach call disposition. The automatic suggestion fills the table from your org's disposition names, and you can adjust it. Which Outreach prospect field fills each Salesfinity contact field. Extra prospect and account fields to bring across onto the contact in the dialer. ## Sessions and reconnecting Outreach issues short-lived access tokens with a refresh token. Salesfinity refreshes the token automatically the moment it expires, both when you use the dialer and when the AI Companion or a routine calls Outreach in the background. If the refresh is rejected, usually because the token was revoked in Outreach, the integration reads as disconnected and you reconnect from the settings card. ## Related Where a call goes after you log it, and what each sync state means. Reconnect prompts, empty imports, and failed syncs. # Integrations Overview Source: https://docs.salesfinity.ai/integrations/overview How Salesfinity connects to your CRM and sales engagement platform, what a connection can do, and how to set one up. Salesfinity is a parallel dialer, so almost every team runs it next to a system of record. An integration connects the two: contacts and call tasks flow **in** from your CRM or sales engagement platform, you dial them in Salesfinity, and every call flows **back out** as an activity on the record it belongs to. Nobody re-keys anything. Import lists and due tasks, log calls as engagements, complete tasks. Import reports and open call tasks, log calls as Tasks, advance cadences. Import sequences and call tasks, log calls with dispositions and purposes. Upload recorded calls for transcription and scoring, work Engage call tasks. ## What an integration does Every CRM and sales engagement integration follows the same three-step loop. Pick a list, report, or sequence in the connected platform, or import everything that is due to you as a call task. Salesfinity pulls those people into a contact list, keeps the link to the original record, and maps their fields onto Salesfinity contact fields using your field mapping. You dial the list with the parallel dialer. Boss Mode, enrichment, dispositions, notes, and recordings all work the same whether the contact came from a CSV or a CRM. When you log a call, Salesfinity queues it and writes it to the platform the contact came from: a call engagement in HubSpot, a completed Task in Salesforce, a call record in Outreach. Open call tasks on that record are marked complete. See [how data moves between platforms](/integrations/data-flow) for the full path. ## Supported platforms Integrations are grouped by what they do. The platforms with dedicated guides on this site are in bold. | Category | Platforms | | ------------------------- | -------------------------------------------------------------------------------- | | CRM | **HubSpot**, **Salesforce** (production and sandbox), Attio, Pipedrive | | Sales engagement | **Outreach**, Salesloft, Apollo, Amplemarket, ActiveCampaign, Lemlist, Smartlead | | Conversation intelligence | **Gong** | | Data providers | Providers you bring your own account for, used by sourcing and enrichment | | Communication | Slack, Gmail | CRM and sales engagement platforms take part in the import, dial, log-back loop above. Data providers and communication tools do not import or log calls. They power sourcing, enrichment, notifications, and email. ## How to integrate In the Salesfinity dashboard go to **Settings → Connections & API**. Each platform shows a card with a short description and a **Connect** button. HubSpot, Salesforce, and Outreach use OAuth. Clicking Connect sends you to the platform's login page, where you approve the permissions Salesfinity asks for. The exact permissions are listed on each platform's guide. Some other platforms use an API key instead, which you paste into the card. After authorizing, the integration opens its configuration. The sections vary a little by platform, but these are the ones you will see most often: * **Call task source** — the user in the connected platform whose call tasks are imported when you import "all tasks" instead of a named list. * **Disposition mapping** — which outcome in the connected platform each Salesfinity disposition becomes when a call is logged. Salesfinity can suggest a full mapping automatically from the platform's outcome names, and you can adjust it. * **Contact field mapping** — which platform property fills each Salesfinity field (name, title, company, phone, email, and so on). * **Custom fields** — extra platform properties to bring across onto the contact, and for some platforms onto the account or deal. Go to the dialer, choose the platform as the list source, pick a list, and import. The first import is the real test of your field mapping, so check a few contacts before dialing. Every person on the team connects their own account. A connection belongs to one Salesfinity user, and calls are logged as that user in the connected platform. The exception is a team-shared integration, described next. ## Sharing an integration with the team A team admin or the team owner can turn on **Share with team** on their own HubSpot, Apollo, or Amplemarket integration. Sharing does two things: * **Mappings are shared.** Every member uses the owner's field and disposition mappings. Members see them read-only with a "Managed by" notice, and only the owner's configuration is editable. * **Members without their own connection use the owner's.** A rep who never connected the platform can still import and log calls through the shared connection. A rep who has connected their own account keeps using it for calls and only inherits the mappings. While a source is shared, each member's **call task source** is set by the admin in the shared configuration under "Call task source per member". A member who has not been assigned one cannot import their due tasks until they are. Only the owner can disconnect a shared integration. Salesforce, Outreach, Salesloft, Pipedrive, and ActiveCampaign are not shareable yet. Each member connects their own account. Team-level platforms such as Gong and Lemlist are connected once for the whole team by design. ## Where to next The full path from import to call log, sync states, and retries. Reconnect prompts, missing permissions, empty imports, and failed syncs. Add, move, and remove people in sequences without leaving Salesfinity. Ask questions of connected platforms and take actions with approval. # Salesforce Source: https://docs.salesfinity.ai/integrations/salesforce Permissions Salesfinity needs in Salesforce, what it imports, what it writes back, and how to configure production and sandbox orgs. The Salesforce integration imports contacts and leads from reports or from your open call tasks, and logs every dialed call back as a completed Task on the record. Connect it from **Settings → Connections & API → Salesforce**. A separate **Salesforce Sandbox** connection lets you test against a sandbox org. ## Architecture ```mermaid theme={null} sequenceDiagram autonumber actor Rep participant SF as Salesfinity participant Org as Salesforce org Rep->>SF: Connect Salesforce SF->>Org: OAuth (connected app, acts as the rep) Org-->>SF: Access + refresh token Rep->>SF: Import a report or "All call tasks" SF->>Org: Run report / query open call Tasks Org-->>SF: Contact and Lead IDs SF->>Org: SOQL for mapped fields, 100 IDs per query Org-->>SF: Records SF-->>Rep: Contact list with Salesforce IDs Rep->>SF: Dial, then log the call SF->>Org: Create completed Task on the Contact or Lead SF->>Org: Mark active cadence step complete (Sales Engagement) Org-->>SF: Task ID SF-->>Rep: Call log shows sync state and link ``` Salesfinity talks to Salesforce only through the standard REST, SOQL, and Analytics APIs as the connected user. There is no managed package, no Apex, no trigger, no custom object, and no custom field. Nothing is installed in your org. ## Permissions required to connect Salesfinity is a Salesforce connected app that authorizes through OAuth. When you connect, the Salesforce login page asks you to allow these OAuth scopes: | Scope | Why Salesfinity needs it | | ------------------------ | -------------------------------------------------------------------------------------------------------- | | `api` | Read and write records through the REST, SOQL, and Analytics APIs. | | `full` | Access the data your user can already see, so imports and call logging respect your org's sharing rules. | | `web` | Open records in Salesforce from links in Salesfinity. | | `id`, `email`, `profile` | Identify the connected user so calls are attributed correctly and "my tasks" resolves to you. | | `content` | Read report content when importing from a report. | There is nothing to install in Salesforce and no package to configure. Your Salesforce admin does need to allow the Salesfinity connected app if your org restricts which apps users may authorize. Salesfinity only ever acts as the connected user. It cannot read a record or run a report that your Salesforce user cannot. ### Production and sandbox Production connects through `login.salesforce.com`. The **Salesforce Sandbox** card connects through `test.salesforce.com` instead. The two are independent integrations with their own mappings, so you can keep a sandbox connected for testing while dialing against production. ## For Salesforce admins: setup checklist Most Salesforce questions about Salesfinity are answered by this list. Work through it once per org; after that, each rep connects their own account in a minute. Salesfinity is an OAuth connected app. If your org restricts which apps users may authorize (**Setup → Connected Apps OAuth Usage**, or a policy of "Admin approved users are pre-authorized"), approve Salesfinity and assign it to the profiles or permission sets your reps use. Reps whose profile is not assigned are refused at the authorization step. The rep's profile or a permission set must have **API Enabled**. Salesfinity does everything through the API, so a user without it cannot connect. Salesfinity acts as the rep, so the rep needs the access listed in [objects and access](#objects-and-access) below. In most orgs a standard sales profile already has it. The one that is commonly missing is **Run Reports** with access to the report folders reps import from. Salesfinity imports from a **contact report** or a **lead report**. The report must include the record ID column (Contact ID or Lead ID), because that is how Salesfinity finds the records to load. An import reads at most the first 2,000 records of a report, so split larger territories into several reports. If your org uses custom Task statuses, a custom "call" type value, or a custom due-date field, set them in the integration's **Task import** section so "All call tasks" and call completion match what reps see in Salesforce. Orgs on the standard Task process can leave the defaults. If Sales Engagement is enabled, Salesfinity advances the current cadence step when a call is logged and lets reps add, move, and remove targets from cadences. If it is not enabled, everything else still works and cadence features are simply absent. Every rep connects from **Settings → Connections & API → Salesforce** with their own login. Calls are logged as the connected user, which is what keeps activity attribution and sharing rules correct. Connecting once as an admin on behalf of everyone would log every call under the admin. ## Objects and access What Salesfinity reads and writes, and the permission the connected user needs for each. Field level security applies: a field the user cannot see is left out of the import rather than causing an error. | Object | Access | What Salesfinity does with it | | --------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | Contact | Read, Create, Edit | Import mapped fields; create a Contact when logging a call to a CSV contact who is not in Salesforce; write phone numbers back | | Lead | Read, Edit | Import mapped fields from lead reports and tasks; write phone numbers back | | Task | Read, Create, Edit | Read open call tasks for "All call tasks"; create a completed Task for every logged call | | Account | Read | Account name and fields on the contact card, account custom fields, account lists in sourcing | | Opportunity | Read | Deal fields on the contact card and opportunity-based lists in sourcing | | Report | Run Reports | Run the chosen report to collect record IDs for import | | User | Read | Resolve the connected user for "my tasks" and list users | | ActionCadence, ActionCadenceStepTracker | Read, plus Sales Engagement | Find the active cadence step for a record and mark it complete; add, move, and remove targets | | Object describe | Standard | Discover fields for the field mapping and custom field pickers | The dialer integration never deletes a Salesforce record. It creates records only in the one case above, a CSV contact with no match in Salesforce. ## How an import runs For a report, Salesfinity runs it through the Analytics API and reads the Contact or Lead ID from each row, up to 2,000. For "All call tasks" it queries your open call Tasks, ordered by due date, and takes the Contact or Lead each one is related to. If a record has several open tasks, the earliest is used. It then queries Contacts and Leads in batches of 100 IDs, selecting only the fields in your contact and lead field mappings plus any custom fields, including relationship fields such as the account name. Each record becomes a contact with its Salesforce ID, object type, a link back to the record, and, for task imports, the task ID and due date. The list is named after the report, or "All call tasks". ## What Salesfinity imports * **A report.** Pick a contact report or a lead report. Salesfinity runs the report and imports the Contact or Lead records it returns. The Salesfinity list is named after the report. * **All call tasks.** Import with no report selected and Salesfinity pulls your open Tasks that are calls, together with the Contact or Lead each task is on. The list is named "All call tasks". Which tasks count as open and which count as calls is set in the [Task import configuration](#configuration). Both Contacts and Leads are supported. Contacts use the contact field mapping; Leads use the lead field mapping. Every imported record keeps its Salesforce ID and its object type, so the call is logged back to the right object. ## What Salesfinity writes back When you log a call on a Salesforce-sourced contact or lead, Salesfinity creates a **Task**: | Task field | Value | | ---------------- | ------------------------------------------------------------------------------------------------------------ | | Related to | The Contact or Lead you dialed | | Status | Completed | | Subject | "Call made to: (number), from: (number)", followed by the sequence name when the contact came from a cadence | | Task subtype | Call | | Call type | Outbound | | Call disposition | The Salesforce value mapped from your Salesfinity disposition | | Call duration | Talk time in seconds | | Description | Your call notes | | Activity date | The day of the call | If the record is enrolled in a **Sales Engagement cadence** and its current step is active, Salesfinity marks that step complete so the cadence advances. This uses Salesforce's standard cadence action and requires Sales Engagement to be enabled in your org; when it is not, the call is still logged and the step is left alone. Two more write paths run outside of call logging: * **Contacts you dial from a CSV or custom list** are logged to Salesforce too, if Salesforce is connected. Salesfinity searches for the person by email and name. If nobody matches, it creates a Contact using your field mapping, dropping any mapped columns that the Contact object does not have, and then logs the call. * **Phone numbers** found by enrichment or edited in the dialer can be written to the Contact or Lead's phone fields. ## Sequences Salesforce Sales Engagement cadences appear in the dialer's sequence controls. You can **add** a contact or lead to a cadence, **remove** it, and **move** it to a different cadence. All three require Sales Engagement in your org. ## Configuration Two separate maps, one for Contacts and one for Leads, from Salesforce fields to Salesfinity contact fields. Relationship fields such as the account name are available for Contacts. Extra Contact, Lead, Account, and Opportunity fields to bring across. Contact and Lead custom fields land on the contact in the dialer. Account and Opportunity fields feed the account view and the AI Companion. Maps each Salesfinity disposition to a Salesforce call disposition value. The automatic suggestion fills the table from your org's picklist values. Controls what "All call tasks" pulls and how tasks are closed. Map three Task fields: * **Status** — the field that says whether a task is open, plus the list of values that count as open. Defaults to Status and "not Completed". * **Task type / call type** — the field and values that identify a task as a call. Defaults to Task Subtype equal to Call. * **Due date** — the field used to filter tasks by due date. Defaults to Activity Date. Orgs with a custom task process should map these to their own fields so the import matches what reps see in Salesforce. ## Sessions and reconnecting Salesforce access tokens are opaque and do not carry an expiry, so Salesfinity refreshes the session on every call using your refresh token. You will not be asked to log in again unless the refresh token is revoked, which happens when an admin revokes the connected app, when your password changes with session revocation, or when the org's refresh token policy expires it. In those cases the integration reads as disconnected and you reconnect from the same settings card. ## Questions admins ask No. Each rep connects with their own Salesforce login and every action runs as that user. A shared integration user would log every call under one name and bypass sharing rules, so Salesfinity does not support it. Salesfinity stores the OAuth access token, refresh token, and instance URL for each connected user in its credential store, and never the user's password. Tokens are never shown in the UI or returned by the API. Disconnecting from Salesfinity deletes them. Revoking the connected app in Salesforce invalidates them immediately. An import costs one report run plus one SOQL query per 100 records. A logged call costs a short identity check, one Task insert, and, with Sales Engagement, one cadence query and one action call. Salesfinity refreshes the session per request with a lightweight identity call rather than a full login. A team dialing all day stays well inside a standard org's daily API allocation. Yes, through Salesforce itself. Salesfinity only sees what the connected user sees, so profiles, permission sets, sharing rules, and field level security all apply unchanged. Contacts and Leads are the supported objects. Custom fields on either are supported through the custom field pickers. A custom Task process is supported through the Task import mapping. Custom objects are not imported. Yes. Connect the **Salesforce Sandbox** card to a sandbox org. It is a separate integration with its own mappings, so nothing from testing touches production. ## Related Where a call goes after you log it, and what each sync state means. Reconnect prompts, empty imports, and failed syncs. # Troubleshooting Integrations Source: https://docs.salesfinity.ai/integrations/troubleshooting What to do when an integration asks you to reconnect, reports a missing permission, imports nothing, or fails to sync a call. Most integration problems come down to one of four things: the connection expired, a permission was not granted, the import scope does not match what you expected, or a CRM rejected a write. Find the symptom below. ## "Please connect or configure your integration before dialing" The dialer checks the integration before you start a session. This message means one of: * **The connection is gone.** The stored session was revoked or could not be refreshed. Go to **Settings → Connections & API**, open the platform, and connect again. Your mappings are kept. * **Configuration is incomplete.** Some platforms need a setting before the first call, for example a default call purpose in Outreach or a disposition mapping. Open the integration's configuration and fill in what is highlighted. ## HubSpot says a permission is missing Messages such as "HubSpot account is missing the Companies read permission" or "missing the Deals read permission" mean the portal did not grant one of the [optional permissions](/integrations/hubspot#optional) when you connected. HubSpot grants optional permissions only if the portal has the feature and the connecting user can approve them. Fix: disconnect HubSpot, connect again, and approve the full list on HubSpot's consent screen. If the permission still does not appear, ask a HubSpot super admin to connect, or check that your HubSpot subscription includes the object in question. ## The integration keeps asking me to reconnect * **Outreach and HubSpot** issue short-lived access tokens that Salesfinity refreshes with a refresh token. If the refresh is rejected, the platform revoked the token. Common causes are a password reset with session revocation, an admin removing the app, or the user being deactivated in the platform. Reconnect from the settings card. * **Salesforce** refreshes on every request. A reconnect prompt means the refresh token itself was revoked, usually by an admin action or an org policy that expires refresh tokens. Reconnect and, if it recurs, ask your Salesforce admin about the connected app's refresh token policy. ## An import returned no contacts Check the import scope first. Importing **with no list selected** pulls your due call tasks, not the whole CRM, and "due" is defined by the platform: | Platform | What "all tasks" means | | ---------- | ----------------------------------------------------------------------------------------------------------------------- | | HubSpot | Open tasks owned by the configured **call task source** user, due today or earlier, that are associated with a contact. | | Salesforce | Open Tasks on the connected user, filtered by the **Task import** configuration (status, call type, due date). | | Outreach | Incomplete call tasks owned by the connected user, due today or later. | Then check these: * **Call task source.** For HubSpot the tasks belong to the user chosen as call task source, not necessarily the person who connected. In a shared integration each member must be assigned one by the admin; until then their import is rejected. * **Task filters.** In Salesforce, a custom task process that does not use Status and Task Subtype needs the Task import fields mapped, or the default filter finds nothing. * **Empty or unreadable list.** A HubSpot list with no members, a Salesforce report that returns no rows, or an Outreach sequence with no prospects imports an empty list. Open it in the platform to confirm. * **Field mapping.** If contacts import but show blank names or phones, the field mapping points at the wrong properties. Fix the mapping and re-import. * **Invalid numbers.** Outreach imports drop phone numbers your Outreach org has flagged as invalid. A contact whose only number is flagged imports without a number. ## A call failed to sync Open the call in call history. The sync state and, for failures, the reason are shown on the call log. The reason is the message the platform returned, prefixed with the HTTP status when there was one. | Reason looks like | Cause | Fix | | -------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | 401 or 403 | The connection expired or lacks a permission. | Reconnect the platform, then retry the sync. | | 404 | The record or task no longer exists in the platform. | The contact was deleted or merged after import. Re-import the list. | | 400 with a field name | The platform rejected a mapped value, for example a disposition that is not a valid picklist value. | Fix the disposition or field mapping and retry. | | 429 | The platform is rate limiting. | Nothing. Salesfinity waits and retries automatically. | | A transport error with no status | The platform did not respond. | Retry. Salesfinity also retries these on its own. | A **warning** state means the call fanned out to several CRMs and one of them failed while the others succeeded. Only the failing CRM needs attention; retrying does not duplicate the call in the CRMs that already have it. See [sync states](/integrations/data-flow#sync-states). ## I cannot disconnect the integration If the message says the integration is shared with the team, you are using a teammate's shared connection. Only its owner can disconnect it. Ask them, or have a team admin turn off sharing. ## Calls are logged as the wrong user Calls are attributed to the platform user who connected the integration. If a manager connected on a rep's behalf, the manager's user is the one Salesfinity acts as. Each rep should connect with their own account. In HubSpot, the **call task source** only changes whose tasks are imported; it does not change who calls are logged as. ## Everything looks right but the CRM is slow HubSpot enforces a per-portal search limit shared across every app and user in the portal. Salesforce and Outreach have their own API limits. Salesfinity throttles its requests and backs off when a platform asks it to, so during a large import or a busy period you may see calls sit in the queued state for longer. They will sync; nothing is lost. ## Still stuck Email [hello@salesfinity.co](mailto:hello@salesfinity.co) with the platform, the time the problem happened, and the sync reason from the call log if there is one. That is usually enough to find the request in Salesfinity's logs. # Connect to Claude.ai Source: https://docs.salesfinity.ai/mcp/claude-ai Set up Salesfinity MCP on Claude.ai desktop and web apps Connect Salesfinity to Claude.ai to manage your sales data through natural conversation. Setup takes under a minute. One-click setup — opens Claude.ai with the Salesfinity connector pre-filled. ## Video walkthrough