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-api-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 examples below.
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"))
# Ends with
persons = client.persons.list(filter=F.field("Email").ends_with("@acme.com"))
# 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¶
For CLI or advanced SDK use, you can use raw filter strings. The SDK supports both official Affinity V2 API syntax and SDK-specific extensions.
Standard Operators (Affinity V2 API compatible)¶
These operators work both with the Affinity API and SDK client-side filtering:
| 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" |
| greater than | > |
field("Count") > 5 |
| greater than or equal | >= |
field("Count") >= 5 |
| less than | < |
field("Count") < 10 |
| less than or equal | <= |
field("Count") <= 10 |
| is NULL | != * |
field("Email") != * |
| is not NULL | = * |
field("Email") = * |
| is empty string | = "" |
field("Notes") = "" |
| collection exact match | = [A, B] |
field("Tags") = [tech, startup] |
| collection contains all | =~ [A, B] |
field("Tags") =~ [tech, startup] |
| collection empty | = [] |
field("Tags") = [] |
SDK Extensions (client-side filtering only)¶
These operators are SDK-specific and only work for client-side filtering with --expand-filter or matches(). They do NOT work with the Affinity API.
Word-Based Aliases [SDK Extension]¶
Human/LLM-friendly aliases for official operators:
| Alias | Equivalent | Example |
|---|---|---|
contains |
=~ |
name contains "Corp" |
starts_with |
=^ |
name starts_with "Acme" |
ends_with |
=$ |
email ends_with "@acme.com" |
gt |
> |
count gt 5 |
gte |
>= |
count gte 5 |
lt |
< |
count lt 10 |
lte |
<= |
count lte 10 |
is null |
!= * |
email is null |
is not null |
= * |
email is not null |
is empty |
= "" or = [] |
tags is empty |
Additional Collection Operators [SDK Extension]¶
| Operator | Syntax | Description |
|---|---|---|
| in list | in [A, B, C] |
Value equals any in list |
| between | between [1, 10] |
Value in range (inclusive) |
| has any | has_any [A, B] |
Array contains any of values (exact match) |
| has all | has_all [A, B] |
Array contains all values (exact match) |
| contains any | contains_any [A, B] |
Any element contains any substring |
| contains all | contains_all [A, B] |
Any element contains all substrings |
Example equivalents (prefer official syntax for API portability):
# These are equivalent:
xaffinity list export 123 --expand-filter '"Team Member" =~ LB' # Official V2 API
xaffinity list export 123 --expand-filter '"Team Member" contains LB' # SDK alias
xaffinity list export 123 --expand-filter 'email = *' # Official V2 API
xaffinity list export 123 --expand-filter 'email is not null' # SDK alias
xaffinity list export 123 --expand-filter 'count > 5' # Official V2 API
xaffinity list export 123 --expand-filter 'count gt 5' # SDK alias
CLI Examples¶
# Simple filter
xaffinity person ls --filter 'Department = "Sales"'
# Contains (case-insensitive)
xaffinity company ls --filter 'Industry =~ "tech"'
# Numeric comparison
xaffinity opportunity ls --filter 'Amount > 100000'
# Multiple conditions with AND
xaffinity person ls --filter 'Status = "Active" & Department = "Sales"'
# Multiple conditions with OR
xaffinity company ls --filter 'Region = "US" | Region = "Canada"'
# Null check
xaffinity person ls --filter 'Manager != *'
# Collection operators (SDK extension)
xaffinity list export 123 --expand-filter 'Status in [Active, Pending]'
xaffinity list export 123 --expand-filter 'Tags has_any [priority, urgent]'
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 persons, 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)
- Supports SDK extension operators (word aliases, collection operators)
- Still useful for reducing output size and focusing on relevant associations
Example¶
# Server-side: only fetch Active opportunities
# Client-side: only include people with valid email status
xaffinity list export 275454 \
--filter "Status=Active" \
--expand persons \
--expand-filter '"Primary Email Status"=Valid | "Primary Email Status"=Unknown | "Primary Email Status" is null' \
--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.