xaffinity CLI Usage
Use this skill when running xaffinity commands to interact with Affinity CRM.
REQUIRED FIRST STEP: Verify API Key
STOP. Before doing ANYTHING else, run this command:
xaffinity config check-key --json
This MUST be your first action when handling any Affinity request.
If "configured": true - Use the pattern field from the output for ALL subsequent commands:
- •If
"pattern": "xaffinity --dotenv --readonly <command> --json"-> use--dotenv - •If
"pattern": "xaffinity --readonly <command> --json"-> no--dotenvneeded
If "configured": false - Stop and help user set up:
- •Tell them: "You need to configure an Affinity API key first."
- •Direct them: Affinity -> Settings -> API -> Generate New Key
- •Tell them to run:
xaffinity config setup-key(do NOT run it for them - it's interactive)
IMPORTANT: Write Operations Require Explicit User Request
Always use --readonly unless user explicitly requests writes.
Write operations include creating, updating, or deleting:
- •Notes, interactions, reminders
- •List entries, field values
- •Persons, companies, opportunities
Destructive Commands Require Double Confirmation
IMPORTANT: Before executing ANY delete command, you MUST:
- •Look up the entity first to show the user what will be deleted
- •Ask the user in your response by showing them the entity details and requesting confirmation
- •Wait for user's next message - do NOT proceed until they explicitly confirm
- •Only after user confirms should you run the delete with
--yes
Example flow:
User: "Delete person 123"
You: xaffinity person get 123 --readonly --json
You: "This will permanently delete John Smith (ID: 123, email: john@example.com).
Type 'yes' to confirm deletion."
[Stop here and wait for user's response]
User: "yes"
You: xaffinity person delete 123 --yes
Destructive commands: person delete, company delete, opportunity delete, note delete, reminder delete, field delete, list entry delete, interaction delete
Note: This is conversation-based confirmation - you ask, then wait for the user's next message. The --yes flag bypasses the CLI's interactive prompt, but you must get explicit user confirmation in the conversation first.
Critical Patterns
| Pattern | Purpose |
|---|---|
--readonly | Prevent accidental data modification (ALWAYS use) |
--json | Structured, parseable output (ALWAYS use) |
--all | Fetch all pages (for exports) |
--yes | Skip confirmation on delete commands (use after user confirms) |
--help | Discover command options (USE THIS, don't guess) |
Common Commands
# Search/Get entities xaffinity person ls --query "John Smith" --json xaffinity person get 123 --json xaffinity person get email:alice@example.com --json xaffinity company get domain:acme.com --json # List all xaffinity person ls --all --json xaffinity company ls --all --json xaffinity list ls --json # Export to CSV xaffinity person ls --all --csv --csv-bom > contacts.csv xaffinity list export LIST_ID --all --csv --csv-bom > output.csv # Export with expanded associations xaffinity list export LIST_ID --expand people --all --csv > output.csv xaffinity list export LIST_ID --expand people --expand companies --all --csv > output.csv
Filtering (Custom Fields Only)
# Filter on custom fields xaffinity person ls --filter 'Department = "Sales"' --all --json xaffinity list export LIST_ID --filter 'Status = "Active"' --all --json # Filter syntax # = exact match # =~ contains # =^ starts with # =$ ends with # != * is NULL # & AND # | OR # Examples --filter 'Status = "Active" & Region = "US"' --filter 'Status = "New" | Status = "Pending"'
Cannot filter: name, email, domain, type - use --all and post-process with jq.
Gotchas & Workarounds
Internal meetings NOT in interactions
The interactions API only shows meetings with external contacts.
# Returns NOTHING for internal-only meetings: xaffinity interaction ls --person-id 123 --type meeting ... # Workaround - use notes: xaffinity note ls --person-id 123 --json # Filter for isMeeting: true
Interactions require --type and entity ID
# Requires --type and one of --person-id, --company-id, --opportunity-id # Date range defaults to last 7 days if not specified xaffinity interaction ls --type meeting --person-id 123 --json # With custom date range (max 1 year): # Note: Dates are interpreted as LOCAL TIME (use Z suffix for explicit UTC) xaffinity interaction ls --type meeting --person-id 123 \ --after 2025-01-01 --before 2025-12-31 --json # Explicit UTC: xaffinity interaction ls --type meeting --person-id 123 \ --after 2025-01-01T00:00:00Z --json
Smart Fields not in API
"Last Meeting", "Next Meeting" are UI-only. Use:
xaffinity person ls --query "Alice" --with-interaction-dates --json xaffinity company ls --query "Acme" --with-interaction-dates --json
List filtering is client-side
All entries fetched, then filtered locally. For efficiency:
# INEFFICIENT - 3 API calls fetching same data: xaffinity list export 123 --filter 'Status = "New"' --all --json > new.json xaffinity list export 123 --filter 'Status = "Active"' --all --json > active.json # BETTER - 1 API call, post-process: xaffinity list export 123 --all --json > all.json jq '[.[] | select(.Status == "New")]' all.json > new.json jq '[.[] | select(.Status == "Active")]' all.json > active.json
Opportunities bound to one list
Cannot move/copy opportunities between lists. Created with --list-id.
Global organizations are read-only
Companies with global: true cannot be modified.
Quick Reference
| Task | Command |
|---|---|
| Find person by email | person get email:user@example.com |
| Find company by domain | company get domain:acme.com |
| Export all contacts | person ls --all --csv --csv-bom > contacts.csv |
| Export pipeline with people | list export LIST_ID --expand people --all --csv > out.csv |
| Get command help | xaffinity <command> --help |
Installation
pip install "affinity-sdk[cli]"
Documentation
- •Full CLI reference:
xaffinity --help - •SDK docs: https://yaniv-golan.github.io/affinity-sdk/latest/