Connectors
/
Affinity
Live · 13 tools

Affinity Integration for AI Agents

The Affinity API is well-documented. Getting your agent to act on real users' pipelines — correctly, across multiple VC firms or sales teams, without mixing one team's deal data into another's — is the part that takes longer than expected.
Affinity
Live

CRM

Sales

Status
Live
Tools
13 pre-built
Auth
Bearer Token
Credential storage
Sandbox support

Bearer Token Auth

Per-user isolation

Relationship intelligence

Deal pipeline tools

The real problem

Why this is harder than it looks

Affinity's REST API is clean and the documentation covers both V1 and V2. A working prototype against a single workspace takes an afternoon. The complexity arrives when you try to support real users in a multi-tenant product — and like Zendesk, Affinity does not use OAuth 2.0.

Affinity authenticates via a static Bearer token — a credential generated per user in Affinity Settings under Manage Apps. That token inherits the permissions of the user who created it. In a B2B product where each of your customers operates their own Affinity workspace, you're managing a credential vault: one API key per user, each scoped to a different workspace. There's no OAuth redirect flow to standardize collection — you have to build the UI to prompt users for their key, store it encrypted, and route every API call with the right token. Enterprise accounts can also restrict which roles can generate API keys at all, meaning a user who appears fully set up may silently fail when their token lacks admin-level access. Additionally, if a user is deactivated in Affinity, their API key is automatically revoked — with no webhook or notification — and calls start returning 401s with no indication of why.

Beyond credential management, the Affinity API splits functionality across V1 and V2 — and V2 doesn't yet cover everything V1 does. An agent that needs to mix list operations (V1) with field-level updates (V2) has to handle two base URLs, two pagination styles, and two response schemas in parallel. Getting this wrong means silent data gaps or misrouted writes.

Scalekit handles credential storage, per-user token injection, and account status tracking. Your agent names a tool and passes parameters. The auth plumbing is not your problem.

Capabilities

What your agent can do with Affinity

Once connected, your agent has 13 pre-built tools covering deal pipelines, relationship intelligence, and CRM records:

  • Search and retrieve people and organizations: search Affinity's network by name, email, or domain; fetch full profiles with interaction dates and relationship scores
  • Manage deal pipelines end-to-end: create, retrieve, update, and list opportunities with stage, owner, and associated entity support
  • Log notes on any entity: attach meeting summaries, due diligence findings, or context notes to people, organizations, or opportunities simultaneously
  • Query relationship strength: retrieve interaction-based scores between team members and external contacts to surface the best warm introduction path
  • Manage list membership: add people or organizations to any Affinity list; list all available pipelines and workspace lists
Setup context

What we're building

This guide connects a deal intelligence agent to Affinity — helping investors and sales teams track pipeline, log context on deals, and surface relationship paths without leaving your product.

🤖
Example agent
Deal intelligence assistant querying pipeline opportunities, logging meeting notes, and surfacing warm intro paths on behalf of each user
🔐
Auth model
Bearer Token — each user supplies their Affinity API key. identifier = your user ID
⚙️
Scalekit account
app.scalekit.com — Client ID, Secret, Env URL
🐍
Runtime
Python 3.8+ · Node.js also available
Setup

1 Setup: One SDK, One credential

Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Affinity secrets, no user tokens, nothing belonging to your users.

pip install scalekit-sdk-python
npm install @scalekit-sdk/node
import scalekit.client import os from dotenv import load_dotenv load_dotenv() scalekit = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) actions = scalekit.actions
import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; const scalekit = new ScalekitClient( process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET ); const actions = scalekit.actions;
Already have credentials?
Connected Accounts

2 Per-User Auth: Creating connected accounts

Affinity uses Bearer Token authentication — there is no OAuth redirect flow. Each user's connected account is provisioned directly with their Affinity API key. The identifier is any unique string from your system.

In the Scalekit dashboard, go to Agent Auth → Connected Accounts for your Affinity connection and click Add account. Supply the user's identifier and their Affinity API key. Scalekit stores the credential encrypted and injects it into every API call automatically.

response = actions.get_or_create_connected_account( connection_name="affinity", identifier="user_aff_123" # your internal user ID ) connected_account = response.connected_account print(f"Status: {connected_account.status}") # Status: ACTIVE — credentials were supplied at account creation
const response = await actions.getOrCreateConnectedAccount({ connectionName: "affinity", identifier: "user_aff_123" // your internal user ID }); const connectedAccount = response.connectedAccount; console.log(`Status: ${connectedAccount.status}`); // Status: ACTIVE — credentials were supplied at account creation

This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.

Credential handling

3 Credential management

Because Affinity uses API keys rather than OAuth, there is no user-facing authorization step. Credentials are entered once in the Scalekit dashboard (or via API) per connected account. Scalekit stores them encrypted and uses them on every tool call.

Credential storage is automatic
Once a connected account is provisioned with an Affinity API key, Scalekit stores it in its encrypted vault and the account is immediately ACTIVE. Every tool call is injected with the correct Bearer token — your agent code never touches it. If the credential becomes invalid (e.g. the user is deactivated in Affinity or rotates their key), the account moves to REVOKED. Check account.status before critical operations and surface a re-credentialing prompt.
Generating an Affinity API key
In Affinity, go to Settings → Manage Apps and click Generate an API key. The key is shown only once — copy it immediately. The key inherits the permissions of the user who generated it; use an account with the appropriate role for the operations your agent will perform. Enterprise accounts may restrict key generation to Admins only.
Calling Affinity

4 Calling Affinity: What your agent writes

With the connected account active, your agent calls Affinity actions using execute_tool. Name the tool, pass parameters. Scalekit handles credential injection and response parsing.

Search for a person

Search Affinity's network by name or email. Use with_interaction_dates to include recency signals — useful for surfacing warm vs. cold contacts.

result = actions.execute_tool( identifier="user_aff_123", tool_name="affinity_search_persons", tool_input={ "term": "Jane Smith", "with_interaction_dates": True, "page_size": 10 } ) # Returns: list of person records with id, name, emails, organization memberships, interaction dates
const result = await actions.executeTool({ identifier: "user_aff_123", toolName: "affinity_search_persons", toolInput: { "term": "Jane Smith", "with_interaction_dates": true, "page_size": 10 } }); // Returns: list of person records with id, name, emails, organization memberships, interaction dates

List pipeline opportunities

Retrieve deals from a specific pipeline list. Use list_id to scope to a particular pipeline — get list IDs first with affinity_list_lists.

result = actions.execute_tool( identifier="user_aff_123", tool_name="affinity_list_opportunities", tool_input={ "list_id": 12058, "page_size": 25 } ) # Returns: paginated deal records with stage, value, associated persons and organizations
const result = await actions.executeTool({ identifier: "user_aff_123", toolName: "affinity_list_opportunities", toolInput: { "list_id": 12058, "page_size": 25 } }); // Returns: paginated deal records with stage, value, associated persons and organizations

Create a note on a deal

Log a note directly on an opportunity — and optionally attach it to the associated person and organization in the same call. All entity ID fields are optional; at least one is required.

result = actions.execute_tool( identifier="user_aff_123", tool_name="affinity_create_note", tool_input={ "content": "Initial call with founder. Strong product-market fit signal. Team has prior exit. Follow up in 2 weeks.", "opportunity_ids": [98431], "person_ids": [44320], "organization_ids": [7133202] } ) # Returns: created note ID and confirmation
const result = await actions.executeTool({ identifier: "user_aff_123", toolName: "affinity_create_note", toolInput: { "content": "Initial call with founder. Strong product-market fit signal. Team has prior exit. Follow up in 2 weeks.", "opportunity_ids": [98431], "person_ids": [44320], "organization_ids": [7133202] } }); // Returns: created note ID and confirmation

Get relationship strength

Retrieve interaction-based scores between your team and an external contact. Omit internal_id to get scores across the whole team — useful for finding the best intro path to a founder or LP.

result = actions.execute_tool( identifier="user_aff_123", tool_name="affinity_get_relationship_strength", tool_input={ "external_id": 44320 # omit internal_id to get scores for all team members } ) # Returns: strength scores per team member, based on email and meeting frequency and recency
const result = await actions.executeTool({ identifier: "user_aff_123", toolName: "affinity_get_relationship_strength", toolInput: { "external_id": 44320 // omit internal_id to get scores for all team members } }); // Returns: strength scores per team member, based on email and meeting frequency and recency
Framework wiring

5 Wiring into your agent framework

Scalekit integrates directly with LangChain. The agent decides what to look up or act on; Scalekit handles credential injection on every invocation. No credential plumbing in your agent logic.

from langchain_anthropic import ChatAnthropic from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from scalekit.langchain import get_tools aff_tools = get_tools( connection_name="affinity", identifier="user_aff_123" ) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a deal intelligence assistant. Use the available tools to help manage Affinity pipeline, log meeting notes, and surface relationship context."), MessagesPlaceholder("chat_history", optional=True), ("human", "{input}"), MessagesPlaceholder("agent_scratchpad"), ]) agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), aff_tools, prompt) result = AgentExecutor(agent=agent, tools=aff_tools).invoke({ "input": "Find all open opportunities in our Series A pipeline and log a summary note on each deal reviewed today" })
import { ChatAnthropic } from "@langchain/anthropic"; import { AgentExecutor, createToolCallingAgent } from "langchain/agents"; import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; import { getTools } from "@scalekit-sdk/langchain"; const affTools = getTools({ connectionName: "affinity", identifier: "user_aff_123" }); const prompt = ChatPromptTemplate.fromMessages([ ["system", "You are a deal intelligence assistant. Use the available tools to help manage Affinity pipeline, log meeting notes, and surface relationship context."], new MessagesPlaceholder("chat_history", true), ["human", "{input}"], new MessagesPlaceholder("agent_scratchpad"), ]); const agent = await createToolCallingAgent({ llm: new ChatAnthropic({ model: "claude-sonnet-4-6" }), tools: affTools, prompt }); const result = await AgentExecutor.fromAgentAndTools({ agent, tools: affTools }).invoke({ input: "Find all open opportunities in our Series A pipeline and log a summary note on each deal reviewed today" });
Other frameworks supported
Tool reference

All 13 Affinity tools

Grouped by object and capability. Your agent calls tools by name — no API wrappers to write.

Opportunities
affinity_create_opportunity
Create a new deal or opportunity in an Affinity pipeline list. Supports associating persons and organizations, setting the name, and assigning an owner
affinity_get_opportunity
Retrieve full deal details including stage, owner, associated persons and organizations, custom field values, and list membership
affinity_update_opportunity
Update an existing opportunity — rename, or add/remove associated persons and organizations by ID
affinity_list_opportunities
List pipeline opportunities with optional filters by list ID. Returns paginated deal records with stage, value, and associated entities
People
affinity_search_persons
Search for people by name or email address. Returns contact info, organization memberships, and optional interaction dates
affinity_get_person
Retrieve a person's full profile including emails, phone numbers, organization memberships, and relationship score. Supports optional interaction dates
Organizations
affinity_search_organizations
Search companies by name or domain. Returns org records with team connections, domain info, and optional interaction metadata
affinity_get_organization
Retrieve a company's full profile including domain, team connections, associated people, deal history, and interaction metadata
Notes
affinity_create_note
Create a plain-text note and attach it to any combination of people, organizations, or opportunities simultaneously
affinity_list_notes
Retrieve notes for a person, organization, or opportunity. Returns paginated records with content, creator, and timestamp
Lists & Relationships
affinity_list_lists
Retrieve all lists in the workspace — people lists, org lists, and deal pipelines. Returns list IDs, names, types, and owner info
affinity_add_to_list
Add a person or organization to an Affinity list by entity ID and list ID
affinity_get_relationship_strength
Retrieve relationship strength scores between team members and an external contact, based on email and meeting frequency and recency. Identify the best warm intro path
Connector notes

Affinity-specific behavior

API keys are revoked when a user is deactivated
If a user is deactivated in Affinity, their API key is automatically and immediately revoked — with no webhook or notification. Calls using that key will start returning 401s. The connected account will move to REVOKED in Scalekit. Surface a re-credentialing flow rather than returning a generic error, and use a key from an active account with appropriate permissions.
API key permissions are role-dependent
Affinity Enterprise accounts can restrict API key generation to Admin roles only. A non-admin user may appear fully set up but fail silently if their role cannot access certain endpoints. Use a key from an account with the right permissions for the operations your agent performs.
list_id is required context for most write operations
Creating an opportunity or adding an entity to a list requires a valid list_id. Use affinity_list_lists first to discover list IDs in the workspace — they are not predictable and differ across Affinity instances. Store list IDs alongside user credentials rather than fetching them on every agent call.
Infrastructure decision

Why not build this yourself

The Affinity API is documented. Credential storage isn't technically hard. But here's what you're actually signing up for:

PROBLEM 01
Per-user Bearer token storage and injection — one encrypted API key per user, each scoped to a different Affinity workspace, with no OAuth flow to standardize collection
PROBLEM 02
Silent revocation when users are deactivated — no webhook, no notification, just 401s — requires per-account status tracking and re-credentialing flows without OAuth lifecycle hooks
PROBLEM 03
Per-user credential isolation in a multi-tenant system — one customer's Affinity token must never be used for another customer's workspace
PROBLEM 04
Role-based key generation restrictions, V1/V2 API surface split, and IP allowlist enforcement are all failure modes that only surface with real users in production

That's one connector. Your agent product will eventually need Salesforce, Gmail, HubSpot, Linear, and whatever else your customers ask for. Each has its own auth quirks and failure modes.

Scalekit maintains every connector. You maintain none of them.

Ready to ship

Ship Affinity now

Free to start. Bearer token storage handled.