From d81215cd7ca43d6da3e9c11e76e001fab4203868 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 21:59:13 -0700 Subject: [PATCH 01/17] improvement(apollo): align tools and block with Apollo API docs --- apps/docs/content/docs/en/tools/apollo.mdx | 171 +++-- .../integrations/data/integrations.json | 2 +- apps/sim/blocks/blocks/apollo.ts | 637 ++++++++++++------ .../migrations/subblock-migrations.ts | 4 + apps/sim/tools/apollo/account_bulk_update.ts | 70 +- apps/sim/tools/apollo/account_create.ts | 40 +- apps/sim/tools/apollo/account_search.ts | 37 +- apps/sim/tools/apollo/account_update.ts | 38 +- apps/sim/tools/apollo/contact_bulk_create.ts | 25 +- apps/sim/tools/apollo/contact_bulk_update.ts | 75 ++- apps/sim/tools/apollo/contact_create.ts | 91 ++- apps/sim/tools/apollo/contact_search.ts | 26 +- apps/sim/tools/apollo/contact_update.ts | 87 ++- apps/sim/tools/apollo/email_accounts.ts | 5 +- apps/sim/tools/apollo/opportunity_create.ts | 41 +- apps/sim/tools/apollo/opportunity_get.ts | 2 +- apps/sim/tools/apollo/opportunity_search.ts | 52 +- apps/sim/tools/apollo/opportunity_update.ts | 39 +- .../tools/apollo/organization_bulk_enrich.ts | 30 +- apps/sim/tools/apollo/organization_enrich.ts | 43 +- apps/sim/tools/apollo/organization_search.ts | 34 +- apps/sim/tools/apollo/people_bulk_enrich.ts | 21 +- apps/sim/tools/apollo/people_enrich.ts | 20 +- apps/sim/tools/apollo/people_search.ts | 55 +- .../sim/tools/apollo/sequence_add_contacts.ts | 111 ++- apps/sim/tools/apollo/sequence_search.ts | 9 +- apps/sim/tools/apollo/task_create.ts | 68 +- apps/sim/tools/apollo/task_search.ts | 24 +- apps/sim/tools/apollo/types.ts | 271 +++++--- 29 files changed, 1480 insertions(+), 648 deletions(-) diff --git a/apps/docs/content/docs/en/tools/apollo.mdx b/apps/docs/content/docs/en/tools/apollo.mdx index d063d017123..1750b405839 100644 --- a/apps/docs/content/docs/en/tools/apollo.mdx +++ b/apps/docs/content/docs/en/tools/apollo.mdx @@ -49,9 +49,14 @@ Search Apollo | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key | | `person_titles` | array | No | Job titles to search for \(e.g., \["CEO", "VP of Sales"\]\) | +| `include_similar_titles` | boolean | No | Whether to return people with job titles similar to person_titles | | `person_locations` | array | No | Locations to search in \(e.g., \["San Francisco, CA", "New York, NY"\]\) | -| `person_seniorities` | array | No | Seniority levels \(e.g., \["senior", "executive", "manager"\]\) | -| `organization_names` | array | No | Company names to search within | +| `person_seniorities` | array | No | Seniority levels \(one of: owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern\) | +| `organization_ids` | array | No | Apollo organization IDs to filter by \(e.g., \["5e66b6381e05b4008c8331b8"\]\) | +| `organization_names` | array | No | Company names to search within \(legacy filter\) | +| `organization_locations` | array | No | Headquarters locations of the people's current employer \(e.g., \['texas', 'tokyo', 'spain'\]\) | +| `q_organization_domains_list` | array | No | Employer domain names \(e.g., \["apollo.io", "microsoft.com"\]\) — up to 1,000, no www. or @ | +| `contact_email_status` | array | No | Email statuses to filter by: verified, unverified, likely to engage, unavailable | | `q_keywords` | string | No | Keywords to search for | | `page` | number | No | Page number for pagination, default 1 \(e.g., 1, 2, 3\) | | `per_page` | number | No | Results per page, default 25, max 100 \(e.g., 25, 50, 100\) | @@ -120,10 +125,13 @@ Search Apollo | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key | -| `organization_locations` | array | No | Company locations to search | -| `organization_num_employees_ranges` | array | No | Employee count ranges \(e.g., \["1-10", "11-50"\]\) | +| `organization_locations` | array | No | Company HQ locations \(cities, US states, or countries\) | +| `organization_not_locations` | array | No | Exclude companies whose HQ is in these locations | +| `organization_num_employees_ranges` | array | No | Employee count ranges as "min,max" strings \(e.g., \["1,10", "250,500", "10000,20000"\]\) | | `q_organization_keyword_tags` | array | No | Industry or keyword tags | | `q_organization_name` | string | No | Organization name to search for \(e.g., "Acme", "TechCorp"\) | +| `organization_ids` | array | No | Apollo organization IDs to include \(e.g., \["5e66b6381e05b4008c8331b8"\]\) | +| `q_organization_domains_list` | array | No | Domain names to filter by \(no www. or @, up to 1,000\) | | `page` | number | No | Page number for pagination \(e.g., 1, 2, 3\) | | `per_page` | number | No | Results per page, max 100 \(e.g., 25, 50, 100\) | @@ -145,8 +153,7 @@ Enrich data for a single organization using Apollo | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key | -| `organization_name` | string | No | Name of the organization \(e.g., "Acme Corporation"\) - at least one of organization_name or domain is required | -| `domain` | string | No | Company domain \(e.g., "apollo.io", "acme.com"\) - at least one of domain or organization_name is required | +| `domain` | string | Yes | Company domain \(e.g., "apollo.io", "acme.com"\) | #### Output @@ -171,8 +178,10 @@ Enrich data for up to 10 organizations at once using Apollo | Parameter | Type | Description | | --------- | ---- | ----------- | | `organizations` | json | Array of enriched organization data | -| `total` | number | Total number of organizations processed | -| `enriched` | number | Number of organizations successfully enriched | +| `total` | number | Total number of domains requested | +| `enriched` | number | Number of unique enriched records | +| `missing_records` | number | Number of domains that could not be enriched | +| `unique_domains` | number | Number of unique domains processed | ### `apollo_contact_create` @@ -188,7 +197,19 @@ Create a new contact in your Apollo database | `email` | string | No | Email address of the contact | | `title` | string | No | Job title \(e.g., "VP of Sales", "Software Engineer"\) | | `account_id` | string | No | Apollo account ID to associate with \(e.g., "acc_abc123"\) | -| `owner_id` | string | No | User ID of the contact owner | +| `owner_id` | string | No | User ID of the contact owner \(accepted by Apollo but not officially documented for POST /contacts\) | +| `organization_name` | string | No | Name of the contact\'s employer \(e.g., "Apollo"\) | +| `website_url` | string | No | Corporate website URL \(e.g., "https://www.apollo.io/"\) | +| `label_names` | array | No | Lists/labels to add the contact to \(e.g., \["Prospects"\]\) | +| `contact_stage_id` | string | No | Apollo ID for the contact stage | +| `present_raw_address` | string | No | Personal location for the contact \(e.g., "Atlanta, United States"\) | +| `direct_phone` | string | No | Primary phone number | +| `corporate_phone` | string | No | Work/office phone number | +| `mobile_phone` | string | No | Mobile phone number | +| `home_phone` | string | No | Home phone number | +| `other_phone` | string | No | Alternative phone number | +| `typed_custom_fields` | json | No | Custom field values keyed by custom field ID | +| `run_dedupe` | boolean | No | When true, Apollo deduplicates against existing contacts | #### Output @@ -212,7 +233,18 @@ Update an existing contact in your Apollo database | `email` | string | No | Email address | | `title` | string | No | Job title \(e.g., "VP of Sales", "Software Engineer"\) | | `account_id` | string | No | Apollo account ID \(e.g., "acc_abc123"\) | -| `owner_id` | string | No | User ID of the contact owner | +| `owner_id` | string | No | User ID of the contact owner \(accepted by Apollo but not officially documented for PATCH /contacts/\{id\}\) | +| `organization_name` | string | No | Name of the contact\'s employer \(e.g., "Apollo"\) | +| `website_url` | string | No | Corporate website URL \(e.g., "https://www.apollo.io/"\) | +| `label_names` | array | No | Lists/labels to add the contact to \(e.g., \["Prospects"\]\) | +| `contact_stage_id` | string | No | Apollo ID for the contact stage | +| `present_raw_address` | string | No | Personal location for the contact \(e.g., "Atlanta, United States"\) | +| `direct_phone` | string | No | Primary phone number | +| `corporate_phone` | string | No | Work/office phone number | +| `mobile_phone` | string | No | Mobile phone number | +| `home_phone` | string | No | Home phone number | +| `other_phone` | string | No | Alternative phone number | +| `typed_custom_fields` | json | No | Custom field values keyed by custom field ID \(accepted by Apollo but not officially documented for PATCH /contacts/\{id\}\) | #### Output @@ -232,6 +264,9 @@ Search your team | `apiKey` | string | Yes | Apollo API key | | `q_keywords` | string | No | Keywords to search for | | `contact_stage_ids` | array | No | Filter by contact stage IDs | +| `contact_label_ids` | array | No | Filter by Apollo label IDs \(lists\) | +| `sort_by_field` | string | No | Sort field: contact_last_activity_date, contact_email_last_opened_at, contact_email_last_clicked_at, contact_created_at, or contact_updated_at | +| `sort_ascending` | boolean | No | When true, sort ascending. Must be used together with sort_by_field | | `page` | number | No | Page number for pagination \(e.g., 1, 2, 3\) | | `per_page` | number | No | Results per page, max 100 \(e.g., 25, 50, 100\) | @@ -251,7 +286,8 @@ Create up to 100 contacts at once in your Apollo database. Supports deduplicatio | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `contacts` | array | Yes | Array of contacts to create \(max 100\). Each contact should include first_name, last_name, and optionally email, title, account_id, owner_id | +| `contacts` | array | Yes | Array of contacts to create \(max 100\). Each contact may include first_name, last_name, email, title, organization_name, owner_id, contact_stage_id, linkedin_url, phone_numbers, and contact_emails | +| `append_label_names` | array | No | Label names to add to all contacts in this request \(e.g., \["Hot Lead"\]\) | | `run_dedupe` | boolean | No | Enable deduplication to prevent creating duplicate contacts. When true, existing contacts are returned without modification | #### Output @@ -273,17 +309,16 @@ Update up to 100 existing contacts at once in your Apollo database. Each contact | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `contacts` | array | Yes | Array of contacts to update \(max 100\). Each contact must include id field, and optionally first_name, last_name, email, title, account_id, owner_id | +| `contact_ids` | array | No | Array of contact IDs to update with the same values. Use either this OR contact_attributes. | +| `contact_attributes` | json | No | Either an array of per-contact updates \(each with id\) or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields | +| `async` | boolean | No | Force asynchronous processing. Automatically enabled for >100 contacts | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `updated_contacts` | json | Array of successfully updated contacts | -| `failed_contacts` | json | Array of contacts that failed to update | -| `total_submitted` | number | Total number of contacts submitted | -| `updated` | number | Number of contacts successfully updated | -| `failed` | number | Number of contacts that failed to update | +| `message` | string | Confirmation message from Apollo | +| `job_id` | string | Async job ID \(returned for >100 contacts\) | ### `apollo_account_create` @@ -293,11 +328,14 @@ Create a new account (company) in your Apollo database | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Apollo API key | +| `apiKey` | string | Yes | Apollo API key \(master key required\) | | `name` | string | Yes | Company name \(e.g., "Acme Corporation"\) | -| `website_url` | string | No | Company website URL | -| `phone` | string | No | Company phone number | -| `owner_id` | string | No | User ID of the account owner | +| `domain` | string | No | Company domain without www. prefix \(e.g., "acme.com"\) | +| `phone` | string | No | Primary phone number for the account | +| `owner_id` | string | No | Apollo user ID of the account owner | +| `account_stage_id` | string | No | Apollo ID for the account stage to assign this account to | +| `raw_address` | string | No | Corporate location \(e.g., "San Francisco, CA, USA"\) | +| `typed_custom_fields` | json | No | Custom field values as \{ custom_field_id: value \} map | #### Output @@ -317,9 +355,12 @@ Update an existing account in your Apollo database | `apiKey` | string | Yes | Apollo API key | | `account_id` | string | Yes | ID of the account to update \(e.g., "acc_abc123"\) | | `name` | string | No | Company name \(e.g., "Acme Corporation"\) | -| `website_url` | string | No | Company website URL | +| `domain` | string | No | Company domain \(e.g., "acme.com"\) | | `phone` | string | No | Company phone number | -| `owner_id` | string | No | User ID of the account owner | +| `owner_id` | string | No | Apollo user ID of the account owner | +| `account_stage_id` | string | No | Apollo ID for the account stage to assign this account to | +| `raw_address` | string | No | Corporate location \(e.g., "San Francisco, CA, USA"\) | +| `typed_custom_fields` | json | No | Custom field values as \{ custom_field_id: value \} map | #### Output @@ -337,9 +378,11 @@ Search your team | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `q_keywords` | string | No | Keywords to search for in account data | -| `owner_id` | string | No | Filter by account owner user ID | +| `q_organization_name` | string | No | Filter accounts by organization name \(partial-match search\) | | `account_stage_ids` | array | No | Filter by account stage IDs | +| `account_label_ids` | array | No | Filter by account label IDs | +| `sort_by_field` | string | No | Sort field: "account_last_activity_date", "account_created_at", or "account_updated_at" | +| `sort_ascending` | boolean | No | Sort ascending when true. Defaults to descending. | | `page` | number | No | Page number for pagination \(e.g., 1, 2, 3\) | | `per_page` | number | No | Results per page, max 100 \(e.g., 25, 50, 100\) | @@ -380,17 +423,17 @@ Update up to 1000 existing accounts at once in your Apollo database (higher limi | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `accounts` | array | Yes | Array of accounts to update \(max 1000\). Each account must include id field, and optionally name, website_url, phone, owner_id | +| `account_ids` | array | No | Array of account IDs to update with the same values \(max 1000\). Use with name/owner_id for uniform updates. Use either this OR account_attributes. | +| `name` | string | No | When using account_ids, apply this name to all accounts | +| `owner_id` | string | No | When using account_ids, apply this owner to all accounts | +| `account_attributes` | json | No | Array of account objects with individual updates \(each must include id\). Example: \[\{"id": "acc1", "name": "Acme", "owner_id": "u1", "account_stage_id": "s1", "typed_custom_fields": \{"field_id": "value"\}\}\] | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `updated_accounts` | json | Array of successfully updated accounts | -| `failed_accounts` | json | Array of accounts that failed to update | -| `total_submitted` | number | Total number of accounts submitted | -| `updated` | number | Number of accounts successfully updated | -| `failed` | number | Number of accounts that failed to update | +| `message` | string | Confirmation message from Apollo | +| `account_ids` | json | IDs of accounts that were updated | ### `apollo_opportunity_create` @@ -402,12 +445,12 @@ Create a new deal for an account in your Apollo database (master key required) | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | | `name` | string | Yes | Name of the opportunity/deal \(e.g., "Enterprise License - Q1"\) | -| `account_id` | string | Yes | ID of the account this opportunity belongs to \(e.g., "acc_abc123"\) | -| `amount` | number | No | Monetary value of the opportunity | -| `stage_id` | string | No | ID of the deal stage | +| `account_id` | string | No | ID of the account this opportunity belongs to \(e.g., "acc_abc123"\) | +| `amount` | string | No | Monetary value as a plain number string with no commas or currency symbols | +| `opportunity_stage_id` | string | No | ID of the opportunity stage | | `owner_id` | string | No | User ID of the opportunity owner | -| `close_date` | string | No | Expected close date \(ISO 8601 format\) | -| `description` | string | No | Description or notes about the opportunity | +| `closed_date` | string | No | Expected close date in YYYY-MM-DD format | +| `typed_custom_fields` | json | No | Custom field values as \{ custom_field_id: value \} map | #### Output @@ -425,10 +468,7 @@ Search and list all deals/opportunities in your team | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key | -| `q_keywords` | string | No | Keywords to search for in opportunity names | -| `account_ids` | array | No | Filter by specific account IDs \(e.g., \["acc_123", "acc_456"\]\) | -| `stage_ids` | array | No | Filter by deal stage IDs | -| `owner_ids` | array | No | Filter by opportunity owner IDs | +| `sort_by_field` | string | No | Sort field: "amount", "is_closed", or "is_won" | | `page` | number | No | Page number for pagination \(e.g., 1, 2, 3\) | | `per_page` | number | No | Results per page, max 100 \(e.g., 25, 50, 100\) | @@ -470,11 +510,11 @@ Update an existing deal/opportunity in your Apollo database | `apiKey` | string | Yes | Apollo API key | | `opportunity_id` | string | Yes | ID of the opportunity to update \(e.g., "opp_abc123"\) | | `name` | string | No | Name of the opportunity/deal \(e.g., "Enterprise License - Q1"\) | -| `amount` | number | No | Monetary value of the opportunity | -| `stage_id` | string | No | ID of the deal stage | +| `amount` | string | No | Monetary value as a plain number string with no commas or currency symbols | +| `opportunity_stage_id` | string | No | ID of the opportunity stage | | `owner_id` | string | No | User ID of the opportunity owner | -| `close_date` | string | No | Expected close date \(ISO 8601 format\) | -| `description` | string | No | Description or notes about the opportunity | +| `closed_date` | string | No | Expected close date in YYYY-MM-DD format | +| `typed_custom_fields` | json | No | Custom field values as \{ custom_field_id: value \} map | #### Output @@ -493,7 +533,6 @@ Search for sequences/campaigns in your team | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | | `q_name` | string | No | Search sequences by name \(e.g., "Outbound Q1", "Follow-up"\) | -| `active` | boolean | No | Filter by active status \(true for active sequences, false for inactive\) | | `page` | number | No | Page number for pagination \(e.g., 1, 2, 3\) | | `per_page` | number | No | Results per page, max 100 \(e.g., 25, 50, 100\) | @@ -516,40 +555,51 @@ Add contacts to an Apollo sequence | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | | `sequence_id` | string | Yes | ID of the sequence to add contacts to \(e.g., "seq_abc123"\) | -| `contact_ids` | array | Yes | Array of contact IDs to add to the sequence \(e.g., \["con_abc123", "con_def456"\]\) | -| `emailer_campaign_id` | string | No | Optional emailer campaign ID | -| `send_email_from_user_id` | string | No | User ID to send emails from | +| `contact_ids` | array | No | Array of contact IDs to add to the sequence \(e.g., \["con_abc123", "con_def456"\]\). Either contact_ids or label_names must be provided. | +| `label_names` | array | No | Array of label names to identify contacts to add to the sequence. Either contact_ids or label_names must be provided. | +| `send_email_from_email_account_id` | string | Yes | ID of the email account to send from. Use the Get Email Accounts operation to look this up. | +| `send_email_from_email_address` | string | No | Specific email address to send from within the email account. | +| `sequence_no_email` | boolean | No | Add contacts even if they have no email address | +| `sequence_unverified_email` | boolean | No | Add contacts with unverified email addresses | +| `sequence_job_change` | boolean | No | Add contacts who recently changed jobs | +| `sequence_active_in_other_campaigns` | boolean | No | Add contacts active in other campaigns | +| `sequence_finished_in_other_campaigns` | boolean | No | Add contacts who finished other campaigns | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `contacts_added` | json | Array of contact IDs added to the sequence | +| `added` | json | Array of contact objects successfully added to the sequence | +| `skipped` | json | Array of contact objects that were skipped, with reasons | +| `skipped_contact_ids` | json | Hash mapping contact IDs to skip reason codes | +| `emailer_campaign` | json | Details of the emailer campaign \(id, name\) | | `sequence_id` | string | ID of the sequence contacts were added to | | `total_added` | number | Total number of contacts added | +| `total_skipped` | number | Total number of contacts skipped | ### `apollo_task_create` -Create a new task in Apollo +Create one or more tasks in Apollo (one task per contact_id, master key required) #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `note` | string | Yes | Task note/description | -| `contact_id` | string | No | Contact ID to associate with \(e.g., "con_abc123"\) | -| `account_id` | string | No | Account ID to associate with \(e.g., "acc_abc123"\) | -| `due_at` | string | No | Due date in ISO format | -| `priority` | string | No | Task priority | -| `type` | string | No | Task type | +| `user_id` | string | Yes | ID of the Apollo user the task is assigned to | +| `contact_ids` | array | Yes | Array of contact IDs. One task is created per contact. | +| `priority` | string | No | Task priority: "high", "medium", or "low" \(defaults to "medium"\) | +| `due_at` | string | Yes | Due date/time in ISO 8601 format \(e.g., "2024-12-31T23:59:59Z"\) | +| `type` | string | Yes | Task type: "call", "outreach_manual_email", "linkedin_step_message", or "action_item" | +| `status` | string | Yes | Task status: "scheduled", "completed", or "archived" | +| `note` | string | No | Free-form note providing context for the task | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `task` | json | Created task data from Apollo | -| `created` | boolean | Whether the task was successfully created | +| `tasks` | json | Array of created tasks \(when returned by Apollo\) | +| `created` | boolean | Whether the request succeeded | ### `apollo_task_search` @@ -560,9 +610,8 @@ Search for tasks in Apollo | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `contact_id` | string | No | Filter by contact ID \(e.g., "con_abc123"\) | -| `account_id` | string | No | Filter by account ID \(e.g., "acc_abc123"\) | -| `completed` | boolean | No | Filter by completion status | +| `sort_by_field` | string | No | Sort field: "task_due_at" or "task_priority" | +| `open_factor_names` | array | No | Filter by status. Common values: \["task_types"\] for open tasks, \["task_completed_at"\] for completed tasks. | | `page` | number | No | Page number for pagination \(e.g., 1, 2, 3\) | | `per_page` | number | No | Results per page, max 100 \(e.g., 25, 50, 100\) | diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 9a1513dc6f8..18dd2b04da4 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -894,7 +894,7 @@ }, { "name": "Create Task", - "description": "Create a new task in Apollo" + "description": "Create one or more tasks in Apollo (one task per contact_id, master key required)" }, { "name": "Search Tasks", diff --git a/apps/sim/blocks/blocks/apollo.ts b/apps/sim/blocks/blocks/apollo.ts index 64bd9448d61..51cca7078b9 100644 --- a/apps/sim/blocks/blocks/apollo.ts +++ b/apps/sim/blocks/blocks/apollo.ts @@ -92,6 +92,21 @@ export const ApolloBlock: BlockConfig = { condition: { field: 'operation', value: 'people_search' }, mode: 'advanced', }, + { + id: 'include_similar_titles', + title: 'Include Similar Titles', + type: 'switch', + condition: { field: 'operation', value: 'people_search' }, + mode: 'advanced', + }, + { + id: 'contact_email_status', + title: 'Contact Email Status', + type: 'code', + placeholder: '["verified", "unverified", "likely_to_engage"]', + condition: { field: 'operation', value: 'people_search' }, + mode: 'advanced', + }, { id: 'contact_stage_ids', title: 'Contact Stage IDs', @@ -100,6 +115,14 @@ export const ApolloBlock: BlockConfig = { condition: { field: 'operation', value: 'contact_search' }, mode: 'advanced', }, + { + id: 'contact_label_ids', + title: 'Contact Label IDs', + type: 'code', + placeholder: '["label_id_1", "label_id_2"]', + condition: { field: 'operation', value: 'contact_search' }, + mode: 'advanced', + }, // People Enrich Fields { @@ -111,10 +134,7 @@ export const ApolloBlock: BlockConfig = { field: 'operation', value: ['people_enrich', 'contact_create', 'contact_update'], }, - required: { - field: 'operation', - value: 'contact_create', - }, + required: { field: 'operation', value: 'contact_create' }, }, { id: 'last_name', @@ -125,10 +145,7 @@ export const ApolloBlock: BlockConfig = { field: 'operation', value: ['people_enrich', 'contact_create', 'contact_update'], }, - required: { - field: 'operation', - value: 'contact_create', - }, + required: { field: 'operation', value: 'contact_create' }, }, { id: 'email', @@ -147,7 +164,7 @@ export const ApolloBlock: BlockConfig = { placeholder: 'Company name', condition: { field: 'operation', - value: ['people_enrich', 'organization_enrich'], + value: ['people_enrich', 'contact_create', 'contact_update'], }, }, { @@ -157,7 +174,11 @@ export const ApolloBlock: BlockConfig = { placeholder: 'example.com', condition: { field: 'operation', - value: ['people_enrich', 'organization_enrich'], + value: ['people_enrich', 'organization_enrich', 'account_create', 'account_update'], + }, + required: { + field: 'operation', + value: ['organization_enrich', 'account_create'], }, }, { @@ -192,9 +213,9 @@ export const ApolloBlock: BlockConfig = { }, { id: 'organizations', - title: 'Organizations (JSON Array)', + title: 'Organizations (JSON Array of domain objects)', type: 'code', - placeholder: '[{"organization_name": "Company A", "domain": "companya.com"}]', + placeholder: '[{"domain": "companya.com"}, {"domain": "companyb.com"}]', condition: { field: 'operation', value: 'organization_bulk_enrich' }, required: true, }, @@ -205,14 +226,38 @@ export const ApolloBlock: BlockConfig = { title: 'Organization Locations', type: 'code', placeholder: '["San Francisco, CA"]', + condition: { field: 'operation', value: ['organization_search', 'people_search'] }, + mode: 'advanced', + }, + { + id: 'organization_not_locations', + title: 'Excluded Organization Locations', + type: 'code', + placeholder: '["Ireland", "Minnesota"]', condition: { field: 'operation', value: 'organization_search' }, mode: 'advanced', }, + { + id: 'organization_ids', + title: 'Organization IDs', + type: 'code', + placeholder: '["org_id_1", "org_id_2"]', + condition: { field: 'operation', value: ['organization_search', 'people_search'] }, + mode: 'advanced', + }, + { + id: 'q_organization_domains_list', + title: 'Organization Domains', + type: 'code', + placeholder: '["apollo.io", "stripe.com"]', + condition: { field: 'operation', value: ['organization_search', 'people_search'] }, + mode: 'advanced', + }, { id: 'organization_num_employees_ranges', title: 'Employee Count Ranges', type: 'code', - placeholder: '["1-10", "11-50", "51-200"]', + placeholder: '["1,10", "11,50", "51,200"]', condition: { field: 'operation', value: 'organization_search' }, mode: 'advanced', }, @@ -229,7 +274,7 @@ export const ApolloBlock: BlockConfig = { title: 'Organization Name', type: 'short-input', placeholder: 'Company name to search', - condition: { field: 'operation', value: 'organization_search' }, + condition: { field: 'operation', value: ['organization_search', 'account_search'] }, }, // Contact Fields @@ -246,10 +291,7 @@ export const ApolloBlock: BlockConfig = { title: 'Job Title', type: 'short-input', placeholder: 'Job title', - condition: { - field: 'operation', - value: ['contact_create', 'contact_update'], - }, + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, mode: 'advanced', }, { @@ -259,17 +301,11 @@ export const ApolloBlock: BlockConfig = { placeholder: 'Apollo account ID', condition: { field: 'operation', - value: [ - 'contact_create', - 'contact_update', - 'account_update', - 'task_create', - 'opportunity_create', - ], + value: ['contact_create', 'contact_update', 'account_update', 'opportunity_create'], }, required: { field: 'operation', - value: ['account_update', 'opportunity_create'], + value: 'account_update', }, }, { @@ -284,7 +320,6 @@ export const ApolloBlock: BlockConfig = { 'contact_update', 'account_create', 'account_update', - 'account_search', 'opportunity_create', 'opportunity_update', ], @@ -292,6 +327,104 @@ export const ApolloBlock: BlockConfig = { mode: 'advanced', }, + { + id: 'website_url', + title: 'Corporate Website URL', + type: 'short-input', + placeholder: 'https://www.apollo.io/', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'label_names', + title: 'Label Names (JSON Array)', + type: 'code', + placeholder: '["Prospects", "VIP"]', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'contact_stage_id', + title: 'Contact Stage ID', + type: 'short-input', + placeholder: 'Apollo contact stage ID', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'present_raw_address', + title: 'Personal Location', + type: 'short-input', + placeholder: 'Atlanta, United States', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'direct_phone', + title: 'Direct Phone', + type: 'short-input', + placeholder: '+1 555 123 4567', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'corporate_phone', + title: 'Corporate Phone', + type: 'short-input', + placeholder: '+1 555 123 4567', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'mobile_phone', + title: 'Mobile Phone', + type: 'short-input', + placeholder: '+1 555 123 4567', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'home_phone', + title: 'Home Phone', + type: 'short-input', + placeholder: '+1 555 123 4567', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'other_phone', + title: 'Other Phone', + type: 'short-input', + placeholder: '+1 555 123 4567', + condition: { field: 'operation', value: ['contact_create', 'contact_update'] }, + mode: 'advanced', + }, + { + id: 'typed_custom_fields', + title: 'Custom Fields (JSON Object)', + type: 'code', + placeholder: '{"custom_field_id": "value"}', + condition: { + field: 'operation', + value: [ + 'contact_create', + 'contact_update', + 'account_create', + 'account_update', + 'opportunity_create', + 'opportunity_update', + ], + }, + mode: 'advanced', + }, + { + id: 'contact_run_dedupe', + title: 'Run Deduplication', + type: 'switch', + condition: { field: 'operation', value: 'contact_create' }, + mode: 'advanced', + }, + // Contact Bulk Operations { id: 'contacts', @@ -304,11 +437,25 @@ export const ApolloBlock: BlockConfig = { }, { id: 'contacts', - title: 'Contacts (JSON Array)', + title: 'Contact IDs (JSON Array)', + type: 'code', + placeholder: '["contact_id_1", "contact_id_2"]', + condition: { field: 'operation', value: 'contact_bulk_update' }, + }, + { + id: 'contact_attributes', + title: 'Contact Attributes (JSON Array of Objects)', type: 'code', - placeholder: '[{"id": "contact_id_1", "first_name": "John", "last_name": "Doe"}]', + placeholder: + '[{"id": "contact_id_1", "first_name": "John", "title": "VP Sales", "owner_id": "user_id"}]', condition: { field: 'operation', value: 'contact_bulk_update' }, - required: true, + }, + { + id: 'async', + title: 'Force Asynchronous Processing', + type: 'switch', + condition: { field: 'operation', value: 'contact_bulk_update' }, + mode: 'advanced', }, { id: 'run_dedupe', @@ -317,6 +464,14 @@ export const ApolloBlock: BlockConfig = { condition: { field: 'operation', value: 'contact_bulk_create' }, mode: 'advanced', }, + { + id: 'append_label_names', + title: 'Append Label Names (JSON Array)', + type: 'code', + placeholder: '["Hot Lead", "Q4 Outreach"]', + condition: { field: 'operation', value: 'contact_bulk_create' }, + mode: 'advanced', + }, // Account Fields { @@ -324,35 +479,31 @@ export const ApolloBlock: BlockConfig = { title: 'Account Name', type: 'short-input', placeholder: 'Company name', - condition: { - field: 'operation', - value: ['account_create', 'account_update'], - }, - required: { - field: 'operation', - value: 'account_create', - }, + condition: { field: 'operation', value: ['account_create', 'account_update'] }, + required: { field: 'operation', value: 'account_create' }, }, { - id: 'website_url', - title: 'Website URL', + id: 'phone', + title: 'Phone Number', type: 'short-input', - placeholder: 'https://example.com', - condition: { - field: 'operation', - value: ['account_create', 'account_update'], - }, + placeholder: 'Company phone', + condition: { field: 'operation', value: ['account_create', 'account_update'] }, mode: 'advanced', }, { - id: 'phone', - title: 'Phone Number', + id: 'account_stage_id', + title: 'Account Stage ID', type: 'short-input', - placeholder: 'Company phone', - condition: { - field: 'operation', - value: ['account_create', 'account_update'], - }, + placeholder: 'Apollo account stage ID', + condition: { field: 'operation', value: ['account_create', 'account_update'] }, + mode: 'advanced', + }, + { + id: 'raw_address', + title: 'Account Address', + type: 'short-input', + placeholder: '123 Main St, San Francisco, CA', + condition: { field: 'operation', value: ['account_create', 'account_update'] }, mode: 'advanced', }, @@ -364,7 +515,7 @@ export const ApolloBlock: BlockConfig = { placeholder: 'Search keywords', condition: { field: 'operation', - value: ['people_search', 'contact_search', 'account_search', 'opportunity_search'], + value: ['people_search', 'contact_search'], }, }, { @@ -375,24 +526,54 @@ export const ApolloBlock: BlockConfig = { condition: { field: 'operation', value: 'account_search' }, mode: 'advanced', }, + { + id: 'account_label_ids', + title: 'Account Label IDs', + type: 'code', + placeholder: '["label_id_1", "label_id_2"]', + condition: { field: 'operation', value: 'account_search' }, + mode: 'advanced', + }, // Account Bulk Operations { id: 'accounts', title: 'Accounts (JSON Array)', type: 'code', - placeholder: - '[{"name": "Company A", "website_url": "https://companya.com", "phone": "+1234567890"}]', + placeholder: '[{"name": "Company A", "domain": "companya.com", "phone": "+1234567890"}]', condition: { field: 'operation', value: 'account_bulk_create' }, required: true, }, { id: 'accounts', - title: 'Accounts (JSON Array)', + title: 'Account IDs (JSON Array)', type: 'code', - placeholder: '[{"id": "account_id_1", "name": "Updated Company Name"}]', + placeholder: '["account_id_1", "account_id_2"]', + condition: { field: 'operation', value: 'account_bulk_update' }, + }, + { + id: 'account_bulk_update_name', + title: 'Uniform Name (used with Account IDs)', + type: 'short-input', + placeholder: 'Updated Account Name', + condition: { field: 'operation', value: 'account_bulk_update' }, + mode: 'advanced', + }, + { + id: 'account_bulk_update_owner_id', + title: 'Uniform Owner ID (used with Account IDs)', + type: 'short-input', + placeholder: 'Apollo user ID', + condition: { field: 'operation', value: 'account_bulk_update' }, + mode: 'advanced', + }, + { + id: 'account_attributes', + title: 'Account Attributes (JSON Array of Objects)', + type: 'code', + placeholder: + '[{"id": "account_id_1", "name": "Acme", "owner_id": "user_id", "account_stage_id": "stage_id"}]', condition: { field: 'operation', value: 'account_bulk_update' }, - required: true, }, // Opportunity Fields @@ -401,46 +582,31 @@ export const ApolloBlock: BlockConfig = { title: 'Opportunity Name', type: 'short-input', placeholder: 'Opportunity name', - condition: { - field: 'operation', - value: ['opportunity_create', 'opportunity_update'], - }, - required: { - field: 'operation', - value: 'opportunity_create', - }, + condition: { field: 'operation', value: ['opportunity_create', 'opportunity_update'] }, + required: { field: 'operation', value: 'opportunity_create' }, }, { id: 'amount', title: 'Amount', type: 'short-input', - placeholder: 'Deal amount (e.g., 50000)', - condition: { - field: 'operation', - value: ['opportunity_create', 'opportunity_update'], - }, + placeholder: 'Plain number, no commas (e.g., 50000)', + condition: { field: 'operation', value: ['opportunity_create', 'opportunity_update'] }, mode: 'advanced', }, { - id: 'stage_id', - title: 'Stage ID', + id: 'opportunity_stage_id', + title: 'Opportunity Stage ID', type: 'short-input', - placeholder: 'Opportunity stage ID', - condition: { - field: 'operation', - value: ['opportunity_create', 'opportunity_update'], - }, + placeholder: 'Apollo opportunity_stage_id', + condition: { field: 'operation', value: ['opportunity_create', 'opportunity_update'] }, mode: 'advanced', }, { - id: 'close_date', + id: 'closed_date', title: 'Close Date', type: 'short-input', - placeholder: 'ISO date (e.g., 2024-12-31)', - condition: { - field: 'operation', - value: ['opportunity_create', 'opportunity_update'], - }, + placeholder: 'YYYY-MM-DD (e.g., 2024-12-31)', + condition: { field: 'operation', value: ['opportunity_create', 'opportunity_update'] }, mode: 'advanced', wandConfig: { enabled: true, @@ -456,17 +622,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n generationType: 'timestamp', }, }, - { - id: 'description', - title: 'Description', - type: 'long-input', - placeholder: 'Opportunity description', - condition: { - field: 'operation', - value: ['opportunity_create', 'opportunity_update'], - }, - mode: 'advanced', - }, // Opportunity Get { @@ -474,36 +629,27 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n title: 'Opportunity ID', type: 'short-input', placeholder: 'Apollo opportunity ID', - condition: { - field: 'operation', - value: ['opportunity_get', 'opportunity_update'], - }, + condition: { field: 'operation', value: ['opportunity_get', 'opportunity_update'] }, required: true, }, - // Opportunity Search Fields - { - id: 'account_ids', - title: 'Account IDs', - type: 'code', - placeholder: '["account_id_1", "account_id_2"]', - condition: { field: 'operation', value: 'opportunity_search' }, - mode: 'advanced', - }, + // Opportunity / Account / Task Search shared sort { - id: 'stage_ids', - title: 'Stage IDs', - type: 'code', - placeholder: '["stage_id_1", "stage_id_2"]', - condition: { field: 'operation', value: 'opportunity_search' }, + id: 'sort_by_field', + title: 'Sort By Field', + type: 'short-input', + placeholder: 'Sort field name', + condition: { + field: 'operation', + value: ['opportunity_search', 'account_search', 'task_search', 'contact_search'], + }, mode: 'advanced', }, { - id: 'owner_ids', - title: 'Owner IDs', - type: 'code', - placeholder: '["user_id_1", "user_id_2"]', - condition: { field: 'operation', value: 'opportunity_search' }, + id: 'sort_ascending', + title: 'Sort Ascending', + type: 'switch', + condition: { field: 'operation', value: ['account_search', 'contact_search'] }, mode: 'advanced', }, @@ -515,15 +661,8 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n placeholder: 'Search by sequence name', condition: { field: 'operation', value: 'sequence_search' }, }, - { - id: 'active', - title: 'Active Only', - type: 'switch', - condition: { field: 'operation', value: 'sequence_search' }, - mode: 'advanced', - }, - // Sequence Fields + // Sequence Add Fields { id: 'sequence_id', title: 'Sequence ID', @@ -537,16 +676,80 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n title: 'Contact IDs (JSON Array)', type: 'code', placeholder: '["contact_id_1", "contact_id_2"]', + condition: { field: 'operation', value: ['sequence_add', 'task_create'] }, + required: { field: 'operation', value: 'task_create' }, + }, + { + id: 'sequence_add_label_names', + title: 'Label Names (JSON Array)', + type: 'code', + placeholder: '["Hot Lead", "Q4 Outreach"]', + condition: { field: 'operation', value: 'sequence_add' }, + mode: 'advanced', + }, + { + id: 'send_email_from_email_account_id', + title: 'Send Email From (Email Account ID)', + type: 'short-input', + placeholder: 'Apollo email account ID', condition: { field: 'operation', value: 'sequence_add' }, required: true, }, + { + id: 'send_email_from_email_address', + title: 'Send Email From (Email Address)', + type: 'short-input', + placeholder: 'sender@example.com', + condition: { field: 'operation', value: 'sequence_add' }, + mode: 'advanced', + }, - // Task Fields + // Task Create Fields { - id: 'note', - title: 'Task Note', - type: 'long-input', - placeholder: 'Task description', + id: 'user_id', + title: 'Assigned User ID', + type: 'short-input', + placeholder: 'Apollo user ID', + condition: { field: 'operation', value: 'task_create' }, + required: true, + }, + { + id: 'priority', + title: 'Priority', + type: 'dropdown', + options: [ + { label: 'High', id: 'high' }, + { label: 'Medium', id: 'medium' }, + { label: 'Low', id: 'low' }, + ], + value: () => 'medium', + condition: { field: 'operation', value: 'task_create' }, + required: true, + }, + { + id: 'type', + title: 'Task Type', + type: 'dropdown', + options: [ + { label: 'Call', id: 'call' }, + { label: 'Outreach Manual Email', id: 'outreach_manual_email' }, + { label: 'LinkedIn Step Message', id: 'linkedin_step_message' }, + { label: 'Action Item', id: 'action_item' }, + ], + value: () => 'action_item', + condition: { field: 'operation', value: 'task_create' }, + required: true, + }, + { + id: 'status', + title: 'Status', + type: 'dropdown', + options: [ + { label: 'Scheduled', id: 'scheduled' }, + { label: 'Completed', id: 'completed' }, + { label: 'Archived', id: 'archived' }, + ], + value: () => 'scheduled', condition: { field: 'operation', value: 'task_create' }, required: true, }, @@ -554,9 +757,9 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n id: 'due_at', title: 'Due Date', type: 'short-input', - placeholder: 'ISO date (e.g., 2024-12-31T23:59:59Z)', + placeholder: 'ISO 8601 (e.g., 2024-12-31T23:59:59Z)', condition: { field: 'operation', value: 'task_create' }, - mode: 'advanced', + required: true, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description. @@ -573,9 +776,20 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes }, }, { - id: 'completed', - title: 'Completed', - type: 'switch', + id: 'task_notes', + title: 'Task Notes', + type: 'long-input', + placeholder: 'Notes for the task', + condition: { field: 'operation', value: 'task_create' }, + mode: 'advanced', + }, + + // Task Search Fields + { + id: 'open_factor_names', + title: 'Open Factor Names', + type: 'code', + placeholder: '["task_types"]', condition: { field: 'operation', value: 'task_search' }, mode: 'advanced', }, @@ -707,75 +921,93 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes }, params: (params) => { const { apiKey, ...rest } = params + const parsedParams: Record = { apiKey, ...rest } - // Parse JSON inputs safely - const parsedParams: any = { apiKey, ...rest } + const parseJsonField = (field: string) => { + const value = (rest as Record)[field] + if (typeof value === 'string' && value.trim() !== '') { + parsedParams[field] = JSON.parse(value) + } + } try { - if (rest.person_titles && typeof rest.person_titles === 'string') { - parsedParams.person_titles = JSON.parse(rest.person_titles) - } - if (rest.person_locations && typeof rest.person_locations === 'string') { - parsedParams.person_locations = JSON.parse(rest.person_locations) - } - if (rest.person_seniorities && typeof rest.person_seniorities === 'string') { - parsedParams.person_seniorities = JSON.parse(rest.person_seniorities) - } - if (rest.organization_names && typeof rest.organization_names === 'string') { - parsedParams.organization_names = JSON.parse(rest.organization_names) - } - if (rest.organization_locations && typeof rest.organization_locations === 'string') { - parsedParams.organization_locations = JSON.parse(rest.organization_locations) - } - if ( - rest.organization_num_employees_ranges && - typeof rest.organization_num_employees_ranges === 'string' - ) { - parsedParams.organization_num_employees_ranges = JSON.parse( - rest.organization_num_employees_ranges - ) - } - if ( - rest.q_organization_keyword_tags && - typeof rest.q_organization_keyword_tags === 'string' - ) { - parsedParams.q_organization_keyword_tags = JSON.parse(rest.q_organization_keyword_tags) - } - if (rest.contact_stage_ids && typeof rest.contact_stage_ids === 'string') { - parsedParams.contact_stage_ids = JSON.parse(rest.contact_stage_ids) + for (const field of [ + 'person_titles', + 'person_locations', + 'person_seniorities', + 'organization_names', + 'organization_locations', + 'organization_not_locations', + 'organization_ids', + 'q_organization_domains_list', + 'contact_email_status', + 'organization_num_employees_ranges', + 'q_organization_keyword_tags', + 'contact_stage_ids', + 'contact_label_ids', + 'account_stage_ids', + 'account_label_ids', + 'people', + 'organizations', + 'contacts', + 'accounts', + 'contact_ids', + 'contact_attributes', + 'account_attributes', + 'label_names', + 'sequence_add_label_names', + 'append_label_names', + 'typed_custom_fields', + 'open_factor_names', + ]) { + parseJsonField(field) } - if (rest.account_stage_ids && typeof rest.account_stage_ids === 'string') { - parsedParams.account_stage_ids = JSON.parse(rest.account_stage_ids) - } - if (rest.people && typeof rest.people === 'string') { - parsedParams.people = JSON.parse(rest.people) - } - if (rest.organizations && typeof rest.organizations === 'string') { - parsedParams.organizations = JSON.parse(rest.organizations) - } - if (rest.contacts && typeof rest.contacts === 'string') { - parsedParams.contacts = JSON.parse(rest.contacts) - } - if (rest.accounts && typeof rest.accounts === 'string') { - parsedParams.accounts = JSON.parse(rest.accounts) - } - if (rest.contact_ids && typeof rest.contact_ids === 'string') { - parsedParams.contact_ids = JSON.parse(rest.contact_ids) - } - if (rest.account_ids && typeof rest.account_ids === 'string') { - parsedParams.account_ids = JSON.parse(rest.account_ids) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + throw new Error(`Invalid JSON input: ${message}`) + } + + const extractIds = (raw: unknown): string[] | undefined => { + if (!Array.isArray(raw)) return undefined + return raw + .map((item) => { + if (typeof item === 'string') return item + if (item && typeof item === 'object' && 'id' in item) { + const id = (item as { id: unknown }).id + return typeof id === 'string' ? id : undefined + } + return undefined + }) + .filter((id): id is string => Boolean(id)) + } + + if (params.operation === 'contact_bulk_update') { + const ids = extractIds(parsedParams.contacts) + if (ids) parsedParams.contact_ids = ids + parsedParams.contacts = undefined + } + + if (params.operation === 'account_bulk_update') { + const ids = extractIds(parsedParams.accounts) + if (ids) parsedParams.account_ids = ids + parsedParams.accounts = undefined + if (rest.account_bulk_update_name) { + parsedParams.name = rest.account_bulk_update_name } - if (rest.stage_ids && typeof rest.stage_ids === 'string') { - parsedParams.stage_ids = JSON.parse(rest.stage_ids) + if (rest.account_bulk_update_owner_id) { + parsedParams.owner_id = rest.account_bulk_update_owner_id } - if (rest.owner_ids && typeof rest.owner_ids === 'string') { - parsedParams.owner_ids = JSON.parse(rest.owner_ids) + parsedParams.account_bulk_update_name = undefined + parsedParams.account_bulk_update_owner_id = undefined + } + + if (params.operation === 'contact_create') { + if (rest.contact_run_dedupe !== undefined) { + parsedParams.run_dedupe = rest.contact_run_dedupe } - } catch (error: any) { - throw new Error(`Invalid JSON input: ${error.message}`) + parsedParams.contact_run_dedupe = undefined } - // Map UI field names to API parameter names if (params.operation === 'account_create' || params.operation === 'account_update') { if (rest.account_name) parsedParams.name = rest.account_name parsedParams.account_name = undefined @@ -785,6 +1017,13 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes parsedParams.account_id = rest.account_id } + if (params.operation === 'sequence_add') { + if (parsedParams.sequence_add_label_names !== undefined) { + parsedParams.label_names = parsedParams.sequence_add_label_names + } + parsedParams.sequence_add_label_names = undefined + } + if ( params.operation === 'opportunity_create' || params.operation === 'opportunity_update' @@ -793,12 +1032,12 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes parsedParams.opportunity_name = undefined } - // Convert page/per_page to numbers if provided if (parsedParams.page) parsedParams.page = Number(parsedParams.page) if (parsedParams.per_page) parsedParams.per_page = Number(parsedParams.per_page) - // Convert amount to number if provided - if (parsedParams.amount) parsedParams.amount = Number(parsedParams.amount) + if (parsedParams.amount !== undefined && parsedParams.amount !== '') { + parsedParams.amount = String(parsedParams.amount) + } return parsedParams }, diff --git a/apps/sim/lib/workflows/migrations/subblock-migrations.ts b/apps/sim/lib/workflows/migrations/subblock-migrations.ts index 0d49aeefb36..a3dba9ae855 100644 --- a/apps/sim/lib/workflows/migrations/subblock-migrations.ts +++ b/apps/sim/lib/workflows/migrations/subblock-migrations.ts @@ -37,6 +37,10 @@ export const SUBBLOCK_ID_MIGRATIONS: Record> = { expandApplicationFormDefinition: '_removed_expandApplicationFormDefinition', expandSurveyFormDefinitions: '_removed_expandSurveyFormDefinitions', }, + apollo: { + contact_ids_bulk: 'contacts', + account_ids_bulk: 'accounts', + }, rippling: { action: '_removed_action', candidateDepartment: '_removed_candidateDepartment', diff --git a/apps/sim/tools/apollo/account_bulk_update.ts b/apps/sim/tools/apollo/account_bulk_update.ts index a78d4f8c45c..6892fffe4b3 100644 --- a/apps/sim/tools/apollo/account_bulk_update.ts +++ b/apps/sim/tools/apollo/account_bulk_update.ts @@ -21,12 +21,31 @@ export const apolloAccountBulkUpdateTool: ToolConfig< visibility: 'hidden', description: 'Apollo API key (master key required)', }, - accounts: { + account_ids: { type: 'array', - required: true, + required: false, + visibility: 'user-or-llm', + description: + 'Array of account IDs to update with the same values (max 1000). Use with name/owner_id for uniform updates. Use either this OR account_attributes.', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'When using account_ids, apply this name to all accounts', + }, + owner_id: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'When using account_ids, apply this owner to all accounts', + }, + account_attributes: { + type: 'json', + required: false, visibility: 'user-or-llm', description: - 'Array of accounts to update (max 1000). Each account must include id field, and optionally name, website_url, phone, owner_id', + 'Array of account objects with individual updates (each must include id). Example: [{"id": "acc1", "name": "Acme", "owner_id": "u1", "account_stage_id": "s1", "typed_custom_fields": {"field_id": "value"}}]', }, }, @@ -38,9 +57,18 @@ export const apolloAccountBulkUpdateTool: ToolConfig< 'Cache-Control': 'no-cache', 'X-Api-Key': params.apiKey, }), - body: (params: ApolloAccountBulkUpdateParams) => ({ - accounts: params.accounts.slice(0, 1000), - }), + body: (params: ApolloAccountBulkUpdateParams) => { + const body: Record = {} + if (params.account_ids && params.account_ids.length > 0) { + body.account_ids = params.account_ids.slice(0, 1000) + } + if (params.name) body.name = params.name + if (params.owner_id) body.owner_id = params.owner_id + if (params.account_attributes && params.account_attributes.length > 0) { + body.account_attributes = params.account_attributes + } + return body + }, }, transformResponse: async (response: Response) => { @@ -54,35 +82,21 @@ export const apolloAccountBulkUpdateTool: ToolConfig< return { success: true, output: { - updated_accounts: data.accounts || data.updated_accounts || [], - failed_accounts: data.failed_accounts || [], - total_submitted: data.accounts?.length || 0, - updated: data.updated_accounts?.length || data.accounts?.length || 0, - failed: data.failed_accounts?.length || 0, + message: data.message ?? null, + account_ids: data.account_ids ?? [], }, } }, outputs: { - updated_accounts: { - type: 'json', - description: 'Array of successfully updated accounts', + message: { + type: 'string', + description: 'Confirmation message from Apollo', + optional: true, }, - failed_accounts: { + account_ids: { type: 'json', - description: 'Array of accounts that failed to update', - }, - total_submitted: { - type: 'number', - description: 'Total number of accounts submitted', - }, - updated: { - type: 'number', - description: 'Number of accounts successfully updated', - }, - failed: { - type: 'number', - description: 'Number of accounts that failed to update', + description: 'IDs of accounts that were updated', }, }, } diff --git a/apps/sim/tools/apollo/account_create.ts b/apps/sim/tools/apollo/account_create.ts index eb341844098..220dfccc606 100644 --- a/apps/sim/tools/apollo/account_create.ts +++ b/apps/sim/tools/apollo/account_create.ts @@ -15,7 +15,7 @@ export const apolloAccountCreateTool: ToolConfig< type: 'string', required: true, visibility: 'hidden', - description: 'Apollo API key', + description: 'Apollo API key (master key required)', }, name: { type: 'string', @@ -23,23 +23,41 @@ export const apolloAccountCreateTool: ToolConfig< visibility: 'user-or-llm', description: 'Company name (e.g., "Acme Corporation")', }, - website_url: { + domain: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Company website URL', + description: 'Company domain without www. prefix (e.g., "acme.com")', }, phone: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Company phone number', + description: 'Primary phone number for the account', }, owner_id: { type: 'string', required: false, visibility: 'user-only', - description: 'User ID of the account owner', + description: 'Apollo user ID of the account owner', + }, + account_stage_id: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Apollo ID for the account stage to assign this account to', + }, + raw_address: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Corporate location (e.g., "San Francisco, CA, USA")', + }, + typed_custom_fields: { + type: 'json', + required: false, + visibility: 'user-only', + description: 'Custom field values as { custom_field_id: value } map', }, }, @@ -52,10 +70,13 @@ export const apolloAccountCreateTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloAccountCreateParams) => { - const body: any = { name: params.name } - if (params.website_url) body.website_url = params.website_url + const body: Record = { name: params.name } + if (params.domain) body.domain = params.domain if (params.phone) body.phone = params.phone if (params.owner_id) body.owner_id = params.owner_id + if (params.account_stage_id) body.account_stage_id = params.account_stage_id + if (params.raw_address) body.raw_address = params.raw_address + if (params.typed_custom_fields) body.typed_custom_fields = params.typed_custom_fields return body }, }, @@ -67,12 +88,13 @@ export const apolloAccountCreateTool: ToolConfig< } const data = await response.json() + const account = data.account ?? (data.id ? data : null) return { success: true, output: { - account: data.account ?? null, - created: !!data.account, + account, + created: !!account, }, } }, diff --git a/apps/sim/tools/apollo/account_search.ts b/apps/sim/tools/apollo/account_search.ts index 78bfa0ce447..511540c7ba6 100644 --- a/apps/sim/tools/apollo/account_search.ts +++ b/apps/sim/tools/apollo/account_search.ts @@ -18,23 +18,36 @@ export const apolloAccountSearchTool: ToolConfig< visibility: 'hidden', description: 'Apollo API key (master key required)', }, - q_keywords: { + q_organization_name: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Keywords to search for in account data', + description: 'Filter accounts by organization name (partial-match search)', }, - owner_id: { - type: 'string', + account_stage_ids: { + type: 'array', required: false, visibility: 'user-only', - description: 'Filter by account owner user ID', + description: 'Filter by account stage IDs', }, - account_stage_ids: { + account_label_ids: { type: 'array', required: false, visibility: 'user-only', - description: 'Filter by account stage IDs', + description: 'Filter by account label IDs', + }, + sort_by_field: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Sort field: "account_last_activity_date", "account_created_at", or "account_updated_at"', + }, + sort_ascending: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Sort ascending when true. Defaults to descending.', }, page: { type: 'number', @@ -59,15 +72,19 @@ export const apolloAccountSearchTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloAccountSearchParams) => { - const body: any = { + const body: Record = { page: params.page || 1, per_page: Math.min(params.per_page || 25, 100), } - if (params.q_keywords) body.q_keywords = params.q_keywords - if (params.owner_id) body.owner_id = params.owner_id + if (params.q_organization_name) body.q_organization_name = params.q_organization_name if (params.account_stage_ids?.length) { body.account_stage_ids = params.account_stage_ids } + if (params.account_label_ids?.length) { + body.account_label_ids = params.account_label_ids + } + if (params.sort_by_field) body.sort_by_field = params.sort_by_field + if (params.sort_ascending !== undefined) body.sort_ascending = params.sort_ascending return body }, }, diff --git a/apps/sim/tools/apollo/account_update.ts b/apps/sim/tools/apollo/account_update.ts index 996e80cc646..acaef42cc60 100644 --- a/apps/sim/tools/apollo/account_update.ts +++ b/apps/sim/tools/apollo/account_update.ts @@ -29,11 +29,11 @@ export const apolloAccountUpdateTool: ToolConfig< visibility: 'user-or-llm', description: 'Company name (e.g., "Acme Corporation")', }, - website_url: { + domain: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Company website URL', + description: 'Company domain (e.g., "acme.com")', }, phone: { type: 'string', @@ -45,13 +45,31 @@ export const apolloAccountUpdateTool: ToolConfig< type: 'string', required: false, visibility: 'user-only', - description: 'User ID of the account owner', + description: 'Apollo user ID of the account owner', + }, + account_stage_id: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Apollo ID for the account stage to assign this account to', + }, + raw_address: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Corporate location (e.g., "San Francisco, CA, USA")', + }, + typed_custom_fields: { + type: 'json', + required: false, + visibility: 'user-only', + description: 'Custom field values as { custom_field_id: value } map', }, }, request: { url: (params: ApolloAccountUpdateParams) => - `https://api.apollo.io/api/v1/accounts/${params.account_id}`, + `https://api.apollo.io/api/v1/accounts/${params.account_id.trim()}`, method: 'PATCH', headers: (params: ApolloAccountUpdateParams) => ({ 'Content-Type': 'application/json', @@ -59,11 +77,14 @@ export const apolloAccountUpdateTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloAccountUpdateParams) => { - const body: any = {} + const body: Record = {} if (params.name) body.name = params.name - if (params.website_url) body.website_url = params.website_url + if (params.domain) body.domain = params.domain if (params.phone) body.phone = params.phone if (params.owner_id) body.owner_id = params.owner_id + if (params.account_stage_id) body.account_stage_id = params.account_stage_id + if (params.raw_address) body.raw_address = params.raw_address + if (params.typed_custom_fields) body.typed_custom_fields = params.typed_custom_fields return body }, }, @@ -75,12 +96,13 @@ export const apolloAccountUpdateTool: ToolConfig< } const data = await response.json() + const account = data.account ?? (data.id ? data : null) return { success: true, output: { - account: data.account ?? null, - updated: !!data.account, + account, + updated: !!account, }, } }, diff --git a/apps/sim/tools/apollo/contact_bulk_create.ts b/apps/sim/tools/apollo/contact_bulk_create.ts index b16a4e94c13..d612e8d5b92 100644 --- a/apps/sim/tools/apollo/contact_bulk_create.ts +++ b/apps/sim/tools/apollo/contact_bulk_create.ts @@ -26,7 +26,13 @@ export const apolloContactBulkCreateTool: ToolConfig< required: true, visibility: 'user-or-llm', description: - 'Array of contacts to create (max 100). Each contact should include first_name, last_name, and optionally email, title, account_id, owner_id', + 'Array of contacts to create (max 100). Each contact may include first_name, last_name, email, title, organization_name, owner_id, contact_stage_id, linkedin_url, phone_numbers, and contact_emails', + }, + append_label_names: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Label names to add to all contacts in this request (e.g., ["Hot Lead"])', }, run_dedupe: { type: 'boolean', @@ -46,12 +52,15 @@ export const apolloContactBulkCreateTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloContactBulkCreateParams) => { - const body: any = { + const body: Record = { contacts: params.contacts.slice(0, 100), } if (params.run_dedupe !== undefined) { body.run_dedupe = params.run_dedupe } + if (params.append_label_names && params.append_label_names.length > 0) { + body.append_label_names = params.append_label_names + } return body }, }, @@ -63,15 +72,17 @@ export const apolloContactBulkCreateTool: ToolConfig< } const data = await response.json() + const createdContacts = data.created_contacts || data.contacts || [] + const existingContacts = data.existing_contacts || [] return { success: true, output: { - created_contacts: data.contacts || data.created_contacts || [], - existing_contacts: data.existing_contacts || [], - total_submitted: data.contacts?.length || 0, - created: data.created_contacts?.length || data.contacts?.length || 0, - existing: data.existing_contacts?.length || 0, + created_contacts: createdContacts, + existing_contacts: existingContacts, + total_submitted: createdContacts.length + existingContacts.length, + created: createdContacts.length, + existing: existingContacts.length, }, } }, diff --git a/apps/sim/tools/apollo/contact_bulk_update.ts b/apps/sim/tools/apollo/contact_bulk_update.ts index 4f254cfc1f1..3ef80d08be4 100644 --- a/apps/sim/tools/apollo/contact_bulk_update.ts +++ b/apps/sim/tools/apollo/contact_bulk_update.ts @@ -21,12 +21,25 @@ export const apolloContactBulkUpdateTool: ToolConfig< visibility: 'hidden', description: 'Apollo API key (master key required)', }, - contacts: { + contact_ids: { type: 'array', - required: true, + required: false, + visibility: 'user-or-llm', + description: + 'Array of contact IDs to update with the same values. Use either this OR contact_attributes.', + }, + contact_attributes: { + type: 'json', + required: false, visibility: 'user-or-llm', description: - 'Array of contacts to update (max 100). Each contact must include id field, and optionally first_name, last_name, email, title, account_id, owner_id', + 'Either an array of per-contact updates (each with id) or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields', + }, + async: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Force asynchronous processing. Automatically enabled for >100 contacts', }, }, @@ -38,9 +51,26 @@ export const apolloContactBulkUpdateTool: ToolConfig< 'Cache-Control': 'no-cache', 'X-Api-Key': params.apiKey, }), - body: (params: ApolloContactBulkUpdateParams) => ({ - contacts: params.contacts.slice(0, 100), - }), + body: (params: ApolloContactBulkUpdateParams) => { + const body: Record = {} + if (params.contact_ids && params.contact_ids.length > 0) { + body.contact_ids = params.contact_ids + } + if (params.contact_attributes) { + if (Array.isArray(params.contact_attributes)) { + if (params.contact_attributes.length > 0) { + body.contact_attributes = params.contact_attributes + } + } else if ( + typeof params.contact_attributes === 'object' && + Object.keys(params.contact_attributes).length > 0 + ) { + body.contact_attributes = params.contact_attributes + } + } + if (params.async !== undefined) body.async = params.async + return body + }, }, transformResponse: async (response: Response) => { @@ -54,35 +84,22 @@ export const apolloContactBulkUpdateTool: ToolConfig< return { success: true, output: { - updated_contacts: data.contacts || data.updated_contacts || [], - failed_contacts: data.failed_contacts || [], - total_submitted: data.contacts?.length || 0, - updated: data.updated_contacts?.length || data.contacts?.length || 0, - failed: data.failed_contacts?.length || 0, + message: data.message ?? null, + job_id: data.job_id ?? null, }, } }, outputs: { - updated_contacts: { - type: 'json', - description: 'Array of successfully updated contacts', - }, - failed_contacts: { - type: 'json', - description: 'Array of contacts that failed to update', - }, - total_submitted: { - type: 'number', - description: 'Total number of contacts submitted', - }, - updated: { - type: 'number', - description: 'Number of contacts successfully updated', + message: { + type: 'string', + description: 'Confirmation message from Apollo', + optional: true, }, - failed: { - type: 'number', - description: 'Number of contacts that failed to update', + job_id: { + type: 'string', + description: 'Async job ID (returned for >100 contacts)', + optional: true, }, }, } diff --git a/apps/sim/tools/apollo/contact_create.ts b/apps/sim/tools/apollo/contact_create.ts index ecc61a0b219..e8dde6381a9 100644 --- a/apps/sim/tools/apollo/contact_create.ts +++ b/apps/sim/tools/apollo/contact_create.ts @@ -51,7 +51,80 @@ export const apolloContactCreateTool: ToolConfig< type: 'string', required: false, visibility: 'user-only', - description: 'User ID of the contact owner', + description: + 'User ID of the contact owner (accepted by Apollo but not officially documented for POST /contacts)', + }, + organization_name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the contact\'s employer (e.g., "Apollo")', + }, + website_url: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Corporate website URL (e.g., "https://www.apollo.io/")', + }, + label_names: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Lists/labels to add the contact to (e.g., ["Prospects"])', + }, + contact_stage_id: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Apollo ID for the contact stage', + }, + present_raw_address: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Personal location for the contact (e.g., "Atlanta, United States")', + }, + direct_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Primary phone number', + }, + corporate_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Work/office phone number', + }, + mobile_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Mobile phone number', + }, + home_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Home phone number', + }, + other_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Alternative phone number', + }, + typed_custom_fields: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'Custom field values keyed by custom field ID', + }, + run_dedupe: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'When true, Apollo deduplicates against existing contacts', }, }, @@ -64,7 +137,7 @@ export const apolloContactCreateTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloContactCreateParams) => { - const body: any = { + const body: Record = { first_name: params.first_name, last_name: params.last_name, } @@ -72,6 +145,20 @@ export const apolloContactCreateTool: ToolConfig< if (params.title) body.title = params.title if (params.account_id) body.account_id = params.account_id if (params.owner_id) body.owner_id = params.owner_id + if (params.organization_name) body.organization_name = params.organization_name + if (params.website_url) body.website_url = params.website_url + if (params.label_names && params.label_names.length > 0) { + body.label_names = params.label_names + } + if (params.contact_stage_id) body.contact_stage_id = params.contact_stage_id + if (params.present_raw_address) body.present_raw_address = params.present_raw_address + if (params.direct_phone) body.direct_phone = params.direct_phone + if (params.corporate_phone) body.corporate_phone = params.corporate_phone + if (params.mobile_phone) body.mobile_phone = params.mobile_phone + if (params.home_phone) body.home_phone = params.home_phone + if (params.other_phone) body.other_phone = params.other_phone + if (params.typed_custom_fields) body.typed_custom_fields = params.typed_custom_fields + if (params.run_dedupe !== undefined) body.run_dedupe = params.run_dedupe return body }, }, diff --git a/apps/sim/tools/apollo/contact_search.ts b/apps/sim/tools/apollo/contact_search.ts index e2c80604634..f9ebe9889e2 100644 --- a/apps/sim/tools/apollo/contact_search.ts +++ b/apps/sim/tools/apollo/contact_search.ts @@ -29,6 +29,25 @@ export const apolloContactSearchTool: ToolConfig< visibility: 'user-only', description: 'Filter by contact stage IDs', }, + contact_label_ids: { + type: 'array', + required: false, + visibility: 'user-only', + description: 'Filter by Apollo label IDs (lists)', + }, + sort_by_field: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Sort field: contact_last_activity_date, contact_email_last_opened_at, contact_email_last_clicked_at, contact_created_at, or contact_updated_at', + }, + sort_ascending: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'When true, sort ascending. Must be used together with sort_by_field', + }, page: { type: 'number', required: false, @@ -52,7 +71,7 @@ export const apolloContactSearchTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloContactSearchParams) => { - const body: any = { + const body: Record = { page: params.page || 1, per_page: Math.min(params.per_page || 25, 100), } @@ -60,6 +79,11 @@ export const apolloContactSearchTool: ToolConfig< if (params.contact_stage_ids?.length) { body.contact_stage_ids = params.contact_stage_ids } + if (params.contact_label_ids?.length) { + body.contact_label_ids = params.contact_label_ids + } + if (params.sort_by_field) body.sort_by_field = params.sort_by_field + if (params.sort_ascending !== undefined) body.sort_ascending = params.sort_ascending return body }, }, diff --git a/apps/sim/tools/apollo/contact_update.ts b/apps/sim/tools/apollo/contact_update.ts index 31ebd0c877f..1de9d4030bb 100644 --- a/apps/sim/tools/apollo/contact_update.ts +++ b/apps/sim/tools/apollo/contact_update.ts @@ -57,13 +57,81 @@ export const apolloContactUpdateTool: ToolConfig< type: 'string', required: false, visibility: 'user-only', - description: 'User ID of the contact owner', + description: + 'User ID of the contact owner (accepted by Apollo but not officially documented for PATCH /contacts/{id})', + }, + organization_name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the contact\'s employer (e.g., "Apollo")', + }, + website_url: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Corporate website URL (e.g., "https://www.apollo.io/")', + }, + label_names: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Lists/labels to add the contact to (e.g., ["Prospects"])', + }, + contact_stage_id: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Apollo ID for the contact stage', + }, + present_raw_address: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Personal location for the contact (e.g., "Atlanta, United States")', + }, + direct_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Primary phone number', + }, + corporate_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Work/office phone number', + }, + mobile_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Mobile phone number', + }, + home_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Home phone number', + }, + other_phone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Alternative phone number', + }, + typed_custom_fields: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Custom field values keyed by custom field ID (accepted by Apollo but not officially documented for PATCH /contacts/{id})', }, }, request: { url: (params: ApolloContactUpdateParams) => - `https://api.apollo.io/api/v1/contacts/${params.contact_id}`, + `https://api.apollo.io/api/v1/contacts/${params.contact_id.trim()}`, method: 'PATCH', headers: (params: ApolloContactUpdateParams) => ({ 'Content-Type': 'application/json', @@ -71,13 +139,26 @@ export const apolloContactUpdateTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloContactUpdateParams) => { - const body: any = {} + const body: Record = {} if (params.first_name) body.first_name = params.first_name if (params.last_name) body.last_name = params.last_name if (params.email) body.email = params.email if (params.title) body.title = params.title if (params.account_id) body.account_id = params.account_id if (params.owner_id) body.owner_id = params.owner_id + if (params.organization_name) body.organization_name = params.organization_name + if (params.website_url) body.website_url = params.website_url + if (params.label_names && params.label_names.length > 0) { + body.label_names = params.label_names + } + if (params.contact_stage_id) body.contact_stage_id = params.contact_stage_id + if (params.present_raw_address) body.present_raw_address = params.present_raw_address + if (params.direct_phone) body.direct_phone = params.direct_phone + if (params.corporate_phone) body.corporate_phone = params.corporate_phone + if (params.mobile_phone) body.mobile_phone = params.mobile_phone + if (params.home_phone) body.home_phone = params.home_phone + if (params.other_phone) body.other_phone = params.other_phone + if (params.typed_custom_fields) body.typed_custom_fields = params.typed_custom_fields return body }, }, diff --git a/apps/sim/tools/apollo/email_accounts.ts b/apps/sim/tools/apollo/email_accounts.ts index ceb854a602f..96b9b58ee94 100644 --- a/apps/sim/tools/apollo/email_accounts.ts +++ b/apps/sim/tools/apollo/email_accounts.ts @@ -36,12 +36,13 @@ export const apolloEmailAccountsTool: ToolConfig< } const data = await response.json() + const accounts = Array.isArray(data) ? data : data.email_accounts || data.data || [] return { success: true, output: { - email_accounts: data.email_accounts || [], - total: data.email_accounts?.length || 0, + email_accounts: accounts, + total: accounts.length, }, } }, diff --git a/apps/sim/tools/apollo/opportunity_create.ts b/apps/sim/tools/apollo/opportunity_create.ts index 4880609e88f..5c949fc692c 100644 --- a/apps/sim/tools/apollo/opportunity_create.ts +++ b/apps/sim/tools/apollo/opportunity_create.ts @@ -28,21 +28,21 @@ export const apolloOpportunityCreateTool: ToolConfig< }, account_id: { type: 'string', - required: true, + required: false, visibility: 'user-or-llm', description: 'ID of the account this opportunity belongs to (e.g., "acc_abc123")', }, amount: { - type: 'number', + type: 'string', required: false, visibility: 'user-or-llm', - description: 'Monetary value of the opportunity', + description: 'Monetary value as a plain number string with no commas or currency symbols', }, - stage_id: { + opportunity_stage_id: { type: 'string', required: false, visibility: 'user-only', - description: 'ID of the deal stage', + description: 'ID of the opportunity stage', }, owner_id: { type: 'string', @@ -50,17 +50,17 @@ export const apolloOpportunityCreateTool: ToolConfig< visibility: 'user-only', description: 'User ID of the opportunity owner', }, - close_date: { + closed_date: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Expected close date (ISO 8601 format)', + description: 'Expected close date in YYYY-MM-DD format', }, - description: { - type: 'string', + typed_custom_fields: { + type: 'json', required: false, - visibility: 'user-or-llm', - description: 'Description or notes about the opportunity', + visibility: 'user-only', + description: 'Custom field values as { custom_field_id: value } map', }, }, @@ -73,15 +73,15 @@ export const apolloOpportunityCreateTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloOpportunityCreateParams) => { - const body: any = { - name: params.name, - account_id: params.account_id, + const body: Record = { name: params.name } + if (params.account_id) body.account_id = params.account_id + if (params.amount !== undefined && params.amount !== null && params.amount !== '') { + body.amount = String(params.amount) } - if (params.amount !== undefined) body.amount = params.amount - if (params.stage_id) body.stage_id = params.stage_id + if (params.opportunity_stage_id) body.opportunity_stage_id = params.opportunity_stage_id if (params.owner_id) body.owner_id = params.owner_id - if (params.close_date) body.close_date = params.close_date - if (params.description) body.description = params.description + if (params.closed_date) body.closed_date = params.closed_date + if (params.typed_custom_fields) body.typed_custom_fields = params.typed_custom_fields return body }, }, @@ -93,12 +93,13 @@ export const apolloOpportunityCreateTool: ToolConfig< } const data = await response.json() + const opportunity = data.opportunity ?? (data.id ? data : null) return { success: true, output: { - opportunity: data.opportunity ?? null, - created: !!data.opportunity, + opportunity, + created: !!opportunity, }, } }, diff --git a/apps/sim/tools/apollo/opportunity_get.ts b/apps/sim/tools/apollo/opportunity_get.ts index 4ef79fea54d..2eb2246e320 100644 --- a/apps/sim/tools/apollo/opportunity_get.ts +++ b/apps/sim/tools/apollo/opportunity_get.ts @@ -27,7 +27,7 @@ export const apolloOpportunityGetTool: ToolConfig< request: { url: (params: ApolloOpportunityGetParams) => - `https://api.apollo.io/api/v1/opportunities/${params.opportunity_id}`, + `https://api.apollo.io/api/v1/opportunities/${params.opportunity_id.trim()}`, method: 'GET', headers: (params: ApolloOpportunityGetParams) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/apollo/opportunity_search.ts b/apps/sim/tools/apollo/opportunity_search.ts index 8f1a6db88e9..c7fa7245292 100644 --- a/apps/sim/tools/apollo/opportunity_search.ts +++ b/apps/sim/tools/apollo/opportunity_search.ts @@ -20,29 +20,11 @@ export const apolloOpportunitySearchTool: ToolConfig< visibility: 'hidden', description: 'Apollo API key', }, - q_keywords: { + sort_by_field: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Keywords to search for in opportunity names', - }, - account_ids: { - type: 'array', - required: false, - visibility: 'user-or-llm', - description: 'Filter by specific account IDs (e.g., ["acc_123", "acc_456"])', - }, - stage_ids: { - type: 'array', - required: false, - visibility: 'user-only', - description: 'Filter by deal stage IDs', - }, - owner_ids: { - type: 'array', - required: false, - visibility: 'user-only', - description: 'Filter by opportunity owner IDs', + description: 'Sort field: "amount", "is_closed", or "is_won"', }, page: { type: 'number', @@ -59,24 +41,18 @@ export const apolloOpportunitySearchTool: ToolConfig< }, request: { - url: 'https://api.apollo.io/api/v1/opportunities/search', - method: 'POST', + url: (params: ApolloOpportunitySearchParams) => { + const query = new URLSearchParams() + query.set('page', String(params.page || 1)) + query.set('per_page', String(Math.min(params.per_page || 25, 100))) + if (params.sort_by_field) query.set('sort_by_field', params.sort_by_field) + return `https://api.apollo.io/api/v1/opportunities/search?${query.toString()}` + }, + method: 'GET', headers: (params: ApolloOpportunitySearchParams) => ({ - 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', 'X-Api-Key': params.apiKey, }), - body: (params: ApolloOpportunitySearchParams) => { - const body: any = { - page: params.page || 1, - per_page: Math.min(params.per_page || 25, 100), - } - if (params.q_keywords) body.q_keywords = params.q_keywords - if (params.account_ids?.length) body.account_ids = params.account_ids - if (params.stage_ids?.length) body.stage_ids = params.stage_ids - if (params.owner_ids?.length) body.owner_ids = params.owner_ids - return body - }, }, transformResponse: async (response: Response) => { @@ -90,10 +66,10 @@ export const apolloOpportunitySearchTool: ToolConfig< return { success: true, output: { - opportunities: data.opportunities || [], - page: data.pagination?.page || 1, - per_page: data.pagination?.per_page || 25, - total_entries: data.pagination?.total_entries || 0, + opportunities: data.opportunities ?? [], + page: data.pagination?.page ?? 1, + per_page: data.pagination?.per_page ?? 25, + total_entries: data.pagination?.total_entries ?? 0, }, } }, diff --git a/apps/sim/tools/apollo/opportunity_update.ts b/apps/sim/tools/apollo/opportunity_update.ts index 8bb31c901dd..aea000414f8 100644 --- a/apps/sim/tools/apollo/opportunity_update.ts +++ b/apps/sim/tools/apollo/opportunity_update.ts @@ -33,16 +33,16 @@ export const apolloOpportunityUpdateTool: ToolConfig< description: 'Name of the opportunity/deal (e.g., "Enterprise License - Q1")', }, amount: { - type: 'number', + type: 'string', required: false, visibility: 'user-or-llm', - description: 'Monetary value of the opportunity', + description: 'Monetary value as a plain number string with no commas or currency symbols', }, - stage_id: { + opportunity_stage_id: { type: 'string', required: false, visibility: 'user-only', - description: 'ID of the deal stage', + description: 'ID of the opportunity stage', }, owner_id: { type: 'string', @@ -50,23 +50,23 @@ export const apolloOpportunityUpdateTool: ToolConfig< visibility: 'user-only', description: 'User ID of the opportunity owner', }, - close_date: { + closed_date: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Expected close date (ISO 8601 format)', + description: 'Expected close date in YYYY-MM-DD format', }, - description: { - type: 'string', + typed_custom_fields: { + type: 'json', required: false, - visibility: 'user-or-llm', - description: 'Description or notes about the opportunity', + visibility: 'user-only', + description: 'Custom field values as { custom_field_id: value } map', }, }, request: { url: (params: ApolloOpportunityUpdateParams) => - `https://api.apollo.io/api/v1/opportunities/${params.opportunity_id}`, + `https://api.apollo.io/api/v1/opportunities/${params.opportunity_id.trim()}`, method: 'PATCH', headers: (params: ApolloOpportunityUpdateParams) => ({ 'Content-Type': 'application/json', @@ -74,13 +74,15 @@ export const apolloOpportunityUpdateTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloOpportunityUpdateParams) => { - const body: any = {} + const body: Record = {} if (params.name) body.name = params.name - if (params.amount !== undefined) body.amount = params.amount - if (params.stage_id) body.stage_id = params.stage_id + if (params.amount !== undefined && params.amount !== null && params.amount !== '') { + body.amount = String(params.amount) + } + if (params.opportunity_stage_id) body.opportunity_stage_id = params.opportunity_stage_id if (params.owner_id) body.owner_id = params.owner_id - if (params.close_date) body.close_date = params.close_date - if (params.description) body.description = params.description + if (params.closed_date) body.closed_date = params.closed_date + if (params.typed_custom_fields) body.typed_custom_fields = params.typed_custom_fields return body }, }, @@ -92,12 +94,13 @@ export const apolloOpportunityUpdateTool: ToolConfig< } const data = await response.json() + const opportunity = data.opportunity ?? (data.id ? data : null) return { success: true, output: { - opportunity: data.opportunity ?? null, - updated: !!data.opportunity, + opportunity, + updated: !!opportunity, }, } }, diff --git a/apps/sim/tools/apollo/organization_bulk_enrich.ts b/apps/sim/tools/apollo/organization_bulk_enrich.ts index c72d31a0e89..9de002fe7bc 100644 --- a/apps/sim/tools/apollo/organization_bulk_enrich.ts +++ b/apps/sim/tools/apollo/organization_bulk_enrich.ts @@ -29,16 +29,20 @@ export const apolloOrganizationBulkEnrichTool: ToolConfig< }, request: { - url: 'https://api.apollo.io/api/v1/organizations/bulk_enrich', + url: (params: ApolloOrganizationBulkEnrichParams) => { + const domains = params.organizations + .slice(0, 10) + .map((o) => o.domain) + .filter((d): d is string => Boolean(d)) + const qs = domains.map((d) => `domains[]=${encodeURIComponent(d)}`).join('&') + return `https://api.apollo.io/api/v1/organizations/bulk_enrich${qs ? `?${qs}` : ''}` + }, method: 'POST', headers: (params: ApolloOrganizationBulkEnrichParams) => ({ 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', 'X-Api-Key': params.apiKey, }), - body: (params: ApolloOrganizationBulkEnrichParams) => ({ - details: params.organizations.slice(0, 10), - }), }, transformResponse: async (response: Response) => { @@ -48,20 +52,28 @@ export const apolloOrganizationBulkEnrichTool: ToolConfig< } const data = await response.json() + const organizations = data.organizations ?? [] return { success: true, output: { - organizations: data.matches || [], - total: data.matches?.length || 0, - enriched: data.matches?.filter((o: any) => o).length || 0, + organizations, + total: data.total_requested_domains ?? organizations.length, + enriched: data.unique_enriched_records ?? organizations.length, + missing_records: data.missing_records ?? 0, + unique_domains: data.unique_domains ?? organizations.length, }, } }, outputs: { organizations: { type: 'json', description: 'Array of enriched organization data' }, - total: { type: 'number', description: 'Total number of organizations processed' }, - enriched: { type: 'number', description: 'Number of organizations successfully enriched' }, + total: { type: 'number', description: 'Total number of domains requested' }, + enriched: { type: 'number', description: 'Number of unique enriched records' }, + missing_records: { + type: 'number', + description: 'Number of domains that could not be enriched', + }, + unique_domains: { type: 'number', description: 'Number of unique domains processed' }, }, } diff --git a/apps/sim/tools/apollo/organization_enrich.ts b/apps/sim/tools/apollo/organization_enrich.ts index d6c1de4f0d7..896f57ce17a 100644 --- a/apps/sim/tools/apollo/organization_enrich.ts +++ b/apps/sim/tools/apollo/organization_enrich.ts @@ -20,43 +20,26 @@ export const apolloOrganizationEnrichTool: ToolConfig< visibility: 'hidden', description: 'Apollo API key', }, - organization_name: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: - 'Name of the organization (e.g., "Acme Corporation") - at least one of organization_name or domain is required', - }, domain: { type: 'string', - required: false, + required: true, visibility: 'user-or-llm', - description: - 'Company domain (e.g., "apollo.io", "acme.com") - at least one of domain or organization_name is required', + description: 'Company domain (e.g., "apollo.io", "acme.com")', }, }, request: { - url: 'https://api.apollo.io/api/v1/organizations/enrich', - method: 'POST', + url: (params: ApolloOrganizationEnrichParams) => { + if (!params.domain) { + throw new Error('domain is required for organization enrichment') + } + return `https://api.apollo.io/api/v1/organizations/enrich?domain=${encodeURIComponent(params.domain.trim())}` + }, + method: 'GET', headers: (params: ApolloOrganizationEnrichParams) => ({ - 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', 'X-Api-Key': params.apiKey, }), - body: (params: ApolloOrganizationEnrichParams) => { - // At least one identifier is required - if (!params.organization_name && !params.domain) { - throw new Error( - 'At least one of organization_name or domain is required for organization enrichment' - ) - } - - const body: any = {} - if (params.organization_name) body.name = params.organization_name - if (params.domain) body.domain = params.domain - return body - }, }, transformResponse: async (response: Response) => { @@ -70,14 +53,18 @@ export const apolloOrganizationEnrichTool: ToolConfig< return { success: true, output: { - organization: data.organization || {}, + organization: data.organization ?? null, enriched: !!data.organization, }, } }, outputs: { - organization: { type: 'json', description: 'Enriched organization data from Apollo' }, + organization: { + type: 'json', + description: 'Enriched organization data from Apollo', + optional: true, + }, enriched: { type: 'boolean', description: 'Whether the organization was successfully enriched', diff --git a/apps/sim/tools/apollo/organization_search.ts b/apps/sim/tools/apollo/organization_search.ts index 18f31d376f0..1b6213039fb 100644 --- a/apps/sim/tools/apollo/organization_search.ts +++ b/apps/sim/tools/apollo/organization_search.ts @@ -24,13 +24,20 @@ export const apolloOrganizationSearchTool: ToolConfig< type: 'array', required: false, visibility: 'user-or-llm', - description: 'Company locations to search', + description: 'Company HQ locations (cities, US states, or countries)', + }, + organization_not_locations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Exclude companies whose HQ is in these locations', }, organization_num_employees_ranges: { type: 'array', required: false, visibility: 'user-or-llm', - description: 'Employee count ranges (e.g., ["1-10", "11-50"])', + description: + 'Employee count ranges as "min,max" strings (e.g., ["1,10", "250,500", "10000,20000"])', }, q_organization_keyword_tags: { type: 'array', @@ -44,6 +51,18 @@ export const apolloOrganizationSearchTool: ToolConfig< visibility: 'user-or-llm', description: 'Organization name to search for (e.g., "Acme", "TechCorp")', }, + organization_ids: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Apollo organization IDs to include (e.g., ["5e66b6381e05b4008c8331b8"])', + }, + q_organization_domains_list: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Domain names to filter by (no www. or @, up to 1,000)', + }, page: { type: 'number', required: false, @@ -67,7 +86,7 @@ export const apolloOrganizationSearchTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloOrganizationSearchParams) => { - const body: any = { + const body: Record = { page: params.page || 1, per_page: Math.min(params.per_page || 25, 100), } @@ -75,6 +94,9 @@ export const apolloOrganizationSearchTool: ToolConfig< if (params.organization_locations?.length) { body.organization_locations = params.organization_locations } + if (params.organization_not_locations?.length) { + body.organization_not_locations = params.organization_not_locations + } if (params.organization_num_employees_ranges?.length) { body.organization_num_employees_ranges = params.organization_num_employees_ranges } @@ -84,6 +106,12 @@ export const apolloOrganizationSearchTool: ToolConfig< if (params.q_organization_name) { body.q_organization_name = params.q_organization_name } + if (params.organization_ids?.length) { + body.organization_ids = params.organization_ids + } + if (params.q_organization_domains_list?.length) { + body.q_organization_domains_list = params.q_organization_domains_list + } return body }, diff --git a/apps/sim/tools/apollo/people_bulk_enrich.ts b/apps/sim/tools/apollo/people_bulk_enrich.ts index cb6c35aa8ea..973de7e2e3d 100644 --- a/apps/sim/tools/apollo/people_bulk_enrich.ts +++ b/apps/sim/tools/apollo/people_bulk_enrich.ts @@ -41,7 +41,17 @@ export const apolloPeopleBulkEnrichTool: ToolConfig< }, request: { - url: 'https://api.apollo.io/api/v1/people/bulk_match', + url: (params: ApolloPeopleBulkEnrichParams) => { + const qs = new URLSearchParams() + if (params.reveal_personal_emails !== undefined) { + qs.set('reveal_personal_emails', String(params.reveal_personal_emails)) + } + if (params.reveal_phone_number !== undefined) { + qs.set('reveal_phone_number', String(params.reveal_phone_number)) + } + const query = qs.toString() + return `https://api.apollo.io/api/v1/people/bulk_match${query ? `?${query}` : ''}` + }, method: 'POST', headers: (params: ApolloPeopleBulkEnrichParams) => ({ 'Content-Type': 'application/json', @@ -50,8 +60,6 @@ export const apolloPeopleBulkEnrichTool: ToolConfig< }), body: (params: ApolloPeopleBulkEnrichParams) => ({ details: params.people.slice(0, 10), - reveal_personal_emails: params.reveal_personal_emails, - reveal_phone_number: params.reveal_phone_number, }), }, @@ -62,13 +70,14 @@ export const apolloPeopleBulkEnrichTool: ToolConfig< } const data = await response.json() + const people = data.people || data.matches || [] return { success: true, output: { - people: data.matches || [], - total: data.matches?.length || 0, - enriched: data.matches?.filter((p: any) => p).length || 0, + people, + total: people.length, + enriched: people.filter((p: unknown) => p).length, }, } }, diff --git a/apps/sim/tools/apollo/people_enrich.ts b/apps/sim/tools/apollo/people_enrich.ts index 80e5f0322ee..0d0a70b337b 100644 --- a/apps/sim/tools/apollo/people_enrich.ts +++ b/apps/sim/tools/apollo/people_enrich.ts @@ -68,7 +68,17 @@ export const apolloPeopleEnrichTool: ToolConfig< }, request: { - url: 'https://api.apollo.io/api/v1/people/match', + url: (params: ApolloPeopleEnrichParams) => { + const qs = new URLSearchParams() + if (params.reveal_personal_emails !== undefined) { + qs.set('reveal_personal_emails', String(params.reveal_personal_emails)) + } + if (params.reveal_phone_number !== undefined) { + qs.set('reveal_phone_number', String(params.reveal_phone_number)) + } + const query = qs.toString() + return `https://api.apollo.io/api/v1/people/match${query ? `?${query}` : ''}` + }, method: 'POST', headers: (params: ApolloPeopleEnrichParams) => ({ 'Content-Type': 'application/json', @@ -76,7 +86,7 @@ export const apolloPeopleEnrichTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloPeopleEnrichParams) => { - const body: any = {} + const body: Record = {} if (params.first_name) body.first_name = params.first_name if (params.last_name) body.last_name = params.last_name @@ -84,12 +94,6 @@ export const apolloPeopleEnrichTool: ToolConfig< if (params.organization_name) body.organization_name = params.organization_name if (params.domain) body.domain = params.domain if (params.linkedin_url) body.linkedin_url = params.linkedin_url - if (params.reveal_personal_emails !== undefined) { - body.reveal_personal_emails = params.reveal_personal_emails - } - if (params.reveal_phone_number !== undefined) { - body.reveal_phone_number = params.reveal_phone_number - } return body }, diff --git a/apps/sim/tools/apollo/people_search.ts b/apps/sim/tools/apollo/people_search.ts index c4841024e8e..4f209a4d80c 100644 --- a/apps/sim/tools/apollo/people_search.ts +++ b/apps/sim/tools/apollo/people_search.ts @@ -23,6 +23,12 @@ export const apolloPeopleSearchTool: ToolConfig< visibility: 'user-or-llm', description: 'Job titles to search for (e.g., ["CEO", "VP of Sales"])', }, + include_similar_titles: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return people with job titles similar to person_titles', + }, person_locations: { type: 'array', required: false, @@ -33,13 +39,41 @@ export const apolloPeopleSearchTool: ToolConfig< type: 'array', required: false, visibility: 'user-or-llm', - description: 'Seniority levels (e.g., ["senior", "executive", "manager"])', + description: + 'Seniority levels (one of: owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern)', + }, + organization_ids: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Apollo organization IDs to filter by (e.g., ["5e66b6381e05b4008c8331b8"])', }, organization_names: { type: 'array', required: false, visibility: 'user-or-llm', - description: 'Company names to search within', + description: 'Company names to search within (legacy filter)', + }, + organization_locations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + "Headquarters locations of the people's current employer (e.g., ['texas', 'tokyo', 'spain'])", + }, + q_organization_domains_list: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Employer domain names (e.g., ["apollo.io", "microsoft.com"]) — up to 1,000, no www. or @', + }, + contact_email_status: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Email statuses to filter by: verified, unverified, likely to engage, unavailable', }, q_keywords: { type: 'string', @@ -70,7 +104,7 @@ export const apolloPeopleSearchTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloPeopleSearchParams) => { - const body: any = { + const body: Record = { page: params.page || 1, per_page: Math.min(params.per_page || 25, 100), } @@ -78,15 +112,30 @@ export const apolloPeopleSearchTool: ToolConfig< if (params.person_titles && params.person_titles.length > 0) { body.person_titles = params.person_titles } + if (params.include_similar_titles !== undefined) { + body.include_similar_titles = params.include_similar_titles + } if (params.person_locations && params.person_locations.length > 0) { body.person_locations = params.person_locations } if (params.person_seniorities && params.person_seniorities.length > 0) { body.person_seniorities = params.person_seniorities } + if (params.organization_ids && params.organization_ids.length > 0) { + body.organization_ids = params.organization_ids + } if (params.organization_names && params.organization_names.length > 0) { body.organization_names = params.organization_names } + if (params.organization_locations && params.organization_locations.length > 0) { + body.organization_locations = params.organization_locations + } + if (params.q_organization_domains_list && params.q_organization_domains_list.length > 0) { + body.q_organization_domains_list = params.q_organization_domains_list + } + if (params.contact_email_status && params.contact_email_status.length > 0) { + body.contact_email_status = params.contact_email_status + } if (params.q_keywords) { body.q_keywords = params.q_keywords } diff --git a/apps/sim/tools/apollo/sequence_add_contacts.ts b/apps/sim/tools/apollo/sequence_add_contacts.ts index 652ddca2dc1..8822702bb28 100644 --- a/apps/sim/tools/apollo/sequence_add_contacts.ts +++ b/apps/sim/tools/apollo/sequence_add_contacts.ts @@ -28,28 +28,66 @@ export const apolloSequenceAddContactsTool: ToolConfig< }, contact_ids: { type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of contact IDs to add to the sequence (e.g., ["con_abc123", "con_def456"]). Either contact_ids or label_names must be provided.', + }, + label_names: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of label names to identify contacts to add to the sequence. Either contact_ids or label_names must be provided.', + }, + send_email_from_email_account_id: { + type: 'string', required: true, visibility: 'user-or-llm', description: - 'Array of contact IDs to add to the sequence (e.g., ["con_abc123", "con_def456"])', + 'ID of the email account to send from. Use the Get Email Accounts operation to look this up.', }, - emailer_campaign_id: { + send_email_from_email_address: { type: 'string', required: false, visibility: 'user-only', - description: 'Optional emailer campaign ID', + description: 'Specific email address to send from within the email account.', }, - send_email_from_user_id: { - type: 'string', + sequence_no_email: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Add contacts even if they have no email address', + }, + sequence_unverified_email: { + type: 'boolean', required: false, visibility: 'user-only', - description: 'User ID to send emails from', + description: 'Add contacts with unverified email addresses', + }, + sequence_job_change: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Add contacts who recently changed jobs', + }, + sequence_active_in_other_campaigns: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Add contacts active in other campaigns', + }, + sequence_finished_in_other_campaigns: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Add contacts who finished other campaigns', }, }, request: { url: (params: ApolloSequenceAddContactsParams) => - `https://api.apollo.io/api/v1/emailer_campaigns/${params.sequence_id}/add_contact_ids`, + `https://api.apollo.io/api/v1/emailer_campaigns/${params.sequence_id.trim()}/add_contact_ids`, method: 'POST', headers: (params: ApolloSequenceAddContactsParams) => ({ 'Content-Type': 'application/json', @@ -57,14 +95,27 @@ export const apolloSequenceAddContactsTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloSequenceAddContactsParams) => { - const body: any = { - contact_ids: params.contact_ids, + const body: Record = { + emailer_campaign_id: params.sequence_id, + send_email_from_email_account_id: params.send_email_from_email_account_id, + } + if (params.contact_ids?.length) body.contact_ids = params.contact_ids + if (params.label_names?.length) body.label_names = params.label_names + if (params.send_email_from_email_address) { + body.send_email_from_email_address = params.send_email_from_email_address } - if (params.emailer_campaign_id) { - body.emailer_campaign_id = params.emailer_campaign_id + if (params.sequence_no_email !== undefined) body.sequence_no_email = params.sequence_no_email + if (params.sequence_unverified_email !== undefined) { + body.sequence_unverified_email = params.sequence_unverified_email } - if (params.send_email_from_user_id) { - body.send_email_from_user_id = params.send_email_from_user_id + if (params.sequence_job_change !== undefined) { + body.sequence_job_change = params.sequence_job_change + } + if (params.sequence_active_in_other_campaigns !== undefined) { + body.sequence_active_in_other_campaigns = params.sequence_active_in_other_campaigns + } + if (params.sequence_finished_in_other_campaigns !== undefined) { + body.sequence_finished_in_other_campaigns = params.sequence_finished_in_other_campaigns } return body }, @@ -78,19 +129,45 @@ export const apolloSequenceAddContactsTool: ToolConfig< const data = await response.json() + const added = Array.isArray(data?.contacts?.added) ? data.contacts.added : [] + const skipped = Array.isArray(data?.contacts?.skipped) ? data.contacts.skipped : [] + const skippedIds = data?.skipped_contact_ids ?? null + return { success: true, output: { - contacts_added: data.contacts || params?.contact_ids || [], - sequence_id: params?.sequence_id || '', - total_added: data.contacts?.length || params?.contact_ids?.length || 0, + added, + skipped, + skipped_contact_ids: skippedIds, + emailer_campaign: data?.emailer_campaign ?? null, + sequence_id: params?.sequence_id || data?.emailer_campaign?.id || '', + total_added: added.length, + total_skipped: skipped.length, }, } }, outputs: { - contacts_added: { type: 'json', description: 'Array of contact IDs added to the sequence' }, + added: { + type: 'json', + description: 'Array of contact objects successfully added to the sequence', + }, + skipped: { + type: 'json', + description: 'Array of contact objects that were skipped, with reasons', + }, + skipped_contact_ids: { + type: 'json', + description: 'Hash mapping contact IDs to skip reason codes', + optional: true, + }, + emailer_campaign: { + type: 'json', + description: 'Details of the emailer campaign (id, name)', + optional: true, + }, sequence_id: { type: 'string', description: 'ID of the sequence contacts were added to' }, total_added: { type: 'number', description: 'Total number of contacts added' }, + total_skipped: { type: 'number', description: 'Total number of contacts skipped' }, }, } diff --git a/apps/sim/tools/apollo/sequence_search.ts b/apps/sim/tools/apollo/sequence_search.ts index 70c5a474d98..c7ed1a8b8ad 100644 --- a/apps/sim/tools/apollo/sequence_search.ts +++ b/apps/sim/tools/apollo/sequence_search.ts @@ -23,12 +23,6 @@ export const apolloSequenceSearchTool: ToolConfig< visibility: 'user-or-llm', description: 'Search sequences by name (e.g., "Outbound Q1", "Follow-up")', }, - active: { - type: 'boolean', - required: false, - visibility: 'user-or-llm', - description: 'Filter by active status (true for active sequences, false for inactive)', - }, page: { type: 'number', required: false, @@ -52,12 +46,11 @@ export const apolloSequenceSearchTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloSequenceSearchParams) => { - const body: any = { + const body: Record = { page: params.page || 1, per_page: Math.min(params.per_page || 25, 100), } if (params.q_name) body.q_name = params.q_name - if (params.active !== undefined) body.active = params.active return body }, }, diff --git a/apps/sim/tools/apollo/task_create.ts b/apps/sim/tools/apollo/task_create.ts index d835fe67574..3fb41645d32 100644 --- a/apps/sim/tools/apollo/task_create.ts +++ b/apps/sim/tools/apollo/task_create.ts @@ -4,7 +4,7 @@ import type { ToolConfig } from '@/tools/types' export const apolloTaskCreateTool: ToolConfig = { id: 'apollo_task_create', name: 'Apollo Create Task', - description: 'Create a new task in Apollo', + description: 'Create one or more tasks in Apollo (one task per contact_id, master key required)', version: '1.0.0', params: { @@ -14,41 +14,48 @@ export const apolloTaskCreateTool: ToolConfig { - const body: any = { note: params.note } - if (params.contact_id) body.contact_id = params.contact_id - if (params.account_id) body.account_id = params.account_id - if (params.due_at) body.due_at = params.due_at + const body: Record = { + user_id: params.user_id, + contact_ids: params.contact_ids, + due_at: params.due_at, + type: params.type, + status: params.status, + } if (params.priority) body.priority = params.priority - if (params.type) body.type = params.type + if (params.note) body.note = params.note return body }, }, @@ -78,20 +88,20 @@ export const apolloTaskCreateTool: ToolConfig 0 - // Apollo's task creation endpoint currently only returns true, not the task object - // Return the request params as the task data since the API doesn't return it return { success: true, output: { - task: data.task ?? null, - created: data === true || !!data.task, + tasks, + created, }, } }, outputs: { - task: { type: 'json', description: 'Created task data from Apollo', optional: true }, - created: { type: 'boolean', description: 'Whether the task was successfully created' }, + tasks: { type: 'json', description: 'Array of created tasks (when returned by Apollo)' }, + created: { type: 'boolean', description: 'Whether the request succeeded' }, }, } diff --git a/apps/sim/tools/apollo/task_search.ts b/apps/sim/tools/apollo/task_search.ts index f85a16851de..d596ad2a8ce 100644 --- a/apps/sim/tools/apollo/task_search.ts +++ b/apps/sim/tools/apollo/task_search.ts @@ -14,23 +14,18 @@ export const apolloTaskSearchTool: ToolConfig { - const body: any = { + const body: Record = { page: params.page || 1, per_page: Math.min(params.per_page || 25, 100), } - if (params.contact_id) body.contact_id = params.contact_id - if (params.account_id) body.account_id = params.account_id - if (params.completed !== undefined) body.completed = params.completed + if (params.sort_by_field) body.sort_by_field = params.sort_by_field + if (params.open_factor_names?.length) body.open_factor_names = params.open_factor_names return body }, }, diff --git a/apps/sim/tools/apollo/types.ts b/apps/sim/tools/apollo/types.ts index 6738fc0645e..33d7e90251e 100644 --- a/apps/sim/tools/apollo/types.ts +++ b/apps/sim/tools/apollo/types.ts @@ -42,32 +42,74 @@ export interface ApolloContact { export interface ApolloAccount { id: string name: string - website_url?: string - phone?: string - owner_id?: string + domain?: string | null + website_url?: string | null + phone?: string | null + sanitized_phone?: string | null + raw_address?: string | null + owner_id?: string | null + account_stage_id?: string | null + existence_level?: string + show_intent?: boolean + has_intent_signal_account?: boolean + typed_custom_fields?: Record created_at: string } export interface ApolloTask { id: string - note: string + user_id?: string contact_id?: string account_id?: string + type?: string + priority?: string + status?: string due_at?: string - completed: boolean - created_at: string + note?: string + created_at?: string + updated_at?: string } export interface ApolloOpportunity { id: string + team_id?: string name: string - account_id: string - amount?: number - stage_id?: string - owner_id?: string - close_date?: string - description?: string + account_id?: string | null + owner_id?: string | null + salesforce_owner_id?: string | null + amount?: number | string | null + amount_in_team_currency?: number | null + forecasted_revenue?: number | null + exchange_rate_code?: string + exchange_rate_value?: number + closed_date?: string | null + actual_close_date?: string | null + description?: string | null + is_closed?: boolean + is_won?: boolean + stage_name?: string | null + opportunity_stage_id?: string | null + opportunity_pipeline_id?: string | null + source?: string + salesforce_id?: string | null + forecast_category?: string + deal_probability?: number + probability?: number | null + created_by_id?: string + stage_updated_at?: string + next_step?: string | null + next_step_date?: string | null + closed_lost_reason?: string | null + closed_won_reason?: string | null + last_activity_date?: string + existence_level?: string + typed_custom_fields?: Record + opportunity_rule_config_statuses?: unknown[] + opportunity_contact_roles?: unknown[] + currency?: { name?: string; iso_code?: string; symbol?: string } + account?: { id?: string; name?: string; website_url?: string | null } created_at: string + updated_at?: string } interface ApolloBaseParams { @@ -77,10 +119,14 @@ interface ApolloBaseParams { // People Search Types export interface ApolloPeopleSearchParams extends ApolloBaseParams { person_titles?: string[] + include_similar_titles?: boolean person_locations?: string[] person_seniorities?: string[] organization_ids?: string[] organization_names?: string[] + organization_locations?: string[] + q_organization_domains_list?: string[] + contact_email_status?: string[] q_keywords?: string page?: number per_page?: number @@ -119,9 +165,13 @@ export interface ApolloPeopleBulkEnrichParams extends ApolloBaseParams { people: Array<{ first_name?: string last_name?: string - organization_name?: string + name?: string email?: string + hashed_email?: string + organization_name?: string domain?: string + id?: string + linkedin_url?: string }> reveal_personal_emails?: boolean reveal_phone_number?: boolean @@ -138,9 +188,12 @@ export interface ApolloPeopleBulkEnrichResponse extends ToolResponse { // Organization Search Types export interface ApolloOrganizationSearchParams extends ApolloBaseParams { organization_locations?: string[] + organization_not_locations?: string[] organization_num_employees_ranges?: string[] q_organization_keyword_tags?: string[] q_organization_name?: string + organization_ids?: string[] + q_organization_domains_list?: string[] page?: number per_page?: number } @@ -156,23 +209,19 @@ export interface ApolloOrganizationSearchResponse extends ToolResponse { // Organization Enrichment Types export interface ApolloOrganizationEnrichParams extends ApolloBaseParams { - organization_name?: string - domain?: string + domain: string } export interface ApolloOrganizationEnrichResponse extends ToolResponse { output: { - organization: ApolloOrganization + organization: ApolloOrganization | null enriched: boolean } } // Bulk Organization Enrichment Types export interface ApolloOrganizationBulkEnrichParams extends ApolloBaseParams { - organizations: Array<{ - organization_name?: string - domain?: string - }> + organizations: Array<{ domain: string }> } export interface ApolloOrganizationBulkEnrichResponse extends ToolResponse { @@ -180,6 +229,8 @@ export interface ApolloOrganizationBulkEnrichResponse extends ToolResponse { organizations: ApolloOrganization[] total: number enriched: number + missing_records: number + unique_domains: number } } @@ -191,6 +242,18 @@ export interface ApolloContactCreateParams extends ApolloBaseParams { title?: string account_id?: string owner_id?: string + organization_name?: string + website_url?: string + label_names?: string[] + contact_stage_id?: string + present_raw_address?: string + direct_phone?: string + corporate_phone?: string + mobile_phone?: string + home_phone?: string + other_phone?: string + typed_custom_fields?: Record + run_dedupe?: boolean } export interface ApolloContactCreateResponse extends ToolResponse { @@ -209,6 +272,17 @@ export interface ApolloContactUpdateParams extends ApolloBaseParams { title?: string account_id?: string owner_id?: string + organization_name?: string + website_url?: string + label_names?: string[] + contact_stage_id?: string + present_raw_address?: string + direct_phone?: string + corporate_phone?: string + mobile_phone?: string + home_phone?: string + other_phone?: string + typed_custom_fields?: Record } export interface ApolloContactUpdateResponse extends ToolResponse { @@ -221,14 +295,20 @@ export interface ApolloContactUpdateResponse extends ToolResponse { // Contact Bulk Create Types export interface ApolloContactBulkCreateParams extends ApolloBaseParams { contacts: Array<{ - first_name: string - last_name: string + first_name?: string + last_name?: string email?: string title?: string + organization_name?: string account_id?: string owner_id?: string + contact_stage_id?: string + linkedin_url?: string + phone_numbers?: Array<{ raw_number: string; position?: number }> + contact_emails?: Array<{ email: string; position?: number }> }> run_dedupe?: boolean + append_label_names?: string[] } export interface ApolloContactBulkCreateResponse extends ToolResponse { @@ -243,24 +323,15 @@ export interface ApolloContactBulkCreateResponse extends ToolResponse { // Contact Bulk Update Types export interface ApolloContactBulkUpdateParams extends ApolloBaseParams { - contacts: Array<{ - id: string - first_name?: string - last_name?: string - email?: string - title?: string - account_id?: string - owner_id?: string - }> + contact_ids?: string[] + contact_attributes?: Array<{ id: string; [key: string]: unknown }> | Record + async?: boolean } export interface ApolloContactBulkUpdateResponse extends ToolResponse { output: { - updated_contacts: ApolloContact[] - failed_contacts: Array<{ id: string; error: string }> - total_submitted: number - updated: number - failed: number + message: string | null + job_id: string | null } } @@ -268,6 +339,9 @@ export interface ApolloContactBulkUpdateResponse extends ToolResponse { export interface ApolloContactSearchParams extends ApolloBaseParams { q_keywords?: string contact_stage_ids?: string[] + contact_label_ids?: string[] + sort_by_field?: string + sort_ascending?: boolean page?: number per_page?: number } @@ -289,9 +363,12 @@ export interface ApolloContactSearchResponse extends ToolResponse { // Account Create Types export interface ApolloAccountCreateParams extends ApolloBaseParams { name: string - website_url?: string + domain?: string phone?: string owner_id?: string + account_stage_id?: string + raw_address?: string + typed_custom_fields?: Record } export interface ApolloAccountCreateResponse extends ToolResponse { @@ -305,9 +382,12 @@ export interface ApolloAccountCreateResponse extends ToolResponse { export interface ApolloAccountUpdateParams extends ApolloBaseParams { account_id: string name?: string - website_url?: string + domain?: string phone?: string owner_id?: string + account_stage_id?: string + raw_address?: string + typed_custom_fields?: Record } export interface ApolloAccountUpdateResponse extends ToolResponse { @@ -319,9 +399,11 @@ export interface ApolloAccountUpdateResponse extends ToolResponse { // Account Search Types export interface ApolloAccountSearchParams extends ApolloBaseParams { - q_keywords?: string - owner_id?: string + q_organization_name?: string account_stage_ids?: string[] + account_label_ids?: string[] + sort_by_field?: string + sort_ascending?: boolean page?: number per_page?: number } @@ -337,7 +419,7 @@ export interface ApolloAccountSearchResponse extends ToolResponse { export interface ApolloAccountBulkCreateParams extends ApolloBaseParams { accounts: Array<{ name: string - website_url?: string + domain?: string phone?: string owner_id?: string }> @@ -355,63 +437,82 @@ export interface ApolloAccountBulkCreateResponse extends ToolResponse { // Account Bulk Update Types export interface ApolloAccountBulkUpdateParams extends ApolloBaseParams { - accounts: Array<{ - id: string - name?: string - website_url?: string - phone?: string - owner_id?: string - }> + account_ids?: string[] + name?: string + owner_id?: string + account_attributes?: Array<{ id: string; [key: string]: unknown }> } export interface ApolloAccountBulkUpdateResponse extends ToolResponse { output: { - updated_accounts: ApolloAccount[] - failed_accounts: Array<{ id: string; error: string }> - total_submitted: number - updated: number - failed: number + message: string | null + account_ids: string[] } } // Sequence Add Contacts Types export interface ApolloSequenceAddContactsParams extends ApolloBaseParams { sequence_id: string - contact_ids: string[] - emailer_campaign_id?: string - send_email_from_user_id?: string + contact_ids?: string[] + label_names?: string[] + send_email_from_email_account_id: string + send_email_from_email_address?: string + sequence_no_email?: boolean + sequence_unverified_email?: boolean + sequence_job_change?: boolean + sequence_active_in_other_campaigns?: boolean + sequence_finished_in_other_campaigns?: boolean +} + +export interface ApolloSequenceAddedContact { + id: string + first_name?: string + last_name?: string + email?: string + status?: string + opened_rate?: number | null + replied_rate?: number | null +} + +export interface ApolloSequenceSkippedContact { + id: string + reason: string } export interface ApolloSequenceAddContactsResponse extends ToolResponse { output: { - contacts_added: string[] + added: ApolloSequenceAddedContact[] + skipped: ApolloSequenceSkippedContact[] + skipped_contact_ids: Record | null + emailer_campaign: { id: string; name: string } | null sequence_id: string total_added: number + total_skipped: number } } // Task Create Types export interface ApolloTaskCreateParams extends ApolloBaseParams { - note: string - contact_id?: string - account_id?: string - due_at?: string + user_id: string + contact_ids: string[] priority?: string - type?: string + due_at: string + type: string + status: string + note?: string } export interface ApolloTaskCreateResponse extends ToolResponse { output: { - task: ApolloTask | null + tasks: ApolloTask[] created: boolean } } // Task Search Types export interface ApolloTaskSearchParams extends ApolloBaseParams { - contact_id?: string - account_id?: string - completed?: boolean + sort_by_field?: string + open_factor_names?: string[] page?: number per_page?: number } @@ -426,13 +527,17 @@ export interface ApolloTaskSearchResponse extends ToolResponse { // Email Accounts List Types export interface ApolloEmailAccountsParams extends ApolloBaseParams {} +export interface ApolloEmailAccount { + id: string + email: string + type?: string + active?: boolean + default?: boolean +} + export interface ApolloEmailAccountsResponse extends ToolResponse { output: { - email_accounts: Array<{ - id: string - email: string - active: boolean - }> + email_accounts: ApolloEmailAccount[] total: number } } @@ -440,12 +545,12 @@ export interface ApolloEmailAccountsResponse extends ToolResponse { // Opportunity Create Types export interface ApolloOpportunityCreateParams extends ApolloBaseParams { name: string - account_id: string - amount?: number - stage_id?: string + account_id?: string + amount?: string + opportunity_stage_id?: string owner_id?: string - close_date?: string - description?: string + closed_date?: string + typed_custom_fields?: Record } export interface ApolloOpportunityCreateResponse extends ToolResponse { @@ -457,10 +562,7 @@ export interface ApolloOpportunityCreateResponse extends ToolResponse { // Opportunity Search Types export interface ApolloOpportunitySearchParams extends ApolloBaseParams { - q_keywords?: string - account_ids?: string[] - stage_ids?: string[] - owner_ids?: string[] + sort_by_field?: string page?: number per_page?: number } @@ -490,11 +592,11 @@ export interface ApolloOpportunityGetResponse extends ToolResponse { export interface ApolloOpportunityUpdateParams extends ApolloBaseParams { opportunity_id: string name?: string - amount?: number - stage_id?: string + amount?: string + opportunity_stage_id?: string owner_id?: string - close_date?: string - description?: string + closed_date?: string + typed_custom_fields?: Record } export interface ApolloOpportunityUpdateResponse extends ToolResponse { @@ -520,7 +622,6 @@ export interface ApolloSequence { // Sequence Search Types export interface ApolloSequenceSearchParams extends ApolloBaseParams { q_name?: string - active?: boolean page?: number per_page?: number } From f1ca746831eb5b3ab287e77783bafc80b74fc8e1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 22:06:55 -0700 Subject: [PATCH 02/17] improvement(apollo): fix tool outputs to match Apollo API response shapes --- apps/sim/tools/apollo/account_bulk_create.ts | 26 ++++++++----- apps/sim/tools/apollo/account_search.ts | 3 +- apps/sim/tools/apollo/contact_search.ts | 3 +- apps/sim/tools/apollo/opportunity_get.ts | 8 +++- apps/sim/tools/apollo/people_bulk_enrich.ts | 39 +++++++++++++++---- apps/sim/tools/apollo/people_enrich.ts | 8 +++- .../sim/tools/apollo/sequence_add_contacts.ts | 4 +- apps/sim/tools/apollo/task_search.ts | 3 +- apps/sim/tools/apollo/types.ts | 27 +++++++------ 9 files changed, 80 insertions(+), 41 deletions(-) diff --git a/apps/sim/tools/apollo/account_bulk_create.ts b/apps/sim/tools/apollo/account_bulk_create.ts index 6b8cc45f4d4..092d30f7a6e 100644 --- a/apps/sim/tools/apollo/account_bulk_create.ts +++ b/apps/sim/tools/apollo/account_bulk_create.ts @@ -50,15 +50,21 @@ export const apolloAccountBulkCreateTool: ToolConfig< } const data = await response.json() + const createdAccounts = Array.isArray(data.created_accounts) + ? data.created_accounts + : Array.isArray(data.accounts) + ? data.accounts + : [] + const existingAccounts = Array.isArray(data.existing_accounts) ? data.existing_accounts : [] return { success: true, output: { - created_accounts: data.accounts || data.created_accounts || [], - failed_accounts: data.failed_accounts || [], - total_submitted: data.accounts?.length || 0, - created: data.created_accounts?.length || data.accounts?.length || 0, - failed: data.failed_accounts?.length || 0, + created_accounts: createdAccounts, + existing_accounts: existingAccounts, + total_submitted: createdAccounts.length + existingAccounts.length, + created: createdAccounts.length, + existing: existingAccounts.length, }, } }, @@ -68,21 +74,21 @@ export const apolloAccountBulkCreateTool: ToolConfig< type: 'json', description: 'Array of newly created accounts', }, - failed_accounts: { + existing_accounts: { type: 'json', - description: 'Array of accounts that failed to create', + description: 'Array of existing accounts returned by Apollo (when duplicates are detected)', }, total_submitted: { type: 'number', - description: 'Total number of accounts submitted', + description: 'Total number of accounts in the response (created + existing)', }, created: { type: 'number', description: 'Number of accounts successfully created', }, - failed: { + existing: { type: 'number', - description: 'Number of accounts that failed to create', + description: 'Number of existing accounts found', }, }, } diff --git a/apps/sim/tools/apollo/account_search.ts b/apps/sim/tools/apollo/account_search.ts index 511540c7ba6..e60686d9347 100644 --- a/apps/sim/tools/apollo/account_search.ts +++ b/apps/sim/tools/apollo/account_search.ts @@ -100,7 +100,7 @@ export const apolloAccountSearchTool: ToolConfig< return { success: true, output: { - accounts: data.accounts ?? null, + accounts: data.accounts ?? [], pagination: data.pagination ?? null, }, } @@ -110,7 +110,6 @@ export const apolloAccountSearchTool: ToolConfig< accounts: { type: 'json', description: 'Array of accounts matching the search criteria', - optional: true, }, pagination: { type: 'json', description: 'Pagination information', optional: true }, }, diff --git a/apps/sim/tools/apollo/contact_search.ts b/apps/sim/tools/apollo/contact_search.ts index f9ebe9889e2..7295b745c50 100644 --- a/apps/sim/tools/apollo/contact_search.ts +++ b/apps/sim/tools/apollo/contact_search.ts @@ -99,7 +99,7 @@ export const apolloContactSearchTool: ToolConfig< return { success: true, output: { - contacts: data.contacts ?? null, + contacts: data.contacts ?? [], pagination: data.pagination ?? null, }, } @@ -109,7 +109,6 @@ export const apolloContactSearchTool: ToolConfig< contacts: { type: 'json', description: 'Array of contacts matching the search criteria', - optional: true, }, pagination: { type: 'json', description: 'Pagination information', optional: true }, }, diff --git a/apps/sim/tools/apollo/opportunity_get.ts b/apps/sim/tools/apollo/opportunity_get.ts index 2eb2246e320..db8613b91eb 100644 --- a/apps/sim/tools/apollo/opportunity_get.ts +++ b/apps/sim/tools/apollo/opportunity_get.ts @@ -47,14 +47,18 @@ export const apolloOpportunityGetTool: ToolConfig< return { success: true, output: { - opportunity: data.opportunity || {}, + opportunity: data.opportunity ?? null, found: !!data.opportunity, }, } }, outputs: { - opportunity: { type: 'json', description: 'Complete opportunity data from Apollo' }, + opportunity: { + type: 'json', + description: 'Complete opportunity data from Apollo', + optional: true, + }, found: { type: 'boolean', description: 'Whether the opportunity was found' }, }, } diff --git a/apps/sim/tools/apollo/people_bulk_enrich.ts b/apps/sim/tools/apollo/people_bulk_enrich.ts index 973de7e2e3d..bd4c53e1578 100644 --- a/apps/sim/tools/apollo/people_bulk_enrich.ts +++ b/apps/sim/tools/apollo/people_bulk_enrich.ts @@ -70,21 +70,46 @@ export const apolloPeopleBulkEnrichTool: ToolConfig< } const data = await response.json() - const people = data.people || data.matches || [] + const matches = Array.isArray(data.matches) + ? data.matches + : Array.isArray(data.people) + ? data.people + : [] return { success: true, output: { - people, - total: people.length, - enriched: people.filter((p: unknown) => p).length, + matches, + total_requested_enrichments: data.total_requested_enrichments ?? matches.length, + unique_enriched_records: data.unique_enriched_records ?? matches.filter(Boolean).length, + missing_records: data.missing_records ?? null, + credits_consumed: data.credits_consumed ?? null, }, } }, outputs: { - people: { type: 'json', description: 'Array of enriched people data' }, - total: { type: 'number', description: 'Total number of people processed' }, - enriched: { type: 'number', description: 'Number of people successfully enriched' }, + matches: { + type: 'json', + description: 'Array of enriched people (null entries indicate no match)', + }, + total_requested_enrichments: { + type: 'number', + description: 'Total number of records submitted for enrichment', + }, + unique_enriched_records: { + type: 'number', + description: 'Number of records successfully enriched', + }, + missing_records: { + type: 'number', + description: 'Number of records that could not be enriched', + optional: true, + }, + credits_consumed: { + type: 'number', + description: 'Number of Apollo credits consumed by this request', + optional: true, + }, }, } diff --git a/apps/sim/tools/apollo/people_enrich.ts b/apps/sim/tools/apollo/people_enrich.ts index 0d0a70b337b..61680d02a4d 100644 --- a/apps/sim/tools/apollo/people_enrich.ts +++ b/apps/sim/tools/apollo/people_enrich.ts @@ -110,14 +110,18 @@ export const apolloPeopleEnrichTool: ToolConfig< return { success: true, output: { - person: data.person || {}, + person: data.person ?? null, enriched: !!data.person, }, } }, outputs: { - person: { type: 'json', description: 'Enriched person data from Apollo' }, + person: { + type: 'json', + description: 'Enriched person data from Apollo', + optional: true, + }, enriched: { type: 'boolean', description: 'Whether the person was successfully enriched' }, }, } diff --git a/apps/sim/tools/apollo/sequence_add_contacts.ts b/apps/sim/tools/apollo/sequence_add_contacts.ts index 8822702bb28..114d30cdaee 100644 --- a/apps/sim/tools/apollo/sequence_add_contacts.ts +++ b/apps/sim/tools/apollo/sequence_add_contacts.ts @@ -131,7 +131,7 @@ export const apolloSequenceAddContactsTool: ToolConfig< const added = Array.isArray(data?.contacts?.added) ? data.contacts.added : [] const skipped = Array.isArray(data?.contacts?.skipped) ? data.contacts.skipped : [] - const skippedIds = data?.skipped_contact_ids ?? null + const skippedIds = Array.isArray(data?.skipped_contact_ids) ? data.skipped_contact_ids : null return { success: true, @@ -158,7 +158,7 @@ export const apolloSequenceAddContactsTool: ToolConfig< }, skipped_contact_ids: { type: 'json', - description: 'Hash mapping contact IDs to skip reason codes', + description: 'Array of contact IDs that were skipped', optional: true, }, emailer_campaign: { diff --git a/apps/sim/tools/apollo/task_search.ts b/apps/sim/tools/apollo/task_search.ts index d596ad2a8ce..6c05ad60db8 100644 --- a/apps/sim/tools/apollo/task_search.ts +++ b/apps/sim/tools/apollo/task_search.ts @@ -71,7 +71,7 @@ export const apolloTaskSearchTool: ToolConfig + total_requested_enrichments: number + unique_enriched_records: number + missing_records: number | null + credits_consumed: number | null } } @@ -355,7 +357,7 @@ export interface ApolloPagination { export interface ApolloContactSearchResponse extends ToolResponse { output: { - contacts: ApolloContact[] | null + contacts: ApolloContact[] pagination: ApolloPagination | null } } @@ -410,7 +412,7 @@ export interface ApolloAccountSearchParams extends ApolloBaseParams { export interface ApolloAccountSearchResponse extends ToolResponse { output: { - accounts: ApolloAccount[] | null + accounts: ApolloAccount[] pagination: ApolloPagination | null } } @@ -428,10 +430,10 @@ export interface ApolloAccountBulkCreateParams extends ApolloBaseParams { export interface ApolloAccountBulkCreateResponse extends ToolResponse { output: { created_accounts: ApolloAccount[] - failed_accounts: Array<{ name: string; error: string }> + existing_accounts: ApolloAccount[] total_submitted: number created: number - failed: number + existing: number } } @@ -483,7 +485,7 @@ export interface ApolloSequenceAddContactsResponse extends ToolResponse { output: { added: ApolloSequenceAddedContact[] skipped: ApolloSequenceSkippedContact[] - skipped_contact_ids: Record | null + skipped_contact_ids: string[] | null emailer_campaign: { id: string; name: string } | null sequence_id: string total_added: number @@ -519,7 +521,7 @@ export interface ApolloTaskSearchParams extends ApolloBaseParams { export interface ApolloTaskSearchResponse extends ToolResponse { output: { - tasks: ApolloTask[] | null + tasks: ApolloTask[] pagination: ApolloPagination | null } } @@ -528,11 +530,12 @@ export interface ApolloTaskSearchResponse extends ToolResponse { export interface ApolloEmailAccountsParams extends ApolloBaseParams {} export interface ApolloEmailAccount { - id: string + id: string | number email: string type?: string active?: boolean default?: boolean + linked_at?: string | null } export interface ApolloEmailAccountsResponse extends ToolResponse { @@ -583,7 +586,7 @@ export interface ApolloOpportunityGetParams extends ApolloBaseParams { export interface ApolloOpportunityGetResponse extends ToolResponse { output: { - opportunity: ApolloOpportunity + opportunity: ApolloOpportunity | null found: boolean } } From e7a0d5382ce3287f1e35b2d90c14e1f48c20ca24 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 22:07:55 -0700 Subject: [PATCH 03/17] chore(apollo): regenerate docs for output changes --- apps/docs/content/docs/en/tools/apollo.mdx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/docs/content/docs/en/tools/apollo.mdx b/apps/docs/content/docs/en/tools/apollo.mdx index 1750b405839..089dac3b519 100644 --- a/apps/docs/content/docs/en/tools/apollo.mdx +++ b/apps/docs/content/docs/en/tools/apollo.mdx @@ -112,9 +112,11 @@ Enrich data for up to 10 people at once using Apollo | Parameter | Type | Description | | --------- | ---- | ----------- | -| `people` | json | Array of enriched people data | -| `total` | number | Total number of people processed | -| `enriched` | number | Number of people successfully enriched | +| `matches` | json | Array of enriched people \(null entries indicate no match\) | +| `total_requested_enrichments` | number | Total number of records submitted for enrichment | +| `unique_enriched_records` | number | Number of records successfully enriched | +| `missing_records` | number | Number of records that could not be enriched | +| `credits_consumed` | number | Number of Apollo credits consumed by this request | ### `apollo_organization_search` @@ -409,10 +411,10 @@ Create up to 100 accounts at once in your Apollo database. Note: Apollo does not | Parameter | Type | Description | | --------- | ---- | ----------- | | `created_accounts` | json | Array of newly created accounts | -| `failed_accounts` | json | Array of accounts that failed to create | -| `total_submitted` | number | Total number of accounts submitted | +| `existing_accounts` | json | Array of existing accounts returned by Apollo \(when duplicates are detected\) | +| `total_submitted` | number | Total number of accounts in the response \(created + existing\) | | `created` | number | Number of accounts successfully created | -| `failed` | number | Number of accounts that failed to create | +| `existing` | number | Number of existing accounts found | ### `apollo_account_bulk_update` @@ -571,7 +573,7 @@ Add contacts to an Apollo sequence | --------- | ---- | ----------- | | `added` | json | Array of contact objects successfully added to the sequence | | `skipped` | json | Array of contact objects that were skipped, with reasons | -| `skipped_contact_ids` | json | Hash mapping contact IDs to skip reason codes | +| `skipped_contact_ids` | json | Array of contact IDs that were skipped | | `emailer_campaign` | json | Details of the emailer campaign \(id, name\) | | `sequence_id` | string | ID of the sequence contacts were added to | | `total_added` | number | Total number of contacts added | From 7447852b22ee0da101d6528725f61756083a79ac Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 22:10:35 -0700 Subject: [PATCH 04/17] fix(apollo): address PR review comments --- apps/sim/blocks/blocks/apollo.ts | 9 ++++++++- apps/sim/tools/apollo/contact_bulk_update.ts | 5 +++++ apps/sim/tools/apollo/organization_bulk_enrich.ts | 1 - apps/sim/tools/apollo/sequence_add_contacts.ts | 11 +++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/sim/blocks/blocks/apollo.ts b/apps/sim/blocks/blocks/apollo.ts index 51cca7078b9..3e9f03e44ee 100644 --- a/apps/sim/blocks/blocks/apollo.ts +++ b/apps/sim/blocks/blocks/apollo.ts @@ -178,7 +178,7 @@ export const ApolloBlock: BlockConfig = { }, required: { field: 'operation', - value: ['organization_enrich', 'account_create'], + value: 'organization_enrich', }, }, { @@ -1024,6 +1024,13 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes parsedParams.sequence_add_label_names = undefined } + if (params.operation === 'task_create') { + if (rest.task_notes !== undefined) { + parsedParams.note = rest.task_notes + } + parsedParams.task_notes = undefined + } + if ( params.operation === 'opportunity_create' || params.operation === 'opportunity_update' diff --git a/apps/sim/tools/apollo/contact_bulk_update.ts b/apps/sim/tools/apollo/contact_bulk_update.ts index 3ef80d08be4..f21a2a6c4b1 100644 --- a/apps/sim/tools/apollo/contact_bulk_update.ts +++ b/apps/sim/tools/apollo/contact_bulk_update.ts @@ -68,6 +68,11 @@ export const apolloContactBulkUpdateTool: ToolConfig< body.contact_attributes = params.contact_attributes } } + if (!body.contact_ids && !body.contact_attributes) { + throw new Error( + 'Apollo bulk update requires either contact_ids or contact_attributes to be provided' + ) + } if (params.async !== undefined) body.async = params.async return body }, diff --git a/apps/sim/tools/apollo/organization_bulk_enrich.ts b/apps/sim/tools/apollo/organization_bulk_enrich.ts index 9de002fe7bc..894514a4d1e 100644 --- a/apps/sim/tools/apollo/organization_bulk_enrich.ts +++ b/apps/sim/tools/apollo/organization_bulk_enrich.ts @@ -39,7 +39,6 @@ export const apolloOrganizationBulkEnrichTool: ToolConfig< }, method: 'POST', headers: (params: ApolloOrganizationBulkEnrichParams) => ({ - 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', 'X-Api-Key': params.apiKey, }), diff --git a/apps/sim/tools/apollo/sequence_add_contacts.ts b/apps/sim/tools/apollo/sequence_add_contacts.ts index 114d30cdaee..3a9b14390ee 100644 --- a/apps/sim/tools/apollo/sequence_add_contacts.ts +++ b/apps/sim/tools/apollo/sequence_add_contacts.ts @@ -95,12 +95,19 @@ export const apolloSequenceAddContactsTool: ToolConfig< 'X-Api-Key': params.apiKey, }), body: (params: ApolloSequenceAddContactsParams) => { + const hasContactIds = !!params.contact_ids?.length + const hasLabelNames = !!params.label_names?.length + if (!hasContactIds && !hasLabelNames) { + throw new Error( + 'Apollo sequence add requires either contact_ids or label_names to be provided' + ) + } const body: Record = { emailer_campaign_id: params.sequence_id, send_email_from_email_account_id: params.send_email_from_email_account_id, } - if (params.contact_ids?.length) body.contact_ids = params.contact_ids - if (params.label_names?.length) body.label_names = params.label_names + if (hasContactIds) body.contact_ids = params.contact_ids + if (hasLabelNames) body.label_names = params.label_names if (params.send_email_from_email_address) { body.send_email_from_email_address = params.send_email_from_email_address } From 721af52d00b08bc972f092e6d2436c984ed68976 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 22:26:22 -0700 Subject: [PATCH 05/17] fix(apollo): allow skipped_contact_ids as hash per Apollo docs --- .../app/(landing)/integrations/data/integrations.json | 2 +- apps/sim/tools/apollo/sequence_add_contacts.ts | 9 +++++++-- apps/sim/tools/apollo/types.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 18dd2b04da4..30d7118792d 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -14173,7 +14173,7 @@ "description": "Hire a pre-hire into an employee position. Converts an applicant into an active employee record with position, start date, and manager assignment." }, { - "name": "Update Worker", + "name": "Update Personal Information", "description": "Update fields on an existing worker record in Workday." }, { diff --git a/apps/sim/tools/apollo/sequence_add_contacts.ts b/apps/sim/tools/apollo/sequence_add_contacts.ts index 3a9b14390ee..3fafc5ae3ad 100644 --- a/apps/sim/tools/apollo/sequence_add_contacts.ts +++ b/apps/sim/tools/apollo/sequence_add_contacts.ts @@ -138,7 +138,11 @@ export const apolloSequenceAddContactsTool: ToolConfig< const added = Array.isArray(data?.contacts?.added) ? data.contacts.added : [] const skipped = Array.isArray(data?.contacts?.skipped) ? data.contacts.skipped : [] - const skippedIds = Array.isArray(data?.skipped_contact_ids) ? data.skipped_contact_ids : null + const rawSkippedIds = data?.skipped_contact_ids + const skippedIds = + Array.isArray(rawSkippedIds) || (rawSkippedIds && typeof rawSkippedIds === 'object') + ? rawSkippedIds + : null return { success: true, @@ -165,7 +169,8 @@ export const apolloSequenceAddContactsTool: ToolConfig< }, skipped_contact_ids: { type: 'json', - description: 'Array of contact IDs that were skipped', + description: + 'Skipped contact IDs — either an array of IDs or a hash mapping ID → reason code', optional: true, }, emailer_campaign: { diff --git a/apps/sim/tools/apollo/types.ts b/apps/sim/tools/apollo/types.ts index 56a73a80de0..ae2310ee1f6 100644 --- a/apps/sim/tools/apollo/types.ts +++ b/apps/sim/tools/apollo/types.ts @@ -485,7 +485,7 @@ export interface ApolloSequenceAddContactsResponse extends ToolResponse { output: { added: ApolloSequenceAddedContact[] skipped: ApolloSequenceSkippedContact[] - skipped_contact_ids: string[] | null + skipped_contact_ids: string[] | Record | null emailer_campaign: { id: string; name: string } | null sequence_id: string total_added: number From 7504a6ab0d252fe261500670e2cd7c6443d30b99 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 22:26:56 -0700 Subject: [PATCH 06/17] docs --- apps/docs/content/docs/en/tools/apollo.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/en/tools/apollo.mdx b/apps/docs/content/docs/en/tools/apollo.mdx index 089dac3b519..701b3696cdf 100644 --- a/apps/docs/content/docs/en/tools/apollo.mdx +++ b/apps/docs/content/docs/en/tools/apollo.mdx @@ -573,7 +573,7 @@ Add contacts to an Apollo sequence | --------- | ---- | ----------- | | `added` | json | Array of contact objects successfully added to the sequence | | `skipped` | json | Array of contact objects that were skipped, with reasons | -| `skipped_contact_ids` | json | Array of contact IDs that were skipped | +| `skipped_contact_ids` | json | Skipped contact IDs — either an array of IDs or a hash mapping ID → reason code | | `emailer_campaign` | json | Details of the emailer campaign \(id, name\) | | `sequence_id` | string | ID of the sequence contacts were added to | | `total_added` | number | Total number of contacts added | From 39f86bd2a88e0cb062c87e4f860a4b6251374d85 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 23:18:32 -0700 Subject: [PATCH 07/17] fix(apollo): add runtime guard for account_bulk_update empty body --- apps/sim/tools/apollo/account_bulk_update.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/sim/tools/apollo/account_bulk_update.ts b/apps/sim/tools/apollo/account_bulk_update.ts index 6892fffe4b3..9e4410f40cc 100644 --- a/apps/sim/tools/apollo/account_bulk_update.ts +++ b/apps/sim/tools/apollo/account_bulk_update.ts @@ -67,6 +67,11 @@ export const apolloAccountBulkUpdateTool: ToolConfig< if (params.account_attributes && params.account_attributes.length > 0) { body.account_attributes = params.account_attributes } + if (!body.account_ids && !body.account_attributes) { + throw new Error( + 'Apollo account bulk update requires either account_ids or account_attributes to be provided' + ) + } return body }, }, From 5a13a99ae6ec65dc637448ca32fca900bc15bd82 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 23:55:01 -0700 Subject: [PATCH 08/17] fix(apollo): require contact_attributes for bulk_update --- apps/sim/tools/apollo/contact_bulk_update.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/tools/apollo/contact_bulk_update.ts b/apps/sim/tools/apollo/contact_bulk_update.ts index f21a2a6c4b1..882be251551 100644 --- a/apps/sim/tools/apollo/contact_bulk_update.ts +++ b/apps/sim/tools/apollo/contact_bulk_update.ts @@ -68,9 +68,9 @@ export const apolloContactBulkUpdateTool: ToolConfig< body.contact_attributes = params.contact_attributes } } - if (!body.contact_ids && !body.contact_attributes) { + if (!body.contact_attributes) { throw new Error( - 'Apollo bulk update requires either contact_ids or contact_attributes to be provided' + 'Apollo bulk update requires contact_attributes (the fields to update). Use contact_attributes alone (array of per-contact updates with id) or together with contact_ids (single object applied to all listed contacts).' ) } if (params.async !== undefined) body.async = params.async From a1103d7d613e88c1ba13788d96180f14ce172365 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 00:12:00 -0700 Subject: [PATCH 09/17] fix(apollo): add subblock id migrations for renamed opportunity fields --- apps/sim/lib/workflows/migrations/subblock-migrations.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/sim/lib/workflows/migrations/subblock-migrations.ts b/apps/sim/lib/workflows/migrations/subblock-migrations.ts index a3dba9ae855..a32414d5404 100644 --- a/apps/sim/lib/workflows/migrations/subblock-migrations.ts +++ b/apps/sim/lib/workflows/migrations/subblock-migrations.ts @@ -40,6 +40,8 @@ export const SUBBLOCK_ID_MIGRATIONS: Record> = { apollo: { contact_ids_bulk: 'contacts', account_ids_bulk: 'accounts', + close_date: 'closed_date', + stage_id: 'opportunity_stage_id', }, rippling: { action: '_removed_action', From aa52f0d047520b1c203230a6b3000ba53dc36cd1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 00:23:25 -0700 Subject: [PATCH 10/17] fix(apollo): tighten account_bulk_update guard and accept object attrs --- apps/sim/tools/apollo/account_bulk_update.ts | 21 +++++++++++++++++--- apps/sim/tools/apollo/types.ts | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/sim/tools/apollo/account_bulk_update.ts b/apps/sim/tools/apollo/account_bulk_update.ts index 9e4410f40cc..f339fd7f2f1 100644 --- a/apps/sim/tools/apollo/account_bulk_update.ts +++ b/apps/sim/tools/apollo/account_bulk_update.ts @@ -64,12 +64,27 @@ export const apolloAccountBulkUpdateTool: ToolConfig< } if (params.name) body.name = params.name if (params.owner_id) body.owner_id = params.owner_id - if (params.account_attributes && params.account_attributes.length > 0) { - body.account_attributes = params.account_attributes + if (params.account_attributes) { + if (Array.isArray(params.account_attributes)) { + if (params.account_attributes.length > 0) { + body.account_attributes = params.account_attributes + } + } else if ( + typeof params.account_attributes === 'object' && + Object.keys(params.account_attributes).length > 0 + ) { + body.account_attributes = params.account_attributes + } + } + const hasUpdateFields = body.account_attributes || body.name || body.owner_id + if (!hasUpdateFields) { + throw new Error( + 'Apollo account bulk update requires update fields. Provide account_attributes (array of per-account updates with id, or single object paired with account_ids), or pair account_ids with name/owner_id to apply uniformly.' + ) } if (!body.account_ids && !body.account_attributes) { throw new Error( - 'Apollo account bulk update requires either account_ids or account_attributes to be provided' + 'Apollo account bulk update requires account_ids (with name/owner_id) or account_attributes (with embedded ids).' ) } return body diff --git a/apps/sim/tools/apollo/types.ts b/apps/sim/tools/apollo/types.ts index ae2310ee1f6..bb18254aaec 100644 --- a/apps/sim/tools/apollo/types.ts +++ b/apps/sim/tools/apollo/types.ts @@ -442,7 +442,7 @@ export interface ApolloAccountBulkUpdateParams extends ApolloBaseParams { account_ids?: string[] name?: string owner_id?: string - account_attributes?: Array<{ id: string; [key: string]: unknown }> + account_attributes?: Array<{ id: string; [key: string]: unknown }> | Record } export interface ApolloAccountBulkUpdateResponse extends ToolResponse { From 35d40dcab243d738ac613afd97ea847a2d6d87b0 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 00:35:44 -0700 Subject: [PATCH 11/17] fix(apollo): require contact_ids with object-form contact_attributes --- apps/sim/tools/apollo/contact_bulk_update.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/sim/tools/apollo/contact_bulk_update.ts b/apps/sim/tools/apollo/contact_bulk_update.ts index 882be251551..81d9835810e 100644 --- a/apps/sim/tools/apollo/contact_bulk_update.ts +++ b/apps/sim/tools/apollo/contact_bulk_update.ts @@ -73,6 +73,11 @@ export const apolloContactBulkUpdateTool: ToolConfig< 'Apollo bulk update requires contact_attributes (the fields to update). Use contact_attributes alone (array of per-contact updates with id) or together with contact_ids (single object applied to all listed contacts).' ) } + if (!Array.isArray(body.contact_attributes) && !body.contact_ids) { + throw new Error( + 'Apollo bulk update with object-form contact_attributes requires contact_ids to identify which contacts to update.' + ) + } if (params.async !== undefined) body.async = params.async return body }, From 3952c4e137cc415775516ecdf9b97c797bcbade7 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 00:48:10 -0700 Subject: [PATCH 12/17] docs(apollo): clarify contact_bulk_update parameter requirements --- apps/docs/content/docs/en/tools/apollo.mdx | 4 ++-- apps/sim/tools/apollo/contact_bulk_update.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/content/docs/en/tools/apollo.mdx b/apps/docs/content/docs/en/tools/apollo.mdx index 701b3696cdf..ccf96c267ef 100644 --- a/apps/docs/content/docs/en/tools/apollo.mdx +++ b/apps/docs/content/docs/en/tools/apollo.mdx @@ -311,8 +311,8 @@ Update up to 100 existing contacts at once in your Apollo database. Each contact | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `contact_ids` | array | No | Array of contact IDs to update with the same values. Use either this OR contact_attributes. | -| `contact_attributes` | json | No | Either an array of per-contact updates \(each with id\) or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields | +| `contact_ids` | array | No | Array of contact IDs to update. Must be paired with an object-form contact_attributes specifying the fields to apply uniformly to all listed contacts. | +| `contact_attributes` | json | No | Required. Either an array of per-contact updates \(each with id\) — used standalone — or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields | | `async` | boolean | No | Force asynchronous processing. Automatically enabled for >100 contacts | #### Output diff --git a/apps/sim/tools/apollo/contact_bulk_update.ts b/apps/sim/tools/apollo/contact_bulk_update.ts index 81d9835810e..08ff6cf53a1 100644 --- a/apps/sim/tools/apollo/contact_bulk_update.ts +++ b/apps/sim/tools/apollo/contact_bulk_update.ts @@ -26,14 +26,14 @@ export const apolloContactBulkUpdateTool: ToolConfig< required: false, visibility: 'user-or-llm', description: - 'Array of contact IDs to update with the same values. Use either this OR contact_attributes.', + 'Array of contact IDs to update. Must be paired with an object-form contact_attributes specifying the fields to apply uniformly to all listed contacts.', }, contact_attributes: { type: 'json', required: false, visibility: 'user-or-llm', description: - 'Either an array of per-contact updates (each with id) or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields', + 'Required. Either an array of per-contact updates (each with id) — used standalone — or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields', }, async: { type: 'boolean', From d1241891b5d013928a5819cf6fa7788c377c13a9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 00:53:08 -0700 Subject: [PATCH 13/17] fix(apollo): handle flat and wrapped contact response shapes --- apps/sim/tools/apollo/contact_create.ts | 5 +++-- apps/sim/tools/apollo/contact_update.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/sim/tools/apollo/contact_create.ts b/apps/sim/tools/apollo/contact_create.ts index e8dde6381a9..c1ebb6e2b62 100644 --- a/apps/sim/tools/apollo/contact_create.ts +++ b/apps/sim/tools/apollo/contact_create.ts @@ -170,12 +170,13 @@ export const apolloContactCreateTool: ToolConfig< } const data = await response.json() + const contact = data?.contact ?? (data?.id ? data : null) return { success: true, output: { - contact: data.contact ?? null, - created: !!data.contact, + contact, + created: !!contact, }, } }, diff --git a/apps/sim/tools/apollo/contact_update.ts b/apps/sim/tools/apollo/contact_update.ts index 1de9d4030bb..3c99984ca6f 100644 --- a/apps/sim/tools/apollo/contact_update.ts +++ b/apps/sim/tools/apollo/contact_update.ts @@ -170,12 +170,13 @@ export const apolloContactUpdateTool: ToolConfig< } const data = await response.json() + const contact = data?.contact ?? (data?.id ? data : null) return { success: true, output: { - contact: data.contact ?? null, - updated: !!data.contact, + contact, + updated: !!contact, }, } }, From ff981274553f5a18f0d8d8acd15d4bd02b8cf74c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 09:53:26 -0700 Subject: [PATCH 14/17] validate --- apps/docs/content/docs/en/tools/apollo.mdx | 20 +++++++---- .../integrations/data/integrations.json | 2 +- apps/sim/tools/apollo/account_bulk_create.ts | 30 ++++++++++++++--- apps/sim/tools/apollo/account_bulk_update.ts | 8 +++++ apps/sim/tools/apollo/organization_enrich.ts | 5 +-- apps/sim/tools/apollo/people_bulk_enrich.ts | 12 ++++++- apps/sim/tools/apollo/people_enrich.ts | 33 ++++++++++++++++++- .../sim/tools/apollo/sequence_add_contacts.ts | 12 +++++-- apps/sim/tools/apollo/task_create.ts | 17 ++++++---- apps/sim/tools/apollo/types.ts | 8 +++++ 10 files changed, 122 insertions(+), 25 deletions(-) diff --git a/apps/docs/content/docs/en/tools/apollo.mdx b/apps/docs/content/docs/en/tools/apollo.mdx index ccf96c267ef..044f6613e81 100644 --- a/apps/docs/content/docs/en/tools/apollo.mdx +++ b/apps/docs/content/docs/en/tools/apollo.mdx @@ -81,12 +81,16 @@ Enrich data for a single person using Apollo | `apiKey` | string | Yes | Apollo API key | | `first_name` | string | No | First name of the person | | `last_name` | string | No | Last name of the person | +| `name` | string | No | Full name of the person \(alternative to first_name/last_name\) | +| `id` | string | No | Apollo ID for the person | +| `hashed_email` | string | No | MD5 or SHA-256 hashed email | | `email` | string | No | Email address of the person | | `organization_name` | string | No | Company name where the person works | | `domain` | string | No | Company domain \(e.g., "apollo.io", "acme.com"\) | | `linkedin_url` | string | No | LinkedIn profile URL | | `reveal_personal_emails` | boolean | No | Reveal personal email addresses \(uses credits\) | -| `reveal_phone_number` | boolean | No | Reveal phone numbers \(uses credits\) | +| `reveal_phone_number` | boolean | No | Reveal phone numbers \(uses credits, requires webhook_url\) | +| `webhook_url` | string | No | Webhook URL for async phone number delivery \(required when reveal_phone_number is true\) | #### Output @@ -106,7 +110,8 @@ Enrich data for up to 10 people at once using Apollo | `apiKey` | string | Yes | Apollo API key | | `people` | array | Yes | Array of people to enrich \(max 10\) | | `reveal_personal_emails` | boolean | No | Reveal personal email addresses \(uses credits\) | -| `reveal_phone_number` | boolean | No | Reveal phone numbers \(uses credits\) | +| `reveal_phone_number` | boolean | No | Reveal phone numbers \(uses credits, requires webhook_url\) | +| `webhook_url` | string | No | Webhook URL for async phone number delivery \(required when reveal_phone_number is true\) | #### Output @@ -397,14 +402,16 @@ Search your team ### `apollo_account_bulk_create` -Create up to 100 accounts at once in your Apollo database. Note: Apollo does not apply deduplication - duplicate accounts may be created if entries share similar names or domains. Master key required. +Create up to 100 accounts at once in your Apollo database. Set run_dedupe=true to deduplicate by domain, organization_id, and name. Master key required. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Apollo API key \(master key required\) | -| `accounts` | array | Yes | Array of accounts to create \(max 100\). Each account should include name \(required\), and optionally website_url, phone, owner_id | +| `accounts` | array | Yes | Array of accounts to create \(max 100\). Each account should include name \(required\), and optionally domain, phone, owner_id | +| `append_label_names` | array | No | Array of label names to add to ALL accounts in this request | +| `run_dedupe` | boolean | No | When true, performs aggressive deduplication by domain, organization_id, and name \(defaults to false\) | #### Output @@ -429,6 +436,7 @@ Update up to 1000 existing accounts at once in your Apollo database (higher limi | `name` | string | No | When using account_ids, apply this name to all accounts | | `owner_id` | string | No | When using account_ids, apply this owner to all accounts | | `account_attributes` | json | No | Array of account objects with individual updates \(each must include id\). Example: \[\{"id": "acc1", "name": "Acme", "owner_id": "u1", "account_stage_id": "s1", "typed_custom_fields": \{"field_id": "value"\}\}\] | +| `async` | boolean | No | When true, processes the update asynchronously. Only supported when using account_ids; returns 422 if used with account_attributes. | #### Output @@ -590,9 +598,9 @@ Create one or more tasks in Apollo (one task per contact_id, master key required | `apiKey` | string | Yes | Apollo API key \(master key required\) | | `user_id` | string | Yes | ID of the Apollo user the task is assigned to | | `contact_ids` | array | Yes | Array of contact IDs. One task is created per contact. | -| `priority` | string | No | Task priority: "high", "medium", or "low" \(defaults to "medium"\) | +| `priority` | string | Yes | Task priority: "high", "medium", or "low" | | `due_at` | string | Yes | Due date/time in ISO 8601 format \(e.g., "2024-12-31T23:59:59Z"\) | -| `type` | string | Yes | Task type: "call", "outreach_manual_email", "linkedin_step_message", or "action_item" | +| `type` | string | Yes | Task type: "call", "outreach_manual_email", or "action_item" | | `status` | string | Yes | Task status: "scheduled", "completed", or "archived" | | `note` | string | No | Free-form note providing context for the task | diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 30d7118792d..78006dcd8bb 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -862,7 +862,7 @@ }, { "name": "Bulk Create Accounts", - "description": "Create up to 100 accounts at once in your Apollo database. Note: Apollo does not apply deduplication - duplicate accounts may be created if entries share similar names or domains. Master key required." + "description": "Create up to 100 accounts at once in your Apollo database. Set run_dedupe=true to deduplicate by domain, organization_id, and name. Master key required." }, { "name": "Bulk Update Accounts", diff --git a/apps/sim/tools/apollo/account_bulk_create.ts b/apps/sim/tools/apollo/account_bulk_create.ts index 092d30f7a6e..b69e908cd9d 100644 --- a/apps/sim/tools/apollo/account_bulk_create.ts +++ b/apps/sim/tools/apollo/account_bulk_create.ts @@ -11,7 +11,7 @@ export const apolloAccountBulkCreateTool: ToolConfig< id: 'apollo_account_bulk_create', name: 'Apollo Bulk Create Accounts', description: - 'Create up to 100 accounts at once in your Apollo database. Note: Apollo does not apply deduplication - duplicate accounts may be created if entries share similar names or domains. Master key required.', + 'Create up to 100 accounts at once in your Apollo database. Set run_dedupe=true to deduplicate by domain, organization_id, and name. Master key required.', version: '1.0.0', params: { @@ -26,7 +26,20 @@ export const apolloAccountBulkCreateTool: ToolConfig< required: true, visibility: 'user-or-llm', description: - 'Array of accounts to create (max 100). Each account should include name (required), and optionally website_url, phone, owner_id', + 'Array of accounts to create (max 100). Each account should include name (required), and optionally domain, phone, owner_id', + }, + append_label_names: { + type: 'array', + required: false, + visibility: 'user-only', + description: 'Array of label names to add to ALL accounts in this request', + }, + run_dedupe: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: + 'When true, performs aggressive deduplication by domain, organization_id, and name (defaults to false)', }, }, @@ -38,9 +51,16 @@ export const apolloAccountBulkCreateTool: ToolConfig< 'Cache-Control': 'no-cache', 'X-Api-Key': params.apiKey, }), - body: (params: ApolloAccountBulkCreateParams) => ({ - accounts: params.accounts.slice(0, 100), - }), + body: (params: ApolloAccountBulkCreateParams) => { + const body: Record = { + accounts: params.accounts.slice(0, 100), + } + if (params.append_label_names?.length) { + body.append_label_names = params.append_label_names + } + if (params.run_dedupe !== undefined) body.run_dedupe = params.run_dedupe + return body + }, }, transformResponse: async (response: Response) => { diff --git a/apps/sim/tools/apollo/account_bulk_update.ts b/apps/sim/tools/apollo/account_bulk_update.ts index f339fd7f2f1..1a328f20773 100644 --- a/apps/sim/tools/apollo/account_bulk_update.ts +++ b/apps/sim/tools/apollo/account_bulk_update.ts @@ -47,6 +47,13 @@ export const apolloAccountBulkUpdateTool: ToolConfig< description: 'Array of account objects with individual updates (each must include id). Example: [{"id": "acc1", "name": "Acme", "owner_id": "u1", "account_stage_id": "s1", "typed_custom_fields": {"field_id": "value"}}]', }, + async: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: + 'When true, processes the update asynchronously. Only supported when using account_ids; returns 422 if used with account_attributes.', + }, }, request: { @@ -87,6 +94,7 @@ export const apolloAccountBulkUpdateTool: ToolConfig< 'Apollo account bulk update requires account_ids (with name/owner_id) or account_attributes (with embedded ids).' ) } + if (params.async !== undefined) body.async = params.async return body }, }, diff --git a/apps/sim/tools/apollo/organization_enrich.ts b/apps/sim/tools/apollo/organization_enrich.ts index 896f57ce17a..77b76477cd0 100644 --- a/apps/sim/tools/apollo/organization_enrich.ts +++ b/apps/sim/tools/apollo/organization_enrich.ts @@ -30,10 +30,11 @@ export const apolloOrganizationEnrichTool: ToolConfig< request: { url: (params: ApolloOrganizationEnrichParams) => { - if (!params.domain) { + const domain = params.domain?.trim() + if (!domain) { throw new Error('domain is required for organization enrichment') } - return `https://api.apollo.io/api/v1/organizations/enrich?domain=${encodeURIComponent(params.domain.trim())}` + return `https://api.apollo.io/api/v1/organizations/enrich?domain=${encodeURIComponent(domain)}` }, method: 'GET', headers: (params: ApolloOrganizationEnrichParams) => ({ diff --git a/apps/sim/tools/apollo/people_bulk_enrich.ts b/apps/sim/tools/apollo/people_bulk_enrich.ts index bd4c53e1578..9c9721ff9a8 100644 --- a/apps/sim/tools/apollo/people_bulk_enrich.ts +++ b/apps/sim/tools/apollo/people_bulk_enrich.ts @@ -36,7 +36,14 @@ export const apolloPeopleBulkEnrichTool: ToolConfig< type: 'boolean', required: false, visibility: 'user-only', - description: 'Reveal phone numbers (uses credits)', + description: 'Reveal phone numbers (uses credits, requires webhook_url)', + }, + webhook_url: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Webhook URL for async phone number delivery (required when reveal_phone_number is true)', }, }, @@ -49,6 +56,9 @@ export const apolloPeopleBulkEnrichTool: ToolConfig< if (params.reveal_phone_number !== undefined) { qs.set('reveal_phone_number', String(params.reveal_phone_number)) } + if (params.webhook_url) { + qs.set('webhook_url', params.webhook_url) + } const query = qs.toString() return `https://api.apollo.io/api/v1/people/bulk_match${query ? `?${query}` : ''}` }, diff --git a/apps/sim/tools/apollo/people_enrich.ts b/apps/sim/tools/apollo/people_enrich.ts index 61680d02a4d..c231bcf3c07 100644 --- a/apps/sim/tools/apollo/people_enrich.ts +++ b/apps/sim/tools/apollo/people_enrich.ts @@ -29,6 +29,24 @@ export const apolloPeopleEnrichTool: ToolConfig< visibility: 'user-or-llm', description: 'Last name of the person', }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Full name of the person (alternative to first_name/last_name)', + }, + id: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Apollo ID for the person', + }, + hashed_email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'MD5 or SHA-256 hashed email', + }, email: { type: 'string', required: false, @@ -63,7 +81,14 @@ export const apolloPeopleEnrichTool: ToolConfig< type: 'boolean', required: false, visibility: 'user-only', - description: 'Reveal phone numbers (uses credits)', + description: 'Reveal phone numbers (uses credits, requires webhook_url)', + }, + webhook_url: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Webhook URL for async phone number delivery (required when reveal_phone_number is true)', }, }, @@ -76,6 +101,9 @@ export const apolloPeopleEnrichTool: ToolConfig< if (params.reveal_phone_number !== undefined) { qs.set('reveal_phone_number', String(params.reveal_phone_number)) } + if (params.webhook_url) { + qs.set('webhook_url', params.webhook_url) + } const query = qs.toString() return `https://api.apollo.io/api/v1/people/match${query ? `?${query}` : ''}` }, @@ -90,7 +118,10 @@ export const apolloPeopleEnrichTool: ToolConfig< if (params.first_name) body.first_name = params.first_name if (params.last_name) body.last_name = params.last_name + if (params.name) body.name = params.name if (params.email) body.email = params.email + if (params.hashed_email) body.hashed_email = params.hashed_email + if (params.id) body.id = params.id if (params.organization_name) body.organization_name = params.organization_name if (params.domain) body.domain = params.domain if (params.linkedin_url) body.linkedin_url = params.linkedin_url diff --git a/apps/sim/tools/apollo/sequence_add_contacts.ts b/apps/sim/tools/apollo/sequence_add_contacts.ts index 3fafc5ae3ad..31683492fec 100644 --- a/apps/sim/tools/apollo/sequence_add_contacts.ts +++ b/apps/sim/tools/apollo/sequence_add_contacts.ts @@ -136,8 +136,16 @@ export const apolloSequenceAddContactsTool: ToolConfig< const data = await response.json() - const added = Array.isArray(data?.contacts?.added) ? data.contacts.added : [] - const skipped = Array.isArray(data?.contacts?.skipped) ? data.contacts.skipped : [] + // Apollo's response shape for this endpoint varies: some payloads return a flat + // `contacts: [...]` array of successfully added contacts, others wrap under + // `contacts: { added, skipped }`. Handle both defensively. + const contactsField = data?.contacts + const added = Array.isArray(contactsField) + ? contactsField + : Array.isArray(contactsField?.added) + ? contactsField.added + : [] + const skipped = Array.isArray(contactsField?.skipped) ? contactsField.skipped : [] const rawSkippedIds = data?.skipped_contact_ids const skippedIds = Array.isArray(rawSkippedIds) || (rawSkippedIds && typeof rawSkippedIds === 'object') diff --git a/apps/sim/tools/apollo/task_create.ts b/apps/sim/tools/apollo/task_create.ts index 3fb41645d32..e1a12004bfa 100644 --- a/apps/sim/tools/apollo/task_create.ts +++ b/apps/sim/tools/apollo/task_create.ts @@ -28,9 +28,9 @@ export const apolloTaskCreateTool: ToolConfig = { user_id: params.user_id, contact_ids: params.contact_ids, + priority: params.priority || 'medium', due_at: params.due_at, type: params.type, status: params.status, } - if (params.priority) body.priority = params.priority if (params.note) body.note = params.note return body }, @@ -87,9 +86,13 @@ export const apolloTaskCreateTool: ToolConfig null) const tasks = Array.isArray(data?.tasks) ? data.tasks : [] - const created = data?.success === true || tasks.length > 0 + const created = + data === true || + data?.success === true || + tasks.length > 0 || + (response.status >= 200 && response.status < 300) return { success: true, diff --git a/apps/sim/tools/apollo/types.ts b/apps/sim/tools/apollo/types.ts index bb18254aaec..4f550b8aaca 100644 --- a/apps/sim/tools/apollo/types.ts +++ b/apps/sim/tools/apollo/types.ts @@ -145,12 +145,16 @@ export interface ApolloPeopleSearchResponse extends ToolResponse { export interface ApolloPeopleEnrichParams extends ApolloBaseParams { first_name?: string last_name?: string + name?: string + id?: string + hashed_email?: string organization_name?: string email?: string domain?: string linkedin_url?: string reveal_personal_emails?: boolean reveal_phone_number?: boolean + webhook_url?: string } export interface ApolloPeopleEnrichResponse extends ToolResponse { @@ -175,6 +179,7 @@ export interface ApolloPeopleBulkEnrichParams extends ApolloBaseParams { }> reveal_personal_emails?: boolean reveal_phone_number?: boolean + webhook_url?: string } export interface ApolloPeopleBulkEnrichResponse extends ToolResponse { @@ -425,6 +430,8 @@ export interface ApolloAccountBulkCreateParams extends ApolloBaseParams { phone?: string owner_id?: string }> + append_label_names?: string[] + run_dedupe?: boolean } export interface ApolloAccountBulkCreateResponse extends ToolResponse { @@ -443,6 +450,7 @@ export interface ApolloAccountBulkUpdateParams extends ApolloBaseParams { name?: string owner_id?: string account_attributes?: Array<{ id: string; [key: string]: unknown }> | Record + async?: boolean } export interface ApolloAccountBulkUpdateResponse extends ToolResponse { From 69aa833d7ddb544e7f0c7e8ba3cd6902a28d5494 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 12:33:54 -0700 Subject: [PATCH 15/17] fix(apollo): mirror bulk_update guard, preserve update fields in migration, expose account_bulk_create options --- apps/sim/blocks/blocks/apollo.ts | 57 ++++++++++++++------ apps/sim/tools/apollo/account_bulk_update.ts | 5 ++ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/apps/sim/blocks/blocks/apollo.ts b/apps/sim/blocks/blocks/apollo.ts index 3e9f03e44ee..65acc80f90c 100644 --- a/apps/sim/blocks/blocks/apollo.ts +++ b/apps/sim/blocks/blocks/apollo.ts @@ -461,7 +461,10 @@ export const ApolloBlock: BlockConfig = { id: 'run_dedupe', title: 'Run Deduplication', type: 'switch', - condition: { field: 'operation', value: 'contact_bulk_create' }, + condition: { + field: 'operation', + value: ['contact_bulk_create', 'account_bulk_create'], + }, mode: 'advanced', }, { @@ -469,7 +472,10 @@ export const ApolloBlock: BlockConfig = { title: 'Append Label Names (JSON Array)', type: 'code', placeholder: '["Hot Lead", "Q4 Outreach"]', - condition: { field: 'operation', value: 'contact_bulk_create' }, + condition: { + field: 'operation', + value: ['contact_bulk_create', 'account_bulk_create'], + }, mode: 'advanced', }, @@ -967,29 +973,46 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes throw new Error(`Invalid JSON input: ${message}`) } - const extractIds = (raw: unknown): string[] | undefined => { - if (!Array.isArray(raw)) return undefined - return raw - .map((item) => { - if (typeof item === 'string') return item - if (item && typeof item === 'object' && 'id' in item) { - const id = (item as { id: unknown }).id - return typeof id === 'string' ? id : undefined + const splitBulkUpdateInput = ( + raw: unknown + ): { ids?: string[]; attributes?: Array> } => { + if (!Array.isArray(raw)) return {} + const ids: string[] = [] + const attributes: Array> = [] + for (const item of raw) { + if (typeof item === 'string') { + ids.push(item) + continue + } + if (item && typeof item === 'object' && 'id' in item) { + const obj = item as Record + const id = obj.id + if (typeof id !== 'string') continue + const otherKeys = Object.keys(obj).filter((k) => k !== 'id') + if (otherKeys.length === 0) { + ids.push(id) + } else { + attributes.push(obj) } - return undefined - }) - .filter((id): id is string => Boolean(id)) + } + } + return { + ids: ids.length > 0 ? ids : undefined, + attributes: attributes.length > 0 ? attributes : undefined, + } } if (params.operation === 'contact_bulk_update') { - const ids = extractIds(parsedParams.contacts) - if (ids) parsedParams.contact_ids = ids + const { ids, attributes } = splitBulkUpdateInput(parsedParams.contacts) + if (attributes) parsedParams.contact_attributes = attributes + if (ids && !attributes) parsedParams.contact_ids = ids parsedParams.contacts = undefined } if (params.operation === 'account_bulk_update') { - const ids = extractIds(parsedParams.accounts) - if (ids) parsedParams.account_ids = ids + const { ids, attributes } = splitBulkUpdateInput(parsedParams.accounts) + if (attributes) parsedParams.account_attributes = attributes + if (ids && !attributes) parsedParams.account_ids = ids parsedParams.accounts = undefined if (rest.account_bulk_update_name) { parsedParams.name = rest.account_bulk_update_name diff --git a/apps/sim/tools/apollo/account_bulk_update.ts b/apps/sim/tools/apollo/account_bulk_update.ts index 1a328f20773..04f76134505 100644 --- a/apps/sim/tools/apollo/account_bulk_update.ts +++ b/apps/sim/tools/apollo/account_bulk_update.ts @@ -94,6 +94,11 @@ export const apolloAccountBulkUpdateTool: ToolConfig< 'Apollo account bulk update requires account_ids (with name/owner_id) or account_attributes (with embedded ids).' ) } + if (body.account_attributes && !Array.isArray(body.account_attributes) && !body.account_ids) { + throw new Error( + 'Apollo account bulk update with object-form account_attributes requires account_ids to identify which accounts to update.' + ) + } if (params.async !== undefined) body.async = params.async return body }, From 391b06bec32ff3942656def12b3c072106574037 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 12:55:31 -0700 Subject: [PATCH 16/17] fix(apollo): don't clobber user contact_attributes in migration; simplify task_create created flag --- apps/sim/blocks/blocks/apollo.ts | 16 ++++++++++++---- apps/sim/tools/apollo/task_create.ts | 7 +------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/sim/blocks/blocks/apollo.ts b/apps/sim/blocks/blocks/apollo.ts index 65acc80f90c..d7f982e973d 100644 --- a/apps/sim/blocks/blocks/apollo.ts +++ b/apps/sim/blocks/blocks/apollo.ts @@ -1004,15 +1004,23 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes if (params.operation === 'contact_bulk_update') { const { ids, attributes } = splitBulkUpdateInput(parsedParams.contacts) - if (attributes) parsedParams.contact_attributes = attributes - if (ids && !attributes) parsedParams.contact_ids = ids + if (attributes && parsedParams.contact_attributes === undefined) { + parsedParams.contact_attributes = attributes + } + if (ids && !attributes && parsedParams.contact_ids === undefined) { + parsedParams.contact_ids = ids + } parsedParams.contacts = undefined } if (params.operation === 'account_bulk_update') { const { ids, attributes } = splitBulkUpdateInput(parsedParams.accounts) - if (attributes) parsedParams.account_attributes = attributes - if (ids && !attributes) parsedParams.account_ids = ids + if (attributes && parsedParams.account_attributes === undefined) { + parsedParams.account_attributes = attributes + } + if (ids && !attributes && parsedParams.account_ids === undefined) { + parsedParams.account_ids = ids + } parsedParams.accounts = undefined if (rest.account_bulk_update_name) { parsedParams.name = rest.account_bulk_update_name diff --git a/apps/sim/tools/apollo/task_create.ts b/apps/sim/tools/apollo/task_create.ts index e1a12004bfa..92daa597e8c 100644 --- a/apps/sim/tools/apollo/task_create.ts +++ b/apps/sim/tools/apollo/task_create.ts @@ -88,17 +88,12 @@ export const apolloTaskCreateTool: ToolConfig null) const tasks = Array.isArray(data?.tasks) ? data.tasks : [] - const created = - data === true || - data?.success === true || - tasks.length > 0 || - (response.status >= 200 && response.status < 300) return { success: true, output: { tasks, - created, + created: true, }, } }, From 3702b26d2433669e670befe069699f6ee0075264 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 7 May 2026 13:22:56 -0700 Subject: [PATCH 17/17] =?UTF-8?q?fix(apollo):=20drop=20undocumented=20task?= =?UTF-8?q?=20type,=20preserve=20mixed-array=20IDs,=20migrate=20note?= =?UTF-8?q?=E2=86=92task=5Fnotes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sim/blocks/blocks/apollo.ts | 21 +++++++++++-------- .../migrations/subblock-migrations.ts | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/sim/blocks/blocks/apollo.ts b/apps/sim/blocks/blocks/apollo.ts index d7f982e973d..248a0e7a5af 100644 --- a/apps/sim/blocks/blocks/apollo.ts +++ b/apps/sim/blocks/blocks/apollo.ts @@ -739,7 +739,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n options: [ { label: 'Call', id: 'call' }, { label: 'Outreach Manual Email', id: 'outreach_manual_email' }, - { label: 'LinkedIn Step Message', id: 'linkedin_step_message' }, { label: 'Action Item', id: 'action_item' }, ], value: () => 'action_item', @@ -1004,10 +1003,12 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes if (params.operation === 'contact_bulk_update') { const { ids, attributes } = splitBulkUpdateInput(parsedParams.contacts) - if (attributes && parsedParams.contact_attributes === undefined) { - parsedParams.contact_attributes = attributes - } - if (ids && !attributes && parsedParams.contact_ids === undefined) { + if (attributes) { + const merged = ids ? [...attributes, ...ids.map((id) => ({ id }))] : attributes + if (parsedParams.contact_attributes === undefined) { + parsedParams.contact_attributes = merged + } + } else if (ids && parsedParams.contact_ids === undefined) { parsedParams.contact_ids = ids } parsedParams.contacts = undefined @@ -1015,10 +1016,12 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes if (params.operation === 'account_bulk_update') { const { ids, attributes } = splitBulkUpdateInput(parsedParams.accounts) - if (attributes && parsedParams.account_attributes === undefined) { - parsedParams.account_attributes = attributes - } - if (ids && !attributes && parsedParams.account_ids === undefined) { + if (attributes) { + const merged = ids ? [...attributes, ...ids.map((id) => ({ id }))] : attributes + if (parsedParams.account_attributes === undefined) { + parsedParams.account_attributes = merged + } + } else if (ids && parsedParams.account_ids === undefined) { parsedParams.account_ids = ids } parsedParams.accounts = undefined diff --git a/apps/sim/lib/workflows/migrations/subblock-migrations.ts b/apps/sim/lib/workflows/migrations/subblock-migrations.ts index a32414d5404..a90860cb571 100644 --- a/apps/sim/lib/workflows/migrations/subblock-migrations.ts +++ b/apps/sim/lib/workflows/migrations/subblock-migrations.ts @@ -42,6 +42,7 @@ export const SUBBLOCK_ID_MIGRATIONS: Record> = { account_ids_bulk: 'accounts', close_date: 'closed_date', stage_id: 'opportunity_stage_id', + note: 'task_notes', }, rippling: { action: '_removed_action',