Affinity Python SDK

A modern, strongly-typed Python wrapper for the Affinity CRM API.
Disclaimer: This is an unofficial community project and is not affiliated with, endorsed by, or sponsored by Affinity. βAffinityβ and related marks are trademarks of their respective owners. Use of the Affinity API is subject to Affinityβs Terms of Service.
Maintainer: GitHub: yaniv-golan
Documentation: https://yaniv-golan.github.io/affinity-sdk/latest/
Affinity's Official MCP Server
As of March 2026, Affinity has released an official MCP Server (beta) for conversational, natural-language access to your CRM data via AI chat clients. It covers relationship intelligence queries, pipeline summaries, meeting activity, and note capture.
This SDK serves a different purpose β it's a full-coverage, strongly-typed Python client for the Affinity API, supporting the complete read/write surface (companies, persons, lists, field values, notes, reminders, webhooks, files, and more). Use it when you need programmatic control, write operations, type safety, or want to build custom integrations and tooling.
For a detailed comparison, see Affinity SDK vs. Official MCP.
Table of Contents
Features
- Complete API coverage - Full V1 + V2 support with smart routing
- CLI included - Scriptable command-line interface for automation
- Strong typing - Full Pydantic V2 models with typed ID classes
- No magic numbers - Comprehensive enums for all API constants
- Automatic pagination - Iterator support for seamless pagination
- Rate limit handling - Automatic retry with exponential backoff
- Response caching - Optional caching for field metadata
- Both sync and async - Full support for both patterns
AI Integrations
- Claude Code plugins - SDK and CLI knowledge for AI-assisted development
- MCP Server - Connect desktop AI tools to Affinity
Installation
Requires Python 3.10+.
Optional (local dev): load .env automatically:
pip install "affinity-sdk[dotenv]"
Optional: install the CLI:
pipx install "affinity-sdk[cli]"
The CLI includes a powerful query command for structured data extraction with filtering, aggregations, and relationship includes. Output formats include JSON, CSV, markdown, and TOON (token-optimized for LLMs).
CLI docs: https://yaniv-golan.github.io/affinity-sdk/latest/cli/
MCP Server
Connect desktop AI tools to Affinity CRM.
Claude Desktop (easiest - MCPB bundle):
- Install CLI:
pipx install "affinity-sdk[cli]"
- (Optional) Pre-configure API key:
xaffinity config setup-key
- If skipped, Claude Desktop will prompt for your API key during MCPB install
- Download the
.mcpb bundle from GitHub Releases
- Double-click to install (or drag to Claude Desktop)
Other clients (Cursor, Windsurf, VS Code + Copilot, Zed, etc.):
These require manual configuration. See the MCP Server docs for step-by-step instructions.
MCP docs: https://yaniv-golan.github.io/affinity-sdk/latest/mcp/
Claude Code Plugins
If you use Claude Code, install plugins for SDK/CLI knowledge:
/plugin marketplace add yaniv-golan/affinity-sdk
/plugin install affinity-crm-sdk-unofficial@xaffinity # SDK patterns
/plugin install affinity-crm-cli-xaffinity-unofficial@xaffinity # CLI patterns + hooks
Plugin docs: https://yaniv-golan.github.io/affinity-sdk/latest/guides/claude-code-plugins/
Documentation
Quick Start
from affinity import Affinity
from affinity.types import FieldType, PersonId
# Recommended: read the API key from the environment (AFFINITY_API_KEY)
client = Affinity.from_env()
# If you use a local `.env` file (requires `affinity-sdk[dotenv]`)
# client = Affinity.from_env(load_dotenv=True)
# Or pass it explicitly
# client = Affinity(api_key="your-api-key")
# Or use as a context manager
with Affinity.from_env() as client:
# List all companies
for company in client.companies.all():
print(f"{company.name} ({company.domain})")
# Get a person with enriched data
person = client.persons.get(
PersonId(12345),
field_types=[FieldType.ENRICHED, FieldType.GLOBAL]
)
print(f"{person.first_name} {person.last_name}: {person.primary_email}")
Usage Examples
Working with Companies
from affinity import Affinity, F
from affinity.models import CompanyCreate
from affinity.types import CompanyId, FieldType
with Affinity(api_key="your-key") as client:
# List companies with filtering (V2 API)
companies = client.companies.list(
filter=F.field("domain").contains("acme"),
field_types=[FieldType.ENRICHED],
)
# Iterate through all companies with automatic pagination
for company in client.companies.all():
print(f"{company.name}: {company.fields}")
# Get a specific company
company = client.companies.get(CompanyId(123))
# Create a company (uses V1 API)
new_company = client.companies.create(
CompanyCreate(
name="Acme Corp",
domain="acme.com",
)
)
# Search by name, domain, or email
results = client.companies.search("acme.com")
# Get list entries for a company
entries = client.companies.get_list_entries(CompanyId(123))
Working with Persons
from affinity import Affinity
from affinity.models import PersonCreate
from affinity.types import PersonType
with Affinity(api_key="your-key") as client:
# Get all internal team members
for person in client.persons.all():
if person.type == PersonType.INTERNAL:
print(f"{person.first_name} {person.last_name}")
# Create a contact
person = client.persons.create(
PersonCreate(
first_name="Jane",
last_name="Doe",
emails=["jane@example.com"],
)
)
# Search by email
results = client.persons.search("jane@example.com")
Working with Lists
from affinity import Affinity, FieldResolver, ResolveMode
from affinity.models import ListCreate
from affinity.types import CompanyId, FieldId, FieldType, ListId, ListType
with Affinity(api_key="your-key") as client:
# Get all lists
for lst in client.lists.all():
print(f"{lst.name} ({lst.type.name})")
# Get a specific list with field metadata
pipeline = client.lists.get(ListId(123))
print(f"Fields: {[f.name for f in pipeline.fields]}")
# Create a new list
new_list = client.lists.create(
ListCreate(
name="Q1 Pipeline",
type=ListType.OPPORTUNITY,
is_public=True,
)
)
# Work with list entries
entries = client.lists.entries(ListId(123))
# List entries with field data
for entry in entries.all(field_types=[FieldType.LIST]):
print(f"{entry.entity.name}: {entry.fields}")
# Look up field values by name (instead of raw field IDs)
# See docs/public/guides/performance.md for details
resolver = FieldResolver(pipeline.fields)
for entry in entries.all(field_types=[FieldType.LIST]):
status = resolver.get(entry, "Status", resolve=ResolveMode.TEXT)
print(f"{entry.entity.name}: {status}")
# Add a company to the list
entry = entries.add_company(CompanyId(456))