Commands¶
No-network commands¶
These commands never call the Affinity API:
xaffinity --helpxaffinity completion bash|zsh|fishxaffinity version(alsoxaffinity --version)xaffinity config pathxaffinity config initxaffinity 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 keyproject:.envfile in current directory (auto-added to.gitignore)user: User config file (config.toml, withchmod 600on 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-readableCommandResultJSON 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-progressfor 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 endis 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 andmeta.truncated; withorderByor an aggregate, the cut comes after sorting and aggregating. A--cursorresumes only with the same--max-records--timeout <secs>: Query timeout (default: 300)--csv/--json: Output format--include-meta: include execution metadata;meta.serverFiltershows 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/--allto adjust).--expand list-entries: include list entries for the person (first page by default; use--max-results/--allto 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-entriesand--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/--allto adjust).--expand list-entries: include list entries for the company (first page by default; use--max-results/--allto fetch more).--expand persons: include people associated with the company (use--max-results/--allto 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-entriesand--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 includesmeta.truncated=trueandmeta.truncationReason="firstPageOnly"when there are more pages--expand <type>(repeatable): expand associated entities or interaction datapersons,companies,opportunities: Expand related entities. A failed fetch follows--expand-on-error(raisefails the export;skipleaves the entry out, with a warning listing the skipped entries; foropportunities, 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 outinteractions: Add interaction date summaries (last/next meeting, email dates). A failed fetch follows--expand-on-error(raisefails the export;skipleaves 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(raisefails the export;skipleaves 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(chatalso works).--after(inclusive) /--before(exclusive) filter the sent time (emails, chat) or start time (meetings, calls);--created-afterand--updated-afterfilter 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 callstitle,startTime,endTime,organizer(meetings),attendees; chat messagescreator,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). Usefield ls --list-id LISTto 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 historysayscreateforadd)--order asc|desc: Oldest first (default) or newest first--max-results <n>,--all,--cursor <cursor>: Paging (one page of up to 100 by default, withnextCursor)
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
lsshowsid,text,type(dropdown,ranked-dropdown,status-dropdown),rank,colorand, for status fields,statusCategoryandwinRate.create,updateanddeletework on list fields only (global fields' options can't be changed through the API) and need Affinity API version 2026-07-15 or newer.createtakes the type from the field's existing options (--typeif it has none). For ranked and status fields--rankdefaults to after the last option and--colorto white; status fields also need--status-category open|won|lost|on-hold(--win-rate 0-100for open ones). Colors: white, gray, blue, green, purple, orange, red.updatechanges only what you pass; renaming changes the value shown on every entry that has the option.deletecannot 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.