affinity_data_migration
ChatGPTUse 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.
affinity_event_setup
ChatGPTSet 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.
affinity_meeting_prep
ChatGPTProduce 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.
create_company
ChatGPTCreate 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_field
ChatGPTCreate a new field (custom column) on a list or globally on an entity. IMPORTANT: - This tool does NOT create dropdown options. When value_type is a dropdown or ranked-dropdown, the field is created with no options. Dropdown options are managed separately: for a list-specific field (list_id provided), use create_list_field_dropdown_option to add options. For a global field (list_id omitted), options must be managed via the Affinity UI/API. - 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 (17, allows_multiple supported) and ranked-dropdown (7, single-select only, supports ranking the options) are different field types with no shared upgrade path afterward. Don't assume either one. If the user's request already makes this clear (e.g. "multi-select dropdown", "ranked dropdown", "single choice"), proceed without asking. Args: name: The field name. Example: "Relationship Strength" value_type: The kind of value the field holds. Options: 0 = person, 1 = company, 3 = number, 4 = date, 5 = location, 6 = text, 17 = dropdown, 7 = ranked-dropdown (single-select only; does not support allows_multiple). 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. allows_multiple: Whether the field accepts multiple values. Only valid for person, company, number, location, and dropdown (17) fields. Defaults to False. 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 newly created field, including id, name, list_id, value_type, allows_multiple, and an (empty) dropdown_options list.
create_interaction
ChatGPTLog 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_list
ChatGPTCreate 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.
create_list_entry
ChatGPTAdds 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_list_field_dropdown_option
ChatGPTCreate a new dropdown option on a list's dropdown field. Adds a selectable value to a dropdown or ranked-dropdown column on a list, e.g. a new "Not Relevant" option on a Communication Status field. IMPORTANT: - option_type must match the field's actual type. Call get_list_fields to check the field's valueType before calling. A mismatch returns a 400. - This does not work on status fields, which Affinity manages separately; the API returns a 400 for them. - For a ranked-dropdown field, both rank and color are required. For a plain dropdown field, neither may be set. 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 FieldValueType.DROPDOWN or FieldValueType.RANKED_DROPDOWN. text: The option label (1 to 255 characters). Must be unique on the field. rank: Sort position for ranked-dropdown options (0 to 2147483647). Required for ranked-dropdown, omit for plain dropdown. color: Tag color for ranked-dropdown options. Options: 0 = white, 1 = gray, 2 = blue, 3 = green, 4 = purple, 5 = orange, 6 = red. Required for ranked-dropdown, omit for plain dropdown. Returns: The created option with id, type, text, and (for ranked-dropdown) rank and color.
create_note
ChatGPTCreates a new HTML note with the supplied parameters. REQUIRED: - content: The note content (cannot be empty) - At least one of: person_ids, organization_ids, or opportunity_id Args: person_ids: List of person IDs company_ids: List of company IDs opportunity_ids: List of opportunity IDs content (str): HTML content of the note. Common formatting options: - <strong> for bold, <em> for italic, <u> for underline - <ul><li> for bullet lists, <ol><li> for numbered lists - <blockquote> for block quotes - <hr> for dividers Returns: dict[str, Any]: API response containing the created note information
create_opportunity
ChatGPTCreates 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).
create_person
ChatGPTCreates 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.
create_reminder
ChatGPTCreates 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 = company, 1 = person, 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. If the user has not given a due date for a one-time reminder, ask before calling. Example: "2025-01-31T00: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.
delete_company
ChatGPTPermanently 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.
delete_list_entries
ChatGPTDeletes 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_list_field_dropdown_option
ChatGPTDelete 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. - This does not work on status fields, which Affinity manages separately; the API returns a 400 for them. - 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.
delete_note
ChatGPTPermanently 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.
delete_opportunity
ChatGPTPermanently 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.
delete_person
ChatGPTPermanently 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.
delete_reminder
ChatGPTDeletes 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_company_info
ChatGPTReturns 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
get_company_list_entries
ChatGPTFetches list entries for the given company. Args: company_id (required): The ID of the company cursor: Cursor for the next or previous page. limit: The number of items to include in the page. Default is 100 Returns: Structured data containing the matching list entries
get_company_relationships
ChatGPTGet 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.
get_coworker_connections
ChatGPTFind 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_current_user
ChatGPTGet 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_entities_attached_to_note
ChatGPTFind entities attached to a note. Allows to retrieve details of a note, or a list of entities that are directly attached to a note. Args: note_id: The note ID that will be used in the search entity_type: Type of entities to retrieve. Options: 0 = company, 1 = person, 8 = opportunity.
get_entity_field_dropdown_options
ChatGPTReturns 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).
get_entity_field_values
ChatGPTGet field values for an entity. Use this tool if the entity is NOT on a list. If the entity is on a list, and you have the list context, prefer get_single_list_entry tool. Args: entity_type: Type of entity. Options: 0 = company, 1 = person, 8 = opportunity. entity_id: ID of the entity Returns: List of JSON objects containing field value data
get_entity_fields
ChatGPTReturns metadata on non-list specific company or person fields. Args: entity_type: Type of entity to return fields for. Options: 0 = company, 1 = person cursor: Cursor for the next or previous page. limit: The number of items to include in the page. Default is 100. Returns: Field metadata
get_entity_file_metadata
ChatGPTGet 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_entity_files
ChatGPTGet 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_field_value_changes
ChatGPTGet 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_investor_executive_connections
ChatGPTFind 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.
get_list_entries
ChatGPTPaginate through list entries on a given list. Each list entry contains basic information about the related person/company/opportunity and list-specific field data. By default, returns only list-specific fields. IMPORTANT: field_ids and field_types are mutually exclusive. They cannot be used together. WARNING: Lists can contain many entries with large field data. Best practices to minimize response size: - MUST start with limit=10 (default), increase only if needed - Select specific fields using field_ids or field_types (use get_list_fields tool to identify fields) - Request additional field types (enriched, global, relationship-intelligence) only when needed - Use pagination (cursor) to iterate through results Args: list_id (required): ID of list cursor: Cursor for the next or previous page. limit: The number of items to include in the page. Default is 10, maximum is 100. field_ids: List of field IDs to return. field_types: List of field types to return. Defaults to ['list']. Available types: 'enriched', 'global', 'relationship-intelligence', 'list' Returns: List of JSON objects containing list entry metadata such as entity info and list-specific field data.
get_list_field_dropdown_options
ChatGPTReturns the dropdown options for a specific dropdown or ranked-dropdown field on a list. Use the returned dropdown option IDs when writing dropdown field values via upsert_list_entry_field_values and upsert_entity_field_value (ranked-dropdown only). 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. limit: The number of items to include in the page. Default is 20, maximum is 100. Returns: Metadata about each dropdown option
get_list_fields
ChatGPTReturns 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. limit: The number of items to include in the page. Default is 100. Returns: Field metadata
get_list_info
ChatGPTGet 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_lists
ChatGPTGet all lists in the user's organization that they have access to Args: cursor: Cursor for the next or previous page. limit: The number of items to include in the page. Default is 100. Returns: List of JSON objects containing list metadata such as list name, entity type, creator ID, and owner ID.
get_meetings
ChatGPTGet 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. limit: Number of items to include in the page. Default is 20, max is 100. Returns: Structured data containing the list of meetings and pagination information. Raises: MeetingsV2AccessError: If the user does not have access to V2 meetings endpoint
get_meetings_for_entity
ChatGPTGet 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. Args: entity_id: The ID of the company, external person, or opportunity entity_type: What type of entity this ID refers to. Options: 0 = company, 1 = person, 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: The next_page_token from the previous response required to retrieve the next page of results. Returns: Structured data containing an array of meetings and pagination for retrieving more results
get_merge_task
ChatGPTChecks 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.
get_notes
ChatGPTFind notes in Affinity. Filter by ID, creator, and/or creation date. Args: filter_expr: Filter expression using Affinity query language. Available fields: - id: unique identifier for the note (operators: =) Example: "id=123" or "(id=1 | id=2 | id=10)" - creator.id: Person ID that created the note (operators: =) Example: "creator.id=456" - createdAt: When the note was created in Affinity (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "createdAt>=2026-01-01T00:00:00Z" Complex examples: - Date range: "createdAt>=2026-01-01T00:00:00Z & createdAt<2026-02-01T00:00:00Z" - By creator in date range: "creator.id=123 & createdAt>=2026-01-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. limit: Number of items to include in the page. Default is 20, max is 100. When you need more than 100 records, use the cursor to paginate over the results. Returns: Structured data containing the list of notes and pagination information.
get_notes_for_entity
ChatGPTGet all notes attached to a specific company, person, or opportunity. For the reverse (finding entities attached to a note), use get_entities_attached_to_note. Args: entity_id: The ID of the company, person, or opportunity entity_type: What type of entity this ID refers to. Options: 0 = company, 1 = person, 8 = opportunity Example: To get notes for company ID 123, use entity_id=123, entity_type=0
get_person_info
ChatGPTReturns basic information and non-list specific field data on the requested person. By default, returns all field data including current organization, job title, 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: person_id (required): The ID of the person 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 person including field data
get_person_list_entries
ChatGPTFetches list entries for the given person. Args: person_id (required): The ID of the person cursor: Cursor for the next or previous page. limit: The number of items to include in the page. Default is 100 Returns: Structured data containing the matching list entries
get_person_relationships
ChatGPTGet the relationship strengths between a person and the people they interact with (relationship intelligence / "how do I know them"). Works for both internal team members and external contacts: - Pass an internal person's ID to see their relationships with external contacts. - Pass an external person's ID to see which internal team members know them. 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: person_id: The ID of the person (internal or external) 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 side) and person2 (the external side), 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.
get_persons_info
ChatGPTReturns basic information and non-list specific field data on multiple requested persons in a single call. Use this instead of calling get_person_info once per person when you already know the exact person IDs you need, e.g. contacts surfaced by get_person_relationships or get_company_relationships. Unlike get_person_info, this does not return all field data by default. Use get_entity_fields to discover field IDs/types, then pass them via field_ids or field_types. IMPORTANT: field_ids and field_types are mutually exclusive. They cannot be used together. Args: person_ids (required): The IDs of the persons. Best for up to ~100 per call (the endpoint's page size cap); use cursor for more. field_ids: list of field IDs to return. field_types: List of field types to return. Unlike get_person_info, this has no default: field data is empty for every person unless field_ids or field_types is passed. 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. total_count: If True, include the total match count in pagination. Returns: A dict with data (list of person objects including field data) and pagination with nextCursor / prevCursor strings (pass directly to the cursor param; absent when there is no such page), nextUrl / prevUrl full URLs (null when there is no such page), and totalCount only when total_count is true.
get_relationship_strengths
ChatGPTGets relationship strengths between an external person and internal team members. Optionally filter to a specific internal person. IMPORTANT: When the user uses first-person language (ex. 'my relationship', 'how do I know', etc.), you MUST call get_current_user first to obtain the internal_person_id. Args: external_person_id: ID of the external person to get relationship strengths for internal_person_id: Optional ID to filter results to a specific internal person Returns: A list of relationship strength records, each containing external_id, internal_id, and strength represented as a float between 0-1. You MUST convert the strength to a percentage between 0 and 100, rounded to the nearest integer (values of .5 or greater round up, values less than .5 round down)
get_reminders
ChatGPTReturns reminders that meet the query parameters if provided. By default, returns reminders for all entities. IMPORTANT: entity_id and entity_type are only applied when both are provided; if only one is set, the entity filter is ignored and reminders for all entities are returned. Args: entity_id: The ID of the company, external person, or opportunity to filter reminders by. entity_type: The type of entity that entity_id refers to. Options: 0 = company, 1 = person, 8 = opportunity. creator_id: Filters to reminders created by this internal person (identified by their person ID). owner_id: Filters to reminders assigned to this internal person (identified by their person ID). completer_id: Filters to reminders completed by this internal person (identified by their person ID). reminder_type: Filters by recurrence type: - ReminderType.ONE_TIME (0): One-time reminders only - ReminderType.RECURRING (1): Recurring reminders only reset_type: Filters recurring reminders by their reset trigger. Only valid when reminder_type=RECURRING. - ReminderResetType.INTERACTION (0): Resets on any interaction - ReminderResetType.EMAIL (1): Resets on email only - ReminderResetType.EVENT (2): Resets on event only status: Filters by reminder status: - ReminderStatus.COMPLETED (0): Completed reminders - ReminderStatus.ACTIVE (1): Active reminders - ReminderStatus.OVERDUE (2): Overdue reminders due_before: ISO 8601 formatted date to retrieve reminders due before this date. Example: "2025-01-31T00:00:00Z" due_after: ISO 8601 formatted date to retrieve reminders due after this date. Example: "2025-01-01T00:00:00Z" page_size: Number of results per page. Default is 10. page_token: The next_page_token from the previous response required to retrieve the next page of results. Returns: Structured data containing the list of reminders and pagination information.
get_saved_view_list_entries
ChatGPTPaginate through list entries on a given saved view. Each list entry contains basic information about the related person/company/opportunity along with the field data that the saved view has been configured to display. Unlike search_list_entries, field selection is not supported here: the saved view itself defines which fields (columns) are returned. Use get_saved_views to discover the view_id for a list. Args: list_id (required): ID of the list. view_id (required): ID of the saved view on that list. 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 10 to keep response size manageable, maximum is 100. Page through with cursor for more entries. Returns: Data array of list entries (entity info plus the view's configured fields) plus a 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).
get_saved_views
ChatGPTPaginate through all saved views the user has access to for a specific List. Args: list_id (required): ID of the list. 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, maximum is 100. Returns: Data array of saved views (each containing id, name, type, createdAt) plus a 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).
get_single_list_entry
ChatGPTRetrieve a single list entry from the given list. The list entry contains basic information about the related person/company/opportunity and list-specific field data. By default, returns only list-specific fields. IMPORTANT: field_ids and field_types are mutually exclusive. They cannot be used together. WARNING: List entry may contain large field data. Best practices to minimize response size: - Select specific fields using field_ids or field_types (use get_list_fields tool to identify fields) - Request additional field types (enriched, global, relationship-intelligence) only when needed Args: list_id (required): ID of list list_entry_id (required): ID of list entry field_ids: List of field IDs to return. field_types: List of field types to return. Defaults to ['list']. Available types: 'enriched', 'global', 'relationship-intelligence', 'list'
get_transcript_fragments
ChatGPTGet dialogue fragments (individual speaker turns) from a transcript. Use pagination to retrieve all fragments from long transcripts. To get transcripts for a specific entity (company, person, opportunity): 1. Use get_notes_for_entity to get notes for that entity 2. Look for notes where type is "ai-notetaker" or "ai-notetaker-reply" 3. Those notes have a transcriptId field - use it with this tool Args: transcript_id: The transcript ID from the transcriptId field in AI Notetaker notes (returned by get_notes_for_entity). cursor: Cursor for the next or previous page. limit: Number of items per page. Default is 20, max is 100. Returns: Paginated list of dialogue fragments with total count. Each fragment contains speaker, content, and timestamps.
get_users
ChatGPTGet a page of users (internal team members) in your Affinity organization. More users may exist than one page holds: keep calling with pagination.nextCursor until it is absent to see everyone. Use this to list, search, or paginate through internal Affinity users, not external contacts. To look up only the authenticated caller, use get_current_user instead. Args: term: Case-insensitive search across first name, last name, and primary email address. Example: "jane" or "jane@affinity.co". filter_expr: Filter expression using Affinity query language. Supported fields: - id: unique identifier for the user (operators: =) Example: "id=1" or "(id=1 | id=2)" - status: the user's account status (operators: =). Values: active, invited, deactivated Example: "status=active" 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 (1-100). Default is 100. Returns: A dict with data (list of user objects, each with id, firstName, lastName, primaryEmailAddress, photoUrl, and status; emailAddresses and role are included only when the caller has the "Manage Users" permission) 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).
issue_entity_files_download_token
ChatGPTGet a single-use download target for a file attached to a company, person, or opportunity. Returns a GET target and a single-use, time-limited token — it does NOT return the file itself. The caller must GET the returned url with the returned headers, within expires_in seconds. IMPORTANT: - This tool never returns file bytes. If you can make HTTP requests yourself, GET the file directly using the returned method/url/headers. If you cannot, give the user curl_example to run themselves. - The Authorization header in the response carries a single-use token, not the caller's own API key. Treat it as a secret — it authorizes exactly one download and expires after expires_in seconds either way. Args: file_id: The unique ID of the entity file to download. Use get_entity_files to list an entity's files and obtain a file_id. Returns: dict with: - method: always "GET" - url: the endpoint to GET the file from - expires_in: seconds until the token expires - headers: headers to include on the GET request - instructions: what to do with method/url/headers - curl_example: a ready-to-run curl command, for a human to execute if the caller cannot make HTTP requests itself
issue_entity_files_upload_token
ChatGPTGet a single-use upload target for attaching a file to a company, person, or opportunity. Returns a PUT target and a single-use, time-limited token — it does NOT upload the file itself. The caller must PUT the raw file bytes to the returned url with the returned headers, within expires_in seconds. IMPORTANT: - This tool never sees or returns file bytes. If you can make HTTP requests yourself, PUT the file directly using the returned method/url/headers. If you cannot, give the user curl_example to run themselves. - The Authorization header in the response carries a single-use token, not the caller's own API key. Treat it as a secret — it authorizes exactly one upload and expires after expires_in seconds either way. - Accepted file extensions/MIME types and a 100 MB size limit are enforced both here and when the PUT is redeemed; a mismatch here fails fast, but passing validation here does not guarantee the PUT will succeed if the declared metadata doesn't match the file that's actually sent. Args: entity_id: The ID of the company, person, or opportunity to attach the file to. entity_type: What type of entity entity_id refers to. Options: 0 = person, 1 = company, 8 = opportunity. filename: The file's name, including its extension (e.g. "pitch_deck.pdf"). filesize: The file's exact size in bytes. Must be between 1 and 100 MB. content_type: The file's MIME type (e.g. "application/pdf"). Returns: dict with: - method: always "PUT" - url: the endpoint to PUT the file bytes to - expires_in: seconds until the token expires - headers: headers to include on the PUT request - instructions: what to do with method/url/headers - curl_example: a ready-to-run curl command, for a human to execute if the caller cannot make HTTP requests itself
market_map_generator
ChatGPTGenerates an interactive investment thesis market map by searching Affinity for companies matching a thesis, scoring each for thesis fit, and rendering a scatter plot (funding stage on the X axis, engagement depth on the Y axis) plus coverage tables. Use this skill whenever a user asks to "build a market map", "generate a market map", "map out my thesis", "what companies do we have in [space]", "show me our coverage of [sector]", or pastes a thesis and asks to visualize it against their Affinity data. Also trigger for "what does our pipeline look like for [thesis]".
merge_companies
ChatGPTMerges two companies (organizations): the duplicate's data moves onto the primary, which survives, unless a global company forces a new combined record. Confirmation gate: with confirm=false (the default) nothing is merged and the response is a preview (primary_company, duplicate_company, outcome, warning, next_step). Show the whole warning to the user in your own message, unquoted, naming both companies and which one survives. Call again with confirm=true only after the user explicitly approves this specific merge; one approval per merge, never a batch. Direction: primary_company_id survives and duplicate_company_id disappears (deleted, or hidden when it is a global company), except for outcome "new_company" (a global primary gaining a domain, or two globals), where Affinity replaces BOTH records with a new company that takes the primary's name and both records' domains; find it afterwards with search_companies_top_matches by one of those domains. If it is unclear which record the user wants to keep, or several records share a name, list them with ids and domains and ask before calling. WARNING: this tool has no undo. An admin can unmerge two local companies from Settings > Duplicate Management > Merged; a merge involving a global (Affinity-enriched) company is irreversible. Use it only for two records that are the SAME company: to remove a company use delete_company, to fix a name or domain use update_company, and never call merge_companies and delete_company for the same pair. The merge is asynchronous: confirm=true submits it, reads the task once and returns. success=true means Affinity accepted the merge, not that it finished, and status is usually "in-progress". Tell the user it is running in the background and that they can ask you to check on it; never promise to check later yourself. To check, call get_merge_task with the returned task_id and entity_type="company": "success" means it finished (get_company_info on the duplicate now 404s), "failed" is final, and if it is still "in-progress" after several minutes an admin can merge the pair from Settings > Duplicate Management. Never resubmit a merge that returned success=true or a 504. Requires an Affinity org admin with the manage-duplicates permission (otherwise 403). Args: primary_company_id (required): Id of the company that is kept. duplicate_company_id (required): Id of the company merged into the primary and then deleted or hidden. Must differ from primary_company_id. confirm: false returns the preview only; true submits the merge. Returns: confirm=false: the preview, nothing merged. primary_company and duplicate_company are {id, name, domain, domains, is_global}; outcome is "local" (duplicate deleted), "hide_global" (global duplicate hidden for your org), "copy_global" (global primary copied locally, same id) or "new_company" (both replaced by a new company). confirm=true: {"success": true, "task_id", "status" ("in-progress" | "success" | "failed", or null when the task could not be read yet), "results_summary" ({total, inProgress, success, failed}, or null likewise), "outcome", "primary_company_id", "duplicate_company_id", "primary_company_name", "duplicate_company_name"}. Errors: 403 without the manage-duplicates permission; 404 when either company is not found; ValueError for equal ids; 504 when the connection dropped after the merge was sent, so it may be queued: do not call again for the pair, check later with get_company_info on duplicate_company_id.
merge_persons
ChatGPTMerges a duplicate person into a primary person, permanently deleting the duplicate. Use when the user asks to merge, combine, or de-duplicate two people (contact records). IMPORTANT: - Cannot be undone through this tool or the external API: no unmerge endpoint exists there. All of the duplicate person's data (notes, list entries, relationships, and more) is transferred to the primary person, then the duplicate is deleted. An org admin can still undo the merge from Affinity's Duplicate Management settings, or by asking Affinity support, but not through this tool. - Confirmation gate: confirm=false (the default) merges nothing and returns a preview naming both persons. Show that warning to the user verbatim before asking them to confirm. Call again with confirm=true only once they approve. Both calls raise a 404 when either person is not found: the API would accept that merge and fail it later. - duplicate_person_id cannot be an internal person (a coworker) or a collaborator: Affinity refuses to merge those away. Both calls raise ValueError for that before anything is sent. If the primary is an external contact, swapping the two ids works. - The merge is asynchronous. A confirmed call reads the status once, which may still be "in-progress"; this tool does not poll. To confirm completion later, call get_merge_task(task_id, entity_type="person"): status "success" means the merge finished, and get_person_info(duplicate_person_id) then 404s (the duplicate is gone). get_person_info(primary_person_id) then reflects the duplicate's transferred data (e.g. its email added to the primary's list). - primary_person_id must be a current, still-mergeable person. If it is itself stale (already merged away earlier, or no longer a real contact), the merge is still accepted but its status cannot be read back: this raises an error rather than returning a result. Check get_person_info(primary_person_id) if that happens. - If a name matches more than one person, call search_persons first and ask the user which record should survive as primary_person_id. Never guess. - Requires the Manage Duplicates permission and organization admin role; a token without it gets a 403. - A 504 means the connection dropped after the merge was sent and Affinity's merge list does not show it yet. The merge may already be queued: do not call merge_persons again for the pair; check later with get_merge_task or get_person_info(duplicate_person_id). Args: primary_person_id: The person id to keep. All of the duplicate's data ends up here. duplicate_person_id: The person id to merge away and delete. Must differ from primary_person_id (raises ValueError before any API call otherwise). confirm: false (default) returns the preview only; true performs the merge. Returns: confirm=false: {"warning", "confirmation_required": true, "next_step"}. confirm=true: {"merge_id", "status" ("in-progress" | "success" | "failed"), "primary_person_id", "duplicate_person_id", "task_id", "error_message" (null unless status is "failed")}.
query_notes
ChatGPTFind notes in Affinity. Filter by ID, creator, and/or creation date. Args: filter_expr: Filter expression using Affinity query language. Available fields: - id: unique identifier for the note (operators: =) Example: "id=123" or "(id=1 | id=2 | id=10)" - creator.id: Person ID that created the note (operators: =) Example: "creator.id=456" - createdAt: When the note was created in Affinity (operators: >, <, >=, <=) Format: ISO 8601 timestamp Example: "createdAt>=2026-01-01T00:00:00Z" Complex examples: - Date range: "createdAt>=2026-01-01T00:00:00Z & createdAt<2026-02-01T00:00:00Z" - By creator in date range: "creator.id=123 & createdAt>=2026-01-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. Note bodies are free text and 20 of them have measured over 100k characters, past the tool-result cap, so response size scales with the notes matched rather than the row count. Narrow filter_expr instead of raising this, and do not sweep a whole result set unprompted — by paging or by raising limit. Report what the first page holds and offer to fetch more. Returns: A dict with data (list of notes) 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).
render_market_map
ChatGPTRender an investment thesis market map as an interactive scatter plot and coverage tables. IMPORTANT: only call this after searching Affinity (and optionally the web) for companies matching the thesis and scoring each for thesis fit, per the market-map-generator skill. This tool does not search, score, or classify anything itself. It draws exactly the companies it is given, so pass the same de-duplicated, quality-filtered results already shown to the user in text. Never invent a company, score, or funding figure not already in those results. Args: thesis: The investment thesis being visualized. your_companies: Companies with interaction history ("tracked" or "org-relevant" source only), plotted on the scatter and listed in the "Your Companies" table. Maximum 20. explore_companies: Companies with no interaction history ("global-cold" or "web" source only), listed in the "Companies to Explore" table only. Maximum 20. Must not repeat a domain already present in your_companies - de-duplicate before calling this tool. Returns: JSON object with thesis, your_companies, explore_companies, a computed stats object (on_map, to_explore, meetings_soon counts), and generated_at (server-side UTC timestamp) for the MCP App to render.
render_warm_intro_graph
ChatGPTRender warm intro paths as an interactive network-map graph. IMPORTANT: only call this when the user asks to see, map, visualize, or diagram the paths. Never render automatically. Call this only after finding and ranking paths with get_person_relationships / get_company_relationships and processing them per the warm intro skill (Steps 2-3). This tool does not fetch or rank anything itself. It draws exactly the paths it is given, so pass the same de-duplicated, capped, scored paths already shown to the user in text. Never invent a node, edge, or score not already in those results. Every edge is an explicit, scored relationship pulled directly from Affinity data (get_person_relationships / get_company_relationships). targets is always one or more actual people: a specific-person ask gives you one target, and an "anyone at the company" ask gives you the list of real candidate contacts the skill actually found, each with their own direct edge to a teammate. The tool marks the single highest-score_pct edge as recommended in the returned data. This keeps "recommended" tied purely to relationship strength. Example, one teammate with a single path to one target: team = [{"id": 1, "name": "Jamie Chen"}] targets = [{"id": 100, "name": "Sam Altman", "title": "CEO"}] edges = [{"teammate_id": 1, "target_id": 100, "score_pct": 90}] Args: team: Internal teammates who could make the introduction. Every entry must be referenced by at least one edge. Maximum 5. targets: The destination, one or more real people internal teammates have connections to. Maximum 5. edges: Ranked paths, matching the warm intro skill's Step 3 cap of 5 total, sorted descending by score_pct. The tool renders the order given and does not re-rank. Maximum 5. Each (teammate_id, target_id) pair must be unique. De-duplicate paths before calling this tool. Returns: JSON object with team, targets, edges, and generated_at (server-side UTC timestamp) for the MCP App to render. Each edge in the returned edges also carries a computed recommended boolean (see above). Text-only clients receive the same object as plain JSON.
search_all_companies
ChatGPTPaginate exhaustively through companies using structured filters, sorts, and/or a keyword. Use this tool when: - The request signals exhaustive intent ("all", "every", "full list", "export") - The user needs field data returned inline with results (field_ids or field_types) - The user needs a total count of matching companies (total_count=True) - Paginating through a complete result set from a previous call For all other searches, including structured-only filters, use search_companies_top_matches() instead. Omitting search_criteria returns every company with default pagination. FIELD SELECTION: field_ids and field_types are mutually exclusive; passing both raises ValueError. Valid field_types are enriched, global, and relationship-intelligence. When both are omitted, only company identity (id, name, domain, domains, isGlobal) is returned. DISCOVERING FIELDS: Use get_entity_fields(entity_type=EntityType.COMPANY, includes=[FieldInclude.FILTERABILITY, FieldInclude.SORTABILITY]) to find field IDs, valueTypes, supported operators, and any required attributeId. EXAMPLE - all companies not contacted in over 90 days, sorted by last contact: search_criteria = { "filters": { "operator": "and", "filters": [{ "fieldId": "last-contact", "attributeId": "date-of-activity", "valueType": "date", "operator": "is-not-within-the-last", "value": {"amount": [90], "unit": "day"}, }], }, "sorts": [{"fieldId": "last-contact", "attributeId": "date-of-activity", "direction": "asc"}], } Args: search_criteria: Optional filters, sorts, and/or keyword. Also accepted as a JSON-encoded string of the same object (clients whose type system cannot express the nested structure, e.g. Copilot Studio, are advertised this form). Omit to paginate through every company. When paging with cursor, pass the EXACT SAME search_criteria used on the call that produced that cursor (a cursor encodes the sort order of the request that minted it, so changing or omitting search_criteria, especially sorts or search, on a follow-up call invalidates the cursor and this tool raises an error). field_ids: Specific field IDs to include per company. Mutually exclusive with field_types. field_types: Field categories to include per company (enriched, global, relationship-intelligence). Mutually exclusive with field_ids. 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. You MUST also re-pass the same search_criteria you used to get this cursor: it is not remembered between calls. limit: Results per page. Default 10, max 100. total_count: If true, include the total number of matching companies in the response. Returns: A dict with data (list of company objects) and pagination with nextCursor / prevCursor strings (pass directly to the cursor param; absent when there is no such page), nextUrl / prevUrl full URLs (null when there is no such page), and totalCount only when total_count is true. If the caller's Affinity seat lacks the export permission this search requires, returns {"data": [], "unsupported": "..."} instead of raising.
search_companies
ChatGPTSearches for companies by name or domain, with optional filtering on interaction dates. For natural language or characteristic-based queries (e.g., "AI startups", "companies like OpenAI", "biotech firms"), use semantic_search() instead. Args: term: A string used to search all the organizations in your team's address book. This could be a name or a domain name. with_interaction_dates: When true, interaction dates will be present on the returned resources. Only organizations that have interactions will be returned. with_interaction_persons: When true, persons for each interaction will be returned. Used in conjunction with with_interaction_dates. with_opportunities: When true, opportunity IDs associated with this organization will be returned. date_filters: Optional dictionary to filter by interaction dates. Keys can be 'min_first_email_date', 'max_last_interaction_date', etc. Dates must be in ISO 8601 format (e.g. '2024-01-01T12:00:00Z'). page_size: Number of results per page. If not specified, defaults to 10 when searching with a term, otherwise 100. page_token: The next_page_token from the previous response required to retrieve the next page of results. Returns: Structured data containing the list of matching companies and pagination token.
search_companies_top_matches
ChatGPTSearch companies using natural language, structured criteria, or both simultaneously. When query and search_criteria are provided, both semantic search and structured search run in parallel and results are merged. Call get_company_info for richer field data on results. DEFAULT for all company lookups, including structured-only filters with no semantic query. Only route to search_all_companies() when the request contains an explicit exhaustive-intent word such as "all", "every", "full list", "complete list", or "export", or when the user needs field data returned inline with results or a total count. Use this tool when discovery phrasing is present ("what companies", "which companies", "find companies", "show me companies"). RULE 1 - EXACT NAME OR DOMAIN LOOKUP (check this FIRST): If the request is to find one specific company by its name or domain ("find Stripe", "look up stripe.com", or resolving a company name to an ID before calling another tool), pass ONLY search_criteria and OMIT query: search_criteria = {"search": {"term": "Stripe"}} The term is matched server-side against company name and primary domain. Adding query to a lookup fires a redundant semantic search that is slower and adds nothing. If a structured-only lookup fails with a 403 permission error, report the error to the user; do NOT retry the lookup through query. RULE 2 - EVERYTHING ELSE (thematic or descriptive searches): Populate query and search_criteria together as they are complementary, except in the scenarios described below. WHEN TO OMIT QUERY (structured only): - Third-party enrichment data (Crunchbase, PitchBook, Dealroom) - Company valuation and revenue metrics - Custom org fields - Pure deterministic field-value match: the request names a specific known value (e.g. "cf-funding-stage = Series B") and semantic adds no value WHEN TO OMIT SEARCH CRITERIA: - Firm-wide relationship scores (ex. "companies where we have a strong relationship") WHEN TO USE BOTH QUERY AND SEARCH CRITERIA For all items below, set query to describe the user's intent AND build a matching search_criteria filter (call get_entity_fields first). Both are required. - Industry/sector: "healthcare", "fintech", "climate tech", "enterprise SaaS", "biotech", "AI" - Technology/business concepts: "solar energy", "payment processing", "gene therapy" - Funding: stage, amount raised, funding date, year founded - Employee metrics: headcount, hiring/departure rates, employee growth - Interaction history: when your firm met with or emailed companies - Time references: "last week", "recently", "this quarter", "past 6 months", "90 days" - Headquarters location: cities, states, countries, regions - Investor information: VC/PE firm names NOT SUPPORTED: - Lookup by similarity to a named company ("companies like OpenAI") - Notes, email body content, or file attachments BUILDING SEARCH_CRITERIA: Call get_entity_fields(entity_type=EntityType.COMPANY, includes=[FieldInclude.FILTERABILITY, FieldInclude.SORTABILITY]) before constructing any filter. This returns exact field IDs, valueTypes, supported operators, and dropdown values. Do not guess field IDs. The API silently returns no results for wrong values. LIST FILTERING: Pass list_ids to scope the semantic search query to companies on specific Affinity lists. Use get_lists to discover IDs. Maximum 100 IDs. list_ids REQUIRES query: passing it without query raises ValueError, it is not ignored. To browse or filter a list's entries without a query, use search_list_entries or get_saved_view_list_entries instead. SORTING: - search_criteria for structured search supports up to 5 sorts - Semantic search ranks by relevance score. If the query describes an ordering preference (e.g. "sorted by last contact"), only one sortable attribute is allowed. - When both searches run, semantic results appear first in relevance order, followed by structured-only results. QUERY GOTCHAS: "I", "we", and "our firm" in query refer to firm-wide data, not personal interaction history. "In our pipeline" filters to org-relevant companies, not membership in a list named "Pipelin…
search_files
ChatGPTSearch files by keyword. Scope the search by passing either file_ids or company_id, or omit both to search across all files in the organization. Each result contains a matched file and a single representative excerpt. Even if a file contains multiple matching passages, it appears exactly once in the response. IMPORTANT: file_ids and company_id are mutually exclusive. They cannot be used together. Args: prompt: The search query. Returns up to limit files ordered by relevance. Prompts with no strong matches may still return low-relevance results. 3-500 characters. file_ids: Optional list of file IDs to restrict the search to. Maximum 100 ids. Mutually exclusive with company_id. company_id: Optional company ID to restrict the search to files associated with that company. Mutually exclusive with file_ids. limit: Number of matches to return. Default is 20. Minimum is 1, maximum is 100. Returns: Structured data with a data array. Each item contains the matching file (id, name), a pageNumber (populated for paginated formats like PDFs), and a preview snippet of the matched text. An empty data array may indicate that there were no matches, or that the organization does not have Deal Assist enabled.
search_list_entries
ChatGPTSearch list entries on a list using filters, sorts, and/or a keyword. Omitting search_criteria returns every entry on the list with default pagination. WARNING: Lists can contain many entries with large field data. To minimize response size, start with the default limit=10 (increase only if needed), use pagination (cursor) to iterate, and return only the fields you need (see FIELD SELECTION). FIELD SELECTION (which fields to return per entry): field_ids and field_types are mutually exclusive; passing both raises ValueError. When both are omitted, defaults to field_types=['list'] (list-specific fields only). Request additional types (enriched, global, relationship-intelligence) only when needed. DISCOVERING FIELDS: Use get_list_fields to find the field IDs you can reference in filters, sorts, and search.fieldIds. Call it with includes=[FieldInclude.FILTERABILITY, FieldInclude.SORTABILITY] to learn which operators each field can be filtered with and whether it can be sorted (including any attributeId a field requires), so you only build filters and sorts the field supports. See FilterGroup, ValueFilter, SortCriterion, and SearchTerm for the structure and accepted values of each criterion type. EXAMPLE - filter by a dropdown field: search_criteria = { "filters": { "operator": "and", "filters": [{ "fieldId": "field-4574182", "valueType": "dropdown", "operator": "is-any-of", "value": [{"dropdownOptionId": 1}, {"dropdownOptionId": 2}], }], }, } EXAMPLE - filter by last-email within 30 days, sort by date added desc: search_criteria = { "filters": { "operator": "and", "filters": [{ "fieldId": "last-email", "attributeId": "date-of-activity", "valueType": "date", "operator": "is-within-the-last", "value": {"amount": [30], "unit": "day"}, }], }, "sorts": [{"fieldId": "created-at", "direction": "desc"}], } EXAMPLE - nested AND/OR (dropdown AND (employees>100 OR employees empty)): search_criteria = { "filters": { "operator": "and", "filters": [ {"fieldId": "field-4574182", "valueType": "dropdown", "operator": "is-any-of", "value": [{"dropdownOptionId": 1}]}, {"operator": "or", "filters": [ {"fieldId": "affinity-data-employees-current", "valueType": "number", "operator": "is-greater-than", "value": 100}, {"fieldId": "affinity-data-employees-current", "valueType": "number", "operator": "is-empty"}, ]}, ], }, } EXAMPLE - keyword search combined with a filter: search_criteria = { "search": {"term": "acme"}, "filters": { "operator": "and", "filters": [{ "fieldId": "field-4574182", "valueType": "dropdown", "operator": "is-not-empty", }], }, } EXAMPLE - filter where assigned-to has any of a few specific people: search_criteria = { "filters": { "operator": "and", "filters": [{ "fieldId": "field-assigned-to", "valueType": "person-multi", "operator": "has-any-of", "value": [{"id": 12345}, {"id": 67890}], }], }, } EXAMPLE - filter where a text field contains "term sheet": search_criteria = { "filters": { "operator": "and", "filters": [{ "fieldId": "field-notes-summary", "valueType": "text", "operator": "contains", "value": "term sheet", }], }, } Args: list_id: The ID of the list to search. search_criteria: Optional SearchCriteria with any of filters, sorts, search. See structure and examples above. Also accepted as a JSON-encoded string of the same object (clients whose type system cannot express the nested structure, e.g. Copilot Studio, are advertised this form). Omit to paginate through every entry. When paging with cursor, pass the EXACT SAME search_criteria used on the call that produced that cursor (a cursor encodes the sort order of the request that minted it, so changing or omitting search_criteria, especially sorts or search, on a follow-up call invalidates the cursor and this tool raises an error). field_ids: Specific field IDs to include per entry. Mutually exclusive with field_types. field_types: Field categories to include per entry. Mutually exclusive with field_ids. cursor: Cursor for the next or previous page. Pass the value of pagination.nextCursor or pagination.prevCursor from a prior response. …
search_notes
ChatGPTSearch notes by keyword. Scope the search by passing either note_ids or company_id, or omit both to search across all notes in the organization. Each result contains a matched note and a single representative excerpt. Even if a note contains multiple matching passages, it appears exactly once in the response. IMPORTANT: note_ids and company_id are mutually exclusive. They cannot be used together. Args: prompt: The search query. Returns up to limit notes ordered by relevance. Prompts with no strong matches may still return low-relevance results. 3-500 characters. note_ids: Optional list of note IDs to restrict the search to. Maximum 100 ids. Mutually exclusive with company_id. company_id: Optional company ID to restrict the search to notes associated with that company. Mutually exclusive with note_ids. limit: Number of matches to return. Default is 20. Minimum is 1, maximum is 100. Returns: Structured data with a data array. Each item contains the matching note id, its kind (one of: note, meeting-note, email-note, chat-message-note, meeting-ai-summary), and a preview snippet of the matched text. An empty data array may indicate that there were no matches, or that the organization does not have Deal Assist enabled.
search_opportunities
ChatGPTSearch for opportunities or list them with optional filters. Args: term: Search term used to search through opportunities page_size: Number of results per page. If not specified, defaults to 10 when searching with a term, otherwise 100. page_token: Pagination token for next page of results Returns: dict[str, Any]: API response containing information on list of opportunities
search_persons
ChatGPTSearches for persons in the Affinity CRM by name or email. Also allows filtering persons based on their interaction history, such as the dates of the first/last emails or meetings. Args: term: A string used to search all the persons in your team's address book. This could be an email address, a first name or a last name. with_interaction_dates: When true, interaction dates will be present on the returned resources. Only persons that have interactions will be returned. with_interaction_persons: When true, persons for each interaction will be returned. Used in conjunction with with_interaction_dates. with_opportunities: When true, opportunity IDs will be returned for each person. with_current_organizations: When true, the organization IDs of each person's current organizations (according to the Affinity Data: Current Organizations column) will be returned. date_filters: Optional dictionary to filter by interaction dates. Keys can be 'min_first_email_date', 'max_last_interaction_date', etc. Dates must be in ISO 8601 format (e.g. '2024-01-01T12:00:00Z'). page_size: Number of results per page. If not specified, defaults to 10 when searching with a term, otherwise 100. page_token: The next_page_token from the previous response required to retrieve the next page of results. Returns: Structured data containing the list of matching persons and pagination token.
semantic_search
ChatGPTFinds companies matching a natural language description about business context, characteristics, or relationship data. If looking up a company by keyword or domain, use search_companies instead. SEARCHABLE ATTRIBUTES: - Industry/sector: "healthcare", "fintech", "climate tech", "enterprise SaaS", "biotech", "AI" Example: "climate tech companies in our pipeline" - Technology/business concepts: "solar energy", "battery storage", "payment processing", "gene therapy" Example: "biotech companies working on extracellular vesicles" - Funding information: stage, funding date, amount raised, year founded Example: "Series A companies that raised more than $10M" - Employee metrics: headcount, hiring/departure rates Example: "companies with more than 100 employees" - Interaction history: search by when your firm met with or emailed companies Example: "companies we emailed recently" - Relationship strength: firm-wide relationship scores Example: "companies where we have strong connections" - Time references: "last week", "recently", "this quarter", "in the past 6 months", "90 days" Example: "companies our firm met with in the past 90 days" - Headquarters location: cities, states, countries, regions Example: "fintech startups in San Francisco" - Investor information: VC/PE firm names Example: "companies backed by Sequoia Capital" LIST FILTERING: - Use list_ids to scope results to companies that are members of specific Affinity lists. - Combine with the prompt to semantically search within a list: Example: "AI companies" with list_ids=[123] → AI companies on list 123 - Use get_lists to discover list IDs. Maximum 100 list IDs per request. SORTING: - Results can be ordered by a single sortable attribute. Cannot sort by multiple attributes. (ex. cannot sort by both total funding and last funding) - Sorting works with or without semantic descriptors: Without descriptors: "Find Series B companies with the most funding" → matches on investment stage and applies sorting for funding With descriptors: "Find Series B cancer diagnosis healthtech companies with the most funding" → optimizes for both semantic relevance and the desired sort order - Formulas and computed sorts are NOT supported (ex., "last funding / total funding") GOTCHAS: - "I", "we", and "our firm" all refer to FIRM-WIDE data. Semantic search does not support personal/individual interaction filtering. "Companies I met with last week" means "companies any member of my firm met with last week." - "In our pipeline" and "in our network" filter to org-relevant companies (companies your firm has interacted with or added to Affinity). They do NOT search for membership in a specific list named "Pipeline" or similar. To search within a specific named list, use get_lists to find its ID and pass it via list_ids. - Interaction and relationship data is always firm-wide. You cannot search by a specific team member's individual relationship or interaction history. NOT SUPPORTED: - Third-party enrichment data such as Crunchbase, PitchBook, Dealroom - Company valuation and revenue metrics - Lookup by similarity to a named company: "companies like OpenAI" - Notes, email body content, or file attachments - Custom fields you've created Args: prompt: Natural language description of the companies to find. Must be between 1 and 500 characters. limit: Number of companies to return. Default is 10. Minimum is 1, maximum is 100. list_ids: Optional list of Affinity list IDs to restrict results to. Maximum 100 IDs. Returns: A list of entities that match the prompt with an explanation of the search. Results prioritize org-relevant companies (companies your firm knows) but also search the universal database.
send_feedback
ChatGPTSends feedback to Affinity about MCP server improvements or additions. IMPORTANT: Use this tool when you cannot fulfill the user's request due to limitations in the current toolset. You MUST immediately offer to submit feedback about the missing capability. Use HTML formatting for the body. Returns: No content if the request was successful
update_company
ChatGPTUpdate an existing company (organization) in Affinity. IMPORTANT: Global companies (enriched from Affinity's global dataset) cannot have their name or domain changed. Only manually created (non-global) companies accept name/domain edits. Call search_companies_top_matches or get_company_info first if you are unsure whether the company is global. At least one of name, domain, or person_ids must be provided; a ValueError is raised before any API call otherwise. Args: company_id (required): The ID of the company to update. name: New name for the company. Omit to leave the name unchanged. Example: "Acme Corporation" domain: New primary domain, hostname only, no protocol or path. Omit to leave the domain unchanged. Example: "acme.com" person_ids: Full set of persons associated with the company. This replaces the existing associations, it is not additive, so include every person who should remain associated. Use search_persons to look up IDs first. Omit to leave the associations unchanged; [] clears all. Example: [1234, 5678] Returns: The updated Company object including id, name, domain, and global fields.
update_list_field_dropdown_option
ChatGPTUpdate (rename or restyle) a dropdown option on a list's dropdown field. Use this to rename a dropdown option (e.g. rename "Pass" to "Passed") or to change a ranked-dropdown option's rank or color. Pass only the values you want to change. IMPORTANT: - This does not work on status fields, which Affinity manages separately; the API returns a 400 for them. - rank and color only apply to ranked-dropdown fields and are ignored on plain 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 update. text: New label for the option (1 to 255 characters). Must stay unique on the field. rank: New sort position (0 to 2147483647). Ranked-dropdown only. color: New tag color. Options: 0 = white, 1 = gray, 2 = blue, 3 = green, 4 = purple, 5 = orange, 6 = red. Ranked-dropdown only. Returns: None. The API returns 204 No Content on success.
update_note
ChatGPTEdit an existing note: replace its text, or change which persons, companies, or opportunities it is attached to. Use this to fix a typo or a wrong association instead of creating a second note. IMPORTANT: only the note's creator can update it; any other caller gets a 403. The content of a note that contains @mentions cannot be changed (400). Reply notes (user-reply, ai-notetaker-reply) accept content only: passing person_ids, company_ids, or opportunity_ids for a reply is rejected. A note's type cannot be changed. Do not retry a 403 or 400 with the same arguments. IMPORTANT: person_ids, company_ids, and opportunity_ids REPLACE the existing associations of that kind, they do not append. Omit a list to leave those associations unchanged. Pass an empty list ([]) to clear that kind, as long as at least one association of any kind remains: a root note cannot be left with none, and clearing the last one fails with 400 "Cannot remove all associations". To add or remove a single association, read the current set with get_entities_attached_to_note and pass the full desired list. Find note_id with query_notes, get_notes_for_entity, or search_notes, or use the id returned by create_note. If two notes could be meant, ask before calling. Args: note_id: ID of the note to update. content: New HTML content that replaces the note's body. Same rules as create_note: allowed tags are <p>, <br>, <strong>, <em>, <u>, <ol>, <ul>, <li>, <span> (no attributes), <a> (href with http, https, or mailto only). Inline styles, classes, <img>, <script>, <iframe>, <style>, <blockquote>, <hr>, <s>, <pre>, <code>, <font>, and mention spans are rejected. The server appends rel and target attributes to every <a> before saving, as on create. Omit to leave the content unchanged; a blank string counts as omitted. person_ids: Full set of person IDs to attach (max 100). Replaces the existing persons. Omit to leave unchanged; [] clears all. Root notes only. company_ids: Full set of company IDs to attach (max 100). Replaces the existing companies. Omit to leave unchanged; [] clears all. Root notes only. opportunity_ids: Full set of opportunity IDs to attach (max 100). Replaces the existing opportunities. Omit to leave unchanged; [] clears all. Root notes only. Returns: {"success": true, "note_id", "note_url"}. The endpoint returns no body; read the content back with query_notes (filter_expr "id=<note_id>") and the associations with get_entities_attached_to_note. Not your note: 403. Unknown or inaccessible note_id: 404. Raises ValueError before any request when no field to change is supplied.
update_opportunity
ChatGPTUpdates an existing Opportunity: renames it or changes its company/person associations. IMPORTANT: company_ids and person_ids REPLACE the existing associations, they do not append. Passing company_ids=[5] on an Opportunity already linked to companies 5 and 9 removes the link to 9. To add or remove a single association, first fetch the current set (e.g. via search_opportunities or get_list_entries) and pass the full desired list. Passing an empty list (company_ids=[]) clears all of that association type. Omitting a param leaves it unchanged. Args: opportunity_id: ID of the Opportunity to update. name: New name for the Opportunity. Omit to leave the name unchanged. company_ids: Full set of company IDs to associate. Replaces existing companies. Omit to leave the associations unchanged; [] clears all. person_ids: Full set of person IDs to associate. Replaces existing persons. Omit to leave the associations unchanged; [] clears all. Returns: JSON object containing the updated Opportunity. Top-level keys: id, name, organization_ids (list[int]), person_ids (list[int]), list_entries (list).
update_person
ChatGPTUpdates an existing person (contact) in Affinity: renames them or changes their email addresses or company associations. IMPORTANT: emails and company_ids REPLACE the existing values, they do not append. Passing company_ids=[5] on a person already linked to companies 5 and 9 removes the link to 9. To add or remove a single value, first fetch the current set (e.g. via get_person_info) and pass the full desired list. Passing an empty list (emails=[]) clears all of that value. Omitting a param leaves it unchanged. 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. At least one of first_name, last_name, emails, or company_ids must be provided; a ValueError is raised before any API call otherwise. Args: person_id: ID of the person to update. first_name: New first name. Omit to leave the first name unchanged. last_name: New last name. Omit to leave the last name unchanged. emails: Full set of email addresses. Replaces existing emails. Omit to leave the emails unchanged; [] clears all. Example: ["alice@affinity.co"]. company_ids: Full set of Affinity company IDs to associate. Replaces existing companies. Omit to leave the associations unchanged; [] clears all. Example: [1687449]. Returns: The updated person object including its id, type, first_name, last_name, primary_email, emails, and organization_ids.
update_reminder
ChatGPTUpdates an existing reminder. IMPORTANT: - Only the fields provided will be updated; omitted fields remain unchanged. - Updates require 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 update. owner_id: Internal person ID to reassign the reminder to. reminder_type: ReminderType.ONE_TIME (0) or ReminderType.RECURRING (1). content: Description or note text for the reminder. due_date: ISO 8601 due date. Required when reminder_type is ONE_TIME. Example: "2025-01-31T00:00:00Z" reset_type: What triggers reset for recurring reminders. Required when reminder_type is RECURRING. ReminderResetType.INTERACTION (0), EMAIL (1), or EVENT (2). reminder_days: Recurrence interval in days. Required when reminder_type is RECURRING. Minimum 1. is_completed: Whether the reminder is completed. Only for ONE_TIME, Do not pass for RECURRING (the API rejects it). Returns: dict[str, Any]: API response containing the updated reminder.
upsert_entity_field_value
ChatGPTUpsert a field value for an entity. Use this tool if the entity is NOT on a list. If the entity is on a list, and you have list context, prefer upsert_list_entry_field_values tool. This tool has two modes of operation: 1. UPDATE MODE (provide field_value_id): - Updates an existing field value with the new value - Clears the field if value=None. Note that after clearing, the field_value_id no longer exists. You must use CREATE mode to set the value again. - For multi-value fields: updates only the specific field_value_id in place - Example: upsert_entity_field_value(value=200, field_value_id=12345) 2. CREATE MODE (provide field_id + entity_id): - Creates a new field value, cannot be empty - For multi-value fields: adds a new value without affecting existing ones - For single-value fields: sets the initial value (errors if already exists) - Example: upsert_entity_field_value(value=200, field_id=456, entity_id=789) Args: value: New value. Structure depends on field type: - dropdown: str representing dropdown value, NOT option ID (ex. "In Progress") - ranked-dropdown: int representing dropdown option ID (ex. 123). Use get_list_field_dropdown_options to obtain the ID - number: int (ex. 1000000) - person: int representing person ID (ex. 1) - company: int representing company ID (ex. 2) - location: Location (ex. Location(city="San Francisco", state="CA")) - datetime: str in ISO 8601 format (ex. "2023-01-01T00:00:00Z") - text: str (ex. "Some new text") - None: clears/empties the field (only valid in UPDATE mode) field_value_id: ID of the field value field_id: ID of the field entity_id: ID of the person, company, or opportunity Returns: JSON object containing the upserted field value Raises: ValueError: if field_id, entity_id, and field_value_id are all provided
upsert_entity_field_values
ChatGPTUpsert up to 100 field values for a company or person. upserts is ALWAYS a JSON list, even for a single field. Wrap every item in [ ... ]. Passing a bare object instead of a one-item list is rejected ("Input should be a valid list"). Use this tool when the entity is NOT on a list. If the entity is on a list and you have list context, prefer upsert_list_entry_field_values. Opportunities are not supported. Opportunity fields live on list entries. Use upsert_list_entry_field_values instead. Each item's value.type must be EXACTLY one of the allowed type strings and must match the field's declared type. Copy it verbatim, don't retype it. Args: entity_type: 0 = person, 1 = company. entity_id: ID of the company or person. upserts: A LIST of field value items. Each item is {"id": "<field-id>", "value": {"type": "<field-type>", "data": <...>}}. Use get_entity_fields to discover field IDs and their types, and get_entity_field_dropdown_options for dropdown option IDs. See ValueToUpsert for the per-type value shapes (those describe each ITEM in the list, not the argument itself). Examples: Single field (still a list): [ {"id": "affinity-data-description", "value": {"type": "text", "data": "Enterprise SaaS, ex-Google ML founders"}} ] Multiple fields in one call (up to 100): [ {"id": "affinity-data-number-of-employees", "value": {"type": "number", "data": 42}}, {"id": "affinity-data-location", "value": {"type": "location", "data": {"city": "San Francisco", "state": "CA", "country": "United States"}}}, {"id": "my-team-field", "value": {"type": "person-multi", "data": [{"id": 101}, {"id": 202}]}} ] Clear a field (data=None): [ {"id": "affinity-data-linkedin-url", "value": {"type": "text", "data": None}} ] Returns: Dict containing the name of the successful operation
upsert_list_entry_field_values
ChatGPTUpsert up to 100 field values for a list entry. Args: list_id: ID of list list_entry_id: ID of list entry upserts: List of field value upserts. Each item has: - id (str): The field ID. Use get_list_fields or get_single_list_entry tools to discover field IDs. - value (ValueToUpsert): An object with type and data keys. Use get_list_field_dropdown_options to look up dropdown option IDs. Examples: - Text field {"id": "affinity-data-description", "value": {"type": "text", "data": "Top prospect"}} - Number field {"id": "my-revenue-field", "value": {"type": "number", "data": 5000000}} - Ranked-dropdown field (get option IDs from get_list_field_dropdown_options) {"id": "my-stage-field", "value": {"type": "ranked-dropdown", "data": {"dropdownOptionId": 42}}} - Person-multi field {"id": "my-owners-field", "value": {"type": "person-multi", "data": [{"id": 101}, {"id": 202}]}} Returns: Dict containing the name of the successful operation
warm_intro
ChatGPTSurface warm introduction paths to a target person or company using Affinity relationship data. Use this skill whenever a user asks how to get introduced to someone, who can introduce them to a contact, how to reach a target at a specific firm, or any variation of: "warm intro", "intro path", "who knows X", "can you find me an intro to Y", "how do I get connected to Z", "who can introduce me to someone at [Company]", or "who in my network knows [Person]". Also trigger if the user asks to draft an intro request or outreach message after paths are surfaced. Always use this skill for warm intro questions — do not attempt to answer from general knowledge or guess at relationships. After presenting paths, the skill can optionally render them as an interactive visual graph if the user asks to see, map, visualize, or diagram them.
add_sdk_hint
Claudecreate_list_entry
Claudecreate_note
Claudecreate_reminder
Claudeexecute_script
Claudeget_company_info
Claudeget_company_list_entries
Claudeget_current_user
Claudeget_entities_attached_to_note
Claudeget_entity_field_values
Claudeget_entity_fields
Claudeget_list_entries
Claudeget_list_field_dropdown_options
Claudeget_list_fields
Claudeget_list_info
Claudeget_lists
Claudeget_meetings
Claudeget_meetings_for_entity
Claudeget_notes
Claudeget_notes_for_entity
Claudeget_person_info
Claudeget_person_list_entries
Claudeget_relationship_strengths
Claudeget_reminders
Claudeget_single_list_entry
Claudeget_transcript_fragments
Claudelist_library_scripts
Claudelist_sdk_documentation
Clauderead_library_script
Clauderead_sdk_documentation_topic
Clauderender_selection
Clauderender_spread
Claudereport_sdk_issue
Claudesave_script_to_library
Claudesearch_companies
Claudesearch_opportunities
Claudesearch_persons
Claudesearch_sdk_hints
Claudesemantic_search
Claudesend_feedback
Claudeupsert_entity_field_value
Claudeupsert_list_entry_field_values
Claude