Filtering¶
V2 list endpoints accept filter expressions to query custom fields.
Important: V2 filters only work with custom fields, not built-in entity properties. Built-in properties like type, firstName, domain, etc. cannot be filtered.
Recommended: Use the Filter Builder¶
Use affinity.F to build type-safe filter expressions:
from affinity import Affinity, F
with Affinity(api_key="your-key") as client:
# ✅ Recommended: Type-safe filter builder
companies = client.companies.list(
filter=F.field("Industry").equals("Software")
)
Benefits of the filter builder:
- ✅ Prevents syntax errors with type checking
- ✅ Handles escaping automatically
- ✅ Makes it clear you're filtering custom fields (via field() method)
- ✅ Provides IDE autocomplete for filter operations
CLI users: The CLI uses raw filter string syntax. See CLI commands reference for examples.
Filter Builder Examples¶
Simple comparisons:
from affinity import Affinity, F
# Equals
persons = client.persons.list(filter=F.field("Department").equals("Sales"))
# Contains (case-insensitive substring match)
companies = client.companies.list(filter=F.field("Industry").contains("Tech"))
# Starts with
persons = client.persons.list(filter=F.field("Title").starts_with("VP"))
# Greater than (for numbers/dates)
opportunities = client.opportunities.list(filter=F.field("Amount").greater_than(100000))
# Is null / is not null
persons = client.persons.list(filter=F.field("Manager").is_null())
Complex logic (AND/OR/NOT):
# AND: Both conditions must be true
active_sales = client.persons.list(
filter=F.field("Department").equals("Sales") & F.field("Status").equals("Active")
)
# OR: Either condition can be true
tech_or_finance = client.companies.list(
filter=F.field("Industry").equals("Technology") | F.field("Industry").equals("Finance")
)
# NOT: Negate a condition
non_archived = client.persons.list(
filter=~F.field("Archived").equals(True)
)
# Complex: (A AND B) OR (C AND D)
result = client.companies.list(
filter=(
(F.field("Industry").equals("Software") & F.field("Region").equals("US"))
| (F.field("Industry").equals("Hardware") & F.field("Region").equals("EU"))
)
)
In list (multiple values):
# Match any value in the list
multi_region = client.companies.list(
filter=F.field("Region").in_list(["US", "Canada", "Mexico"])
)
Raw Filter Strings (Advanced)¶
For CLI or advanced SDK use, you can use raw filter strings:
| Meaning | Operator | Example |
|---|---|---|
| and | & |
field("A") = 1 & field("B") = 2 |
| or | | |
field("A") = 1 | field("B") = 2 |
| not | ! |
!(field("A") = 1) |
| equals | = |
field("Industry") = "Software" |
| not equals | != |
field("Status") != "inactive" |
| starts with | =^ |
field("Name") =^ "Ac" |
| ends with | =$ |
field("Name") =$ "Inc" |
| contains | =~ |
field("Title") =~ "Manager" |
| is NULL | != * |
field("Email") != * |
| is not NULL | = * |
field("Email") = * |
CLI example:
xaffinity person ls --filter 'Department = "Sales"'
SDK with raw string:
# Less safe than using F, but works
persons = client.persons.list(filter='field("Department") = "Sales"')
What can be filtered?¶
✅ Custom fields (added to entities in Affinity):
Python SDK:
- F.field("Department").equals("Sales")
- F.field("Status").contains("Active")
CLI (raw filter syntax):
- Department = "Sales"
- Status =~ "Active"
❌ Built-in properties (cannot be filtered with V2 filter expressions):
- Person: type, firstName, lastName, primaryEmail, emailAddresses
- Company: name, domain, domains
- Opportunity: name, listId
For built-in properties, retrieve all data and filter client-side (see CSV Export Guide for examples).
Filtering in List Exports (CLI)¶
The list export command supports two filter options with identical syntax but different behavior:
| Option | What It Filters | Where Filtering Happens |
|---|---|---|
--filter |
List entries | Server-side (API) |
--expand-filter |
Expanded entities (people, companies) | Client-side (after fetch) |
Why the difference?¶
The Affinity API supports filtering for list entries, but does not support filtering associations.
When you use --expand people, the CLI:
- Fetches the list entries (can be filtered with
--filter) - For each entry, fetches ALL associated people (API returns all, no filter option)
- Filters the people locally based on
--expand-filter
This means --expand-filter:
- Uses the same syntax as
--filterfor consistency - Is applied after fetching data (doesn't reduce API calls)
- Still useful for reducing output size and focusing on relevant associations
Supported operators for --expand-filter¶
| Syntax | Meaning | Example |
|---|---|---|
= |
Exact match | name=Alice |
!= |
Not equal | name!=Bob |
=* |
IS NOT NULL (has value) | email=* |
!=* |
IS NULL (empty/not set) | email!=* |
=~ |
Contains substring | name=~Corp |
\| |
OR | status=Unknown \| status=Valid |
& |
AND | status=Valid & role=CEO |
! |
NOT (prefix) | !(status=Inactive) |
() |
Grouping | (status=A \| status=B) & role=CEO |
Example¶
# Server-side: only fetch Active opportunities
# Client-side: only include people with valid email status
xaffinity list export 275454 \
--filter "Status=Active" \
--expand people \
--expand-filter "Primary Email Status=Valid | Primary Email Status=Unknown | Primary Email Status!=*" \
--all --csv output.csv
Performance consideration¶
Since --expand-filter is client-side, all associations are still fetched from the API.
For large lists with many associations, the export may take time even if the filter
reduces the final output significantly. Use --dry-run to estimate API calls.