Skip to content

Commands

No-network commands

These commands never call the Affinity API:

  • xaffinity --help
  • xaffinity completion bash|zsh|fish
  • xaffinity version (also xaffinity --version)
  • xaffinity config path
  • xaffinity config init
  • xaffinity config check-key (reads files, but never calls API)

Configuration

xaffinity config path

Show the path to the configuration file.

xaffinity config path

xaffinity config init

Create a new configuration file with template.

xaffinity config init
xaffinity config init --force  # Overwrite existing

xaffinity config check-key

Check if an API key is configured. Returns exit code 0 if key found, 1 if not found.

This command checks (in order): 1. --api-key-file / --api-key-stdin passed to this command (stdin is not read; a key file must exist and be non-empty, or the key counts as not configured) 2. AFFINITY_API_KEY environment variable 3. .env file in current directory (found even without --dotenv; commands still need --dotenv to use it) 4. AFFINITY_API_KEY_FILE 5. AFFINITY_API_KEY_COMMAND (not run; its presence counts) 6. User config file (config.toml)

xaffinity config check-key
xaffinity config check-key --json
xaffinity config check-key && echo "Key exists"
xaffinity --api-key-file ./key.txt config check-key --json

The --json output includes: - configured: boolean indicating if a key was found - source: where the key was found ("flag", "environment", "dotenv", "file", "command", "config", or null) - pattern: the command pattern to use with that key (it repeats --api-key-file <path> or --api-key-stdin when the key came from a flag)

xaffinity config setup-key

Securely configure your Affinity API key. Prompts for the key with hidden input (not echoed to screen).

Options:

  • --scope [project|user]: Where to store the key
  • project: .env file in current directory (auto-added to .gitignore)
  • user: User config file (config.toml, with chmod 600 on Unix)
  • --force: Overwrite existing key without confirmation
  • --validate/--no-validate: Test key against API after storing (default: validate)
# Interactive setup (prompts for scope)
xaffinity config setup-key

# Store in current project's .env file
xaffinity config setup-key --scope project

# Store in user config (works across all projects)
xaffinity config setup-key --scope user

# Overwrite existing key
xaffinity config setup-key --force

# Skip API validation
xaffinity config setup-key --no-validate

Get your API key from Affinity API Settings.

xaffinity config update-check

Shows or changes the automatic update check (once a day in interactive sessions; see Update notifications).

xaffinity config update-check               # Show current settings
xaffinity config update-check --status      # Show update status
xaffinity config update-check --now         # Check for updates now
xaffinity config update-check --background  # Start a background check (for MCP/automation)
xaffinity config update-check --disable     # Turn checks off (--enable to turn them on)

The check asks the package index (PyPI) for the latest version; it never calls the Affinity API.

Global options

These options can be used with any command:

  • --json / --output json: emit machine-readable CommandResult JSON to stdout.
  • --help --json: emit machine-readable command documentation (see Scripting: Machine-Readable Help).
  • --trace: emit request/response/error trace lines to stderr (safe redaction). Recommended with --no-progress for long-running commands.
  • --beta: enable beta V2 endpoints. No current command requires it (merges are GA).
  • --session-cache <dir>: enable session caching using the specified directory.
  • --no-cache: disable session caching for this command.

Pipeline Optimization

When running multiple CLI commands in a pipeline, you can enable session caching to avoid redundant API calls for metadata like field definitions and list resolution.

Using session commands

# Start a session (creates temp cache directory)
export AFFINITY_SESSION_CACHE=$(xaffinity session start)

# Run your commands - metadata is cached across invocations
xaffinity list export "My List" --json > entries.json
xaffinity field ls --list-id 12345
xaffinity person get 12345

# Check session status
xaffinity session status

# End session (cleanup)
xaffinity session end
unset AFFINITY_SESSION_CACHE

Best practice for scripts (with automatic cleanup)

#!/bin/bash
export AFFINITY_SESSION_CACHE=$(xaffinity session start) || exit 1
trap 'xaffinity session end; unset AFFINITY_SESSION_CACHE' EXIT

# Your commands here - cleanup happens automatically on exit, error, or Ctrl+C
xaffinity list export "My List" | jq '.entries[]' | xaffinity person get

Quick one-liner pattern (subshell)

For simple pipelines, use a subshell - the session dir is cleaned up by TTL:

(
  export AFFINITY_SESSION_CACHE=$(xaffinity session start)
  xaffinity list export "My List" | xaffinity person get
)

Cache behavior

  • Cache is scoped to your API key and Affinity API version (multi-tenant safe)
  • Default TTL is 10 minutes (configurable via AFFINITY_SESSION_CACHE_TTL)
  • Cache is file-based in the specified directory
  • session end is idempotent - safe to call multiple times

Environment variables

Variable Description
AFFINITY_SESSION_CACHE Directory for session cache files
AFFINITY_SESSION_CACHE_TTL Cache TTL in seconds (default: 600)

Session commands

Command Description
xaffinity session start Create session cache, output path
xaffinity session end Clean up session cache directory
xaffinity session status Show cache stats (entries, size, age)

Debugging

Use --trace to see cache hit/miss information:

xaffinity --trace list export "My List"
# trace #+ cache hit: list_fields_12345
# trace #- cache miss: saved_views_12345

Use --no-cache to bypass session cache:

xaffinity --no-cache list export "My List"

Identity

xaffinity whoami

Validates credentials and prints tenant/user context.

xaffinity whoami
xaffinity whoami --json | jq

URL resolution

xaffinity resolve-url <url>

Parses an Affinity UI URL (including tenant hosts like https://<tenant>.affinity.co/... or https://<tenant>.affinity.com/...) and validates it by fetching the referenced object.

xaffinity resolve-url "https://app.affinity.co/companies/67890"
xaffinity resolve-url "https://mydomain.affinity.com/companies/67890" --json

Query

xaffinity query

Execute structured JSON queries against Affinity data. Supports complex filtering, aggregations, grouping, and including related entities.

# Simple query
xaffinity query --query '{"from": "persons", "limit": 10}'

# Filter with conditions (Affinity filters list fields on its side)
xaffinity query --query '{"from": "listEntries", "where": {"and": [{"path": "listName", "op": "eq", "value": "Dealflow"}, {"path": "fields.Status", "op": "eq", "value": "Active"}]}}'

# Include related entities
xaffinity query --query '{"from": "persons", "include": ["companies"], "limit": 50}'

# Aggregation
xaffinity query --query '{"from": "opportunities", "groupBy": "status", "aggregate": {"count": {"count": true}}}'

# Dry-run to preview execution plan
xaffinity query --query '{"from": "persons", "include": ["companies"]}' --dry-run

# From file
xaffinity query --file query.json

Key options:

  • --query <json>: Inline JSON query string
  • --file <path>: Read query from JSON file
  • --dry-run: Show execution plan without running (estimated API calls)
  • --dry-run-verbose: Detailed plan with step breakdown
  • --max-records <n>: Safety limit (default: 10000). Matches past it are dropped with a warning and meta.truncated; with orderBy or an aggregate, the cut comes after sorting and aggregating. A --cursor resumes only with the same --max-records
  • --timeout <secs>: Query timeout (default: 300)
  • --csv / --json: Output format
  • --include-meta: include execution metadata; meta.serverFilter shows which conditions Affinity's search applied (pushed), which ran locally and why (local), and whether Affinity sorted (sort)

A related record that is gone (deleted, or merged into another) reads as empty in include and expand; any other lookup error, or failing to read a list's field definitions when the query uses a list field, fails the query with that error's exit code.

Conditions on fields go to Affinity's search where it can answer them; the full where still runs locally on what comes back. A where on companies or persons must include a condition on one of their fields (fields.<name>). XAFFINITY_QUERY_SERVER_FILTER=0 turns server filtering off for list entries (query on listEntries and list export); company and person filters always use it.

See Query Language Reference and Query Guide for full documentation.

People

xaffinity person ls

List persons. Supports field selection, filter expressions, and free-text search.

Options:

  • --query <term> / -q <term>: free-text search (name or email)
  • --field <id-or-name> (repeatable): field ID or name to include
  • --field-type <type> (repeatable): field type to include (global, enriched, relationship-intelligence)
  • --filter <expression> (repeatable, values ANDed): filter expression; must include a condition on a person field (Affinity's search applies it)
  • --page-size, --cursor, --max-results, --all: pagination options
  • --csv: output as CSV (to stdout)
  • --csv-bom: add UTF-8 BOM for Excel compatibility

Note: --filter goes through Affinity's search: the conditions on person fields that Affinity can answer are applied on its side, and the whole filter (including attributes like firstName or primaryEmail) then runs on what comes back. A filter with only attribute conditions is refused; search names and emails with --query. When part of the filter runs locally, --all or --max-results is required. Filters on enriched fields warn that a few records may be missed. The next-page cursor of a filtered run starts with xsearch2: and --cursor alone resumes it. --query and --filter can't be combined. With --json, meta.serverFilter lists the conditions Affinity applied (pushed) and those checked locally (local); a --cursor resume doesn't report it.

Note: --query can be combined with --field and --field-type to search with field data.

xaffinity person ls
xaffinity person ls --page-size 50
xaffinity person ls --field-type enriched --all
xaffinity person ls --filter 'Seniority = "Executive" & primaryEmail =~ "@acme.com"' --max-results 50
xaffinity person ls --query "alice@example.com"
xaffinity person ls --query "Alice" --field-type enriched --all
xaffinity person ls --all --csv > people.csv
xaffinity person ls --all --csv --csv-bom > people.csv

See the CSV Export Guide for more details.

xaffinity person get <personSelector>

Fetch a person by id, UI URL (including tenant hosts), or a resolver selector.

Examples:

xaffinity person get 12345
xaffinity person get "https://mydomain.affinity.com/persons/12345"
xaffinity person get email:alice@example.com
xaffinity person get 'name:"Alice Smith"'

Field selection:

  • --all-fields: include all supported (non-list-specific) fields.
  • --field <id-or-exact-name> (repeatable)
  • --field-type <type> (repeatable)
  • --no-fields: skip fields entirely.

Expansions:

  • --expand lists: include lists the person is on (auto-paginates up to a safe cap; use --max-results / --all to adjust).
  • --expand list-entries: include list entries for the person (first page by default; use --max-results / --all to fetch more).
  • --list <id-or-exact-name>: filter list entries to a specific list (implies --expand list-entries).
  • --list-entry-field <id-or-exact-name> (repeatable): project list-entry fields into columns (implies --expand list-entries). Field names are only allowed with --list.
  • --show-list-entry-fields: render per-list-entry Fields tables in human output (requires --expand list-entries and --max-results <= 3). Mutually exclusive with --list-entry-field.
  • --list-entry-fields-scope list-only|all: control which fields appear in list-entry tables (human output only).

Interaction dates (V1 API):

  • --with-interaction-dates: include interaction date summaries (last/next meeting, email dates). Uses V1 API.
  • --with-interaction-persons: include person IDs involved in each interaction (requires --with-interaction-dates).
xaffinity person get 12345 --all-fields --expand lists
xaffinity person get 12345 --expand list-entries --list "Dealflow" --max-results 200
xaffinity person get 12345 --expand list-entries --list "Dealflow" --list-entry-field Stage --list-entry-field Amount
xaffinity person get 12345 --expand list-entries --max-results 1 --show-list-entry-fields
xaffinity person get 12345 --expand list-entries --max-results 1 --show-list-entry-fields --list-entry-fields-scope all
xaffinity person get 12345 --all-fields --expand lists --json | jq '.data.person.name'

# Include interaction dates (last meeting, next meeting, email dates)
xaffinity person get 12345 --with-interaction-dates --json
xaffinity person get 12345 --with-interaction-dates --with-interaction-persons --json

xaffinity person create

xaffinity person create --first-name Ada --last-name Lovelace --email ada@example.com
xaffinity person create --first-name Ada --last-name Lovelace --company-id 67890

xaffinity person update <personId>

xaffinity person update 12345 --email ada@example.com --email ada@work.com
xaffinity person update 12345 --first-name Ada --last-name Byron

xaffinity person field <personId>

xaffinity person field 12345 --set "Current Job Title" "CEO" --set "Phone Number" "+1-555-0123"
xaffinity person field 12345 --set "Source of Introduction" 12346 --unset Tags
xaffinity person field 12345 --get "Current Organization"

All --set / --set-json / --unset of one command are written in one request that Affinity applies completely or not at all: if one value is rejected, no field changes. Global, enriched and Source of Introduction fields can be written by name or field id; interaction fields (First Email, ...) and enriched dropdowns can't. Multi-value fields take at most 100 values. A value equal to the current one is not rewritten. JSON output: created ({fieldId, name, value}) and cleared ({fieldId, name}).

These writes use Affinity API version 2026-07-15 or newer: without --api-version the CLI sends that version for the write; with --api-version 2024-01-01 the command exits 2 (api_version_error) before sending anything.

xaffinity person delete <personId>

xaffinity person delete 12345

xaffinity person merge-history ls / get <mergeId>

Past person merges, newest first. Same options as company merge-history below.

xaffinity person relationships <person>

How strongly your team members are connected to this person. Same options as company relationships below.

xaffinity person merge <primaryId> <duplicateId>

Merges the duplicate into the primary; the duplicate is removed and this cannot be undone. Asks for confirmation; pass --yes to skip it (needed in scripts that don't answer the prompt).

xaffinity person merge 111 222 --yes

xaffinity person files ls <person>

Lists the files attached to a person. Takes --max-results, --all, --page-size and --cursor.

xaffinity person files ls 12345
xaffinity person files ls 12345 --all --json

xaffinity person files read <personId> --file-id <fileId>

Reads one file's content, base64-encoded, in chunks (--limit, default 1MB). The output's nextOffset is the --offset for the next chunk (null when done).

xaffinity person files read 12345 --file-id 456 --json
xaffinity person files read 12345 --file-id 456 --offset 1048576 --json

xaffinity person files download <personId>

With --file-id, downloads one file to --out. Without it, downloads every file attached to the person into the --out folder, with a manifest.json (--concurrency, --max-files, --overwrite).

xaffinity person files download 12345 --file-id 456 --out ./doc.pdf
xaffinity person files download 12345 --out ./bundle

Saved files use the original filename when possible; if several files share a name, the file id is appended.

xaffinity person files upload <personId>

Uploads one or more files to a person.

xaffinity person files upload 12345 --file doc.pdf
xaffinity person files upload 12345 --file a.pdf --file b.pdf

With --json, each entry in data.uploads includes fileId (and createdAt) of the new file, so you can pass it straight to files read --file-id / files download --file-id on the same record. fileId is null if the API did not return the created file.

Companies

xaffinity company ls

List companies. Supports field selection, filter expressions, and free-text search.

Options:

  • --query <term> / -q <term>: free-text search (name or domain)
  • --field <id-or-name> (repeatable): field ID or name to include
  • --field-type <type> (repeatable): field type to include (global, enriched, relationship-intelligence)
  • --filter <expression> (repeatable, values ANDed): filter expression; must include a condition on a company field (Affinity's search applies it)
  • --page-size, --cursor, --max-results, --all: pagination options
  • --csv: output as CSV (to stdout)
  • --csv-bom: add UTF-8 BOM for Excel compatibility

Note: --filter goes through Affinity's search: the conditions on company fields that Affinity can answer are applied on its side, and the whole filter (including attributes like name or domain) then runs on what comes back. A filter with only attribute conditions (e.g. name =~ "Acme") is refused, since it would mean reading Affinity's whole company directory; search names and domains with --query. When part of the filter runs locally, --all or --max-results is required. Filters on enriched fields warn that a few records may be missed. The next-page cursor of a filtered run starts with xsearch2: and --cursor alone resumes it. --query and --filter can't be combined. With --json, meta.serverFilter lists the conditions Affinity applied (pushed) and those checked locally (local); a --cursor resume doesn't report it.

Note: --query can be combined with --field and --field-type to search with field data.

xaffinity company ls
xaffinity company ls --page-size 50
xaffinity company ls --field-type enriched --all
xaffinity company ls --filter 'Industry = "Software"'
xaffinity company ls --filter 'Industry = "Software" & name =~ "Labs"' --max-results 50
xaffinity company ls --query "example.com"
xaffinity company ls --query "Example" --field-type enriched --all
xaffinity company ls --all --csv > companies.csv
xaffinity company ls --all --csv --csv-bom > companies.csv

See the CSV Export Guide for more details.

xaffinity company search <prompt>

Find companies from a natural-language description (V2 semantic search). The prompt (1–500 characters) describes what you want rather than a name; results come ranked by a relevance score. To find a company by name or domain, use company ls --query or company get "domain:..." instead.

Options:

  • --list <name-or-id> (repeatable, max 100): only companies on these lists (company lists only)
  • --max-results N / --limit N / -n N: 1–100 (API default 100; larger values are clamped to 100)

No pagination: one request returns at most 100 companies. JSON output has one row per company (id, name, domain, domains, isGlobal, score) and the API's reading of the prompt in meta.explanation; table output shows the explanation above the rows.

xaffinity company search "AI infrastructure startups"
xaffinity company search "seed-stage fintech in Europe" --list "Dealflow" -n 20 --json

xaffinity company get <companySelector>

Fetch a company by id, UI URL (including tenant hosts), or a resolver selector.

Examples:

xaffinity company get 67890
xaffinity company get "https://mydomain.affinity.com/companies/67890"
xaffinity company get domain:wellybox.com
xaffinity company get 'name:"WellyBox"'

Default output is a cheap lookup. Without flags, no field values and no list entries are fetched: data.company.fields is {"requested": false, "data": {}} and data.listEntries is absent. That means not fetched, not empty. In JSON, meta.notRequested lists what was skipped and the flag that fetches it, e.g. [{"key": "data.company.fields", "flag": "--all-fields"}, {"key": "data.listEntries", "flag": "--expand list-entries"}]. Expansions are returned next to the entity (data.listEntries, data.lists, data.persons), not inside data.company. person get behaves the same way; opportunity get reports data.opportunity.fields (fetch with --details).

Field selection:

  • --all-fields: include all supported (non-list-specific) fields.
  • --field <id-or-exact-name> (repeatable)
  • --field-type <type> (repeatable)
  • --no-fields: skip fields entirely.

Expansions:

  • --expand lists: include lists the company is on (auto-paginates up to a safe cap; use --max-results / --all to adjust).
  • --expand list-entries: include list entries for the company (first page by default; use --max-results / --all to fetch more).
  • --expand persons: include people associated with the company (use --max-results / --all to control volume).
  • --list <id-or-exact-name>: filter list entries to a specific list (implies --expand list-entries).
  • --list-entry-field <id-or-exact-name> (repeatable): project list-entry fields into columns (implies --expand list-entries). Field names are only allowed with --list.
  • --show-list-entry-fields: render per-list-entry Fields tables in human output (requires --expand list-entries and --max-results <= 3). Mutually exclusive with --list-entry-field.
  • --list-entry-fields-scope list-only|all: control which fields appear in list-entry tables (human output only).

Interaction dates (V1 API):

  • --with-interaction-dates: include interaction date summaries (last/next meeting, email dates). Uses V1 API.
  • --with-interaction-persons: include person IDs involved in each interaction (requires --with-interaction-dates).
xaffinity company get 67890 --all-fields --expand lists
xaffinity company get 67890 --expand list-entries --list "Dealflow" --max-results 200
xaffinity company get 67890 --expand list-entries --list "Dealflow" --list-entry-field Stage --list-entry-field Amount
xaffinity company get 67890 --expand list-entries --max-results 1 --show-list-entry-fields
xaffinity company get 67890 --expand list-entries --max-results 1 --show-list-entry-fields --list-entry-fields-scope all
xaffinity company get 67890 --expand persons --max-results 50
xaffinity company get 67890 --all-fields --expand lists --json | jq '.data.company.name'

# Include interaction dates (last meeting, next meeting, email dates)
xaffinity company get 67890 --with-interaction-dates --json
xaffinity company get 67890 --with-interaction-dates --with-interaction-persons --json

xaffinity company create

xaffinity company create --name "Acme Corp" --domain acme.com
xaffinity company create --name "Acme Corp" --person-id 12345

xaffinity company update <companyId>

xaffinity company update 67890 --domain acme.com
xaffinity company update 67890 --person-id 12345 --person-id 12346

xaffinity company field <companyId>

xaffinity company field 67890 --set Stage "Series A" --set-json '{"Technologies": ["Python", "Go"]}'
xaffinity company field 67890 --unset Stage
xaffinity company field 67890 --get Description

All --set / --set-json / --unset of one command are written in one request that Affinity applies completely or not at all: if one value is rejected, no field changes. Global, enriched and Source of Introduction fields can be written by name or field id; interaction fields (First Email, ...) and enriched dropdowns can't. Multi-value fields take at most 100 values. A value equal to the current one is not rewritten. JSON output: created ({fieldId, name, value}) and cleared ({fieldId, name}).

These writes use Affinity API version 2026-07-15 or newer: without --api-version the CLI sends that version for the write; with --api-version 2024-01-01 the command exits 2 (api_version_error) before sending anything.

xaffinity company delete <companyId>

xaffinity company delete 67890

xaffinity company merge-history ls / get <mergeId>

Past company merges, newest first: id, status, taskId, startedAt, primaryCompanyId, duplicateCompanyId, completedAt, errorMessage.

xaffinity company merge-history ls --status failed
xaffinity company merge-history ls --task-id https://api.affinity.co/v2/tasks/company-merges/<id>
xaffinity company merge-history get 481975

Needs the "Manage duplicates" permission and the organization admin role.

xaffinity company relationships <company>

Who on your team knows people at this company, and how well (V2).

xaffinity company relationships 123 --min-score 0.3 --max-results 20
xaffinity company relationships domain:acme.com --order asc --all

Rows: person1, person2 (personId, name, email), interactionScore (0.0-1.0, strongest first by default) and linkedInConnectedOn. Relationships known only from LinkedIn have score 0, so any --min-score above 0 drops them. Needs Affinity API version 2026-07-15 or newer; a profile pinned to an older version gets an error.

xaffinity company merge <primaryId> <duplicateId>

Merges the duplicate into the primary; the duplicate is removed and this cannot be undone. Asks for confirmation; pass --yes to skip it (needed in scripts that don't answer the prompt).

xaffinity company merge 111 222 --yes

xaffinity company files ls <company>

Lists the files attached to a company. Takes --max-results, --all, --page-size and --cursor.

xaffinity company files ls 9876
xaffinity company files ls 9876 --all --json

xaffinity company files read <companyId> --file-id <fileId>

Reads one file's content, base64-encoded, in chunks (--limit, default 1MB). The output's nextOffset is the --offset for the next chunk (null when done).

xaffinity company files read 9876 --file-id 456 --json
xaffinity company files read 9876 --file-id 456 --offset 1048576 --json

xaffinity company files download <companyId>

With --file-id, downloads one file to --out. Without it, downloads every file attached to the company into the --out folder, with a manifest.json (--concurrency, --max-files, --overwrite).

xaffinity company files download 9876 --file-id 456 --out ./doc.pdf
xaffinity company files download 9876 --out ./bundle

Saved files use the original filename when possible; if several files share a name, the file id is appended.

xaffinity company files upload <companyId>

Uploads one or more files to a company.

xaffinity company files upload 9876 --file doc.pdf
xaffinity company files upload 9876 --file a.pdf --file b.pdf

With --json, each entry in data.uploads includes fileId (and createdAt) of the new file, so you can pass it straight to files read --file-id / files download --file-id on the same record. fileId is null if the API did not return the created file.

Opportunities

xaffinity opportunity ls

List opportunities.

Options:

  • --page-size, --cursor, --max-results, --all: pagination options
  • --csv: output as CSV (to stdout)
  • --csv-bom: add UTF-8 BOM for Excel compatibility
xaffinity opportunity ls
xaffinity opportunity ls --page-size 200 --all --json
xaffinity opportunity ls --all --csv > opportunities.csv
xaffinity opportunity ls --all --csv --csv-bom > opportunities.csv

See the CSV Export Guide for more details.

xaffinity opportunity get <opportunitySelector>

Fetch an opportunity by id or UI URL (including tenant hosts).

xaffinity opportunity get 123
xaffinity opportunity get "https://mydomain.affinity.com/opportunities/123"
xaffinity opportunity get 123 --details

Notes: - --details fetches a fuller payload with associations and list entries.

xaffinity opportunity create

Create a new opportunity.

xaffinity opportunity create --name "Series A" --list "Dealflow"
xaffinity opportunity create --name "Series A" --list 123 --person-id 1 --company-id 2

xaffinity opportunity update <opportunityId>

Update an opportunity (replaces association arrays when provided).

xaffinity opportunity update 123 --name "Series A (Closed)"
xaffinity opportunity update 123 --person-id 1 --person-id 2

xaffinity opportunity delete <opportunityId>

xaffinity opportunity delete 123

xaffinity opportunity field <opportunityId>

Gets, sets and unsets an opportunity's field values (--get, --set, --set-json, --unset). An opportunity's fields are its list entry's fields, so the options and rules are those of list entry field.

xaffinity opportunity field 123 --get Status --get Stage
xaffinity opportunity field 123 --set Status "Active" --set Stage "Negotiation"
xaffinity opportunity field 123 --unset Status

xaffinity opportunity files ls <opportunity>

Lists the files attached to an opportunity. Takes --max-results, --all, --page-size and --cursor.

xaffinity opportunity files ls 123
xaffinity opportunity files ls 123 --all --json

xaffinity opportunity files read <opportunityId> --file-id <fileId>

Reads one file's content, base64-encoded, in chunks (--limit, default 1MB). The output's nextOffset is the --offset for the next chunk (null when done).

xaffinity opportunity files read 123 --file-id 456 --json
xaffinity opportunity files read 123 --file-id 456 --offset 1048576 --json

xaffinity opportunity files download <opportunityId>

With --file-id, downloads one file to --out. Without it, downloads every file attached to the opportunity into the --out folder, with a manifest.json (--concurrency, --max-files, --overwrite).

xaffinity opportunity files download 123 --file-id 456 --out ./doc.pdf
xaffinity opportunity files download 123 --out ./bundle

Saved files use the original filename when possible; if several files share a name, the file id is appended.

xaffinity opportunity files upload <opportunityId>

Uploads one or more files to an opportunity.

xaffinity opportunity files upload 123 --file doc.pdf
xaffinity opportunity files upload 123 --file a.pdf --file b.pdf

With --json, each entry in data.uploads includes fileId (and createdAt) of the new file, so you can pass it straight to files read --file-id / files download --file-id on the same record. fileId is null if the API did not return the created file.

Lists

xaffinity list ls

xaffinity list ls
xaffinity list ls --query pipeline --json
xaffinity list ls --all --json

--query keeps lists whose name contains the text (case-insensitive; matched by Affinity). --type filters each page after it is read, so without --all or --max-results only the first page is checked; when that page has none of the type but more pages exist, a warning says so.

xaffinity list create

xaffinity list create --name "Dealflow" --type opportunity --private
xaffinity list create --name "People" --type person --public --owner-id 42

xaffinity list get <list>

Accepts a list ID or an exact list name.

The Fields table includes a valueType column (e.g., dropdown-multi, ranked-dropdown).

xaffinity list get 123
xaffinity list get "My Pipeline" --json

xaffinity list export <list>

Exports list entries with selected fields. This is the most powerful CSV export command, supporting custom fields and complex filtering.

Options:

  • --csv: output as CSV (to stdout)
  • --csv-bom: add UTF-8 BOM for Excel compatibility
  • --field <id-or-name> (repeatable): include specific fields
  • --saved-view <name>: use a saved view's field selection (server-side filtering)
  • --filter <expression> (repeatable, values ANDed): filter expression; Affinity's search applies what it can, the rest runs locally (see below)
  • --company-id <id> (repeatable): entity-scoped export — return only rows where the entry is for this company
  • --person-id <id> (repeatable): entity-scoped export — return only rows where the entry is for this person
  • --first-page-only: fetch a single page of results; output includes meta.truncated=true and meta.truncationReason="firstPageOnly" when there are more pages
  • --expand <type> (repeatable): expand associated entities or interaction data
  • persons, companies, opportunities: Expand related entities. A failed fetch follows --expand-on-error (raise fails the export; skip leaves the entry out, with a warning listing the skipped entries; for opportunities, a failed check of one opportunity leaves out only that opportunity, with a warning); auth errors always fail it, and an opportunity that is gone is left out
  • interactions: Add interaction date summaries (last/next meeting, email dates). A failed fetch follows --expand-on-error (raise fails the export; skip leaves the summary empty with a warning); auth errors always fail it
  • --check-unreplied: Check for unreplied incoming messages (email/chat) for each list entry. An entity that is gone (deleted or merged) has nothing unreplied; any other failed check follows --expand-on-error (raise fails the export; skip leaves it empty with a warning), and auth errors always fail it
  • --unreplied-types <types>: Comma-separated types to check: email, chat, all (default: email,chat)
  • --unreplied-lookback-days <days>: Lookback period for unreplied message detection (default: 30)

--filter and Affinity's search. Conditions on list fields that Affinity's search can answer are applied on its side, so only matching entries are downloaded; the whole filter still runs locally on what comes back. When Affinity applies the whole filter, --all, --max-results and --first-page-only are not required (page one is the first page of matches). When part of the filter runs locally, one of them is required, otherwise the command exits with code 2 and the error's details.local names the conditions that stay local. The next-page cursor of a filtered export starts with xsearch2: and carries the search, so --cursor alone resumes it. If Affinity refuses the search (e.g. the API key lacks the "Export data from Lists" permission), the export reads the whole list and filters locally, with a warning. With --json, meta.serverFilter reports the split (in --dry-run, data.serverFilter): pushed (conditions Affinity's search applied), local (what runs only on your machine, with reasons) and, when the search was refused or turned off, fallback (why; the whole filter then ran locally). A --cursor resume doesn't report it. CSV and table output report the split as warnings on stderr. XAFFINITY_QUERY_SERVER_FILTER=0 turns server filtering off for list entries (query on listEntries and list export); company and person filters always use it. --saved-view also filters on Affinity's side; for one known company or person use --company-id / --person-id. See Filtering for which conditions Affinity applies.

Truncation envelope. JSON output includes a meta object with truncated (boolean) and, when truncated=true, a truncationReason string naming the cause (firstPageOnly here; field history-bulk uses partialFailure). Always check this before treating a response as complete.

xaffinity list export 123 --csv > out.csv
xaffinity list export "My Pipeline" --saved-view "Board" --csv > out.csv
xaffinity list export 123 --field Stage --field Amount --filter '"Stage" = "Active"' --csv > out.csv
xaffinity list export 123 --filter 'Stage = "Active" & entityName =~ "Labs"' --max-results 500 --csv > out.csv
xaffinity list export 123 --csv --csv-bom > out.csv

# Entity-scoped export (dedup-on-create pattern)
xaffinity list export "Pipeline" --company-id 555 --json
xaffinity list export "Pipeline" --person-id 12345 --json

# Single-page probe — useful when you only need the first batch
xaffinity list export "Pipeline" --first-page-only --json

# Include interaction dates (last meeting, next meeting, email dates)
xaffinity list export "Dealflow" --expand interactions --json
xaffinity list export "Dealflow" --expand interactions --csv > pipeline.csv

# Check for unreplied incoming messages (email + chat)
xaffinity list export "Pipeline" --check-unreplied --json
xaffinity list export "Pipeline" --check-unreplied --unreplied-lookback-days 60 --csv > unreplied.csv

# Check email-only unreplied messages
xaffinity list export "Pipeline" --check-unreplied --unreplied-types email --json

See the CSV Export Guide for more details.

List Entry Commands

Shorthand: All list entry commands are also available as top-level entry commands:

xaffinity entry get 123 456          # Same as: xaffinity list entry get 123 456
xaffinity entry field 123 456 ...    # Same as: xaffinity list entry field 123 456 ...

xaffinity list entry get <list> <entryId>

Shows one list entry with its field values and field names.

xaffinity list entry get "Portfolio" 12345
xaffinity list entry get 67890 12345 --json

xaffinity list entry add <list>

xaffinity list entry add 123 --person-id 12345
xaffinity list entry add "Dealflow" --company-id 67890

xaffinity list entry delete <list> <entryId>

xaffinity list entry delete 123 98765

xaffinity list entry field <list> <entryId>

Unified command for getting, setting, appending, and unsetting field values.

# Set a single field
xaffinity list entry field "Portfolio" 123 --set Status "Active"

# Set multiple fields
xaffinity list entry field "Portfolio" 123 --set Status "Active" --set Priority "High"

# Append to a multi-value field (e.g., tags)
xaffinity list entry field "Portfolio" 123 --append Tags "Priority"

# Set a location
xaffinity list entry field "Portfolio" 123 --set HQ '{"city": "Paris", "country": "France"}'

# Unset a field (remove all values)
xaffinity list entry field "Portfolio" 123 --unset OldField

# Unset a specific value from a multi-value field
xaffinity list entry field "Portfolio" 123 --unset-value Tags "OldTag"

# Batch set via JSON
xaffinity list entry field "Portfolio" 123 --set-json '{"Status": "Active", "Priority": "High"}'

# Get specific field values
xaffinity list entry field "Portfolio" 123 --get Status --get Priority

# Swap tags (append new, remove old)
xaffinity list entry field "Portfolio" 123 --append Tags "NewTag" --unset-value Tags "OldTag"

Notes: - Field names are resolved case-insensitively - Field IDs (field-123) can be used directly - All --set, --set-json and --unset of one command go to Affinity in one request, applied all-or-nothing: if Affinity rejects any value, no field changes. --set replaces the field's value; --unset clears it. Use --append to add to multi-value fields; --append on a single-value field is refused (use --set) - The same field named twice (by name, other casing or field-<id>) is refused - JSON output: created lists each field set (fieldId, name, value), cleared each field --unset; deleted counts values removed by --unset-value - opportunity field works the same way (an opportunity's fields are its list entry's fields) - Numbers are sent as numbers (--set Amount 5); anything else is refused before any write - true/false anywhere in a --set-json value (including inside a list or object, e.g. {"Tags": [true]}) is refused before any write (exit 2, validation_error): Affinity has no boolean field type; write text ("true"), a number or a dropdown option - Locations are JSON objects with any of streetAddress (or street_address), city, state, country, continent: --set HQ '{"city": "Paris", "country": "France"}' - Multi-value company, person and location fields hold at most 100 values per write; a request that would exceed it is refused before anything changes - --get is exclusive with write operations - --get returns resolved objects for person/company reference fields (with firstName, lastName, primaryEmailAddress, etc.) and full dropdown option data (with text, color). Output format matches list export. - Operation order: one request for --set/--set-json/--unset → --append → --unset-value - If an --append or --unset-value step fails (including Affinity answering a delete with success: false, exit 1 api_error), the command stops. If this command already changed something, the error is partial_write (exit 1), whose message and details name it: written lists fields set, unset or appended; removed lists {field, value} for each value an earlier --unset-value deleted - If the failing --append or --unset-value got no response from Affinity (a timeout or network error), it may or may not have been applied: the message says so, and details.uncertain lists {field, action, value} for it (action is append or remove). With nothing else changed, the error is network_error (exit 1) with the same details.uncertain. Re-running the command is safe: an append skips values already there and removing a value that is gone succeeds

Notes

xaffinity note ls

xaffinity note ls
xaffinity note ls --person-id 123 --json

xaffinity note get <noteId>

xaffinity note get 9876

xaffinity note search <prompt>

Keyword search over the text of all notes (V2 note search). The prompt is 3–500 characters. Use note ls to list the notes attached to a record instead.

Options:

  • --company-id <companySelector>: only notes on this company (id, URL, name:..., domain:...)
  • --note-id <id> (repeatable, max 100): only search these notes; exclusive with --company-id
  • --max-results N / --limit N / -n N: 1–100 (API default 20; larger values are clamped to 100)

Each row has noteId, kind (note, meeting-note, email-note, ai-summary, meeting-ai-summary, chat-message-note) and preview, the matching passage. Table output trims the preview to about 200 characters; JSON and the other formats keep it in full. Results are ordered by relevance with no pagination; a prompt with no strong match can still return low-relevance hits.

xaffinity note search "Series B terms"
xaffinity note search "pricing" --company-id "domain:acme.com" -n 5 --json
xaffinity note get 9876   # full text of a hit

xaffinity note feed

Notes across the organization, newest first (V2). note ls / note get (V1, per entity) are unchanged.

xaffinity note feed --created-after -7d --max-results 50
xaffinity note feed --creator-id 123 --with-attached

Rows: id, type (entities, interaction, ai-notetaker), creator, createdAt, updatedAt, content (HTML), mentionedPersonIds, interactionId, transcriptId. --with-attached adds repliesCount and companyIds / personIds / opportunityIds with totals. Filters: --created-after (inclusive), --created-before (exclusive), --updated-after, --creator-id; replies are not included.

xaffinity note replies <noteId>

The replies to a note: id, type, creator, content, parentId, createdAt.

xaffinity note create

xaffinity note create --content "Met with the team" --person-id 123
xaffinity note create --content "<p>Meeting notes</p>" --type html --company-id 456
xaffinity note create --content "Agreed" --parent-id 9876

Affinity stores notes as HTML. Plain text (the default) is escaped and wrapped in paragraphs: a blank line starts a new paragraph. With --type html, <b>/<i> become <strong>/<em>, and what Affinity rejects (style/class attributes, img, pre, code, blockquote, links other than http, https or mailto, ...) fails before anything is sent, naming the allowed tags. A reply (--parent-id) has no persons, companies or opportunities of its own. The output has the same keys as note get.

xaffinity note update <noteId>

xaffinity note update 9876 --content "Updated note content"
xaffinity note update 9876 --content "<p><strong>Decision:</strong> go</p>" --type html

The content is plain text unless you pass --type html, whatever the note was before. The text of a note that @mentions people can't be changed through the API.

xaffinity note delete <noteId>

xaffinity note delete 9876

Files

Files on one record are under company|person|opportunity files ...; file-url <fileId> returns a presigned download link.

xaffinity file search <prompt>

Keyword search inside the contents of all files (V2 file search). The prompt is 3–500 characters.

Options:

  • --company-id <companySelector>: only files on this company
  • --file-id <id> (repeatable, max 100): only search these files; exclusive with --company-id
  • --max-results N / --limit N / -n N: 1–100 (API default 20; larger values are clamped to 100)

One row per file: fileId, name, pageNumber (null for files without pages) and preview, the matching passage (trimmed in table output, full in JSON).

xaffinity file search "pitch deck"
xaffinity file search "revenue projections" --company-id 12345 --json

xaffinity file-url <fileId>

Returns a presigned download URL for a file (valid for about 60 seconds; no API key needed to use it).

xaffinity file-url 456
xaffinity file-url 456 --json

Reminders

Reminders use the V2 API. Only a reminder's creator or owner may change it there; when V2 refuses, update and delete retry through the V1 API. A type change also goes through V1. Every reminder is attached to exactly one person, company or opportunity.

xaffinity reminder ls

The --due-after and --due-before options accept the same date formats as --due-date: - ISO-8601: 2025-01-15, 2025-01-15T09:00:00Z - Relative: +7d, +2w, +1m, +1y - Keywords: now, today, tomorrow, yesterday

xaffinity reminder ls
xaffinity reminder ls --owner-id 42 --status active --json
xaffinity reminder ls --due-after today --due-before +7d

Results come soonest-due first, up to 100 per page (--page-size); --all fetches every page. Rows: id, type, status, content, dueDate, resetType (interaction, email or event), reminderDays, ownerId, creatorId, completerId, personId, companyId, opportunityId, createdAt, updatedAt, completedAt.

xaffinity reminder get <reminderId>

xaffinity reminder get 12345

xaffinity reminder create

The --due-date option accepts multiple formats: - ISO-8601: 2025-01-15, 2025-01-15T09:00:00Z - Relative: +7d (7 days), +2w (2 weeks), +1m (1 month), +1y (1 year) - Keywords: now, today, tomorrow, yesterday

xaffinity reminder create --owner-id 42 --type one-time --due-date +7d --person-id 123
xaffinity reminder create --owner-id 42 --type one-time --due-date tomorrow --person-id 123
xaffinity reminder create --owner-id 42 --type one-time --due-date 2025-01-15T09:00:00Z --person-id 123
xaffinity reminder create --owner-id 42 --type recurring --reset-type interaction --reminder-days 3 --company-id 456

--reset-type is interaction, email or event (meeting is accepted for event); --reminder-days is 1-3650.

xaffinity reminder update <reminderId>

Only the options you pass change.

xaffinity reminder update 12345 --content "Follow up after demo"
xaffinity reminder update 12345 --completed
xaffinity reminder update 12345 --clear-content

--completed on a recurring reminder moves it to its next due date; --not-completed reopens a one-time reminder.

xaffinity reminder delete <reminderId>

xaffinity reminder delete 12345

Interactions

xaffinity interaction ls

List interactions for an entity. Requires --type, one entity selector (--person-id, --company-id, or --opportunity-id), and a date range (--days or --after). Auto-chunks date ranges > 1 year into API-compatible segments.

xaffinity interaction ls --type email --person-id 123 --days 30
xaffinity interaction ls --type meeting --person-id 123 --after 2025-01-01 --before 2025-02-01
xaffinity interaction ls --type call --company-id 456 --days 365 --json

xaffinity interaction feed

Emails, meetings, calls or chat messages across the organization (V2), not tied to one entity.

xaffinity interaction feed --type email --after -7d --max-results 50
xaffinity interaction feed --type meeting --after 2025-06-01 --before 2025-07-01 --all
xaffinity interaction feed --type chat --updated-after -1d
  • --type email|meeting|call|chat-message (chat also works).
  • --after (inclusive) / --before (exclusive) filter the sent time (emails, chat) or start time (meetings, calls); --created-after and --updated-after filter when Affinity logged or last changed the item (items never changed are left out by --updated-after). Times are rounded outward to whole seconds.
  • Rows: emails have subject, from, to, cc; meetings and calls title, startTime, endTime, organizer (meetings), attendees; chat messages creator, participants. Each list holds up to 10 people (email, personId, name) plus a total (toTotal, …).
  • Only items the API key's user may see; an email subject they may not see shows as ********.
  • No sort option. For a sync, page to the end and keep the latest times yourself.

xaffinity interaction get <interactionId>

xaffinity interaction get 2468 --type meeting

xaffinity interaction create

xaffinity interaction create --type meeting --person-id 123 --content "Met to discuss roadmap" --date 2025-01-10T14:00:00Z
xaffinity interaction create --type email --person-id 123 --content "Intro email" --date 2025-01-05T09:15:00Z --direction outgoing

xaffinity interaction update <interactionId>

xaffinity interaction update 2468 --type meeting --content "Updated meeting notes"

xaffinity interaction delete <interactionId>

xaffinity interaction delete 2468 --type meeting

Fields

xaffinity field ls

xaffinity field ls --entity-type company
xaffinity field ls --list-id 123 --json

xaffinity field create

xaffinity field create --name "Stage" --entity-type opportunity --value-type dropdown --list-specific
xaffinity field create --name "Co-investors" --entity-type company --value-type company-multi

--value-type accepts what the API can create: text, number, datetime, location, dropdown, ranked-dropdown, person, company, and the -multi variants number-multi, location-multi, dropdown-multi, person-multi, company-multi (a -multi type implies --allows-multiple). Read-only types such as interaction, formula-number or filterable-text cannot be created.

xaffinity field delete <fieldId>

xaffinity field delete field-123

xaffinity field history

Show field value change history for a field: on one entity, or across all entities.

xaffinity field history FIELD_ID [OPTIONS]

Arguments:

  • FIELD_ID (required): Field ID (e.g., field-123). Use field ls --list-id LIST to find IDs.

Options:

  • --person-id <id>: Filter by person
  • --company-id <id>: Filter by company
  • --opportunity-id <id>: Filter by opportunity
  • --list-entry-id <id>: Filter by list entry
  • --action-type <type>: Filter by action (create, update, delete)
  • --changed-after <when>: Only changes at or after this time (ISO date/datetime, or relative such as -30d)
  • --order desc|asc: Newest first (default) or oldest first
  • --max-results <n>: Limit number of results (the most recent N, or the oldest N with --order asc)

Use at most one entity selector. Without one, history covers every entity, so --changed-after and/or --max-results is required.

Each row's value is the value at that point. For a dropdown it is an object with text ({"id": 11, "text": "Lead", "rank": 1, "color": 2}); an option deleted since is {"id": null, "text": "Reach Out", "deleted": true}, so compare stages by value.text. Telling the two apart needs the field's type: when some value is plain text, the CLI reads the field's metadata (one call); if that fails, values are shown as sent, with a warning. field history-bulk rows have the same value.

xaffinity field history field-123 --person-id 456
xaffinity field history field-123 --company-id 789 --action-type update
xaffinity --json field history field-123 --list-entry-id 101 --max-results 20
xaffinity field history field-123 --changed-after -30d --order asc

xaffinity field changes

List field value changes across all entities and fields (V2): what changed, where, who changed it, and when. Useful for delta sync and audits.

xaffinity field changes [OPTIONS]

Options (filters combine with AND; repeated values of one option combine with OR):

  • --field-id <id>: Only this field (repeatable)
  • --list-entry-id <id>: Only this list entry (repeatable)
  • --changer-id <id>: Only changes made by this person
  • --changed-after <when> / --changed-before <when>: Time window (at or after / before; rounded outward to whole seconds)
  • --action-type add|update|delete: Kind of change (V2 names; field history says create for add)
  • --order asc|desc: Oldest first (default) or newest first
  • --max-results <n>, --all, --cursor <cursor>: Paging (one page of up to 100 by default, with nextCursor)

There is no person/company/opportunity filter; use field history for one entity. Only fields with change tracking are included. Each row has id, fieldId, fieldName, fieldEntityType, fieldScope (global/list), entityId, listEntryId, listId, actionType, valueType, value, changedAt, changerId, changerName. A dropdown value is a reference: {"referenceType": "entity", "id": 3, "text": "Won"}, or for an option deleted since {"referenceType": "deleted-entity", "displayValue": "Old"} (displayValue, not text).

xaffinity field changes --field-id field-123 --changed-after -7d
xaffinity field changes --list-entry-id 101 --order desc --max-results 20
xaffinity --json field changes --changed-after 2025-06-01T00:00:00Z --all

For delta sync, store the latest changedAt you processed and pass it as --changed-after next time. It is inclusive, so skip ids you already have.

xaffinity field history-bulk

A field's change history for many list entries at once (pipeline analysis).

xaffinity field history-bulk field-358027 --list-id Dealflow --all --dry-run
xaffinity field history-bulk field-358027 --list-id Dealflow --all
xaffinity field history-bulk field-358027 --list-id Dealflow --max-results 50
xaffinity field history-bulk field-358027 --list-entry-ids 100,200,300

With --all on a list of 100 or more entries, the history is read field-wide: one call per 10,000 changes instead of one per entry, with the same rows. Otherwise there is one call per entry, 15 at a time. --strategy field|entries forces either (a forced field read that fails is an error; it never falls back to per-entry calls); the dry run shows fieldKind, the strategy and estimatedApiCalls (the per-entry worst case). Changes on entries no longer on the list are left out (a warning says how many). Rows are sorted by changedAt. When some per-entry fetches fail, the rest are returned with a warning per failure and meta.truncated=true, meta.truncationReason="partialFailure" (exit 0); when every fetch fails, or on an authentication or permission error, the command fails. When every fetch fails and the failures share an exit code, the command keeps it (4 not found; 5 rate limit or server error); otherwise it exits 1.

Global fields (company or person fields, not the list's own): their changes belong to the company or person, not to a list entry. The CLI reads them per company/person (one call each) and labels each row with that company's or person's entry on the list (listEntryId); they include changes made before it joined the list. Field-wide, a global field's history covers the whole org, so auto uses it only if it fits in one call (otherwise per company/person, with a warning) and a forced --strategy field reads up to 200,000 changes, then stops with an error. --list-entry-ids can't map entries to companies, so a global field there is a usage error, as are enriched fields (no change history) and fields that aren't on the list.

Each row's value is as in field history: dropdown options are objects with text, and an option deleted since is {"id": null, "text": "...", "deleted": true}.

xaffinity field options

Options of a dropdown, ranked-dropdown or status field.

xaffinity field options ls field-123 --list-id Dealflow        # list field (also opportunity fields)
xaffinity field options ls field-456 --entity-type company     # global company/person field
xaffinity field options create field-123 --list-id Dealflow --text "Due diligence"
xaffinity field options update field-123 4567 --list-id Dealflow --text "Closed - won" --color green
xaffinity field options delete field-123 4567 --list-id Dealflow --yes
  • ls shows id, text, type (dropdown, ranked-dropdown, status-dropdown), rank, color and, for status fields, statusCategory and winRate.
  • create, update and delete work on list fields only (global fields' options can't be changed through the API) and need Affinity API version 2026-07-15 or newer.
  • create takes the type from the field's existing options (--type if it has none). For ranked and status fields --rank defaults to after the last option and --color to white; status fields also need --status-category open|won|lost|on-hold (--win-rate 0-100 for open ones). Colors: white, gray, blue, green, purple, orange, red.
  • update changes only what you pass; renaming changes the value shown on every entry that has the option.
  • delete cannot be undone: Affinity also clears the field on every list entry that has the option. It asks for confirmation (naming the option); scripts pass --yes.

Transcripts

Meeting transcripts from Affinity's AI Notetaker. Only transcripts your API key's user may see are returned.

xaffinity transcript ls

xaffinity transcript ls --created-after -7d
xaffinity transcript ls --max-results 50 --with-note

Metadata only: id, createdAt, languageCode and a note reference (noteId, type, interactionId, transcriptId); --with-note includes the whole AI Notetaker note. --created-after (inclusive) and --created-before (exclusive) are rounded outward to whole seconds. Paging: --max-results, --all, --cursor.

xaffinity transcript get <transcriptId>

xaffinity transcript get 123            # note + preview of the first fragments
xaffinity transcript get 123 --all      # every fragment

Fragments have content, speaker, startTimestamp and endTimestamp (offsets such as 00:00:06); fragmentsTotal says how many exist. --max-results N fetches the first N.

Relationship Strengths

xaffinity relationship-strength ls

Relationship strengths between a person outside your organization (--external-id, required) and your team's users (--internal-id for one user).

xaffinity relationship-strength ls --external-id 12345
xaffinity relationship-strength ls --external-id 12345 --internal-id 42

Tasks

xaffinity task ls

Company or person merge tasks (batches of merges), newest first.

xaffinity task ls --kind company-merge --status failed

Needs the "Manage duplicates" permission and the organization admin role.

xaffinity task get <taskUrl>

xaffinity task get https://api.affinity.co/tasks/person-merges/123

xaffinity task wait <taskUrl>

xaffinity task wait https://api.affinity.co/tasks/person-merges/123 --timeout 120

Webhooks

Webhooks send CRM events to a URL (V2 API, beta; Affinity API version 2026-07-15 or later). Without permission to manage all webhooks, you see and change only the webhooks you created. create, update and delete ask for confirmation; scripts pass --yes.

xaffinity webhook ls

xaffinity webhook ls --all
xaffinity webhook ls --created-after 2026-01-01 --id 12 --id 13

Rows: id, url, status (active/disabled), subscriptions, allEvents (true when the webhook receives every event type), creatorId, createdAt, updatedAt.

xaffinity webhook get <webhookId>

xaffinity webhook get 12

xaffinity webhook create

xaffinity webhook create --url https://hooks.example.com/affinity --event list_entry.created --event field_value.updated

Name the events with --event (repeatable); --all-events sends every change in the organization. Affinity sends a test delivery to the URL first and rejects an unreachable or duplicate URL.

xaffinity webhook update <webhookId>

xaffinity webhook update 12 --status disabled
xaffinity webhook update 12 --event note.created

Only what you pass changes; --event replaces the whole event list. Prints the webhook as read back.

xaffinity webhook delete <webhookId>

xaffinity webhook delete 12

Teams and users

xaffinity team ls

The teams you can see (V2, beta; Affinity API 2026-09-17+).

xaffinity team ls --all
xaffinity team ls --name-contains invest --with-members

--name matches exactly, --name-contains anywhere. --with-members / --with-lists add the first 10 members / accessible lists and the totals. --sort name|-name|createdAt|-createdAt.

xaffinity team get <teamId>

xaffinity team get 42 --with-members --with-lists

xaffinity team members <teamId> / xaffinity team lists <teamId>

Every member of a team (userId, name, email, addedAt), or every list it has access to (id, name, type, isPublic). team get --with-members / --with-lists show only the first 10. Paging: --max-results, --all, --cursor.

xaffinity team members 42 --all
xaffinity team lists 42

xaffinity user ls

The organization's internal users. A user's id is also their person id.

xaffinity user ls --term ann
xaffinity user ls --status active --all

Rows: id, name, email, status, and role / emails (null without the "Manage Users" permission).

xaffinity user get <userId>

xaffinity user get 12345

Meetings

xaffinity meeting get <meetingId>

One meeting: title, times, organizer, creator and attendees (V2, API 2026-07-15+). For many meetings, use interaction feed --type meeting.

xaffinity meeting get 123456

Note attachments

xaffinity note attached <noteId>

Every company, person and opportunity a note is attached to (all of them; note get shows a preview).

xaffinity note attached 555

Feedback

xaffinity feedback send

Send feedback to Affinity's product team. Asks for confirmation (--yes in scripts): the text leaves your organization.

xaffinity feedback send --type api --subject "Note edits with mentions" --body "..."

Duplicates and warm intros

xaffinity company duplicates / xaffinity person duplicates

Pairs Affinity thinks are duplicates, not yet merged or dismissed (V2, beta; API 2026-07-15+).

xaffinity company duplicates --match domain-match --max-results 20
xaffinity person duplicates --term dana

Rows: suggestionId, matchCriteria, primary and duplicate (id, name, domain or email). Company criteria: domain-match (high confidence), domain-redirect (medium), name (low); person: intelligent (medium), name (low). Merge with company merge / person merge.

xaffinity company connections <company>

Warm intros into a company: people working there today whom someone you know may know (V2, beta).

xaffinity company connections domain:acme.com
xaffinity company connections 12345 --type investor-executive --all

--type coworkers (default; worked together before: sharedEmployer, from, to) or investor-executive (an investor you know backed a company the target ran: investorFirm, round, roundDate). One row per target (name, title, LinkedIn), strongest first, with via.