- Brand
- Affinity
- Category
- Sales & CRM
- Primary Subcategory
- Investor & Deal-Flow Relationship CRM
Integration details
Description
Bring your Affinity data into ChatGPT to search contacts, companies, and deals; prep for meetings using your actual notes and interaction history; and update records as deals progress. Every email captured, every meeting logged, every relationship scored by your firm is now available directly in the conversation.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- Investor & Deal-Flow Relationship CRM
- Secondary Subcategories
- None listed
- Brand
- Affinity
- Access
- Account required
- First tracked
- 2026-05-22
- Tool count
- 87
- Market
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Your score is coming
ChatGPT now suggests Plugins on its own when they match a user's request.Your Plugin Discovery Score measures how often yours appears, and it will show here as soon as it’s ready.
What discovery looks like

Get alerts for Affinity
Get updates when Affinity’s Discoverability Score or category rank changes.
Competing in ChatGPT Investor & Deal-Flow Relationship CRM
View Category87 tools agents can invoke
Use when a user wants to import, migrate, or load data into Affinity — companies, people, or opportunities — from any source: another CRM, a CSV or spreadsheet export. Also use when the user wants to re-import records that failed a previous import, or clean and prepare a file for Affinity's CSV importer. Do NOT use for: LinkedIn connections import (Settings → LinkedIn Data), email/meeting interaction history (comes from email sync), or restoring deleted data.
Set up a complete event tracking workflow in Affinity CRM for private capital teams. Use this skill whenever a user wants to track an event in Affinity — including creating global fields for attendance, building lists for attendees (companies and/or people), adding list-level fields for event-specific data, sourcing attendees from CRM relationships, reviewing historical event data, drafting event communications, or processing post-event attendance from a CSV or manual list. Also use when the user mentions "event list", "conference tracking", "attendee list", "event setup in Affinity", "post-event follow-up", or "update attendance status". This skill runs an interactive, end-to-end workflow directly against the Affinity MCP.
Report what changed in an LP fundraising pipeline in Affinity over a chosen window — new and increased commitments, stage changes, losses, and LPs needing attention — grounded only in Affinity data: notes, logged field changes, list-entry creation dates, meetings, and last-email dates. Use whenever the user asks what changed in their LP pipeline, wants an LP, fundraising, or investor-relations update, asks for a weekly brief, asks who moved or what happened with LPs or limited partners since last week, or wants fundraising progress from Affinity. Trigger even without the word "brief": "what's new with our LPs", "catch me up on the raise", and "anything move on the fundraise" all belong here. For LPs investing into the firm's funds, not for deal or portfolio pipelines. Reads the CRM; writes only on explicit confirmation.
Produce a relationship-intelligence pre-meeting brief from Affinity CRM data via the Affinity MCP. Use whenever the user asks to "prep me for", "prepare for", "brief me on", "get me ready for", or "tell me about" an upcoming meeting, call, person, or company, or asks what they should know before a meeting. Always runs a full multi-tool Affinity sequence and synthesizes it, never a single lookup. Read-only: never writes to the CRM.
Anti-portfolio report: finds companies the firm passed on or lost, while there was still time to invest, that went on to raise their next-stage round in a chosen period. Ranks the most painful misses, shows where every data point comes from, and breaks the misses down by pass reason, industry, geography and team. Use when someone asks for an anti-portfolio review or report, missed deals, "who did we pass on that just raised", a quarterly or annual pass review, deals they personally passed on, or patterns in pass reasons. Runs on demand against one deal pipeline list in Affinity.
Find reference-check sources on a named person (a founder, CEO, executive, hiring candidate, operating partner, or advisor), or on the founders of a named company. Builds the background from a pitch deck or other material the user has, public sources such as funding announcements, and Affinity files, then uses Affinity relationship data to find who on the user's team knows them, former colleagues and ex-employees, early investors and co-investors, angels and board members, customers that are portfolio or pipeline companies, people seen in meetings with them, and what past notes already say. Use whenever the user asks for references, backchannel, reference checks, "who can vouch for", "who worked with", "who can tell me about" a person, or wants diligence on a founder or management team, or is vetting a candidate for a portfolio company, operating partner, board, or advisor role, even if they never say the word "reference". Read-only: never writes to the CRM or sends anything.
Collect recent dated public signals (product launches, funding rounds, executive hires and departures, acquisitions, partnerships, IPO filings, press coverage, earnings) for a given list of companies, each with a source link and a confidence state. Use whenever the user asks "what's new at these companies?", "any recent news, funding, or exec changes for X, Y, Z?", "what have these companies launched lately?", or wants a news scan of named companies or of an Affinity list (a list link works). Other skills also call it to get signals for their own company lists. Searches company blogs and feeds first, then the web. Read-only: never writes to the CRM. Always use this skill for company news scans instead of answering from general knowledge.
Create a new company in Affinity. IMPORTANT: Call `search_companies_top_matches` first with the company's name or domain to avoid creating duplicates. Affinity does not deduplicate on create. Args: name: The company's name. Must be at least 1 character. Example: "Acme Corporation" domain: The company's primary domain, hostname only, no protocol or path. Example: "acme.com" person_ids: IDs of persons to associate with the company at creation time. Use `search_persons` to look up person IDs first. These associations may be applied asynchronously by the API. Example: [1234, 5678] Returns: The newly created Company object including id, name, domain, domains, and global fields. If `person_ids` were provided, the returned company may still show an empty or stale `persons` list immediately after creation because person associations are assigned asynchronously. Fetch the company again later if you need to verify the associated persons.
Create a new field (custom column) on a list or globally on an entity. IMPORTANT: - value_type takes a string such as "text" or "dropdown-multi". A "-multi" type holds many values. The returned id is a field_id such as "field-1234", not a number. - This tool does NOT create dropdown options. When value_type is a dropdown or ranked-dropdown, the field is created with no options. For a list-specific field (list_id provided), pass the returned id as field_id to `create_list_field_dropdown_option`. For a global field (list_id omitted), options must be managed in the Affinity app. - Omitting `list_id` creates a global field on every entity of `entity_type`. This requires org-level "manage global fields" permission and will fail without it. Prefer passing a `list_id` unless the user explicitly wants a global field. - If the user asks for a "dropdown" field without saying whether it should allow one selection or multiple, ask before calling this tool. "dropdown" holds one selection, "dropdown-multi" holds many, and "ranked-dropdown" holds one selection from ranked options. A field cannot change from one of these types to another later. If the user's request already makes this clear (for example "multi-select dropdown", "ranked dropdown", "single choice"), proceed without asking. - Some organizations cannot save a field description yet. If this tool says so, no field was created. Call it again without description. - A global opportunity field (entity_type=8, list_id omitted) cannot have a description. - A name that another field already uses returns a 400 ("<name> is an existing column."). Args: name: The field name. Example: "Relationship Strength" value_type: The kind of value the field holds. Options: "text", "number", "datetime", "location", "location-multi", "person", "person-multi", "company", "company-multi", "dropdown", "dropdown-multi", "ranked-dropdown". entity_type: The entity the field applies to. One of EntityType.COMPANY, EntityType.PERSON, or EntityType.OPPORTUNITY. For a list-specific field this must match the list's entity type. list_id: ID of the list to attach the field to. When provided, the field is created as list-specific. When omitted, a global field is created (see IMPORTANT). Use `get_lists` to look up list IDs. description: What the field is for (up to 255 characters). Omit it for no description. is_required: Whether the field is required. Only meaningful for list-specific fields. Setting this to True requires list-admin permission; it is only sent when True so that non-admin editors can still create optional fields. Defaults to False. Returns: The created field: id (the field_id for `update_field` and the dropdown option tools, for example "field-1234"), type ("list" or "global"), name, description, valueType, and isRequired.
Log an interaction (meeting, call, or chat message) with one or more people. Args: interaction_type: The kind of interaction to log: - InteractionType.MEETING (0) - InteractionType.CALL (1) - InteractionType.CHAT_MESSAGE (2) If unclear which type the user means, ask before calling. date: When the interaction occurred, in ISO 8601 format. Example: "2025-01-31T10:56:29Z" person_ids: IDs of the persons involved in the interaction. At least one is required. Use search_persons to look up person IDs by name or email. content: Notes describing the interaction. direction: Direction of the message. Required when interaction_type is CHAT_MESSAGE; not applicable to meetings and calls (omit it for those). - ChatMessageDirection.SENT (0): message sent to the contact - ChatMessageDirection.RECEIVED (1): message received from the contact Returns: dict[str, Any]: API response containing the created interaction.
Create a new list for companies, people, or opportunities. IMPORTANT: The entity type is fixed when the list is created and cannot be changed later. A company list holds companies, a person list holds people, and an opportunity list holds opportunities. IMPORTANT: Setting is_public=True requires the "Share accessible Lists globally" permission. Without it, the request fails with a 403. Default to a private list (is_public=False) unless the user explicitly asks for a public/shared list. This tool only creates the empty list. To add entities, call create_list_entry (companies/people) or create_opportunity (opportunities) afterward. Args: name (required): Name of the list (1 to 255 characters). entity_type (required): Entity type the list holds. One of EntityType.COMPANY, EntityType.PERSON, or EntityType.OPPORTUNITY. is_public: Whether the list is visible to everyone in the organization. Defaults to False (private to the creator). Returns: JSON object with the new list's id, name, type, isPublic, creatorId, and ownerId.
Adds an existing company or person as a list entry on the specified list. Opportunities CANNOT be added using this tool. Args: list_id: ID of the list where the entry is being added. entity_id: ID of the existing person or company to add to this list. Returns: JSON object containing information about the newly created list entry.
Create a new dropdown option on a list's dropdown field. Adds a selectable value to a dropdown, ranked-dropdown, or status-dropdown column on a list, e.g. a new "Not Relevant" option on a Communication Status field or a new "Negotiating" status on a deal pipeline's Status field. IMPORTANT: - option_type must match the field's actual type. A mismatch returns a 400. Call `get_list_field_dropdown_options` first and use the `type` of the existing options. Do not rely on `get_list_fields`: it reports a list's Status field as valueType "ranked-dropdown", but its options are "status-dropdown". - For a ranked-dropdown field, both rank and color are required. For a plain dropdown field, none of rank, color, status_category, or win_rate may be set. - For a status-dropdown field (a list's Status field), rank, color, and status_category are all required; win_rate is optional. Args: list_id: ID of the list the field belongs to. Use `get_lists` to look it up. field_id: Human-readable field ID, e.g. "field-1234". Get it from `get_list_fields`. option_type: The field's dropdown type. One of DropdownOptionType.DROPDOWN, DropdownOptionType.RANKED_DROPDOWN, or DropdownOptionType.STATUS_DROPDOWN (serialized as "dropdown", "ranked-dropdown", or "status-dropdown"). text: The option label (1 to 255 characters). Must be unique on the field. rank: Sort position for ranked-dropdown and status-dropdown options (0 to 2147483647). Required for those two types, omit for plain dropdown. color: Tag color for ranked-dropdown and status-dropdown options. Options: 0 = white, 1 = gray, 2 = blue, 3 = green, 4 = purple, 5 = orange, 6 = red. Required for those two types, omit for plain dropdown. status_category: Status-dropdown only; required. One of "open", "won", "lost", "on-hold". win_rate: Status-dropdown only; optional. Probability (0 to 100) that an entity in this status progresses to a "won" status. Only valid when status_category is "open". Returns: The created option with id, type, text, and (for ranked-dropdown and status-dropdown) rank and color, plus statusCategory and winRate for status-dropdown.
Create a note attached to entities or a meeting, or as a reply to an existing note. IMPORTANT: When picking `meeting_id` from get_meetings_for_entity, ALWAYS use the response's `v2_id` field if it is present and non-null. Only fall back to the `id` field when `v2_id` is absent. Meetings, calls, and chat messages each allow at most one root note. This tool can create that root note only for a meeting, using meeting_id. A meeting synced from a calendar may not have one yet, so creating a meeting note when one already exists fails with an error that names the existing note's id. Calls and chat messages are created with their note, so they always already have a root note. To add to a call or chat message, or to a meeting that already has a note, reply instead: set note_type=USER_REPLY and parent_note_id to that note's id, and tell the user you are replying. parent_note_id accepts the root note of a meeting, call, or chat message. To find the note id: - create_interaction and get_meetings_for_entity return it in their response, so reply directly. - get_meetings does not return it, so try creating the meeting note first. If it fails with the root-note error, retry as a USER_REPLY using the id from the error. Args: note_type: Which kind of note to create. Options: - NoteType.ENTITIES (0): note tied to persons, companies, or opportunities. Requires at least one of person_ids, company_ids, opportunity_ids. - NoteType.INTERACTION (1): note tied to a meeting. Requires meeting_id. Entity IDs may be supplied for additional associations. - NoteType.USER_REPLY (2): reply to an existing note. Requires parent_note_id. Cannot be combined with entity IDs or interaction params. If unclear which type the user wants, ask before calling. content: HTML content of the note. Allowed tags (with no attributes other than those explicitly noted): <p>, <br>, <strong>, <em>, <u>, <ol>, <ul>, <li>, <span> (no attributes), <a> (only href, with http, https, or mailto URL schemes). Disallowed (any of these will cause the request to fail): inline style attributes, class attributes, <img>, <script>, <iframe>, <style>, <blockquote>, <hr>, <s>, <pre>, <code>, <font>. Mentions: mention spans (<span data-type="note-mention" ...>) are also disallowed. Mentions cannot be created or modified through this endpoint. Anchor normalization: the server appends rel="noopener noreferrer" and target="_blank" to every <a> before the note is saved, so the saved HTML will contain those attributes even though they were not sent. person_ids: Person IDs to attach the note to. Max 100. Used by ENTITIES and INTERACTION. company_ids: Company IDs to attach the note to. Max 100. Used by ENTITIES and INTERACTION. opportunity_ids: Opportunity IDs to attach the note to. Max 100. Used by ENTITIES and INTERACTION. meeting_id: ID of the meeting. Only required when note_type=INTERACTION. See the IMPORTANT note above for which id to use when sourcing this from a V1 response. parent_note_id: ID of the parent note to reply to. Only required when note_type=USER_REPLY. May be the root note of a meeting, call, or chat message. Returns: The created note containing id, type, content, creator, mentions, createdAt, updatedAt, plus additional fields depending on note type, and note_url pointing to the note in the Affinity CRM.
Creates a new Opportunity on the specified list. The list must be an opportunity-type list. Companies and/or persons can optionally be associated with the new Opportunity at creation time. The list entry on the target list is auto-created, no separate `create_list_entry` call is needed. IMPORTANT: Call `search_opportunities` first with the opportunity's name to avoid creating duplicates. Affinity does not deduplicate on create. NOTE: Custom field values cannot be set during creation. If the user wants to set field values on the new Opportunity, follow up with `get_list_fields` to discover field IDs and `upsert_list_entry_field_values` to set them on the list entry returned in `list_entries[0]`. Args: name: Name of the Opportunity. list_id: ID of the opportunity-type list. company_ids: IDs of existing companies to associate with the new Opportunity. person_ids: IDs of existing persons to associate with the new Opportunity. Returns: JSON object containing the newly created Opportunity. Top-level keys: `id`, `name`, `organization_ids` (list[int]), `person_ids` (list[int]), `list_entries` (list).
Creates a new person (contact) in Affinity. Use for adding people not yet in the CRM, e.g. importing from a CSV or a hand-entered contact. IMPORTANT: - Call `search_persons` first with the person's name or email to avoid creating duplicates. Affinity does not deduplicate on create. - `company_ids` takes Affinity company IDs (integers), not company names or domains. If the user provides a company name or domain, resolve it via `search_companies_top_matches` first and pass the resulting IDs. If unclear, ask before calling. Args: first_name: Required. The person's first name. last_name: Required. The person's last name. emails: Required. List of email addresses for the person. Pass an empty list `[]` if none are known. Example: `["alice@affinity.co"]`. company_ids: Optional list of Affinity company IDs the person is associated with. Example: `[1687449]`. Returns: The newly created person object including its assigned `id`, `type`, `first_name`, `last_name`, `primary_email`, `emails`, and `organization_ids`.
Creates a new reminder. SCHEDULING BY DAY NAME: If the user specifies a recurring reminder by day name (e.g. "every Friday", "every Sunday"), calculate the next occurrence of that day from today's date and use it as the due_date for the first reminder. Set reminder_days to match the recurrence interval (e.g. 7 for weekly, 14 for bi-weekly). You must still supply reset_type and the other recurring fields documented under reset_type and reminder_days below; this section only helps derive due_date and reminder_days. Args: owner_id: Required. Internal person ID who owns the reminder (team member only; not an external contact). reminder_type: Required. ReminderType.ONE_TIME (0) or ReminderType.RECURRING (1). If unclear, ask before calling. entity_id: Required with entity_type. ID of the company, external/collaborator person, or opportunity to attach the reminder to. If the user has not chosen a target yet, ask before calling. entity_type: Required with entity_id. Options: 0 = person, 1 = company, 8 = opportunity. content: Optional description or note text for the reminder. due_date: Required when reminder_type is ONE_TIME; omit for recurring unless you are supplying a first due date (e.g. day-name scheduling). ISO 8601 timestamp at noon UTC. If the user has not given a due date for a one-time reminder, ask before calling. The API stores due_date as an instant and renders it in the user's local timezone, so the chosen instant must land on the correct calendar day after that rendering. Use noon UTC (e.g. "2025-01-31T12:00:00Z") -- it lands on the same calendar day in every timezone from UTC-12 through UTC+11. Do not use UTC midnight ("2025-01-31T00:00:00Z"): it rolls back to the previous day in every Western timezone. Example: "2025-01-31T12:00:00Z" reset_type: Required when reminder_type is RECURRING. What triggers the reset: ReminderResetType.INTERACTION (0), EMAIL (1), or EVENT (2). If missing, ask before calling. reminder_days: Required when reminder_type is RECURRING; minimum 1. If missing, ask before calling. is_completed: Optional; only for ONE_TIME. Whether the reminder is completed on creation. Do not pass for RECURRING (the API rejects it). Returns: dict[str, Any]: API response containing the created reminder.
Audit the health of an Affinity CRM and deliver a workbook and a one-page HTML audit summary: what is in place today (Your CRM), every check that was run and what it found (Checks), one row per finding with what to do, who does it and a status to track (Findings), and the reference tabs behind them. Use when a user asks for a "CRM audit", to "audit our CRM", "audit our data", "check data quality", "how healthy is our Affinity data", "clean up our CRM", "data hygiene report", or asks what is wrong with or missing from their lists, fields, companies or people. Also trigger for a single audit by name ("just audit our lists"). Also trigger when an audit request asks to find or merge duplicates: the skill explains that duplicate records are out of scope and points to Duplicate Management. Read-only and recommendation-only: never modifies records and never executes clean-up, even when asked.
Flags which companies in a customer's own Affinity list or pipeline are statistically due to raise again, using the customer's own Affinity MCP connector -- no custom artifact or hosted tool needed, just Claude plus their connector. Trigger on typing "RTR" (case-insensitive, standalone or as a prefix like "RTR my pipeline list") or on a plain-language ask like "which of my portfolio companies are due to raise," "check this list for re-raise signals," "run the ready-to-raise estimate," or pasting/uploading/referencing a company list with a raise-timing ask. Runs the full methodology directly against the customer's own live Affinity data, calculated fresh at the moment it's invoked.
Permanently deletes one or more companies (organizations) from Affinity by company id. Confirmation gate: with confirm=false (the default) nothing is deleted. The response is a summary (warning, companies_preview, total, global_count, related_totals, next_step). Show the whole warning to the user in your own message, unquoted, keeping every company name it shows, the count, and the sentence that says what is kept: a short question alone hides how far the deletion reaches. Call again with confirm=true only after the user approves (or already approved this deletion in the conversation). A user who wants to see what the deletion affects does not need another delete_company call: read it yourself with get_company_list_entries, get_company_info and get_reminders. next_step carries the details. WARNING: this deletes the company record itself, not just a list membership. List entries on all lists, field values, and tagged reminders are deleted with it. Notes, files, people, and opportunities are kept. There is no undo. Global companies (enriched from Affinity's global dataset) cannot be deleted. global_count covers the whole batch. A confirmed call still tries every id and reports the global ones in failed with a 422. If the user removes a company from a list and you hold list entry ids, use delete_list_entries instead; the company record stays. Deleting a company also removes all its list entries. Never call both tools for the same company. company_ids are company ids, NOT list entry ids. Get them from search_companies_top_matches, get_company_info, the id returned by create_company, or the entity id of an entry on a company list (search_list_entries, get_single_list_entry); ask if the target is unclear. Args: company_ids: 1-100 company ids per call. A repeated id counts toward the limit. Repeated ids are removed before the preview and the deletion. confirm: false returns the summary only; true performs the deletion. Returns: confirm=false: the summary, nothing deleted. related_totals is {"list_entries": n, "partial": bool}: how many list entries the batch takes with it, partial=true when the number is a lower bound because an id could not be read. Lists hidden from the API key are never counted and do not set partial: the API filters them out with no signal. confirm=true: {"success", "deleted", "deleted_count", "failed": [{company_id, status_code, error}], "failed_count"}, one attempt per id. Unknown id: 422 (v1 has no 404). Global company: 422 "Cannot delete organization because it is global". API key without External API access: 403.
Permanently delete a field (custom column) and every value set on it. Works on a custom field of a list, or on a custom global company field. Confirmation gate: with confirm=false (the default) nothing is deleted. The response is a summary (warning, field, records_with_values, list_name for a list field, next_step). records_with_values is the number of list entries or companies with a value on the field, or null when Affinity could not count them. Show the whole warning to the user in your own message, keeping the field name, the list name and the count. Call again with confirm=true only after the user approves (or already approved this deletion in the conversation). IMPORTANT: - Use the field's `type` from `get_list_fields` or `get_entity_fields`. For "list", pass the list_id. For a "global" company field, omit list_id. - Person fields, global opportunity fields, and enriched or native fields are not supported. A field on an opportunity list is a list field, so pass its list_id. - A list field needs the Admin role on the list. A user with the Standard role can delete it only when no list entry has a value on it. A company field needs the org-level "manage global fields" permission. Without the permission, the confirmed call returns a 403. - A list field that Affinity manages, such as a list's Status field, returns a 400 ("Field cannot be deleted"). - To remove one option from a dropdown field, use `delete_list_field_dropdown_option`, not this tool. Args: field_id: ID of a custom field, for example "field-1234". list_id: ID of the list that owns a list field. Omit it for a global company field. confirm: false returns the summary only; true performs the deletion. Returns: confirm=false: the summary, nothing deleted. confirm=true: {"success": true, "field_id": field_id}. A field or list that does not exist returns a 404 ("Resource not found"). A global company field with a list_id returns the same 404. Omit list_id for it.
Deletes one or more list entries, removing those entities' rows from a list. Confirmation gate: with confirm=false (the default) nothing is deleted — the response is a summary of what would be deleted (warning, entries_preview, total, list_type, list_name, next_step). Present the warning to the user in your own message, unquoted, keeping every entry name it shows, the count, the list name, and the sentence that says what the deletion does to the entity itself. A short confirmation question is not enough on its own: the user cannot tell how far the deletion reaches. Call again with confirm=true only after the user explicitly approves (or already approved this specific deletion in the conversation). For deletions spanning several lists, collect each list's summary first and ask once. WARNING: on an opportunity list, deleting an entry PERMANENTLY DELETES the underlying opportunity (requires manage permission, otherwise 403). On company/person lists only the membership is removed; the entity is untouched. If the user refers to the opportunity itself, use delete_opportunity. If the user refers to the company itself, and wants it removed from Affinity and not only from the list, use delete_company. If the user refers to the person themselves, and wants them removed from Affinity and not only from the list, use delete_person. If the user removes rows from a list and you hold list entry ids, this tool is correct, also on an opportunity list. Never call both tools for the same entity. list_entry_ids are the entries' own ids, NOT company/person/opportunity ids. Get them from create_list_entry, search_list_entries, get_company_list_entries, get_person_list_entries, or get_single_list_entry; ask if the target is unclear. Args: list_id: ID of the list the entries belong to. list_entry_ids: 1-100 entry ids per call. A repeated id counts toward the limit. Repeated ids are removed. confirm: false returns the summary only; true performs the deletion. Returns: confirm=false: the summary, nothing deleted. confirm=true: {"success", "deleted", "deleted_count", "failed": [{list_entry_id, status_code, error}], "failed_count"}, one attempt per entry. Invalid id: 422 (v1 has no 404).
Delete a dropdown option from a list's dropdown field, e.g. removing an outdated "Associate" option from a Seniority field. IMPORTANT: - Deleting an option cascades: it is removed from every list entry that had it selected. This cannot be undone. - Works on dropdown, ranked-dropdown, and status-dropdown fields. - Call `get_list_field_dropdown_options` first to get the dropdown_option_id. Args: list_id: ID of the list the field belongs to. field_id: Human-readable field ID, e.g. "field-1234". dropdown_option_id: ID of the dropdown option to delete. Returns: None. The API returns 204 No Content on success.
Permanently deletes one or more notes from Affinity by note id. Confirmation gate: with confirm=false (the default) nothing is deleted. The response is a summary (warning, total, next_step). Show the whole warning to the user in your own message, unquoted, keeping the count and the sentence that says what is kept: a short question alone hides how far the deletion reaches. Call again with confirm=true only after the user approves (or already approved this deletion in the conversation). A user who wants to see what the deletion affects does not need another delete_note call: read it yourself with query_notes and get_entities_attached_to_note. next_step carries the details. WARNING: the note's attached files go with it. Deleting a root note also deletes every reply under it, user and AI Notetaker replies alike; deleting a reply removes only that reply. Pass the root only: a reply listed in the same call as its root is already gone by the time its own DELETE runs and comes back as a 404 under failed, although the note is deleted. The persons, companies, and opportunities the note is attached to are kept. There is no undo, for admins either: the note is deleted outright, not archived. To fix a note's text or the records it is attached to instead, use update_note. You can only delete notes you created. Another user's note fails with 403 for that id, unless the organization has the admin-only-delete-any-note feature and the caller holds the manage-all-notes permission. note_ids are note ids. Get them from query_notes, get_notes_for_entity, search_notes, or the id returned by create_note; if two notes could be meant, resolve which one instead of guessing. Args: note_ids: 1-100 note ids per call. A repeated id counts toward the limit. Repeated ids are removed before the preview and the deletion. confirm: false returns the summary only; true performs the deletion. Returns: confirm=false: the summary, nothing deleted. confirm=true: {"success", "deleted", "deleted_count", "failed": [{note_id, status_code, error}], "failed_count"}, one attempt per id. Not your note: 403. Unknown or inaccessible id: 404. The other ids are still deleted.
Permanently deletes one or more opportunities (deals) from Affinity by opportunity id. Confirmation gate: with confirm=false (the default) nothing is deleted. The response is a summary of what would be deleted (warning, opportunities_preview, total, next_step). Present the warning to the user in your own message, unquoted, keeping every opportunity name it shows, the count, and the sentence that says what is kept. A short confirmation question is not enough on its own: the user cannot tell how far the deletion reaches. Call again with confirm=true only after the user explicitly approves (or already approved this specific deletion in the conversation). WARNING: this deletes the opportunity record itself, not just a list membership. Its list entries, all list-specific field values and their change history, and any reminders tagged to it are deleted with it. Notes and files attached to it are kept but detached from it. There is no undo. If the user only wants to rename or re-associate an opportunity, use update_opportunity. If the user removes rows from a list and you hold list entry ids, use delete_list_entries instead, also on an opportunity list. Deleting an opportunity also removes its list entry. Never call both tools for the same opportunity. opportunity_ids are opportunity ids, NOT list entry ids. Get them from search_opportunities, from the id returned by create_opportunity, or from the entity id of an entry on an opportunity list (search_list_entries, get_single_list_entry); ask if the target is unclear. Args: opportunity_ids: 1-100 opportunity ids per call. A repeated id counts toward the limit. Repeated ids are removed before the preview and the deletion. confirm: false returns the summary only; true performs the deletion. Returns: confirm=false: the summary, nothing deleted. confirm=true: {"success", "deleted", "deleted_count", "failed": [{opportunity_id, status_code, error}], "failed_count"}, one attempt per id. Unknown id: 422 (v1 has no 404). List not visible: 403; Restricted Opportunities orgs also need manage permission.
Permanently deletes one or more people (contacts) from Affinity by person id. Confirmation gate: with confirm=false (the default) nothing is deleted. The response is a summary (warning, persons_preview, total, internal_count, related_totals, next_step). Show the whole warning to the user in your own message, unquoted, keeping every person name it shows, the count, and the sentence that says what is kept: a short question alone hides how far the deletion reaches. Call again with confirm=true only after the user approves (or already approved this deletion in the conversation). A user who wants to see what the deletion affects does not need another delete_person call: read it yourself with get_person_list_entries, get_person_info, get_reminders and query_notes. next_step carries the details. WARNING: this deletes the person record itself, not a list membership and not a company link. List entries on all lists, field values, reminders tagged to the person, and notes the person wrote are deleted with them. Person field values on other records that point to them are deleted too. Other notes, files, emails, and meetings are kept but lose the link. There is no undo. Internal people (your coworkers) cannot be deleted; the preview marks the ones it can see and internal_count covers the whole batch. Affinity also refuses people whose email domain belongs to your organization, and no API field reports that, so a confirmed call can still report them in failed with a 422. If the user takes a person off one list and you hold list entry ids, use delete_list_entries; the person stays. To unlink a person from a company use update_person with company_ids=[]. Never call two of these tools for the same person. person_ids are person ids, NOT list entry ids. Get them from search_persons, get_person_info, get_persons_info, the id returned by create_person, or the entity id of an entry on a person list (search_list_entries, get_single_list_entry); ask if the target is unclear. Args: person_ids: 1-100 person ids per call. A repeated id counts toward the limit. Repeated ids are removed before the preview and the deletion. confirm: false returns the summary only; true performs the deletion. Returns: confirm=false: the summary, nothing deleted. related_totals is {"list_entries": n, "partial": bool}: how many list entries the batch takes with it, partial=true when the number is a lower bound because an id could not be read. Lists hidden from the API key are never counted and do not set partial: the API filters them out with no signal. confirm=true: {"success", "deleted", "deleted_count", "failed": [{person_id, status_code, error}], "failed_count"}, one attempt per id. Unknown id: 422 (v1 has no 404). Internal or inferred-internal person: 422 "Deleting internal or inferred_internal person is not allowed.". API key without External API access: 403.
Deletes a reminder by ID. Requires the current user to be either the creator OR owner of the reminder, otherwise the API returns a permissions error. Args: reminder_id: The ID of the reminder to delete. Returns: JSON object containing confirmation of successful deletion.
Get the logged calls that the current user can see, with their dates and attendees. Each call has a `startTime`: the date and time of the call that the user gave when they logged it. `createdAt` is when the record was created in Affinity, not when the call took place. To select calls by the day they took place, filter on `startTime`. The results include only the calls that the current user has permission to see, so a count of them is not an organization total. Calls come in id order, which is the order in which Affinity stored them, not the order of their dates. The first page holds the calls that Affinity stored first. To get recent calls, filter on `startTime`. To get the calls of one company, person, or opportunity, use get_interactions_for_entity. It returns calls newest first. Args: filter_expr: Filter expression using Affinity query language. Available fields: - id: unique identifier for the call (operators: =) Example: "id=1" or "(id=1 | id=2)" - startTime: When the call took place (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "startTime>=2025-01-01T00:00:00Z" - createdAt: When the call was created in Affinity (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "createdAt>=2025-01-01T00:00:00Z" - updatedAt: When the call was last updated (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "updatedAt<=2025-12-31T23:59:59Z" Date range example: "startTime>=2025-01-01T00:00:00Z & startTime<2025-02-01T00:00:00Z" Boolean logic: & (AND), | (OR), () for grouping Full filtering spec: https://developer.affinity.co/pages/external-api-v2/filtering cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of items to include in the page. Default is 20, max is 20. To get more calls, page through with `cursor`. Returns: A dict with `data` (list of calls) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page). Each call has `id`, `title` (null when the call has no title, "********" when the user cannot see it), `startTime`, `endTime` (can be null), `allDay`, `loggingType` (always "manual"), `creator` (an attendee, or null), `createdAt`, `updatedAt` (can be null), and `attendeesPreview` (`data`, a list of attendees, and `totalCount`). Each attendee has `emailAddress` and `person`, which is null when no person in Affinity has that address.
Get the logged chat messages that the current user can see, with their dates, direction, and participants. The API returns no message text. A user logs a chat message by hand, and the text they enter is kept as a note on the message. get_interactions_for_entity returns the ids of these notes. To read the text of a note, use query_notes with filter_expr "id=<note id>". Chat messages come newest first by `createdAt`, which is when a user logged the message in Affinity, not when it was sent. To select messages by the date they were sent, filter on `sentAt`. To get the chat messages of one company, person, or opportunity, use get_interactions_for_entity. It returns chat messages newest first by the date they were sent. Args: filter_expr: Filter expression using Affinity query language. Available fields: - id: unique identifier for the chat message (operators: =) Example: "id=1" or "(id=1 | id=2)" - sentAt: When the message was sent (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "sentAt>=2025-01-01T00:00:00Z" - createdAt: When the message was logged in Affinity (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "createdAt>=2025-01-01T00:00:00Z" - updatedAt: When the message was last updated (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "updatedAt<=2025-12-31T23:59:59Z" Date range example: "sentAt>=2025-01-01T00:00:00Z & sentAt<2025-02-01T00:00:00Z" Boolean logic: & (AND), | (OR), () for grouping Full filtering spec: https://developer.affinity.co/pages/external-api-v2/filtering cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of items to include in the page. Default is 20, max is 100. Returns: A dict with `data` (list of chat messages) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page). Each chat message has `id`, `sentAt`, `direction` ("sent" or "received"), `loggingType` (always "manual"), `creator`, `createdAt`, `updatedAt` (can be null), and `participantsPreview` (`data`, a list of persons, and `totalCount`).
Returns basic information and non-list specific field data on the requested company. By default, returns all field data including location, description, and more. Use get_entity_fields tool if you need to filter to specific fields. IMPORTANT: field_ids and field_types are mutually exclusive. They cannot be used together. Args: company_id (required): The ID of the company field_ids: List of field IDs to return. field_types: List of field types to return. Defaults to ['enriched', 'global', 'relationship-intelligence'] Returns: Metadata about the specified company including field data. A field on an opportunity you can't manage may come back with type "hidden" and a masked value.
Fetches list entries for the given company. Args: company_id (required): The ID of the company cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: The number of items to include in the page. Default is 100, max is 100 Returns: A dict with `data` (list of list entries) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page).
Get the relationship strengths between your team and the people associated with a company ("who do we know at this company"). Returns person-to-person relationships, not a single company-level score: for the given company, each result pairs an internal team member (person1) with an external contact who works at that company (person2), plus the strength of their relationship. Results are always ordered by strength (highest first), then by id, unless overridden with `order_by`. Relationships with a strength of 0 and no LinkedIn connection, and externals hidden in your org, are excluded. Each result's `interactionScore` is a float between 0 and 1. Convert it to a percentage between 0 and 100, rounded to the nearest integer (.5 or greater rounds up, less than .5 rounds down) when presenting it to the user. A relationship derived purely from a LinkedIn connection, with no interaction history, has an interactionScore of 0. Each result also has a `linkedIn` field: `{"connectedOn": <date>}` when the two people are connected on LinkedIn (whether or not the relationship also has interaction history), or `null` when there is no LinkedIn connection between them. Supported filter property (FIQS syntax): - interactionScore (>, <, >=, <=) e.g. "interactionScore > 0.5" Args: company_id: The ID of the company to get relationships for. cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of items per page (1-100). Default is 100. filter_expr: FIQS filter string to narrow results by interactionScore. Example: "interactionScore >= 0.7". order_by: Sort order. "interactionScore" (ascending) or "-interactionScore" (descending). Omit to keep the default order. total_count: When true, the response pagination object includes the total number of matching relationships. Default is false. Returns: JSON object with: - data: list of relationship objects, each containing person1 (the internal team member) and person2 (the external contact at the company), each with id, firstName, lastName, and primaryEmailAddress, plus interactionScore (float between 0 and 1) and linkedIn ({"connectedOn": <date>} or null). - pagination: object with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page); totalCount is included only when total_count is true.
Find warm intro paths to a company through former coworkers: people at your org whose work history overlaps with people currently at the target company. These are INFERRED second-degree connections derived from shared employment history, not from your org's own interaction history. The people surfaced may never have emailed or met anyone at your org. For relationship strengths based on your org's actual interactions ("who do we know at this company"), use `get_company_relationships` or `get_person_relationships` instead. IMPORTANT: `company_id` is an Affinity company ID, not a name or domain. Call `search_companies_top_matches` first to resolve a company name to its ID. An empty `data` list means there are no inferred connections for the company. Args: company_id: The ID of the target company whose people to find former coworkers of. cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of target-person groups per page (1-50), not individual connections. Default is 20. Values outside 1-50 raise ValueError before any API call. total_count: When true, the response pagination object includes the total number of matching groups. Default is false. Returns: JSON object with: - data: list of groups, one per person at the target company. Each group contains the target person and a `connections` array; each connection pairs a person at your org with the inference that links them, including `sharedEmployer` (the company where they overlapped) and `overlapStartDate` / `overlapEndDate` for the overlap period. - pagination: object with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page); totalCount is included only when total_count is true.
Get information about the current user. Use this tool to verify authentication and understand available API access levels. Returns: Information about the user, their current organization, and API key permissions.
Get the emails that the current user can see, with their dates, subjects, direction, and participants. IMPORTANT: The API never returns email bodies, only metadata. If the user cannot see the subject of an email, `subject` is "********". Emails come in id order, which is the order in which Affinity stored them, not the order of their dates. The first page holds the emails that Affinity stored first. To get recent emails, filter on `sentAt`. To get the emails of one company, person, or opportunity, use get_interactions_for_entity. It returns emails newest first. Args: filter_expr: Filter expression using Affinity query language. Available fields: - id: unique identifier for the email (operators: =) Example: "id=1" or "(id=1 | id=2)" - sentAt: When the email was sent (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "sentAt>=2025-01-01T00:00:00Z" - createdAt: When the email was added to Affinity (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "createdAt>=2025-01-01T00:00:00Z" - updatedAt: When the email was last updated (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "updatedAt<=2025-12-31T23:59:59Z" Date range example: "sentAt>=2025-01-01T00:00:00Z & sentAt<2025-02-01T00:00:00Z" Boolean logic: & (AND), | (OR), () for grouping Full filtering spec: https://developer.affinity.co/pages/external-api-v2/filtering cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of items to include in the page. Default is 20, max is 20. To get more emails, page through with `cursor`. Returns: A dict with `data` (list of emails) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page). Each email has `id`, `sentAt`, `subject`, `direction` ("sent" when someone in the organization sent it, else "received"), `loggingType` (always "automated"), `createdAt`, `updatedAt` (can be null), `from` (an attendee), and `toParticipantsPreview` and `ccParticipantsPreview` (each with `data`, a list of attendees, and `totalCount`). Each attendee has `emailAddress` and `person`, which is null when no person in Affinity has that address.
Find a page of entities directly attached to a note. For the reverse (finding notes attached to an entity), use get_notes_for_entity. A note may have more attachments than one page holds. To answer questions that depend on every attachment (counts, "is X attached", totals), keep calling with `pagination.nextCursor` until it is absent, or pass total_count=True first to learn how many there are. Args: note_id: The note ID that will be used in the search entity_type: Type of entities to retrieve. Options: 0 = person, 1 = company, 8 = opportunity. cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of entities to include in the page. Default is 20, max is 100. total_count: If True, include the total number of attached entities of this type as `pagination.totalCount`. Costs an extra count query, so request it only when the count itself matters. Returns: JSON object with: - data: list of attached entities of the requested type. - pagination: `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param) and `nextUrl` / `prevUrl` full URLs. A missing or null `nextCursor` means this is the last page. `totalCount` is present only when total_count is True.
Returns the dropdown options for a dropdown, ranked-dropdown, or status-dropdown field on a company or person. Use the returned dropdown option IDs when writing dropdown field values via upsert_entity_field_values. Company and person fields are global to the org, so options are resolved from the field alone; no entity ID is needed. A status-dropdown option additionally carries `rank`, `color`, `statusCategory` (one of "open", "won", "lost", "on-hold"), and `winRate` (0-100, only set when statusCategory is "open"). Args: entity_type: 0 = person, 1 = company. field_id: human-readable string ID of field to return options for (ex. "field-123") cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: The number of items to include in the page. Default is 20, maximum is 100. Returns: A dict with `data` (metadata about each dropdown option) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page).
Returns metadata on non-list specific company or person fields. Args: entity_type: Type of entity to return fields for. Options: 0 = person, 1 = company cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: The number of items to include in the page. Default is 100, max is 100. filter: Filter fields by name. `name` is the only filterable property. Two operators, both case-sensitive, so match the field's actual capitalization: - Exact match: name="Location" - Substring match: name=~"Funding" Wrap any value containing spaces in double quotes, e.g. name=~"Last Funding" Combine clauses with & (AND), | (OR), ! (NOT) and parentheses, e.g. name=~"Funding" | name="Location" includes: Extra per-field metadata to return, omitted by default. Request these before building a search_companies_top_matches, search_all_companies, or search_persons filter or sort to confirm the field supports the operator and ordering you intend. - FieldInclude.FILTERABILITY: how the field can be filtered. - FieldInclude.SORTABILITY: whether and how the field can be sorted. See Returns for the shape of each and how to map it onto a search_companies_top_matches, search_all_companies, or search_persons filter or sort. Returns: A dict with `data` (field metadata) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page). Each field includes id, name, type, valueType, and enrichmentSource. Metadata only, no value. `valueType` may also be "note", "reminder", or "list-multi". get_company_info and get_person_info return the value for each. When requested via `includes`, each field also carries `filterability` and/or `sortability`, describing how (if at all) it can be used in a search_companies_top_matches, search_all_companies, or search_persons filter or sort. filterability - how the field can be filtered, or null if it cannot be: - filterableFieldType: "field-only" to filter the field directly, or "attribute-on-field" to filter one of its sub-attributes instead. - operators: the filter operators the field allows. Each operator's `id` (e.g. "is-any-of") is the value to use as the filter `operator`. `name` is human-readable, `numberOfValuesRequired` (none/one/two/multi) is how many values the operator takes, and `relativeDateUnits` lists the time units allowed for relative-date operators. - attributes: only for "attribute-on-field". Each sub-attribute has an `id` (use as the filter `attributeId`), `name`, `valueType` (use as the filter `valueType`), and its own `operators`. sortability - how the field can be sorted, or null if it cannot be: - sortableFieldType: "field-only" to sort by the field directly, or "attribute-on-field" to sort by one of its sub-attributes instead. - attributes: only for "attribute-on-field". Each has an `id` (use as the sort `attributeId`), `name`, and `valueType`.
Get metadata for a single entity file (attachment / document) by ID. Use this to hydrate a file found via search_files with details it does not carry, such as who uploaded it and when. search_files only returns id, name, pageNumber, and preview; this fills in uploader_id and created_at (plus any other metadata fields returned for the file). IMPORTANT: - Use search_files (or get_entity_files) first to find a file and obtain its id; this tool does not search. - Use download_entity_file for the file's actual content; this tool only returns metadata. - This endpoint has no 404 path. A file id that is missing, deleted, or owned by another org fails validation upstream and returns a 422 whose message reads "expected <id> to be a valid id for model Affinity::Models::EntityFile". Treat that 422 as "file does not exist or is not accessible": do not retry, and do not treat it as a malformed request. An ACL denial on a file that does exist returns 403. Either way the call fails outright, never with a partial record. Args: file_id: The unique ID of the entity file to look up. Returns: The file's metadata: id, name, size, uploader_id, created_at, the entity ID field (organization_id/person_id/opportunity_id), and entity_type (0 = person, 1 = company, 8 = opportunity) with entity_type_name (the same value decoded to "person"/"company"/"opportunity"). The two entity_type fields are added by this tool, derived from the entity ID field; the API does not return them. Pass entity_type back to any tool taking an entity_type argument, and show entity_type_name to a person. Both are absent when the file carries no recognized entity ID field, so treat them as optional.
Get files attached to a company, person, or opportunity, or every file in the org. Returns metadata only (id, name, size, timestamps); file contents are not downloaded. Pass both entity_id and entity_type to scope the result to one entity. Omit both to get every file in the org, ordered reverse chronologically (newest entity_files.id first). NO SERVER-SIDE UPLOADER OR DATE FILTER: this endpoint does not accept an uploader or date-range filter. For requests like "what files did I add in the last 2 weeks", omit entity_id/entity_type, page through the org-wide results with page_token, and filter the returned entries yourself by uploader_id and created_at. An org-wide call returns ONE page of files. The org may have more files than one page holds. To answer questions that depend on every file (counts, "did I upload X", date-window sweeps), keep calling with "next_page_token" until it is absent. Args: entity_id: The ID of the company, person, or opportunity whose files should be retrieved. Omit together with entity_type for an org-wide, unfiltered list. entity_type: What type of entity the entity_id refers to. Options: 0 = person, 1 = company, 8 = opportunity. Must be provided together with entity_id, or omitted together with it. page_token: Token for the next page of an org-wide call. Pass the value of "next_page_token" from a prior response. Do NOT parse or modify the token string. Ignored when entity_id/entity_type are provided. page_size: Results per page for an org-wide call. Default 100, max 500. Ignored when entity_id/entity_type are provided. Returns: For an entity-scoped call: structured data containing the list of file metadata entries associated with the entity, under "entity_files". Each entry includes id, name, size, uploader_id, created_at, and the entity ID field (organization_id/person_id/opportunity_id), plus entity_type (0 = person, 1 = company, 8 = opportunity) and entity_type_name (the same value decoded to "person"/"company"/"opportunity"). The two entity_type fields are added by this tool, derived from whichever entity ID field the entry carries; the API does not return them. They are the same fact in two forms: entity_type is the numeric code to pass back to any tool taking an entity_type argument (including this one), and entity_type_name is the label to show a person. An entry with no recognized entity ID field carries neither, so treat both as optional. For an org-wide call: one page of the same under "entity_files", ordered reverse chronologically, plus "next_page_token" (pass directly to the page_token param) when more files exist. A missing "next_page_token" means this is the last page.
Get a paginated list of field value changes across the organization. Designed for delta sync: bound the first call with a `changedAt` filter, then store `pagination.nextCursor` and pass it to subsequent calls to retrieve only new changes since the last sync. The scope is the whole organization and a change's `value` can be a text field, so an unfiltered first call sweeps the org's entire edit history at 100 rows a page. Narrow by `changedAt` rather than paging to the start of time, and do not sweep unprompted — report what the first page holds and offer to fetch more. To get the change history of one company, person, or opportunity: 1. Find the record. If the user gives the record type, use only the search tool for that type: `search_companies_top_matches`, `search_persons`, or `search_opportunities`. If the user gives only a name, search all three types. If more than one record matches, stop and ask the user which record they mean before step 2, also when only one of the records is on a list. Ask with the AskUserQuestion tool if you have it and there are at most 4 records. Give each option the record's type, its name, and one detail that tells the records apart: the domain for a company, the email for a person, or the ID for an opportunity. For more than 4 records, or without that tool, ask in plain text. 2. Find its list entry IDs. For a company, use `get_company_list_entries`. For a person, use `get_person_list_entries`. An opportunity from `search_opportunities` has its list entry IDs in `list_entries`. 3. Call this tool with `filter_expr="listEntry.id=<id>"`. For more than one list entry, combine the IDs with `|`. A `listEntry.id` filter returns changes to list fields only. Changes to global fields have `listEntry: null`, so this filter does not return them. Tell the user that the history covers list fields only. Supported filter properties (FIQS syntax, combine with & and |): - field.id (=) e.g. "field.id=field-1234" - listEntry.id (=) e.g. "listEntry.id=5678" - changer.id (=) e.g. "changer.id=9012" - changedAt (>, <, >=, <=) e.g. "changedAt>=2025-01-01T00:00:00Z" - actionType (=) one of: add, update, delete Args: cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of items per page (1-100). Default is 100. filter_expr: FIQS filter string to narrow results. order_by: Sort order. "changedAt" (ascending, default) or "-changedAt" (descending). Returns: JSON object with: - data: list of field value change objects, each containing id, field, entity, listEntry, changer, changedAt, actionType, type, and value. - pagination: object with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page). The value shape depends on the type discriminator field: - text, filterable-text, filterable-text-multi: string - number, number-multi: float - datetime: ISO 8601 string - location, location-multi: {streetAddress, city, state, country, continent} - person, person-multi: {referenceType, id, firstName, lastName, primaryEmailAddress, type} or {referenceType: "deleted-entity", displayValue} - company, company-multi: {referenceType, id, name, domain} or {referenceType: "deleted-entity", displayValue} - dropdown, dropdown-multi: {referenceType, id, value} or {referenceType: "deleted-entity", displayValue} - ranked-dropdown: {referenceType, id, value, rank} or {referenceType: "deleted-entity", displayValue}
Get the calls, chat messages, or emails (not meetings) of a specific company, person, or opportunity. For meetings, use get_meetings_for_entity. The results include only the interactions that the current user has permission to see. IMPORTANT: The date range between start_time and end_time cannot exceed 365 days. A larger range is clamped to the most recent 365 days (start_time moves forward), and a `warnings` entry in the response states the window that was used. To fetch older interactions, call again with an earlier end_time. start_time must be strictly before end_time: an inverted or empty range causes an error, not a clamp. Args: interaction_type: The kind of interaction to get: - InteractionType.CALL (1) - InteractionType.CHAT_MESSAGE (2) - InteractionType.EMAIL (3) If unclear which type the user means, ask before calling. entity_id: The ID of the company, external person, or opportunity. A person must be an external contact or a collaborator: the API rejects an internal person with "Person is not an external or collaborator". entity_type: What type of entity this ID refers to. Options: 0 = person, 1 = company, 8 = opportunity start_time: ISO 8601 timestamp. Only interactions that took place at or after this time are returned. Must be before end_time. Format: "2025-01-01T00:00:00Z" end_time: ISO 8601 timestamp. Only interactions that took place before this time are returned. Must be after start_time. Format: "2025-01-01T00:00:00Z" direction: Return only chat messages or emails in this direction. When not supplied, returns both. Not applicable to calls (omit it for those). Options: - ChatMessageDirection.SENT (0): sent by someone in your organization - ChatMessageDirection.RECEIVED (1): received by someone in your organization internal_person_id: ID of an internal person that took part in the interactions. This parameter filters the interactions of the given entity down to those in which this internal person took part. Cannot be used to find all of an internal person's interactions. page_size: Number of results per page. Default is 10, maximum is 100. page_token: Token for the next page. Pass the value of "next_page_token" from the previous response. If "next_page_token" is null, the response is the last page. To answer questions that depend on every interaction in the range, call again with each new token until it is null. Do NOT parse or modify the token string. Returns: A dict with `next_page_token` and one list of interactions, newest first by the date of the interaction. The key of the list depends on interaction_type: - CALL: `events`. Each call has `id`, `v2_id` (the call's id in get_calls), `date` and `start_time` (both the time of the call that the user logged), `end_time`, `updated_at`, `title` (null when the call has no title or the user cannot see it), `type` (1), `attendees` (email addresses), `persons`, `manual_creator_id`, `notes` (ids of the notes on the call), and `ical_uid`. - CHAT_MESSAGE: `chat_messages`. Each message has `id`, `date` (when it was sent), `direction` (0 = sent, 1 = received), `type` (2), `persons`, `manual_creator_id`, and `notes` (ids of the notes on the message, which hold the message text). - EMAIL: `emails`. Each email has `id`, `date` (when it was sent), `subject` (null when the user cannot see it), `type` (3), `direction` (0 = sent, 1 = received), `from` (a person, or `{"email": ...}` when no person has that address), and `to` and `cc` (the persons that match those addresses, without the addresses that match no person). Email bodies are never returned. Each person has `id`, `type`, `first_name`, `last_name`, `primary_email`, and `emails`. To read the text of a note, use query_notes with filter_expr "id=<note id>". If the requested date range exceeded 365 days, a `warnings` list describes the clamped window that was used.
Find warm intro paths to a company through investor-executive connections: investors at your firm who are connected to executives at the target company via a shared portfolio company. These are INFERRED second-degree connections derived from investment relationships, not from your org's own interaction history. The people surfaced may never have emailed or met anyone at your org. For relationship strengths based on your org's actual interactions ("who do we know at this company"), use `get_company_relationships` or `get_person_relationships` instead. IMPORTANT: `company_id` is an Affinity company ID, not a name or domain. Call `search_companies_top_matches` first to resolve a company name to its ID. An empty `data` list means there are no inferred connections for the company. Args: company_id: The ID of the target company whose executives to find connections to. cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of target-person groups per page (1-50), not individual connections. Default is 20. Values outside 1-50 raise ValueError before any API call. total_count: When true, the response pagination object includes the total number of matching groups. Default is false. Returns: JSON object with: - data: list of groups, one per person at the target company. Each group contains the target person and a `connections` array; each connection pairs an investor at your org with the inference that links them, including `investingFirm` and `portfolioCompany` (the shared portfolio company behind the connection). - pagination: object with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page); totalCount is included only when total_count is true.
Returns the dropdown options for a specific dropdown, ranked-dropdown, or status-dropdown field on a list. Use the returned dropdown option IDs when writing dropdown field values via upsert_list_entry_field_values. A status-dropdown option additionally carries `rank`, `color`, `statusCategory` (one of "open", "won", "lost", "on-hold"), and `winRate` (0-100, only set when statusCategory is "open"). Args: list_id: ID of list field_id: human-readable string ID of field to return options for (ex. "field-123") cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: The number of items to include in the page. Default is 20, maximum is 100. Returns: A dict with `data` (metadata about each dropdown option) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page).
Returns metadata on fields available for a given list. Args: list_id: ID of list to return fields for. cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: The number of items to include in the page. Default is 100, max is 100. filter: Filter fields by name. `name` is the only filterable property. Two operators, both case-sensitive, so match the field's actual capitalization: - Exact match: name="Location" - Substring match: name=~"Funding" Wrap any value containing spaces in double quotes, e.g. name=~"Last Funding" Combine clauses with & (AND), | (OR), ! (NOT) and parentheses, e.g. name=~"Funding" | name="Location" includes: Extra per-field metadata to return, omitted by default. Request these before building a search_list_entries filter or sort to confirm the field supports the operator and ordering you intend. - FieldInclude.FILTERABILITY: how the field can be filtered. - FieldInclude.SORTABILITY: whether and how the field can be sorted. See Returns for the shape of each and how to map it onto a search_list_entries filter or sort. Returns: A dict with `data` (field metadata) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page). Each field includes id, name, type, valueType, and enrichmentSource. Metadata only, no value. `valueType` may also be "note", "reminder", or "list-multi". get_single_list_entry returns the value for each. When requested via `includes`, each field also carries `filterability` and/or `sortability`, describing how (if at all) it can be used in a search_list_entries filter or sort. filterability - how the field can be filtered, or null if it cannot be: - filterableFieldType: "field-only" to filter the field directly, or "attribute-on-field" to filter one of its sub-attributes instead. - operators: the filter operators the field allows. Each operator's `id` (e.g. "is-any-of") is the value to use as the filter `operator`. `name` is human-readable, `numberOfValuesRequired` (none/one/two/multi) is how many values the operator takes, and `relativeDateUnits` lists the time units allowed for relative-date operators. - attributes: only for "attribute-on-field". Each sub-attribute has an `id` (use as the filter `attributeId`), `name`, `valueType` (use as the filter `valueType`), and its own `operators`. sortability - how the field can be sorted, or null if it cannot be: - sortableFieldType: "field-only" to sort by the field directly, or "attribute-on-field" to sort by one of its sub-attributes instead. - attributes: only for "attribute-on-field". Each has an `id` (use as the sort `attributeId`), `name`, and `valueType`.
Get metadata on a single list Args: list_id (required): ID of list Returns: JSON object containing list metadata such as list name, entity type, creator ID, and owner ID.
Get a page of lists in the user's organization that they have access to, optionally filtered by name. More lists may exist than one page holds: keep calling with `pagination.nextCursor` until it is absent to see every list. Args: term: Case-insensitive substring filter on list name. Returns lists whose name contains this string anywhere. Not fuzzy - typos will miss; if results are empty, try a shorter or different keyword. cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: The number of items to include in the page. Default is 100, max is 100. Returns: A dict with `data` (list metadata such as list name, entity type, creator ID, and owner ID) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page).
Get information about past and future meeting interactions and their attendees. Args: filter_expr: Filter expression using Affinity query language. Available fields: - id: unique identifier for the meeting (operators: =) Example: "id=1" or "(id=1 | id=2)" - startTime: When the meeting was scheduled (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "startTime>2025-01-01T01:00:00Z" - createdAt: When the meeting was created in Affinity (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "createdAt>=2025-01-01T00:00:00Z" - updatedAt: When the meeting was last updated (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "updatedAt<=2025-12-31T23:59:59Z" Complex examples: - Date range: "startTime>=2025-01-01T00:00:00Z & startTime<2025-02-01T00:00:00Z" - Multiple date filters: "startTime>2025-01-01T00:00:00Z & createdAt>=2024-12-01T00:00:00Z" Boolean logic: & (AND), | (OR), () for grouping Full filtering spec: https://developer.affinity.co/pages/external-api-v2/filtering cursor: Cursor for the next or previous page. Pass the value of `pagination.nextCursor` or `pagination.prevCursor` from a prior response. Do NOT parse or modify the cursor string. limit: Number of items to include in the page. Default is 20, max is 20. Larger pages time out upstream, so page through with `cursor` instead. Returns: A dict with `data` (list of meetings) and `pagination` with `nextCursor` / `prevCursor` strings (pass directly to the `cursor` param; absent when there is no such page) and `nextUrl` / `prevUrl` full URLs (null when there is no such page). If the org has no access to the unified meetings endpoint, returns `{"data": [], "unsupported": "..."}` pointing to get_meetings_for_entity instead of raising.
Get information about meetings for a specific company, person, or opportunity. IMPORTANT: The date range between start_time and end_time cannot exceed 365 days. Be mindful of leap years. A larger range is automatically clamped to the most recent 365 days (start_time moves forward) and the response carries a `warnings` entry stating the window actually used; call again with an earlier end_time to fetch older meetings. start_time must be strictly before end_time: an inverted or empty range is rejected with an error, not clamped. Args: entity_id: The ID of the company, external person, or opportunity. A person must be an external contact or a collaborator: the API rejects an internal person with "Person is not an external or collaborator". entity_type: What type of entity this ID refers to. Options: 0 = person, 1 = company, 8 = opportunity start_time: ISO 8601 formatted timestamp to filter meetings starting from this time. Must be before end_time Format: "2025-01-01T00:00:00Z" end_time: ISO 8601 formatted timestamp to filter meetings ending before this time. Must be after start_time Format: "2025-01-01T00:00:00Z" Maximum 365 day range examples: ✓ VALID: start_time: "2025-02-25T00:00:00Z", end_time: "2026-02-25T00:00:00Z" ✓ VALID: start_time: "2025-01-01T00:00:00Z", end_time: "2025-12-31T23:59:59Z" ✓ VALID (leap year): start_time: "2024-01-01T00:00:00Z", end_time: "2024-12-31T00:00:00Z" (365 days, not 366) ✗ INVALID: start_time: "2025-02-25T00:00:00Z", end_time: "2026-02-25T23:59:59Z" (exceeds 1 year by 23:59:59) internal_person_id: ID of an internal person that was involved in the meetings. This parameter filters down the set of interactions related to the given entity to only those in which this internal person was involved. Cannot be used to find all of an internal person's interactions. logging_type: Filter by logging classification. When not supplied, returns all logging types. Options: - LoggingType.ALL (0): Both automatically and manually logged interactions - LoggingType.MANUAL (1): Only manually logged interactions page_size: Number of results per page. Default is 10, maximum is 100. page_token: Token for the next page. Pass the value of "next_page_token" from the previous response; when a response has no "next_page_token", it is the last page. To answer questions that depend on every meeting in the range, keep calling with it until it is absent. Do NOT parse or modify the token string. Returns: Structured data containing an array of meetings and pagination for retrieving more results. Each meeting includes a `v2_id` field when the unified meetings feature gate is enabled for the caller's org, and a `notes` array holding any notes already on the meeting. If the requested date range exceeded 365 days, a `warnings` list describes the clamped window that was used.
Checks whether a company or person merge has finished, by its task id. Use after merge_companies or merge_persons returned with confirm=true: both return a task_id, and this reads GET /v2/tasks/<entity>-merges/{task_id} once. It does not poll. A merge can stay "in-progress" for several minutes while Affinity retries it; read again when the user asks. "failed" is final. IMPORTANT: entity_type must match the tool that produced the task: a company task read as "person" (or the reverse) is a 404, the same as an unknown id. Requires the Manage Duplicates permission and organization admin role, like the merge tools; a token without it gets a 403. Args: task_id (required): The task_id returned by merge_companies or merge_persons, a UUID such as "76251d92-99e1-416f-a3d2-0935435eb672". Anything that is not a UUID raises ValueError before any API call. entity_type (required): "company" for a merge_companies task, "person" for a merge_persons task. Returns: {"task_id", "entity_type", "status", "results_summary"}. status is "in-progress", "success" or "failed"; results_summary is {"total", "inProgress", "success", "failed"} counts for the task (a merge from these tools is a task of one). Errors: 404 "Resource not found" for an unknown id or the wrong entity_type; 403 without the Manage Duplicates permission.
Affinity ChatGPT Plugin FAQ
How the directory, categories and Discoverability Score work.
Read the methodologyHow do I improve Affinity's ChatGPT Plugin discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Affinity alternatives on ChatGPT?
As of 2026-11-02, Affinity competes with 4Degrees, 4Degrees - KSA, Carta CRM, MadeMarket, Rings AI in ChatGPT Investor & Deal-Flow Relationship CRM, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.