# 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
## Prerequisites
* A [Claude Pro, Team, or Enterprise](https://claude.ai/upgrade) subscription (MCP integrations require a paid plan)
* A Salesfinity API key — generate one in [Settings → Connections & API](https://web.salesfinity.co/dashboard/settings)
## Step-by-step setup
Go to [claude.ai](https://claude.ai) and click on your profile icon in the bottom-left corner, then select **Settings**.
In the settings panel, click **Integrations** in the sidebar. You'll see a section for MCP integrations.
Click **Add more integrations**, then enter the MCP server URL:
```
https://mcp.salesfinity.ai/mcp
```
You'll be redirected to the Salesfinity authorization page. Enter your API key and click **Connect**.
Your API key is securely exchanged for an OAuth token. Salesfinity never stores your key on the MCP server.
Return to Claude and start a new conversation. The Salesfinity tools are now available. Try asking:
*"Show me all my contact lists"*
## Verifying the connection
After setup, you can verify the integration is working by looking for the Salesfinity icon in the tools section at the bottom of the Claude chat input. You can also ask Claude:
*"Can you check my Salesfinity connection by getting my team info?"*
Claude will call the `get_team` tool and display your team details.
## Troubleshooting
| Issue | Solution |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| "Unauthorized" error | Your API key may be invalid or expired. Generate a new one in [Settings](https://web.salesfinity.co/dashboard/settings) and re-authorize. |
| Tools not appearing | Make sure the integration shows as "Connected" in Claude Settings → Integrations. Try removing and re-adding it. |
| "Invalid API Key" on auth page | Double-check you're using an API key from the Connections & API section, not another credential. |
Need help? Email us at [hello@salesfinity.co](mailto:hello@salesfinity.co) or visit the [Help Center](https://support.salesfinity.ai).
# Connect to Claude Code
Source: https://docs.salesfinity.ai/mcp/claude-code
Set up Salesfinity MCP in the Claude Code CLI
Use Salesfinity directly from your terminal with [Claude Code](https://docs.anthropic.com/en/docs/claude-code).
## Setup
Add the Salesfinity MCP server to your Claude Code configuration:
```bash theme={null}
claude mcp add salesfinity \
--transport streamable-http \
https://mcp.salesfinity.ai/mcp
```
The first time you use a Salesfinity tool, Claude Code will open the OAuth authorization page in your browser. Enter your API key to connect.
## Verify the connection
```bash theme={null}
claude
> Can you get my team info from Salesfinity?
```
## Example usage
```bash theme={null}
# Ask about your contact lists
> Show me all my contact lists sorted by name
# Create a list
> Create a contact list called "Demo Prospects" with user_id 680edc0d1504192884a148e0
# Check analytics
> What's my team's call performance for the last 7 days?
# Search call logs
> Show me all answered calls from this week with recordings
```
## Configuration file
If you prefer to edit the config file directly, add this to your `~/.claude.json` or project `.claude.json`:
```json theme={null}
{
"mcpServers": {
"salesfinity": {
"type": "streamable-http",
"url": "https://mcp.salesfinity.ai/mcp"
}
}
}
```
# Other MCP Clients
Source: https://docs.salesfinity.ai/mcp/other-clients
Connect Salesfinity to Cursor, Windsurf, and other MCP-compatible clients
The Salesfinity MCP server works with any client that supports the [Model Context Protocol](https://modelcontextprotocol.io). Below are setup instructions for popular clients.
## Server details
| Field | Value |
| --------------------- | -------------------------------------------- |
| **Server URL** | `https://mcp.salesfinity.ai/mcp` |
| **Transport** | Streamable HTTP |
| **Authentication** | OAuth 2.0 (Authorization Code flow) |
| **Authorization URL** | `https://mcp.salesfinity.ai/oauth/authorize` |
| **Token URL** | `https://mcp.salesfinity.ai/oauth/token` |
## Cursor
Add to your Cursor MCP settings (`.cursor/mcp.json` in your project or global config):
```json theme={null}
{
"mcpServers": {
"salesfinity": {
"url": "https://mcp.salesfinity.ai/mcp"
}
}
}
```
Open the Cursor settings UI, navigate to **MCP**, and verify the Salesfinity server shows as connected.
## Windsurf
Add to your Windsurf MCP configuration (`~/.codeium/windsurf/mcp_config.json`):
```json theme={null}
{
"mcpServers": {
"salesfinity": {
"serverUrl": "https://mcp.salesfinity.ai/mcp"
}
}
}
```
## Generic MCP client
For any MCP-compatible client, use these connection details:
* **Transport type**: Streamable HTTP
* **Endpoint**: `https://mcp.salesfinity.ai/mcp`
* **Auth**: The server supports OAuth 2.0 discovery via `/.well-known/oauth-authorization-server`
The OAuth flow will prompt you to enter your Salesfinity API key on first connection.
Client configuration varies — check your specific client's documentation for the exact format. The server URL and transport type are the same across all clients.
# MCP Overview
Source: https://docs.salesfinity.ai/mcp/overview
Connect Salesfinity to Claude AI and other MCP-compatible clients
The Salesfinity MCP (Model Context Protocol) server lets AI assistants like Claude interact directly with your Salesfinity account — managing contact lists, reviewing call logs, analyzing performance, and more — all through natural language.
One-click setup — opens Claude.ai with the Salesfinity connector pre-filled. Requires a Claude Pro, Team, or Enterprise plan.
## What is MCP?
[Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that lets AI applications securely connect to external tools and data sources. Instead of copy-pasting data between Salesfinity and your AI assistant, MCP gives the assistant direct access to your Salesfinity data.
## What can you do with Salesfinity MCP?
Create lists, add or remove contacts, search and filter across all your lists
Get analytics overviews, SDR performance breakdowns, and list-level metrics
Search and filter call logs, view AI-scored calls with coaching recommendations
Create, update, and monitor webhook subscriptions and delivery logs
### Example prompts
Once connected, you can ask Claude things like:
* *"Show me all my contact lists"*
* *"Create a new list called Q2 Outbound with these 5 contacts..."*
* *"How many calls did my team make last week? What was the connect rate?"*
* *"Show me the top-performing SDRs this month"*
* *"Which contact lists have the highest answer rates?"*
* *"Show me all AI-scored calls below 50 from last week"*
* *"Set up a webhook to notify me on Slack when a call is logged"*
## Available tools
The MCP server exposes the following tools to AI assistants:
| Tool | Description |
| --------------------------------- | ----------------------------------------------------------------- |
| `get_contact_lists` | List all contact lists with search, filtering, and sorting |
| `get_contact_list_by_id` | Get a single list with all contacts, search, sort, and pagination |
| `create_contact_list` | Create a new contact list with contacts |
| `add_contact_to_list` | Add a contact to an existing list |
| `remove_contact_from_list` | Remove a contact from a list |
| `delete_contact_list` | Permanently delete a contact list |
| `reimport_contacts` | Reimport contacts to the dialing queue |
| `get_team` | Get team info and members |
| `get_call_logs` | Search and filter call logs |
| `get_call_log_by_id` | Get a specific call log |
| `get_scored_calls` | List AI-scored calls with filters |
| `get_scored_call_by_id` | Get a scored call with full AI analysis |
| `get_dispositions` | List all call dispositions |
| `get_disposition_by_id` | Get a specific disposition |
| `get_sequences` | List all sequences |
| `get_sequence_by_id` | Get a specific sequence |
| `get_snoozed_contacts` | List snoozed contacts with filters |
| `get_snoozed_contact_by_id` | Get a snoozed contact |
| `get_snoozed_contact_by_linkedin` | Look up snoozed contact by LinkedIn |
| `update_snoozed_contact` | Update a snoozed contact |
| `delete_snoozed_contact` | Remove a contact from the snooze list |
| `get_follow_ups` | List follow-up tasks |
| `get_custom_fields` | Get custom field definitions |
| `get_analytics_overview` | Aggregated metrics with growth rates |
| `get_list_performance` | Performance metrics by contact list |
| `get_sdr_performance` | Performance metrics by SDR |
| `create_webhook` | Create a webhook subscription |
| `get_webhooks` | List all webhooks |
| `get_webhook_by_id` | Get a specific webhook |
| `update_webhook` | Update a webhook |
| `delete_webhook` | Delete a webhook |
| `get_webhook_events` | List available webhook event types |
| `get_webhook_logs` | Get delivery logs for a webhook |
## Next steps
Step-by-step setup for Claude.ai (Desktop & Web)
Setup for Claude Code CLI
Cursor, Windsurf, and other MCP-compatible clients
Detailed documentation for every MCP tool
# Tool Reference
Source: https://docs.salesfinity.ai/mcp/tools
Detailed documentation for every Salesfinity MCP tool
This page documents every tool available through the Salesfinity MCP server, including parameters, behavior, and usage tips.
## Contact Lists
### get\_contact\_lists
List all contact lists for your team. Returns metadata only — use `get_contact_list_by_id` to see the contacts inside a list.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------------------------------------------------------------- |
| `search` | string | No | Search lists by name (case-insensitive) |
| `user` | string | No | Filter by user ID |
| `sort` | string | No | Sort by field. Prefix with `-` for descending. Default: `-createdAt` |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (default 10, max 100) |
***
### get\_contact\_list\_by\_id
Get a single contact list with all its contacts. Supports searching, sorting, and paginating the contacts within the list.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------------------------------------------------------- |
| `id` | string | Yes | Contact list ID |
| `search` | string | No | Search contacts by first name, last name, email, or company |
| `sort` | string | No | Sort contacts by field. Prefix with `-` for descending |
| `page` | number | No | Page number for contacts (default 1) |
| `limit` | number | No | Contacts per page (default 50) |
***
### create\_contact\_list
Create a new contact list with contacts. Maximum 2,000 contacts per list.
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | ---------------------------------------------------------------- |
| `name` | string | Yes | List name (1–100 characters) |
| `user_id` | string | Yes | User ID who will own this list |
| `contacts` | array | Yes | Array of contact objects (see [Contact object](#contact-object)) |
***
### add\_contact\_to\_list
Add a single contact to an existing list. After adding, call `reimport_contacts` to include them in the dialing queue.
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------------------- |
| `list_id` | string | Yes | Contact list ID |
| All contact fields | — | — | See [Contact object](#contact-object) |
***
### remove\_contact\_from\_list
Remove a contact from a list.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------------- |
| `list_id` | string | Yes | Contact list ID |
| `contact_id` | string | Yes | Contact ID to remove |
***
### delete\_contact\_list
Permanently delete a contact list and all its contacts. This action is irreversible.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------- |
| `list_id` | string | Yes | Contact list ID to delete |
***
### reimport\_contacts
Reimport contacts from the source list to the dialing queue. Call this after adding contacts to make them available for dialing.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | --------------------------- |
| `list_id` | string | Yes | Contact list ID to reimport |
***
## Team
### get\_team
Get team information including all team members. Takes no parameters.
***
## Call Logs
### get\_call\_logs
Search and filter call logs with extensive filtering options.
| Parameter | Type | Required | Description |
| ------------------------------- | --------- | -------- | ------------------------------------------------------- |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (1–100, default 10) |
| `sort` | string | No | Sort field. Default: `-createdAt` |
| `start_date` | string | No | Filter start date (ISO 8601) |
| `end_date` | string | No | Filter end date (ISO 8601) |
| `outcome` | string | No | Filter by outcome: `answered`, `no-answer`, `cancelled` |
| `direction` | string | No | Filter by direction: `inbound`, `outbound` |
| `min_duration` | number | No | Minimum call duration (seconds) |
| `max_duration` | number | No | Maximum call duration (seconds) |
| `has_recording` | boolean | No | Filter by recording availability |
| `answered_by` | string | No | Filter by who answered: `human`, `machine_start` |
| `is_completed` | boolean | No | Filter by completion status |
| `disposition_ids` | number\[] | No | Filter by disposition IDs |
| `exclude_negative_dispositions` | boolean | No | Only return positive dispositions |
| `user_ids` | string\[] | No | Filter by user IDs |
| `contact_list_ids` | string\[] | No | Filter by contact list IDs |
| `sequences` | string\[] | No | Filter by sequence IDs |
| `search` | string | No | Full-text search across contact name, company, phone |
***
### get\_call\_log\_by\_id
Get a specific call log by its ID.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| `id` | string | Yes | Call log ID |
***
## Scored Calls
### get\_scored\_calls
List AI-scored calls with insights, scoring facets, and coaching recommendations.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ---------------------------------- |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (1–100, default 10) |
| `sort` | string | No | Sort field. Default: `-createdAt` |
| `start_date` | string | No | Filter start date (ISO 8601) |
| `end_date` | string | No | Filter end date (ISO 8601) |
| `min_score` | number | No | Minimum total score (0–100) |
| `max_score` | number | No | Maximum total score (0–100) |
| `user_id` | string | No | Filter by user ID |
***
### get\_scored\_call\_by\_id
Get a specific scored call with full AI-generated insight, facets, and recommendations.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------- |
| `id` | string | Yes | Scored call ID |
***
## Dispositions
### get\_dispositions
Get all available call dispositions for the team. Takes no parameters.
***
### get\_disposition\_by\_id
Get a specific disposition by ID.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------- |
| `id` | string | Yes | Disposition ID |
***
## Sequences
### get\_sequences
List all sequences with pagination.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------ |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (default 10, max 100) |
***
### get\_sequence\_by\_id
Get a specific sequence by ID.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| `id` | string | Yes | Sequence ID |
***
## Snoozed Contacts
### get\_snoozed\_contacts
List snoozed contacts with filtering.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | ------------------------------------ |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (default 10, max 100) |
| `sort` | string | No | Sort field |
| `linkedin_username` | string | No | Filter by LinkedIn username |
| `external_contact_id` | string | No | Filter by external CRM contact ID |
| `email` | string | No | Filter by email |
| `phone_number` | string | No | Filter by phone number |
***
### get\_snoozed\_contact\_by\_id
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------ |
| `id` | string | Yes | Snoozed contact ID |
***
### get\_snoozed\_contact\_by\_linkedin
Look up a snoozed contact by LinkedIn username.
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | ----------------- |
| `username` | string | Yes | LinkedIn username |
***
### update\_snoozed\_contact
Update a snoozed contact's details or extend the snooze duration.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | -------------------------------- |
| `id` | string | Yes | Snoozed contact ID |
| `linkedin_username` | string | No | LinkedIn username |
| `external_contact_id` | string | No | External CRM contact ID |
| `email` | string | No | Email address |
| `phone_number` | string | No | Phone number |
| `snooze_until` | string | No | New snooze expiration (ISO 8601) |
***
### delete\_snoozed\_contact
Remove a contact from the snooze list, allowing them to appear in call lists again.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------ |
| `id` | string | Yes | Snoozed contact ID |
***
## Follow-up Tasks
### get\_follow\_ups
List follow-up tasks with pagination.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------ |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (default 10, max 100) |
| `sort` | string | No | Sort field |
***
## Custom Fields
### get\_custom\_fields
Get all custom field definitions configured for the team. Use these when adding contacts with custom fields. Takes no parameters.
***
## Analytics
### get\_analytics\_overview
Get aggregated metrics with growth rates compared to the previous period.
| Parameter | Type | Required | Description |
| ----------------- | --------- | -------- | ------------------------- |
| `start_date` | string | Yes | Start date (ISO 8601) |
| `end_date` | string | Yes | End date (ISO 8601) |
| `user_ids` | string\[] | No | Filter by user IDs |
| `disposition_ids` | string\[] | No | Filter by disposition IDs |
***
### get\_list\_performance
Performance metrics grouped by contact list.
| Parameter | Type | Required | Description |
| ----------------- | --------- | -------- | ------------------------------------ |
| `start_date` | string | Yes | Start date (ISO 8601) |
| `end_date` | string | Yes | End date (ISO 8601) |
| `user_ids` | string\[] | No | Filter by user IDs |
| `disposition_ids` | string\[] | No | Filter by disposition IDs |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (default 10, max 100) |
| `search` | string | No | Search by list name |
***
### get\_sdr\_performance
Performance metrics grouped by SDR.
| Parameter | Type | Required | Description |
| ----------------- | --------- | -------- | ------------------------------------ |
| `start_date` | string | Yes | Start date (ISO 8601) |
| `end_date` | string | Yes | End date (ISO 8601) |
| `user_ids` | string\[] | No | Filter by user IDs |
| `disposition_ids` | string\[] | No | Filter by disposition IDs |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (default 10, max 100) |
| `search` | string | No | Search by SDR name or email |
***
## Webhooks
### create\_webhook
Create a webhook subscription.
| Parameter | Type | Required | Description |
| -------------- | --------- | -------- | ---------------------------------------------- |
| `name` | string | Yes | Descriptive name |
| `events` | string\[] | Yes | Event types: `CALL_LOGGED`, `CONTACT_SNOOZED` |
| `url` | string | Yes | URL to receive POST requests |
| `dispositions` | number\[] | No | Filter `CALL_LOGGED` events by disposition IDs |
***
### get\_webhooks
List all configured webhooks. Takes no parameters.
***
### get\_webhook\_by\_id
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ----------- |
| `webhook_id` | string | Yes | Webhook ID |
***
### update\_webhook
| Parameter | Type | Required | Description |
| -------------- | --------- | -------- | --------------------------------------------- |
| `webhook_id` | string | Yes | Webhook ID |
| `name` | string | Yes | Descriptive name |
| `events` | string\[] | Yes | Event types: `CALL_LOGGED`, `CONTACT_SNOOZED` |
| `url` | string | Yes | Webhook URL |
| `dispositions` | number\[] | No | Filter `CALL_LOGGED` by disposition IDs |
***
### delete\_webhook
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ----------- |
| `webhook_id` | string | Yes | Webhook ID |
***
### get\_webhook\_events
List available webhook event types and their descriptions. Takes no parameters.
***
### get\_webhook\_logs
Get delivery logs for a specific webhook.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------ |
| `webhook_id` | string | Yes | Webhook ID |
| `page` | number | No | Page number (default 1) |
| `limit` | number | No | Items per page (default 10, max 100) |
| `sort` | string | No | Sort field |
***
## Contact object
Used when creating lists or adding contacts:
| Field | Type | Required | Description |
| --------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `first_name` | string | Yes | Contact's first name |
| `last_name` | string | No | Last name |
| `email` | string | No | Email address |
| `phone_numbers` | array | No | Phone numbers (max 10). Each has `type` (`mobile`/`direct`/`office`), `number`, optional `country_code` and `extension` |
| `company` | string | No | Company name |
| `title` | string | No | Job title |
| `linkedin` | string | No | LinkedIn profile URL |
| `website` | string | No | Website URL |
| `notes` | string | No | Notes (max 2,000 characters) |
| `priority` | number | No | Priority |
| `timezone` | string | No | Timezone |
| `custom_fields` | array | No | Custom fields (max 10). Each has `type` (`string`/`number`/`boolean`), `label`, and optional `value` |
# For Managers & Leaders
Source: https://docs.salesfinity.ai/mcp/use-cases/managers
Use cases for VP of Sales, SDR Managers, and team leads using Claude x Salesfinity MCP
Prompts and workflows for sales leaders who need to monitor team performance, coach reps, forecast pipeline, and run efficient operations.
## Performance Analysis & Diagnostics
### Weekly Performance Pulse
Get a complete picture of team performance with automated week-over-week comparison.
**Prompt:** "Pull this week's call data across all reps. Compare dials, conversations, and meetings booked vs last week. Flag any rep who dropped more than 20% in any metric and suggest why."
**What Claude does:** Pulls analytics overview for both periods, compares SDR performance metrics, and highlights reps with significant drops alongside potential explanations from their call patterns.
**Tools used:** `get_analytics_overview`, `get_sdr_performance`, `get_call_logs`
***
### Rep-vs-Rep Benchmarking
Understand the behavioral differences between your top and bottom performers.
**Prompt:** "Compare my top 3 reps against my bottom 3 reps over the last 30 days. What are the specific behavioral differences in call volume, connect rate, conversation duration, and meeting conversion?"
**Tools used:** `get_sdr_performance`, `get_scored_calls`, `get_call_logs`
***
### Ramp Tracking for New Hires
Monitor whether new reps are hitting milestones on schedule.
**Prompt:** "Pull call data for reps who started in the last 90 days. Show me their week-over-week progression on dials, connects, and meetings. Which new hires are ramping on pace and which need intervention?"
**Tools used:** `get_team`, `get_sdr_performance`, `get_call_logs`
***
### Disposition Pattern Analysis
Diagnose whether your team is wasting dials on bad data or poor timing.
**Prompt:** "Across the full team over the last 4 weeks, break down call dispositions. What percentage are no-answers, voicemails, gatekeepers, conversations, and meetings? Are we wasting dials on bad data?"
**Tools used:** `get_dispositions`, `get_call_logs`, `get_analytics_overview`
***
### Time-of-Day Optimization
Find the optimal dialing windows for your team.
**Prompt:** "Analyze connect rates and meeting-booked rates by hour of day and day of week across the team. When should we be dialing for maximum live conversations?"
**Tools used:** `get_call_logs`, `get_analytics_overview`
***
### List Performance Audit
Identify which lists are producing results and which should be retired.
**Prompt:** "Pull performance metrics grouped by contact list. Which lists are producing the highest connect rates and meeting rates? Which lists should we retire or deprioritize?"
**Tools used:** `get_list_performance`, `get_contact_lists`
***
### Meeting-Set Quality Check
Ensure reps are booking real, qualified meetings — not just padding numbers.
**Prompt:** "Pull all meetings booked in the last 30 days. Cross-reference with scored calls. Are reps booking real qualified meetings or are they setting low-quality appointments that will no-show?"
**Tools used:** `get_scored_calls`, `get_call_logs`, `get_sdr_performance`
***
### Attrition Risk Detector
Spot disengaged reps before they quit.
**Prompt:** "Look at rep activity trends over the last 6 weeks. Flag any reps whose dial volume, conversation count, or meeting output has been declining consistently — they might be disengaged or about to churn."
**Tools used:** `get_sdr_performance`, `get_call_logs`
***
### Territory/Vertical Performance Comparison
Find which market segments respond best to cold outreach.
**Prompt:** "If our lists are segmented by industry or territory, compare conversion metrics across segments. Which verticals are most receptive to cold outreach right now?"
**Tools used:** `get_list_performance`, `get_contact_lists`, `get_call_logs`
***
### Monthly Board-Ready Snapshot
Generate an executive summary you can drop straight into a board deck.
**Prompt:** "Generate a clean executive summary of our outbound engine performance for this month: total dials, conversations, meetings, conversion rates at each stage, and month-over-month trends. Format it so I can drop it into a board deck."
**Tools used:** `get_analytics_overview`, `get_sdr_performance`, `get_list_performance`
***
## Coaching & Rep Development
### Individual Rep Coaching Plan
Build a data-driven coaching plan for any rep on your team.
**Prompt:** "Pull all call data, scored calls, and dispositions for \[Rep Name] over the last 2 weeks. Identify their top 3 strengths and top 3 areas for improvement. Write a specific coaching plan with talk track suggestions."
**Tools used:** `get_call_logs`, `get_scored_calls`, `get_sdr_performance`
***
### Scored Call Review Session Prep
Prepare for 1:1s with concrete call examples.
**Prompt:** "Pull the 5 highest-scored and 5 lowest-scored calls for \[Rep Name] this month. Summarize what went right in the top calls and what went wrong in the bottom calls so I can prep for our 1:1."
**Tools used:** `get_scored_calls`
***
### Objection Handling Analysis
Discover which objections your team faces most and who handles them best.
**Prompt:** "Across all scored calls this month, what are the most common objections prospects raise? Which reps handle them best? Pull examples of effective objection responses from top performers."
**Tools used:** `get_scored_calls`, `get_sdr_performance`
***
### Opening Line Effectiveness
A/B test cold call openers using real data.
**Prompt:** "Analyze scored calls across the team. Compare different cold call openers — permission-based, pattern-interrupt, direct, referral-based. Which openers lead to conversations lasting over 60 seconds and ultimately to meetings?"
**Tools used:** `get_scored_calls`, `get_call_logs`
***
### Talk-to-Listen Ratio Audit
Find out if reps are talking too much and not listening enough.
**Prompt:** "For reps with low meeting conversion despite high conversation counts, pull their scored calls. Are they talking too much? What's the ratio compared to reps who convert at higher rates?"
**Tools used:** `get_scored_calls`, `get_sdr_performance`
***
### Discovery Question Quality
Build a discovery framework from what's actually working.
**Prompt:** "Pull scored calls that resulted in meetings booked. What discovery questions are being asked? Now pull calls that had conversations but no meeting. What questions are missing? Build a recommended discovery framework from the data."
**Tools used:** `get_scored_calls`, `get_call_logs`
***
### Peer Learning Matchup
Pair struggling reps with the right mentor for their specific gap.
**Prompt:** "Identify which rep is best at each stage: getting past gatekeepers, opening conversations, handling 'not interested,' booking the meeting. Pair struggling reps with the right mentor for their specific gap."
**Tools used:** `get_sdr_performance`, `get_scored_calls`, `get_call_logs`
***
### Weekly Coaching Digest
Automated coaching notes for every rep, every Monday.
**Prompt:** "Pull last week's data for each rep. Generate a 3-bullet coaching note per rep: one thing they did well, one thing to work on, one specific call to re-listen to."
**Tools used:** `get_sdr_performance`, `get_scored_calls`, `get_call_logs`
***
### New Hire Scorecard
Benchmark new reps against where your top performers were at the same stage.
**Prompt:** "For reps in their first 30 days, pull their call scores, conversation rates, and compare to where our current top performers were at the same point in their ramp. Are they tracking ahead or behind?"
**Tools used:** `get_team`, `get_sdr_performance`, `get_scored_calls`
***
### Call Confidence Tracker
Track whether reps are getting more comfortable on calls over time.
**Prompt:** "Track conversation duration trends for each rep over time. Are they getting more comfortable staying on calls longer? Increasing average conversation length usually correlates with better outcomes — show me the trend."
**Tools used:** `get_call_logs`, `get_sdr_performance`
***
## Pipeline & Forecasting
### Meeting Flow Forecast
Predict meeting output based on current trends.
**Prompt:** "Based on our current dial volume, connect rates, and meeting conversion rates, how many meetings will we book this month if trends hold? What would happen if we increased dials by 15%?"
**Tools used:** `get_analytics_overview`, `get_sdr_performance`
***
### Funnel Leak Diagnosis
Quantify exactly where you're losing potential meetings.
**Prompt:** "Show me where we're losing the most potential meetings. Is it at the dial-to-connect stage (bad data or timing), connect-to-conversation (poor openers), or conversation-to-meeting (weak discovery or ask)? Quantify each leak."
**Tools used:** `get_analytics_overview`, `get_call_logs`, `get_dispositions`
***
### Follow-Up Pipeline Mining
Recover meetings hiding in your follow-up backlog.
**Prompt:** "Pull all follow-up tasks across the team. How many are overdue? Which reps have the largest follow-up backlog? Prioritize follow-ups by likelihood to convert based on original conversation scores."
**Tools used:** `get_follow_ups`, `get_scored_calls`, `get_sdr_performance`
***
### Snoozed Contact Re-engagement
Turn snoozed contacts back into active pipeline.
**Prompt:** "Pull all snoozed contacts. Segment by snooze reason and time snoozed. Which ones are due for re-engagement? Generate a prioritized list of snoozed contacts to add back to active lists."
**Tools used:** `get_snoozed_contacts`, `get_contact_lists`
***
### Meeting-to-Revenue Attribution
Calculate the revenue value of every dial.
**Prompt:** "Pull all meetings booked over the last quarter. Help me build a simple attribution model: how many dials does it take to generate one meeting, and if I know our meeting-to-opportunity and opportunity-to-close rates, what's the revenue value per dial?"
**Tools used:** `get_analytics_overview`, `get_call_logs`, `get_sdr_performance`
***
## Team Operations & Workflow
### Daily Standup Data Pack
Get a 60-second standup brief with zero prep.
**Prompt:** "Pull yesterday's numbers for each rep: dials, conversations, meetings. Rank them. Give me a quick narrative I can use to kick off our morning standup in 60 seconds."
**Tools used:** `get_sdr_performance`, `get_analytics_overview`
***
### Contest & Leaderboard Generator
Run data-driven sales contests with blended scoring.
**Prompt:** "Create a weekly leaderboard based on a blended score: 40% meetings booked, 30% conversations had, 30% dial volume. Rank all reps. Who's winning this week?"
**Tools used:** `get_sdr_performance`, `get_analytics_overview`
***
### List Refresh Recommendations
Know when your lists go stale before performance drops.
**Prompt:** "Look at all active contact lists. Which ones have been active for over 30 days with declining connect rates? Recommend which lists to refresh, retire, or reimport."
**Tools used:** `get_contact_lists`, `get_list_performance`
***
### Capacity Planning
Plan headcount with data, not gut feeling.
**Prompt:** "Based on current per-rep averages for dials, conversations, and meetings, how many reps do I need to hit 200 meetings per month? What if I improve conversion rates by 10% through coaching instead of hiring?"
**Tools used:** `get_analytics_overview`, `get_sdr_performance`
***
### Sequence Performance by Stage
Identify which sequences need to be rewritten.
**Prompt:** "Pull performance data for each sequence. Which sequences are driving the most conversations and meetings? Are there sequences with high dial volume but low conversion that need to be rewritten?"
**Tools used:** `get_sequences`, `get_list_performance`, `get_call_logs`
# Use Cases
Source: https://docs.salesfinity.ai/mcp/use-cases/overview
Practical ways to use Claude x Salesfinity MCP to automate workflows and boost sales performance
The Salesfinity MCP integration turns Claude into your personal sales analyst, coach, and operations assistant. Below are real-world use cases organized by role — each one is a prompt you can use directly in Claude.
## Who is this for?
Performance analysis, coaching plans, pipeline forecasting, and team operations for VP of Sales, SDR Managers, and team leads.
Self-coaching, daily workflow optimization, prospecting prep, and personal performance tracking for individual contributors.
Reporting, A/B testing, advanced analytics, and power-user workflows useful to anyone on the team.
## How it works
Every use case below is a natural-language prompt you can paste directly into Claude. Behind the scenes, Claude uses the Salesfinity MCP tools to pull your real data, analyze it, and give you actionable answers.
**No dashboards to build. No spreadsheets to wrangle. Just ask.**
Follow the [setup guide](/mcp/claude-ai) to connect your account (takes under a minute).
Each use case includes a ready-to-use prompt. Customize the timeframe, rep names, or metrics to fit your needs.
Claude pulls your live Salesfinity data, runs the analysis, and returns actionable insights — all in natural language.
## Quick examples
Here are a few prompts to get a feel for what's possible:
> "Pull this week's call data across all reps. Compare dials, conversations, and meetings booked vs last week. Flag any rep who dropped more than 20%."
> "What's my conversation-to-meeting rate compared to the team average? What's the single biggest thing I should change?"
> "Generate a clean executive summary of our outbound performance this month — total dials, conversations, meetings, conversion rates, and month-over-month trends."
Explore the full use case library by role below.
# For Sales Reps & SDRs
Source: https://docs.salesfinity.ai/mcp/use-cases/reps
Use cases for individual sales reps and SDRs using Claude x Salesfinity MCP
Prompts and workflows for individual contributors who want to improve their performance, optimize their daily workflow, and close more meetings.
## Self-Coaching & Improvement
### Personal Performance Check-In
Track your own trends without waiting for your manager.
**Prompt:** "Pull my call data for the last 2 weeks. How am I trending on dials, connects, and meetings compared to the prior 2 weeks? Where's my biggest drop-off?"
**What Claude does:** Pulls your call logs and analytics for both periods, calculates the delta on each metric, and identifies your weakest conversion point.
**Tools used:** `get_call_logs`, `get_analytics_overview`
***
### My Best Calls Analysis
Build a personal playbook from your own top performances.
**Prompt:** "Pull my top 5 scored calls this month. What did I do consistently in those calls that I should repeat? Give me a personal playbook based on what's already working for me."
**Tools used:** `get_scored_calls`
***
### My Worst Calls Debrief
Turn bad calls into learning opportunities.
**Prompt:** "Pull my lowest-scored calls this week. What went wrong? Was it my opener, my discovery, my ask, or something else? Give me specific fixes."
**Tools used:** `get_scored_calls`
***
### Conversion Rate Self-Audit
Find the one thing that will make the biggest impact on your numbers.
**Prompt:** "What's my conversation-to-meeting rate compared to the team average? If I'm below average, what's the single biggest thing I should change based on my call patterns?"
**Tools used:** `get_sdr_performance`, `get_scored_calls`
***
### Personal Objection Tracker
Track which objections trip you up and get better responses.
**Prompt:** "Across my conversations this month, what objections am I hearing most? How am I handling them based on call scores? Suggest better responses for my top 3 objections."
**Tools used:** `get_scored_calls`, `get_call_logs`
***
### Talk Track Refinement
Get a data-driven rewrite of your talk track.
**Prompt:** "Pull my last 10 conversations. Am I asking enough discovery questions? Am I pitching too early? Give me a revised talk track outline based on what the data shows."
**Tools used:** `get_scored_calls`, `get_call_logs`
***
## Daily Workflow & Prioritization
### Morning Dial Plan
Start every day with an optimized schedule.
**Prompt:** "Based on my contact lists and historical connect rates by time of day, what should my dialing schedule look like today to maximize conversations?"
**Tools used:** `get_contact_lists`, `get_call_logs`, `get_list_performance`
***
### Follow-Up Priority Queue
Never miss a hot follow-up again.
**Prompt:** "Pull my follow-up tasks. Rank them by urgency and likelihood to convert. Which 5 follow-ups should I hit first thing today?"
**Tools used:** `get_follow_ups`, `get_scored_calls`
***
### Snoozed Contact Check
Stay on top of contacts coming back from snooze.
**Prompt:** "Which of my snoozed contacts are coming due this week? Give me context on why they were snoozed and a suggested re-engagement approach for each."
**Tools used:** `get_snoozed_contacts`
***
### List Selection Helper
Dial the right list at the right time.
**Prompt:** "I have multiple lists assigned to me. Based on connect rates and meeting conversion, which list should I prioritize dialing today?"
**Tools used:** `get_contact_lists`, `get_list_performance`
***
### End-of-Day Recap
Close out every day knowing exactly where you stand.
**Prompt:** "Pull today's numbers for me. How many dials, conversations, meetings? How does today compare to my daily average this month? What should I do differently tomorrow?"
**Tools used:** `get_call_logs`, `get_analytics_overview`
***
## Prospecting & Preparation
### Pre-Call Research Brief
Walk into every dial session fully prepared.
**Prompt:** "I'm about to dial into \[List Name]. Pull the list details and any past call history. What personas am I calling? What's the historical connect rate? Any patterns I should know about?"
**Tools used:** `get_contact_list_by_id`, `get_list_performance`, `get_call_logs`
***
### Title-Specific Talk Track
Customize your pitch by persona.
**Prompt:** "Based on team-wide data, which titles/personas convert best on my lists? Give me a customized opener and value prop for each of the top 3 converting titles."
**Tools used:** `get_call_logs`, `get_scored_calls`, `get_list_performance`
***
### Gatekeeper Strategy
Get past gatekeepers more consistently.
**Prompt:** "Pull calls where the disposition was gatekeeper across my recent activity. How often am I getting past them? What approaches are working for other reps on the team?"
**Tools used:** `get_call_logs`, `get_dispositions`, `get_scored_calls`
***
### Win Pattern Cloning
Copy what's working from your best-performing teammates.
**Prompt:** "Who on my team has the highest meeting conversion rate on the same lists I'm dialing? Pull their scored calls and extract the patterns I should copy."
**Tools used:** `get_sdr_performance`, `get_scored_calls`, `get_list_performance`
# For Everyone
Source: https://docs.salesfinity.ai/mcp/use-cases/shared
Reporting, analytics, and advanced use cases for the entire sales team
Prompts and workflows useful to anyone on the team — reps, managers, and ops alike.
## Reporting & Communication
### Custom Report Builder
Generate any report in seconds, formatted for sharing.
**Prompt:** "Generate a report on \[specific metric] for \[specific time period] for \[specific reps or team]. Format it as a clean summary I can share in Slack."
**What Claude does:** Pulls the relevant data from analytics, call logs, or SDR performance, formats it with clear headers and metrics, and presents it ready to copy-paste.
**Tools used:** `get_analytics_overview`, `get_sdr_performance`, `get_call_logs`
***
### QBR Data Pack
Pull everything you need for a quarterly business review in one prompt.
**Prompt:** "Pull all key outbound metrics for the quarter: total dials, conversations, meetings, conversion rates, rep rankings, list performance, and trend analysis. Organize it for a quarterly business review."
**Tools used:** `get_analytics_overview`, `get_sdr_performance`, `get_list_performance`
***
### Client-Facing Outbound Report
For teams that manage outbound for clients, generate a clean performance report.
**Prompt:** "Generate a white-labeled performance report showing ROI on parallel dialing: total dials, conversations, meetings booked, conversion rates at each stage, and comparison to industry benchmarks."
**Tools used:** `get_analytics_overview`, `get_list_performance`, `get_sdr_performance`
***
### A/B Test Analysis
Measure which scripts, approaches, or strategies actually win.
**Prompt:** "We ran two different opener scripts this month across different reps. Pull the scored calls for each group and compare meeting conversion rates. Which script won and by how much?"
**Tools used:** `get_scored_calls`, `get_sdr_performance`, `get_call_logs`
***
### Disposition Trend Alerting
Catch data quality or timing issues before they tank your numbers.
**Prompt:** "Monitor disposition trends over the last 4 weeks. If voicemail rates are climbing or conversation rates are dropping, flag it early so we can investigate data quality or timing issues."
**Tools used:** `get_call_logs`, `get_dispositions`, `get_analytics_overview`
***
## Advanced & Power User
### AI Call Scoring Calibration
Validate whether your AI call scores actually predict real outcomes.
**Prompt:** "Pull 20 scored calls across the spectrum (high, medium, low scores). Review the scoring criteria against actual outcomes (did the call lead to a meeting?). Are our AI scores predictive of real results?"
**Tools used:** `get_scored_calls`, `get_call_logs`
***
### Multi-Variable Analysis
Identify which combination of factors most predicts a booked meeting.
**Prompt:** "Analyze which combination of factors most predicts a booked meeting: time of day, day of week, list source, rep, persona title, conversation duration, number of discovery questions asked. Rank the variables by impact."
**Tools used:** `get_call_logs`, `get_scored_calls`, `get_list_performance`, `get_sdr_performance`
***
### Competitive Intel from Conversations
Mine your call data for competitive intelligence.
**Prompt:** "Across scored calls this month, are prospects mentioning competitors? Which competitors come up most? What are the common switching triggers or objections related to competitors?"
**Tools used:** `get_scored_calls`
***
### ICP Refinement from Data
Let your actual conversion data tell you who to target.
**Prompt:** "Based on which titles, industries, and company sizes are actually converting into meetings, does our current ICP match reality? Where should we adjust our targeting?"
**Tools used:** `get_call_logs`, `get_list_performance`, `get_scored_calls`, `get_contact_lists`
***
### Coaching ROI Measurement
Prove that your coaching investment is paying off.
**Prompt:** "I coached \[Rep Name] on their discovery questions 3 weeks ago. Pull their before-and-after data. Did their conversation-to-meeting rate improve? By how much?"
**Tools used:** `get_sdr_performance`, `get_scored_calls`, `get_call_logs`
# Accounts from Signals
Source: https://docs.salesfinity.ai/tutorials/accounts-from-signals
Build account lists from what companies are doing right now: hiring for a role, running or missing a technology, showing buying intent, or matching a size and department profile.
**You will end up with:** a company table where every row carries the signal that put it there,
such as an open-roles count, so the reason to call is on the list itself.
**Before you start:** signals come from providers your team has enabled. Hiring and intent
signals from live job postings, technology and department-size profiles, and similarity are each
a separate source. If a signal is not available to your team, the companion says so instead of
guessing.
## The signals Salesfinity supports
| Signal | What you say | What you get |
| ---------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Hiring for a role | "companies hiring SDRs right now" | Companies with open postings for that role, with an Open Roles count per row |
| Hiring with a stack gap | "hiring SDRs but no dialer yet" | Companies with the problem and no incumbent to displace |
| Buying intent | "companies working on SOC 2 compliance", "going through post-merger integration" | Companies whose job postings talk about that initiative |
| Technology in use | "companies using Snowflake", "Orum customers" | Companies verified to run that product |
| Department size | "at least 5 SDRs", "10+ account executives" | Companies that clear the headcount bar in that department |
| Firmographic profile | industry, size band, plus any of the above in one ask | Companies qualified on all of it in one pass |
| Person recently changed jobs | "VPs of Sales new in seat in the last 90 days" | People, not companies; see [Track job changes](/tutorials/track-job-changes) |
## Steps
Sizing a signal search is free. Ask how many match first, then tighten or loosen until the
pool is the size you want to work.
```text theme={null}
How many US companies with 100 to 2,000 employees are hiring SDRs right now?
```
The companion answers with the pool size and no rows, so refining costs nothing.
Intent topics and technologies resolve against a verified taxonomy. If you are not sure your
phrase is a real signal, ask.
```text theme={null}
Can you filter companies on "revenue operations transformation"? What similar signals exist?
```
```text theme={null}
Build a list of companies in the US hiring SDRs in the last 60 days, 100 to 2,000
employees, that don't already use a dialer.
```
Each row lands with its Open Roles count. Recency defaults to 90 days when you do not name a
window.
Profile searches qualify size, department headcount, technology, industry, and hiring in a
single pass, so say everything at once.
```text theme={null}
Companies in financial services with 200 to 5,000 employees, at least 5 SDRs, using
Salesforce, that have hired sales reps in the last 3 months.
```
For a named account, ask what they are hiring for right now. This is pre-call research, not
list building, and each posting read has a small per-row cost.
```text theme={null}
Is Acme hiring right now, and for which roles?
```
## What happens behind the scenes
Hiring and intent searches read live job postings and filter on job title and posting recency
natively, so "hiring SDRs in the last 30 days" is a real filter and not a semantic guess. The
stack-gap option excludes companies already running a category of product. Technology and
department-size searches qualify companies against verified data in one call. Every signal
search appends to the current company list unless you ask for a new one.
## Prompts to try
```text theme={null}
Companies going through a merger or acquisition, 500+ employees, in the US.
```
```text theme={null}
Who is hiring data engineers in Austin right now? Size it first.
```
```text theme={null}
Companies using HubSpot with at least 10 AEs, then find their VP of Sales.
```
```text theme={null}
Which of these signals can I filter on: "cold calling", "outbound sales", "pipeline generation"?
```
## Next
Signals count toward the market pillar of the Account Score.
Reach the people behind the signal.
# Build an Account List
Source: https://docs.salesfinity.ai/tutorials/build-account-list
Describe your ideal company in plain language and get a reviewed table of accounts on the canvas, then grow, trim, and enrich it.
**You will end up with:** a named table of companies on the canvas, each with website, industry,
size, and location, ready to score or to find contacts at.
**Before you start:** nothing beyond an open companion. A connected data provider widens the
search, but Salesfinity's own data network is always there.
## Steps
Say what a great account looks like, in the words you would use to a new hire. Include
industry, size, and geography when they matter. Do not add qualifiers you do not care about;
the companion will not invent any either.
```text theme={null}
Build me a list of 50 B2B SaaS companies in the United States with 50 to 500 employees
that sell to sales teams.
```
The companion plans the search, runs it, and lands the results on the canvas as a table named
after your ask, something like "US B2B SaaS, sales tools".
The reply tells you how many rows look on target. The companion keeps every row it found so
you can judge for yourself; nothing is deleted without you asking. If the count is low, the
companion already refined and searched again. If it is still low, sharpen the description.
```text theme={null}
Too broad. Only companies whose product is used by SDR or inside sales teams.
```
The working list is remembered for the conversation, so "more" means more of the same.
```text theme={null}
Find 25 more like these.
```
```text theme={null}
Remove everyone outside the US and anyone under 50 employees.
```
Trimming uses the columns the table already has. It is free and instant.
Firmographics for companies are free. Ask for what will help you decide.
```text theme={null}
Add industry, headcount, and HQ location to every row.
```
If you already have company names, paste them. The companion keys each one by domain and
puts them on the canvas as a table you can work exactly like a sourced one.
```text theme={null}
Put these on a list: Gong, Outreach, Salesloft, Apollo, Orum, Nooks.
```
## What happens behind the scenes
The companion hands your request to its sourcing specialist. The specialist parses the ask into
a structured plan, decides whether you want companies or people, and picks the sources. For a
"build me a list" request it sources, checks how many rows are on target, and automatically
retries with new, non-overlapping angles until the list is good. Results append to the working
list; a new table is started only when you ask for a materially different audience.
## Prompts to try
```text theme={null}
Find 30 mid-market logistics companies in Texas that run their own fleet.
```
```text theme={null}
List fintech startups in the UK that raised a Series A or B in the last 18 months.
```
```text theme={null}
Start a new list: hospitals and health systems in the Midwest with 1,000+ employees.
```
```text theme={null}
Name this list "Q4 Texas logistics".
```
## Next
Rank the list by your own history and ICP fit.
Turn accounts into people you can dial.
# Find Contacts at Target Accounts
Source: https://docs.salesfinity.ai/tutorials/find-contacts
Turn a company table into a people table with three to five threads per account, pick the buying committee, enrich phone numbers with approval, and export a dialable list.
**You will end up with:** a people table linked to your accounts, ranked, trimmed to a buying
committee if you want, with phone numbers where found, exported as a contact list in the dialer.
**Before you start:** a company table on the canvas. Finding people costs credits per row found.
Phone and email enrichment cost one credit per row where a number or address is found, and
always wait for your approval.
## Steps
Give three to five titles spanning manager, director, and VP so each account is
multi-threaded, not a single name.
```text theme={null}
Find the SDR Manager, Director of Sales Development, VP of Sales, and CRO at each of
these accounts.
```
The companion runs the most precise source over every account first, then cheaper sources
only on the accounts still short, so credits are not spent twice on the same company. The
people table lands on the canvas linked to the company table.
People scores appear moments after the list lands. To keep the strongest few per account:
```text theme={null}
Keep the top 3 per account as the buying committee.
```
Everyone stays on the list with a Committee Rank column. Say "keep only the committee" to
trim the rest, and confirm when asked.
```text theme={null}
Add the account's open deals and last activity beside each contact.
```
Account columns copy down onto people as "Account: Open Deals" and so on, and re-run when
you enrich the company table further.
```text theme={null}
Get mobile numbers for these contacts.
```
The companion states the row count and shows an approval card. Only rows where a number is
found are charged, and landlines and low-grade numbers are discarded before they reach the
list. Email works the same way; ask for "work emails" or "personal emails".
```text theme={null}
Export this as a contact list called "Committee, Texas logistics".
```
The list appears in the dialer immediately. Boss Mode can validate the numbers from there.
## Existing contacts vs new contacts
* "**Pull the contacts we've spoken with** at these accounts" reads your CRM's associated
contacts, with CRM IDs kept. Add "only the ones with logged activity" to keep it to real
relationships.
* "**Find** the VP of Sales at these accounts" discovers new people from the data network.
The companion routes each phrasing to the right place, so say which one you mean.
## What happens behind the scenes
Contacts found from a company table carry a link to their account row. That link powers the
committee selection per account, the account-column carry-down, and the account-strength part
of each person's score. Export creates a normal Salesfinity contact list, the same kind a CSV
upload creates, so everything in the dialer, from Boss Mode to CRM logging, works on it.
## Prompts to try
```text theme={null}
Find 2 people in RevOps and 2 in Sales leadership at each account, US only.
```
```text theme={null}
Who at these companies have we already called? Add the last disposition.
```
```text theme={null}
Why is Jane Doe ranked first at Acme?
```
```text theme={null}
Add work emails for the committee only.
```
## Next
Validate the numbers before the first dial.
What is screened out and what is charged.
# Build a Look-Alike Account List
Source: https://docs.salesfinity.ai/tutorials/lookalike-accounts
Seed a search with your best customers, a pasted set of companies, or a single competitor, and get a list of companies that look like them.
**You will end up with:** a company table of look-alikes, appended to or separate from the
seed list, ready to score and to find contacts at.
**Before you start:** similarity search needs a look-alike data provider connected under
**Settings → Connections & API → Data Providers**, such as Ocean. Without one the companion
falls back to semantic discovery and says so. Seeding from closed-won needs a CRM connected.
## Three ways to seed
| Seed | Prompt shape |
| ---------------------------------------------- | ------------------------------------------------------- |
| Your closed-won accounts | "companies like our closed-won accounts from last year" |
| A list you paste or already have on the canvas | "more companies like the ones on this list" |
| One named company | "competitors of Stripe", "alternatives to Gong" |
Every one of those phrasings is the same ask, similarity, and routes to the same search.
## Steps
```text theme={null}
Load our closed-won accounts from the last 18 months.
```
Or paste names:
```text theme={null}
Put these on a list: Rippling, Deel, Gusto, Justworks.
```
```text theme={null}
Find 100 companies that look like these.
```
The whole seed table's domains are used at once. Add constraints when you want them:
```text theme={null}
Find 100 companies like these in the US, 51 to 500 employees, in SaaS.
```
```text theme={null}
Who competes with Orum? Build a list.
```
The companion resolves the company to its real website first, so a same-named business is
not substituted.
```text theme={null}
Remove any that are already in HubSpot, and any that are on the seed list.
```
Both checks are free. Overlap with another table and CRM matching use the columns and IDs
already on the rows.
```text theme={null}
Score these against our "Outbound dialer ICP" profile, then find the VP of Sales and
Director of Sales Development at the top 30.
```
## Look-alike people
The same idea works for people. Seed with a few of your best contacts and ask for more like
them; the companion returns people with similar roles and backgrounds for you to review.
```text theme={null}
Find people like our top 10 champions on this list.
```
## What happens behind the scenes
Similarity search sends the seed domains to the provider, which returns companies close to
them in industry, size, and description. It is discovery only: it never returns phones or
emails, so it is always followed by a contact search and, if you want, enrichment. The
resulting rows carry firmographics so you can trim by size or geography before spending on
contacts.
## Prompts to try
```text theme={null}
Alternatives to Salesloft with under 200 employees, then score them.
```
```text theme={null}
Companies like the 20 accounts we booked meetings with last quarter.
```
```text theme={null}
Start a new list: look-alikes of our enterprise wins only, excluding anything already in Salesforce.
```
## Next
Rank the look-alikes before you spend on contacts.
Multi-thread the ones that score well.
# Tutorials
Source: https://docs.salesfinity.ai/tutorials/overview
Step-by-step tutorials for building, scoring, verifying, and working account and contact lists with the AI Companion, with prompts you can paste as they are.
These tutorials teach one outcome each, in the order a rep or a manager would actually do the
work. Every tutorial gives you prompts to paste into the AI Companion word for word. You do not
need to know any tool names. Describe what you want and the companion picks the right path.
## Before your first list
Open the companion panel from anywhere in the dashboard. Lists you build appear on the
**canvas** next to the chat as tables, one for companies and one for people, and stay there
between sessions.
The companion only uses sources your team has connected under **Settings → Connections &
API**. A CRM (HubSpot or Salesforce) unlocks the CRM tutorials. Data providers you connect
under Data Providers widen where new accounts and contacts come from; Salesfinity's own data
network is always available.
Finding companies and people, adding research columns, and enriching phone numbers or
emails spend credits per row. The companion tells you the row count and shows an approval
card before anything paid runs. Scoring, CRM matching, CRM columns, and filtering an existing
table are free.
## How a list gets built
Every list-building conversation follows the same loop, whether you ask for it or not:
1. **Plan.** The companion works out whether you want companies or people, which titles and
locations you named, and which sources fit.
2. **Source.** It runs the search, keeps everything it found for you to review, and reports how
many look on target. If too few do, it refines the query and searches again rather than
handing you a thin list.
3. **Enrich.** It adds the free columns that help you judge the list, and offers the paid ones.
4. **Score.** Company lists can be scored against your own history and ICP. People lists rank
themselves moments after they land.
5. **Export.** When you say so, a people list becomes a contact list in the dialer.
Say "find 10 more" to grow the same list. Say "start a new list" when the audience changes.
## The tutorials
Describe your ideal company and get a table of accounts to work.
Companies that are hiring for a role, running a technology, or showing buying intent.
Rank a list by your own history, CRM relationship, and ICP fit, then tune the weights.
Mark which rows exist in HubSpot or Salesforce and bring the owner and activity alongside.
Load closed-lost deals with their loss reasons, find the right people, and dial.
Find who is new in seat, re-check your contacts, and set a routine that watches for moves.
Multi-thread every account, pick the buying committee, enrich phones, and export to the dialer.
Turn your best customers into a list of companies that look just like them.
Ready-made skills for the most common list-building plays, written so you can paste them
straight into the Skills tab.
## Three habits that make every list better
* **Name the level, not just the role.** "SDR managers, directors of sales development, and VPs
of sales" gets you three threads per account. "SDR manager" gets you one.
* **Say where.** Location is a real filter for structured sources. Leave it out and you get the
world.
* **Ask why before you trust a number.** Every score and every research column keeps its
evidence. "Why is Acme 84?" gets you the arithmetic, not a guess.
# Re-engage Closed-Lost Accounts
Source: https://docs.salesfinity.ai/tutorials/reengage-closed-lost
Load closed-lost deals from HubSpot or Salesforce with their loss reasons, find the decision makers at each account, and turn them into a dialable list.
**You will end up with:** a company table of closed-lost accounts with deal stage, amount, close
date, owner, and loss reason on every row; a people table of decision makers at those accounts
with the deal context beside each person; and, when you approve it, phone numbers.
**Before you start:** HubSpot or Salesforce connected. Loading from the CRM and adding CRM
columns are free. Finding new contacts and enriching phones cost credits.
## Steps
```text theme={null}
Load our closed-lost deals from the last 12 months onto a list.
```
The companion loads the accounts behind those deals with the CRM IDs preserved, and the deal
columns arrive automatically: Deal Stage, Deal Amount, Close Date, Deal Owner, deal name,
and Loss Reason. A first load returns about a hundred; say "pull them all" for the full set.
Any deal property can be a filter, in your own words.
```text theme={null}
Only deals over $20,000 that closed lost after January 1st.
```
```text theme={null}
Only the ones lost to a competitor. Group them by loss reason.
```
Two different asks. The people you already spoke with live in the CRM; new decision makers
are net-new discovery.
```text theme={null}
Pull the contacts we already talked to at these accounts.
```
```text theme={null}
Find the VP of Sales, Director of Sales Development, and SDR Manager at each of these.
```
Contacts built from a company table stay linked to their account, so the account columns
can be copied down onto each person as "Account: Loss Reason", "Account: Deal Amount", and
so on.
```text theme={null}
Bring the loss reason and deal amount onto each contact.
```
Now a rep sees why the deal was lost before dialing the person.
The companion stops before spending credits on phones and states the row count.
```text theme={null}
Enrich phone numbers for these 60 contacts.
```
Approve the card, then:
```text theme={null}
Export this as a contact list called "Closed-lost re-engage Q4".
```
## What happens behind the scenes
A deal-grain load resolves accounts through the deal or opportunity records and attaches the
deal columns in the same pass, so there is no separate "loss reason" research step. Contacts
found at those accounts are linked to their account row, which is what lets account columns be
carried onto people. The companion refuses to auto-enrich phones; every paid step waits for
your approval.
## Prompts to try
```text theme={null}
Closed-won accounts from last year, then find companies like them.
```
```text theme={null}
Which closed-lost accounts had a champion who has since changed jobs?
```
```text theme={null}
Load my own accounts in Salesforce that haven't been touched in 90 days.
```
```text theme={null}
Load the accounts on the "Enterprise Q3" HubSpot list and add their open deals.
```
## Next
Do the same starting from closed-won.
Save this as a monthly skill.
# Score Your Accounts
Source: https://docs.salesfinity.ai/tutorials/score-accounts
Rank a company list with the transparent Account Score, build a Scoring Profile that captures what a great account looks like for your team, and tune the weights.
**You will end up with:** a Score column on your company table, a per-account breakdown you can
open, and a saved Scoring Profile the whole team scores against.
**Before you start:** a company table on the canvas. Scoring is free. Your own call history and
CRM make it sharper, but neither is required.
## What the score is made of
The Account Score is a number from 0 to 100 assembled from four pillars, and every account keeps
the per-signal arithmetic behind its number:
| Pillar | Signals |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Engagement | Your team's own history with the account: meetings booked, real conversations, callbacks, and negatives such as "not interested" or do-not-call |
| CRM | Open deals, past wins, recent losses |
| Fit | How well the account matches the list's ICP criteria, or your Scoring Profile |
| Market | Signals already on the rows: hiring, growth, funding |
An account with too little data shows **no score** rather than an invented one. Scores can go
down for good reasons, such as a recent "not interested", and the breakdown says so.
## Steps
```text theme={null}
Score these accounts and tell me which to work first.
```
The reply says how many were scored, how many had calling history or CRM records, and the
top few. Scores appear on the canvas.
Never guess at a score. Ask, and the companion reads the stored breakdown.
```text theme={null}
Why is Acme scored 84?
```
Weights can be moved per signal or per pillar. The companion re-scores and reports what moved.
```text theme={null}
Weight meetings booked higher and care less about funding.
```
A Scoring Profile is your team's saved definition of a great account: weighted criteria,
some marked must-have. The companion drafts one for you from what it knows about your
business and your closed-won accounts, then asks a few questions before saving.
```text theme={null}
Draft a scoring profile for us. We sell a parallel dialer to outbound sales teams.
```
Expect a draft with labels and weights and two to four follow-ups such as "should an SDR
team of 5+ be a must-have?" Answer them and the profile is created.
```text theme={null}
Score this list against our "Outbound dialer ICP" profile.
```
Criteria that list data can answer run free every time. Criteria that need per-account
research are reported as pending with a count, and only run when you approve.
```text theme={null}
Make "uses a parallel dialer" a must-have and add a check for "hiring SDRs where the job
description mentions cold calling".
```
## People scoring
People lists rank themselves moments after they land, using persona fit, each person's own
calling history with your team, the strength of the account around them, and CRM context. Ask
for a fresh ranking, a reason, or the buying committee:
```text theme={null}
Re-score these people and keep the top 3 per account.
```
A persona profile works like an account profile for people: function, seniority, skills,
tenure, and disqualifiers such as "no interns".
## Prompts to try
```text theme={null}
Which 20 of these accounts should we call this week?
```
```text theme={null}
What changed since the last time we scored this list?
```
```text theme={null}
List our scoring profiles.
```
```text theme={null}
Why did Globex drop?
```
## Next
Work the top of the ranking first.
Save the weekly re-score as a skill.
# Track Job Changes
Source: https://docs.salesfinity.ai/tutorials/track-job-changes
Find people who are new in seat, re-check whether your existing contacts have moved, and set up a routine that watches a list for job changes on a schedule.
**You will end up with:** a people list filtered to recent movers, your existing contacts
re-checked against their current employer with any conflicts flagged for review, and a routine
that repeats the check and reports to your inbox.
**Before you start:** contacts need a LinkedIn URL for a re-check. Routines must be enabled for
your team; if the Routines page says it is not, ask your account manager.
## Two different questions
* **Who is new in seat?** A search filter. "VPs of Sales who started in the last 90 days" is a
people search with a job-change window.
* **Did my contacts move?** A refresh of people you already have. Salesfinity re-reads each
contact's current title and company and flags the ones that no longer match, instead of
silently overwriting your data.
## Steps
```text theme={null}
Find VPs of Sales and Heads of Revenue at US SaaS companies with 200 to 2,000 employees who
started their role in the last 90 days.
```
New-in-seat is a native filter, so the list is built from tenure data rather than guessed.
Load the list first, then ask for the refresh. It costs no phone or email credits.
```text theme={null}
Load the contacts from our "Enterprise champions" list.
```
```text theme={null}
Re-check whether any of these people have changed jobs.
```
The companion reports something like "4 people may have changed jobs — review?" A new
employer that contradicts what you have is **flagged, not applied**, so you decide whether
to update the contact, keep it, or remove it.
```text theme={null}
For the 4 who moved: update their title and company, and add a column with their previous
company.
```
```text theme={null}
Find who replaced them at the previous company.
```
The second prompt is a normal people search scoped to the old employer, which is how you
keep the account while following the champion.
Open **Routines** and create one. Give it a name and paste the instruction below as the
prompt, choose a weekly schedule in your timezone, and set the write mode to
**require approval** so nothing changes without you. Run the test run once, answer anything
it asks, and activate it.
```text theme={null}
Load the contacts from our "Enterprise champions" list and re-check whether any of them
have changed jobs. For anyone flagged, list their name, old company, new company, and new
title. Do not change any records. Send the summary to me.
```
Each week the run lands in your routine inbox. If you later allow writes, the routine can
also update the contacts and add the movers to a "Followed champions" list.
## What happens behind the scenes
A re-check reads each contact's current profile through Salesfinity's data network, keyed by
LinkedIn URL. A value that agrees with the contact's known employer is applied; a value that
contradicts it is held back and reported. A routine runs the same conversation on a schedule
with no one at the keyboard; its write mode decides whether the run may change records, must
ask you first, or is read-only.
## Prompts to try
```text theme={null}
Which of our closed-lost champions have changed jobs since the deal closed?
```
```text theme={null}
Directors of Demand Gen new in seat in the last 60 days at companies using HubSpot.
```
```text theme={null}
Re-check only the contacts on this list that are missing a current title.
```
## Next
Reach the movers at their new company.
Schedules, event triggers, and write modes.
# Check Who Is Already in Your CRM
Source: https://docs.salesfinity.ai/tutorials/verify-crm
Match every row on a list against HubSpot or Salesforce, then bring the owner, last activity, open deals, and any CRM field alongside so nobody calls an account a teammate owns.
**You will end up with:** a CRM Match column on the table, plus any CRM columns you asked for,
such as owner or last activity, filled per row from your real CRM.
**Before you start:** HubSpot or Salesforce connected. Matching and CRM columns are free.
## Steps
```text theme={null}
Check which of these are already in HubSpot.
```
The companion matches every row and fills the CRM Match column. For contacts it tries
LinkedIn URL, email, name at the company, and email domain, in that order. For accounts it
matches on website. Name the CRM when you have two connected; otherwise both are searched
and a fault in one can muddy the answer from the other.
A row can be matched, not found, or **could not verify**. The last one means a lookup failed,
for example a rate limit, and is labeled with the reason. Salesfinity never reads a failed
lookup as "not in the CRM".
```text theme={null}
Retry the ones that couldn't be verified.
```
Any table with CRM matches can carry CRM columns. These are read per row and are free.
```text theme={null}
Add the HubSpot owner, last activity, and open deals for each account.
```
Available account columns: owner, last activity, next activity, pipeline, deal stage, open
deals, loss reason, tasks, recent engagement, and notes. Contact columns: match, owner,
engagement, and an email summary.
Any standard or custom property in your CRM can become a column. Say the field in your own
words and the companion finds the matching property in your portal's schema.
```text theme={null}
Add a column with the "main competitor" field from Salesforce.
```
If the property does not exist, the companion says so rather than inventing one.
```text theme={null}
Remove the ones owned by someone else on the team, and keep the rest.
```
```text theme={null}
Show me only the accounts not in the CRM.
```
## What happens behind the scenes
Matching runs against the CRM's own search, one row at a time, with the lookups that fail
recorded separately from the lookups that found nothing. That distinction is what lets the
companion say "could not verify" instead of "not there". CRM columns come from the record's
associations and properties, so what you see is exactly what is in the portal at that moment.
## Prompts to try
```text theme={null}
Are any of these people already in Salesforce? Name the owner if so.
```
```text theme={null}
Which of these accounts have an open deal, and at what stage?
```
```text theme={null}
Add "renewal date" and "contract value" from HubSpot to each row.
```
```text theme={null}
Which of these contacts have we emailed in the last 90 days?
```
## Next
Start from the CRM instead of matching to it.
What happens after you dial a CRM-matched contact.
# Winning Skills
Source: https://docs.salesfinity.ai/tutorials/winning-skills
Ready-made skills for the list-building plays that work, written in the format the Skills tab accepts, with the prompt that triggers each one.
A skill turns one of these tutorials into something the whole team runs the same way every
time. Each skill below is complete: copy the block into **Skills → New Skill** (name,
description, and content), or say "save this as a skill" after you have run the play once by
hand and let the companion write it for you. See [Creating skills](/ai-companion/creating-skills)
for the structure and the guided flow.
Every skill here uses only free steps by default and stops before anything that spends credits.
Edit the guardrails if your team wants a skill to enrich without asking.
## Weekly territory refresh
Trigger: "run my weekly refresh", "what should I work this week".
```markdown theme={null}
---
name: Weekly territory refresh
description: Loads the rep's own CRM accounts, scores them, and returns
the 20 to work this week with the reason for each. Use for "weekly
refresh", "what should I work this week", or Monday planning.
---
## Goal
A ranked list of 20 accounts from the rep's own territory to work this
week, each with its score reason and last activity.
## Inputs
- CRM — default: the team's connected CRM
- how many — default: 20
## Steps
1. Load the rep's own accounts from the CRM (their book, modified in the
last 180 days).
2. Add the owner, last activity, next activity, and open deals columns.
3. Score the accounts against the team's default scoring profile.
4. Take the top N by score. If fewer than N have a score, say how many
were skipped for lack of data and stop.
## Output
A table: Account, Score, Top reason, Last activity, Open deals. Then one
line naming the three accounts that moved most since last week.
## Guardrails
Read-only. Never enrich phones or emails. Never delete rows.
```
## Closed-lost re-engagement
Trigger: "re-engage closed-lost", "who did we lose last year that we should call".
```markdown theme={null}
---
name: Closed-lost re-engagement
description: Builds a dialable list of decision makers at accounts lost
in a time window, with the loss reason beside each person. Use for
"re-engage closed-lost" or "win-back list".
---
## Goal
A people list of 2 to 3 decision makers per closed-lost account, each
carrying the deal's loss reason, amount, and close date, ready to export.
## Inputs
- time window — default: last 12 months
- minimum deal amount — default: none
- titles — default: VP of Sales, Director of Sales Development, SDR Manager
## Steps
1. Load closed-lost deals from the CRM for the window. Apply the minimum
amount if given.
2. Remove accounts that have an open deal today.
3. Find the target titles at each remaining account.
4. Bring the loss reason, deal amount, and close date onto each contact.
5. Stop and tell the user the contact count. Offer phone enrichment; do
not run it.
## Output
A people table with Account: Loss Reason, Account: Deal Amount, and
Account: Close Date columns, grouped by loss reason in the summary.
## Guardrails
Never enrich phones or emails without approval. Never change CRM records.
```
## Hiring-signal prospecting
Trigger: "who is hiring SDRs", "signal list for this week".
```markdown theme={null}
---
name: Hiring-signal prospecting
description: Finds companies hiring for a role in the last 60 days that
do not run a product in our category, sizes the pool first, and lands
a scored account list. Use for "hiring signal list" or "who's hiring
SDRs".
---
## Goal
A scored list of companies with an active hiring signal for our buyer's
team and no incumbent product, capped at a size the rep can work.
## Inputs
- role being hired — default: SDR
- product category to exclude — default: dialer software
- geography — default: United States
- headcount band — default: 100 to 2,000
- cap — default: 50
## Steps
1. Size the search for free. If the pool is under 20, widen recency to 90
days and the headcount band, then size again.
2. Run the hiring search with the stack-gap exclusion. Keep the Open Roles
count on every row.
3. Check which accounts are already in the CRM and add the owner column.
4. Remove accounts owned by someone else.
5. Score the rest.
## Output
A company table sorted by score: Company, Open Roles, Score, CRM owner.
State the pool size you found and how many were removed as owned.
## Guardrails
Do not find contacts or enrich anything. Stop at the account list.
```
## Pre-call account brief
Trigger: "brief me on Acme", "prep me for my call with Acme".
```markdown theme={null}
---
name: Pre-call account brief
description: One-screen briefing on a company before a call, from our
own data first. Use for "brief me on", "prep me for my call with", or
"what do we know about".
---
## Goal
A briefing a rep can read in 60 seconds before dialing.
## Inputs
- company name — required
## Steps
1. Read the CRM record and its associated contacts, open deals, and
recent activity.
2. Read our call history with the account: dials, connects, dispositions,
and the last three call summaries.
3. Read any battle card that names a competitor on the record.
4. Check the account's current hiring activity.
5. Only if steps 1 to 3 return nothing, offer external research; do not
run it unasked.
## Output
Sections in this order: Relationship (owner, stage, last touch), People
we know (name, title, last disposition), Open deals, Recent calls (three
lines), Hiring right now, Suggested opener (one sentence).
## Guardrails
Internal data first. Never create or update any record.
```
## New-in-seat outreach
Trigger: "who's new in seat", "new leaders at our target accounts".
```markdown theme={null}
---
name: New-in-seat outreach
description: Finds leaders who started a target role in the last 90 days
at companies matching our ICP, with the account's CRM status beside
each. Use for "new in seat", "new leaders", or "recently hired VPs".
---
## Goal
A people list of recent movers into buying roles, ranked, with a note on
whether their new company is already a customer or an open opportunity.
## Inputs
- titles — default: VP of Sales, CRO, Head of Revenue
- job-change window — default: 90 days
- ICP filters — default: US, 200 to 2,000 employees, B2B software
## Steps
1. Find people with the titles who changed jobs within the window at
companies matching the ICP filters.
2. Check whether each person's company is in the CRM and add the account
owner and deal stage.
3. Let people scoring run, then list the top 25.
## Output
A people table: Name, New title, Company, Started, CRM status, Score.
Flag any mover whose new company is an existing customer.
## Guardrails
Never enrich phones or emails without approval.
```
## Look-alike expansion from wins
Trigger: "expand from our wins", "more companies like our customers".
```markdown theme={null}
---
name: Look-alike expansion from wins
description: Turns last year's closed-won accounts into a scored list of
look-alike companies not yet in the CRM. Use for "expand from wins" or
"companies like our customers".
---
## Goal
A scored company list of look-alikes to our best customers, excluding
anything we already have in the CRM.
## Inputs
- seed window — default: closed-won in the last 18 months
- how many — default: 100
- constraints — default: same country as the seeds
## Steps
1. Load closed-won accounts for the window.
2. Find look-alikes seeded from that whole table, with the constraints.
3. Remove any that are in the CRM or on the seed list.
4. Score the rest against the team's default scoring profile.
## Output
A company table sorted by score, with the firmographic columns, and a
one-line note on which seed accounts the top results most resemble.
## Guardrails
Stop at the account list. Do not find contacts or enrich.
```
## Making these yours
* **Add your titles.** Replace the default titles in Inputs with the ones that book meetings for
your team.
* **Name your profile.** Where a skill says "the team's default scoring profile", put your
profile's name once you have one. See [Score your accounts](/tutorials/score-accounts).
* **Put the recurring ones on a schedule.** The weekly refresh and the new-in-seat search are
natural [routines](/ai-companion/overview#routines). Attach the skill to the routine and set
the write mode to require approval.
* **Test with one account.** After saving, run the skill on a single known account and check
that every column it promises actually filled.