@memberjunction/actions-apollo 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +346 -206
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -4,12 +4,108 @@ Apollo.io data enrichment action classes for MemberJunction that enable automate
4
4
 
5
5
  ## Overview
6
6
 
7
- This package provides two primary action classes that integrate with Apollo.io's data enrichment services:
7
+ This package provides two server-side action classes that integrate with Apollo.io's data enrichment services to automatically populate account and contact records with company information, social profiles, technology stacks, employment history, and education data. Both actions extend `BaseAction` from `@memberjunction/actions` and are registered via `@RegisterClass` for automatic discovery by the MemberJunction Actions engine.
8
+
9
+ Key capabilities:
10
+
11
+ - **Account enrichment** -- company address, phone, description, social URLs, technology stacks, and associated contacts discovered via organization domain lookup
12
+ - **Contact enrichment** -- bulk email verification, social profile URLs, employment history, and education history via people matching
13
+ - **Configurable field mappings** -- JSON-based parameter configuration maps Apollo.io fields to your custom entity fields
14
+ - **Rate limit handling** -- automatic retry with intelligent backoff for both per-minute and hourly Apollo.io rate limits
15
+ - **Batch processing** -- concurrent group processing with configurable batch sizes and pagination for large datasets
16
+
17
+ For general Actions framework architecture and design philosophy, see the [parent Actions README](../README.md) and [Actions CLAUDE.md](../CLAUDE.md).
18
+
19
+ ## Architecture
20
+
21
+ ```mermaid
22
+ flowchart TB
23
+ subgraph Engine["MemberJunction Actions Engine"]
24
+ AE["ActionEngineServer"]
25
+ end
26
+
27
+ subgraph Apollo["@memberjunction/actions-apollo"]
28
+ AccAction["ApolloEnrichmentAccountsAction"]
29
+ ConAction["ApolloEnrichmentContactsAction"]
30
+ Config["Configuration\n(API key, batch sizes)"]
31
+ Types["Apollo Type Definitions"]
32
+ end
33
+
34
+ subgraph ApolloAPI["Apollo.io REST API"]
35
+ OrgEnrich["/organizations/enrich"]
36
+ BulkMatch["/people/bulk_match"]
37
+ PeopleSearch["/mixed_people/search"]
38
+ end
39
+
40
+ subgraph MJCore["MemberJunction Core"]
41
+ Meta["Metadata"]
42
+ RV["RunView"]
43
+ BE["BaseEntity"]
44
+ end
45
+
46
+ AE -->|executes| AccAction
47
+ AE -->|executes| ConAction
48
+ AccAction --> Config
49
+ ConAction --> Config
50
+ AccAction --> Types
51
+ ConAction --> Types
52
+ AccAction -->|HTTP via axios| OrgEnrich
53
+ AccAction -->|HTTP via axios| PeopleSearch
54
+ ConAction -->|HTTP via axios| BulkMatch
55
+ ConAction -->|HTTP via axios| PeopleSearch
56
+ AccAction -->|read/write entities| MJCore
57
+ ConAction -->|read/write entities| MJCore
58
+
59
+ style Engine fill:#2d6a9f,stroke:#1a4971,color:#fff
60
+ style Apollo fill:#7c5295,stroke:#563a6b,color:#fff
61
+ style ApolloAPI fill:#b8762f,stroke:#8a5722,color:#fff
62
+ style MJCore fill:#2d8659,stroke:#1a5c3a,color:#fff
63
+ ```
8
64
 
9
- - **ApolloAccountsEnrichmentAction** - Enriches account/organization records with company information, technologies used, and associated contacts
10
- - **ApolloContactsEnrichmentAction** - Enriches contact records with verified email addresses, employment history, and education details
65
+ ### Account Enrichment Data Flow
66
+
67
+ ```mermaid
68
+ flowchart LR
69
+ Start["Load accounts\nmatching filter"] --> OrgAPI["Call /organizations/enrich\nper domain"]
70
+ OrgAPI --> UpdateAcct["Update account\nfields"]
71
+ OrgAPI --> TechRec["Create/update\ntechnology records"]
72
+ OrgAPI --> PeopleAPI["Call /mixed_people/search\nfor domain contacts"]
73
+ PeopleAPI --> CreateContact["Create/update\ncontact records"]
74
+ CreateContact --> History["Create education\nhistory records"]
75
+ UpdateAcct --> Next["Next account"]
76
+ TechRec --> Next
77
+ History --> Next
78
+
79
+ style Start fill:#2d6a9f,stroke:#1a4971,color:#fff
80
+ style OrgAPI fill:#b8762f,stroke:#8a5722,color:#fff
81
+ style UpdateAcct fill:#2d8659,stroke:#1a5c3a,color:#fff
82
+ style TechRec fill:#2d8659,stroke:#1a5c3a,color:#fff
83
+ style PeopleAPI fill:#b8762f,stroke:#8a5722,color:#fff
84
+ style CreateContact fill:#2d8659,stroke:#1a5c3a,color:#fff
85
+ style History fill:#2d8659,stroke:#1a5c3a,color:#fff
86
+ style Next fill:#64748b,stroke:#475569,color:#fff
87
+ ```
11
88
 
12
- These actions are designed to work within the MemberJunction framework and can be configured through action parameters to map Apollo.io data to your custom entity fields.
89
+ ### Contact Enrichment Data Flow
90
+
91
+ ```mermaid
92
+ flowchart LR
93
+ Start["Page contacts\nmatching filter"] --> Batch["Batch into groups\nof 10"]
94
+ Batch --> BulkAPI["Call /people/bulk_match\nper batch"]
95
+ BulkAPI --> Update["Update contact\nfields from matches"]
96
+ Update --> EmpHist["Upsert employment\nhistory"]
97
+ Update --> EduHist["Upsert education\nhistory"]
98
+ EmpHist --> NextBatch["Next batch"]
99
+ EduHist --> NextBatch
100
+
101
+ style Start fill:#2d6a9f,stroke:#1a4971,color:#fff
102
+ style Batch fill:#64748b,stroke:#475569,color:#fff
103
+ style BulkAPI fill:#b8762f,stroke:#8a5722,color:#fff
104
+ style Update fill:#2d8659,stroke:#1a5c3a,color:#fff
105
+ style EmpHist fill:#7c5295,stroke:#563a6b,color:#fff
106
+ style EduHist fill:#7c5295,stroke:#563a6b,color:#fff
107
+ style NextBatch fill:#64748b,stroke:#475569,color:#fff
108
+ ```
13
109
 
14
110
  ## Installation
15
111
 
@@ -20,9 +116,9 @@ npm install @memberjunction/actions-apollo
20
116
  ## Prerequisites
21
117
 
22
118
  1. An active Apollo.io account with API access
23
- 2. Apollo.io API key (set as environment variable `APOLLO_API_KEY`)
24
- 3. MemberJunction framework properly configured
25
- 4. Target entities for storing enriched data (Accounts, Contacts, etc.)
119
+ 2. Apollo.io API key set as the environment variable `APOLLO_API_KEY`
120
+ 3. MemberJunction framework properly configured with server-side action engine
121
+ 4. Target entities configured for storing enriched data (accounts, contacts, technologies, etc.)
26
122
 
27
123
  ## Configuration
28
124
 
@@ -34,257 +130,301 @@ APOLLO_API_KEY=your_apollo_api_key_here
34
130
 
35
131
  ### Configuration Constants
36
132
 
37
- The package uses the following configuration values (defined in `config.ts`):
133
+ The package defines the following defaults in `config.ts`:
38
134
 
39
- - `ApolloAPIEndpoint`: 'https://api.apollo.io/v1' - Apollo.io API base URL
40
- - `EmailSourceName`: 'Apollo.io' - Source name for enriched emails
41
- - `GroupSize`: 10 - Maximum records per API batch request
42
- - `ConcurrentGroups`: 1 - Number of concurrent API request groups
43
- - `MaxPeopleToEnrichPerOrg`: 500 - Maximum contacts to enrich per organization
44
- - `ApolloAPIKey`: Read from environment variable `APOLLO_API_KEY`
135
+ | Constant | Default | Description |
136
+ |---|---|---|
137
+ | `ApolloAPIEndpoint` | `https://api.apollo.io/v1` | Apollo.io API base URL |
138
+ | `EmailSourceName` | `Apollo.io` | Source label applied to enriched emails |
139
+ | `GroupSize` | `10` | Records per API batch request (Apollo max is 10) |
140
+ | `ConcurrentGroups` | `1` | Number of concurrent API request groups |
141
+ | `MaxPeopleToEnrichPerOrg` | `500` | Maximum contacts to enrich per organization |
142
+ | `ApolloAPIKey` | `process.env.APOLLO_API_KEY` | Read from environment at startup |
45
143
 
46
144
  ## Usage
47
145
 
48
- ### Apollo Accounts Enrichment Action
146
+ ### Account Enrichment
49
147
 
50
- This action enriches account/organization records by looking up company information using domain names.
148
+ The `ApolloEnrichmentAccountsAction` enriches account/organization records by looking up company information using domain names. It can optionally discover contacts at the organization, track technology stacks, and create education history records.
51
149
 
52
150
  #### Parameters
53
151
 
54
- The action accepts the following parameters as JSON strings:
152
+ | Parameter | Required | Type | Description |
153
+ |---|---|---|---|
154
+ | `AccountEntityFieldMappings` | Yes | JSON string | Maps account entity fields (see `AccountEntityFields` below) |
155
+ | `AccountTechnologyEntityFieldMappings` | No | JSON string | Maps technology relationship fields |
156
+ | `TechnologyCategoryEntityFieldMappings` | No | JSON string | Maps technology category fields |
157
+ | `ContactEntityFieldMappings` | No | JSON string | Maps contact entity fields for discovered contacts |
158
+ | `ContactEducationHistoryEntityFieldMappings` | No | JSON string | Maps education history fields |
55
159
 
56
- **Required:**
57
- - `AccountEntityFieldNameJSON` - Maps account entity fields
58
-
59
- **Optional:**
60
- - `AccountTechnologyEntityFieldNameJSON` - Maps account technology relationship fields
61
- - `TechnologyCategoryEntityFieldNameJSON` - Maps technology category fields
62
- - `ContactEntityFieldNameJSON` - Maps contact entity fields
63
- - `ContactEducationHistoryEntityFieldNameJSON` - Maps contact education history fields
64
-
65
- **AccountEntityFieldNameJSON Structure:**
66
- ```typescript
67
- {
68
- EntityName: string; // Target entity name (e.g., "Accounts")
69
- DomainParamName: string; // Field containing company domain
70
- AccountIDName: string; // Primary key field name
71
- EnrichedAtField: string; // Timestamp field for tracking enrichment
72
- ExtraFilter?: string; // SQL filter for selecting records to process
73
-
74
- // Optional mapping fields
75
- AddressFieldName?: string; // Street address field
76
- CityFieldNameName?: string; // City field
77
- StateProvinceFieldName?: string; // State/province field
78
- PostalCodeFieldName?: string; // Postal code field
79
- DescriptionFieldName?: string; // Company description field
80
- PhoneNumberFieldName?: string; // Phone number field
81
- CountryFieldName?: string; // Country field
82
- LinkedInFieldName?: string; // LinkedIn URL field
83
- LogoURLFieldName?: string; // Company logo URL field
84
- FacebookFieldName?: string; // Facebook URL field
85
- TwitterFieldName?: string; // Twitter URL field
86
- }
87
- ```
160
+ #### AccountEntityFields Structure
88
161
 
89
- **AccountTechnologyEntityFieldNameJSON Structure:**
90
162
  ```typescript
91
163
  {
92
- EntityName: string; // Technology relationship entity name
93
- AccountIDFieldName: string; // Foreign key to account
94
- TechnologyIDFieldName: string; // Foreign key to technology
95
- MatchFoundFieldName: string; // Field indicating if match was found
96
- EndedUseAtFieldName: string; // Field for marking end of technology use
164
+ EntityName: string; // Target entity name (e.g., "Accounts")
165
+ DomainField: string; // Field containing company domain
166
+ AccountIDField: string; // Primary key field name
167
+ EnrichedAtField: string; // Timestamp field for tracking enrichment
168
+ Filter: string; // SQL filter for selecting records to process
169
+ AddressField?: string; // Street address
170
+ CityField?: string; // City
171
+ StateProvinceField?: string; // State/province
172
+ PostalCodeField?: string; // Postal code
173
+ DescriptionField?: string; // Company description
174
+ PhoneNumberField?: string; // Phone number
175
+ CountryField?: string; // Country
176
+ LinkedInField?: string; // LinkedIn URL
177
+ LogoURLField?: string; // Company logo URL
178
+ FacebookField?: string; // Facebook URL
179
+ TwitterField?: string; // Twitter URL
97
180
  }
98
181
  ```
99
182
 
100
- #### Example Usage
183
+ #### Example
101
184
 
102
185
  ```typescript
103
- import { ApolloAccountsEnrichmentAction } from '@memberjunction/actions-apollo';
104
- import { ActionEngine } from '@memberjunction/actions';
186
+ import { ActionEngineServer } from '@memberjunction/actions';
105
187
 
106
- // Register the action with the engine
107
- const engine = new ActionEngine();
108
- const action = new ApolloAccountsEnrichmentAction();
188
+ const engine = ActionEngineServer.Instance;
109
189
 
110
- // Execute the action
111
190
  const result = await engine.RunAction({
112
- ActionName: 'Apollo Enrichment - Accounts',
113
- Params: [
114
- {
115
- Name: 'AccountEntityFieldNameJSON',
116
- Value: JSON.stringify({
117
- EntityName: 'Accounts',
118
- DomainParamName: 'Domain',
119
- AccountIDName: 'ID',
120
- EnrichedAtField: 'LastEnrichedAt',
121
- // ... other mappings
122
- })
123
- }
124
- ],
125
- ContextUser: currentUser
191
+ ActionName: 'ApolloEnrichmentAccountsAction',
192
+ Params: [
193
+ {
194
+ Name: 'AccountEntityFieldMappings',
195
+ Value: JSON.stringify({
196
+ EntityName: 'Accounts',
197
+ DomainField: 'Domain',
198
+ AccountIDField: 'ID',
199
+ EnrichedAtField: 'LastEnrichedAt',
200
+ Filter: 'Domain IS NOT NULL AND LastEnrichedAt IS NULL',
201
+ CityField: 'City',
202
+ StateProvinceField: 'StateProvince',
203
+ LinkedInField: 'LinkedInURL',
204
+ DescriptionField: 'Description'
205
+ })
206
+ },
207
+ {
208
+ Name: 'AccountTechnologyEntityFieldMappings',
209
+ Value: JSON.stringify({
210
+ EntityName: 'Account Technologies',
211
+ AccountIDField: 'AccountID',
212
+ TechnologyIDField: 'TechnologyID',
213
+ TechnologyField: 'Technology',
214
+ CategoryField: 'Category',
215
+ EndedUseAtField: 'EndedUseAt'
216
+ })
217
+ },
218
+ {
219
+ Name: 'ContactEntityFieldMappings',
220
+ Value: JSON.stringify({
221
+ EntityName: 'Contacts',
222
+ EmailField: 'Email',
223
+ AccountIDField: 'AccountID',
224
+ EnrichedAtField: 'LastEnrichedAt',
225
+ FirstNameField: 'FirstName',
226
+ LastNameField: 'LastName',
227
+ TitleField: 'Title',
228
+ EmailSourceField: 'EmailSource',
229
+ ActivityCountField: 'ActivityCount'
230
+ })
231
+ }
232
+ ],
233
+ ContextUser: contextUser
126
234
  });
127
235
  ```
128
236
 
129
- ### Apollo Contacts Enrichment Action
237
+ ### Contact Enrichment
130
238
 
131
- This action enriches contact records by matching on name and email combinations.
239
+ The `ApolloEnrichmentContactsAction` enriches existing contact records by matching on name and email combinations through Apollo's bulk people matching API.
132
240
 
133
241
  #### Parameters
134
242
 
135
- The action accepts the following string parameters:
136
-
137
- **Required:**
138
- - `EntityName` - Target entity name containing contacts
139
- - `EmailField` - Field name containing email addresses
140
- - `FirstNameField` - Field name containing first names
141
- - `LastNameField` - Field name containing last names
142
- - `AccountNameField` - Field name containing account/organization names
143
- - `EnrichedAtField` - Field name for tracking enrichment timestamp
144
- - `FilterParam` - SQL filter for selecting records to process
243
+ | Parameter | Required | Type | Description |
244
+ |---|---|---|---|
245
+ | `EntityName` | Yes | string | Target entity containing contacts |
246
+ | `EmailField` | Yes | string | Field name for email addresses |
247
+ | `FirstNameField` | Yes | string | Field name for first names |
248
+ | `LastNameField` | Yes | string | Field name for last names |
249
+ | `TitleField` | Yes | string | Field name for job titles |
250
+ | `EnrichedAtField` | Yes | string | Field name for enrichment timestamp |
251
+ | `Filter` | Yes | string | SQL filter to select contacts for enrichment |
252
+ | `ProfilePictureURLField` | No | string | Field for profile picture URLs |
253
+ | `AccountNameField` | No | string | Field for account/company names |
254
+ | `DomainField` | No | string | Field for company domains |
255
+ | `LinkedInField` | No | string | Field for LinkedIn profile URLs |
256
+ | `TwitterField` | No | string | Field for Twitter profile URLs |
257
+ | `FacebookField` | No | string | Field for Facebook profile URLs |
258
+ | `EmploymentHistoryFieldMappings` | No | JSON string | Employment history entity field mappings |
259
+ | `EducationHistoryFieldMappings` | No | JSON string | Education history entity field mappings |
260
+
261
+ #### EmploymentHistoryFieldMappings Structure
145
262
 
146
- **Optional:**
147
- - `domainParam` - Field name containing company domain
148
- - `linkedinParam` - Field name for storing LinkedIn URLs
149
- - `EmploymentHistoryFieldMappings` - JSON string with employment history field mappings
150
- - `EducationHistoryFieldMappings` - JSON string with education history field mappings
151
-
152
- **EmploymentHistoryFieldMappings Structure:**
153
263
  ```typescript
154
264
  {
155
- EmploymentHistoryEntityName: string; // Employment history entity name
156
- EmploymentHistoryContactIDFieldName: string; // Foreign key to contact
157
- EmploymentHistoryOrganizationFieldName: string; // Organization name field
158
- EmploymentHistoryTitleFieldName: string; // Job title field
265
+ EmploymentHistoryEntityName: string; // Employment history entity
266
+ EmploymentHistoryContactIDFieldName: string; // Foreign key to contact
267
+ EmploymentHistoryOrganizationFieldName: string; // Organization name field
268
+ EmploymentHistoryTitleFieldName: string; // Job title field
159
269
  }
160
270
  ```
161
271
 
162
- **EducationHistoryFieldMappings Structure:**
272
+ #### EducationHistoryFieldMappings Structure
273
+
163
274
  ```typescript
164
275
  {
165
- EducationHistoryEntityName: string; // Education history entity name
166
- EducationtHistoryContactIDFieldName: string; // Foreign key to contact
167
- EducationtHistoryInstitutionFieldName: string; // Institution name field
168
- EducationtHistoryDegreeFieldName: string; // Degree field
276
+ EducationHistoryEntityName: string; // Education history entity
277
+ EducationHistoryContactIDFieldName: string; // Foreign key to contact
278
+ EducationHistoryInstitutionFieldName: string; // Institution name field
279
+ EducationHistoryDegreeFieldName: string; // Degree field
169
280
  }
170
281
  ```
171
282
 
172
- #### Example Usage
283
+ #### Example
173
284
 
174
285
  ```typescript
175
- import { ApolloContactsEnrichmentAction } from '@memberjunction/actions-apollo';
286
+ import { ActionEngineServer } from '@memberjunction/actions';
287
+
288
+ const engine = ActionEngineServer.Instance;
176
289
 
177
290
  const result = await engine.RunAction({
178
- ActionName: 'Apollo Enrichment - Contacts',
179
- Params: [
180
- { Name: 'EntityName', Value: 'Contacts' },
181
- { Name: 'EmailField', Value: 'Email' },
182
- { Name: 'FirstNameField', Value: 'FirstName' },
183
- { Name: 'LastNameField', Value: 'LastName' },
184
- { Name: 'AccountNameField', Value: 'AccountName' },
185
- { Name: 'EnrichedAtField', Value: 'LastEnrichedAt' },
186
- { Name: 'FilterParam', Value: 'Email IS NOT NULL AND LastEnrichedAt IS NULL' },
187
- { Name: 'domainParam', Value: 'Domain' },
188
- { Name: 'linkedinParam', Value: 'LinkedIn' },
189
- {
190
- Name: 'EmploymentHistoryFieldMappings',
191
- Value: JSON.stringify({
192
- EmploymentHistoryEntityName: 'ContactEmploymentHistory',
193
- EmploymentHistoryContactIDFieldName: 'ContactID',
194
- EmploymentHistoryOrganizationFieldName: 'Organization',
195
- EmploymentHistoryTitleFieldName: 'Title'
196
- })
197
- }
198
- ],
199
- ContextUser: currentUser
291
+ ActionName: 'ApolloEnrichmentContactsAction',
292
+ Params: [
293
+ { Name: 'EntityName', Value: 'Contacts' },
294
+ { Name: 'EmailField', Value: 'Email' },
295
+ { Name: 'FirstNameField', Value: 'FirstName' },
296
+ { Name: 'LastNameField', Value: 'LastName' },
297
+ { Name: 'TitleField', Value: 'Title' },
298
+ { Name: 'EnrichedAtField', Value: 'LastEnrichedAt' },
299
+ { Name: 'Filter', Value: 'Email IS NOT NULL AND LastEnrichedAt IS NULL' },
300
+ { Name: 'DomainField', Value: 'Domain' },
301
+ { Name: 'LinkedInField', Value: 'LinkedInURL' },
302
+ {
303
+ Name: 'EmploymentHistoryFieldMappings',
304
+ Value: JSON.stringify({
305
+ EmploymentHistoryEntityName: 'Contact Employment Histories',
306
+ EmploymentHistoryContactIDFieldName: 'ContactID',
307
+ EmploymentHistoryOrganizationFieldName: 'Organization',
308
+ EmploymentHistoryTitleFieldName: 'Title'
309
+ })
310
+ },
311
+ {
312
+ Name: 'EducationHistoryFieldMappings',
313
+ Value: JSON.stringify({
314
+ EducationHistoryEntityName: 'Contact Education Histories',
315
+ EducationHistoryContactIDFieldName: 'ContactID',
316
+ EducationHistoryInstitutionFieldName: 'Institution',
317
+ EducationHistoryDegreeFieldName: 'Degree'
318
+ })
319
+ }
320
+ ],
321
+ ContextUser: contextUser
200
322
  });
201
323
  ```
202
324
 
203
- ## Features
204
-
205
- ### Account Enrichment
206
- - Company information (address, phone, description, social media URLs)
207
- - Technology stack detection and tracking
208
- - Automatic contact discovery and creation
209
- - Technology category management
210
- - Historical technology usage tracking
211
-
212
- ### Contact Enrichment
213
- - Bulk email verification and discovery (up to 10 contacts per API call)
214
- - Employment history tracking with date ranges
215
- - Education history tracking with degree information
216
- - Social media profile URLs (LinkedIn, Twitter, Facebook)
217
- - Title exclusion filtering (excludes members, students, volunteers)
218
- - Pagination support for processing large datasets
219
- - Duplicate contact detection across accounts
220
-
221
- ### Error Handling & Rate Limiting
222
- - Automatic retry with intelligent backoff for rate limits (1 minute for general limits, 1 hour for hourly limits)
223
- - Handles both per-minute and per-hour rate limits with different wait times
224
- - Comprehensive error logging using MemberJunction's logging system
225
- - Batch processing to optimize API usage and respect Apollo.io limits
226
- - Graceful handling of missing or incomplete data
227
- - Transaction rollback support for failed operations
228
-
229
- ## API Integration
230
-
231
- The package integrates with the following Apollo.io API endpoints:
232
-
233
- - `/organizations/enrich` - Organization enrichment
234
- - `/people/bulk_match` - Bulk contact matching
235
- - `/mixed_people/search` - People search by domain
325
+ ## API Reference
326
+
327
+ ### Exported Classes
328
+
329
+ #### `ApolloEnrichmentAccountsAction`
330
+
331
+ Registered as `"ApolloEnrichmentAccountsAction"` via `@RegisterClass(BaseAction)`. Extends `BaseAction`.
332
+
333
+ **Processing behavior:**
334
+ - Queries accounts matching the configured filter
335
+ - For each account, calls `/organizations/enrich` with the domain
336
+ - Updates account fields with enriched organization data
337
+ - Optionally creates/updates technology stack records with historical tracking (marks ended technologies)
338
+ - Optionally discovers and creates contact records via `/mixed_people/search`
339
+ - Processes recursively up to 5 times to handle remaining records
340
+ - Supports concurrent domain processing (configurable via `ConcurrentGroups`)
341
+
342
+ #### `ApolloEnrichmentContactsAction`
343
+
344
+ Registered as `"ApolloEnrichmentContactsAction"` via `@RegisterClass(BaseAction)`. Extends `BaseAction`.
345
+
346
+ **Processing behavior:**
347
+ - Pages through contact records matching the configured filter (500 per page)
348
+ - Batches contacts into groups of 10 for Apollo's `/people/bulk_match` endpoint
349
+ - Updates matching contacts with enriched social profiles and company data
350
+ - Optionally creates/updates employment and education history records
351
+ - Supports secondary enrichment via `/mixed_people/search` for organization-level lookups
352
+
353
+ ### Exported Types
354
+
355
+ All types are exported from `src/generic/apollo.types.ts`:
356
+
357
+ | Type | Description |
358
+ |---|---|
359
+ | `ProcessPersonRecordGroupParams` | Parameters for batch contact group processing |
360
+ | `ApolloBulkPeopleRequest` | Request payload for `/people/bulk_match` |
361
+ | `ApolloBulkPeopleRequestDetail` | Individual person detail within a bulk request |
362
+ | `ApolloBulkPeopleResponse` | Response from `/people/bulk_match` |
363
+ | `ContactEntityFields` | Field mapping configuration for contact entities |
364
+ | `ContactEducationHistoryEntityFields` | Field mapping for education history entities |
365
+ | `TechnologyCategoryEntityFields` | Field mapping for technology category entities |
366
+ | `AccountTechnologyEntityFields` | Field mapping for account-technology relationship entities |
367
+ | `AccountEntityFields` | Field mapping configuration for account entities |
368
+ | `ProcessSingleDomainParams` | Parameters for processing a single domain enrichment |
369
+ | `OrganizationEnrichmentRequest` | Request for `/organizations/enrich` |
370
+ | `OrganizationEnrichmentResponse` | Response from organization enrichment |
371
+ | `OrganizationEnrichmentOrganization` | Detailed organization data from Apollo |
372
+ | `OrganizationEnrichmentOrganizationAccount` | Account data within organization response |
373
+ | `TechnologyMap` | Technology record with name, category, and UID |
374
+ | `SearchPeopleResponse` | Response from `/mixed_people/search` |
375
+ | `SearchPeopleResponsePerson` | Individual person data from search response |
376
+ | `EmploymentHistory` | Employment/education history entry |
377
+
378
+ ## Rate Limiting and Error Handling
379
+
380
+ Both action classes include a `WrapApolloCall` method that provides:
381
+
382
+ - **Automatic retry** on HTTP 429 (Too Many Requests) responses
383
+ - **Per-minute backoff**: 60-second wait on standard rate limit responses
384
+ - **Hourly backoff**: 60-minute wait when Apollo's hourly rate limit is detected (contact action only)
385
+ - **Exception handling**: Catches both Axios response errors and thrown exceptions for 429 status codes
386
+ - **Comprehensive logging** via MemberJunction's `LogError` and `LogStatus` utilities
387
+
388
+ ### Title Filtering
389
+
390
+ Both actions automatically exclude contacts with the following job titles to maintain data quality:
391
+ - `member`
392
+ - `student member`
393
+ - `student`
394
+ - `volunteer`
395
+
396
+ ## Apollo.io API Endpoints Used
397
+
398
+ | Endpoint | HTTP Method | Used By | Purpose |
399
+ |---|---|---|---|
400
+ | `/organizations/enrich` | GET | Accounts action | Organization data by domain |
401
+ | `/people/bulk_match` | POST | Contacts action | Bulk contact matching (up to 10 per request) |
402
+ | `/mixed_people/search` | POST | Both actions | People search by organization domain |
236
403
 
237
404
  ## Dependencies
238
405
 
239
- - `@memberjunction/core` - Core MemberJunction functionality
240
- - `@memberjunction/core-entities` - Entity definitions
241
- - `@memberjunction/actions` - Action framework
242
- - `@memberjunction/global` - Global utilities
243
- - `axios` - HTTP client for API requests
244
-
245
- ## Best Practices
246
-
247
- 1. **Batch Processing**: The actions automatically batch records to optimize API usage and respect rate limits.
248
-
249
- 2. **Field Mapping**: Carefully map Apollo.io fields to your entity fields to ensure data consistency.
250
-
251
- 3. **Filtering**: Use the filter parameters to process only records that need enrichment, avoiding unnecessary API calls.
252
-
253
- 4. **Error Monitoring**: Monitor the action logs for failed enrichments and rate limit issues.
254
-
255
- 5. **Data Quality**: The actions include validation for email domains and exclude certain titles to maintain data quality.
406
+ | Package | Purpose |
407
+ |---|---|
408
+ | `@memberjunction/actions` | Base action class (`BaseAction`) and action engine |
409
+ | `@memberjunction/actions-base` | Action parameter types (`ActionParam`, `ActionResultSimple`, `RunActionParams`) |
410
+ | `@memberjunction/core` | `Metadata`, `RunView`, `BaseEntity`, logging utilities, `UserInfo`, `CompositeKey` |
411
+ | `@memberjunction/core-entities` | MemberJunction entity definitions |
412
+ | `@memberjunction/global` | `@RegisterClass` decorator for action registration |
413
+ | `axios` | HTTP client for Apollo.io API requests |
256
414
 
257
415
  ## Limitations
258
416
 
259
- - Maximum 10 records per bulk API request (Apollo.io limitation)
260
- - Rate limits apply based on your Apollo.io subscription (handled automatically with retries)
417
+ - Maximum 10 records per bulk API request (Apollo.io API limitation)
418
+ - Rate limits apply based on your Apollo.io subscription tier (handled automatically with retries)
261
419
  - Personal emails may not be revealed in GDPR-compliant regions
262
- - Some enrichment data may be incomplete depending on Apollo.io's data coverage
263
- - Account enrichment processes domains sequentially to avoid overwhelming the system
264
- - Contact enrichment uses pagination with a maximum of 500 contacts per organization
420
+ - Account enrichment processes domains sequentially within each concurrent group
421
+ - Contact enrichment paginates with a maximum of 500 contacts per organization
422
+ - Account enrichment recurses up to 5 times to prevent infinite loops
265
423
  - Excluded job titles (member, student member, student, volunteer) are automatically skipped
266
424
 
267
- ## Troubleshooting
268
-
269
- ### Common Issues
270
-
271
- 1. **Missing API Key**: Ensure `APOLLO_API_KEY` environment variable is set
272
- 2. **Rate Limits**: The action will automatically retry after waiting for rate limit windows
273
- 3. **No Matches Found**: Check that input data (domains, emails) are valid and formatted correctly
274
- 4. **Field Mapping Errors**: Verify that all mapped fields exist in your target entities
275
-
276
- ### Debug Logging
277
-
278
- The actions use MemberJunction's logging system. Monitor logs for:
279
- - Enrichment progress
280
- - API response details
281
- - Error messages
282
- - Rate limit notifications
283
-
284
- ## Contributing
285
-
286
- This package is part of the MemberJunction open-source project. Contributions are welcome following the project's contribution guidelines.
287
-
288
- ## License
425
+ ## Related Packages
289
426
 
290
- ISC License - see the MemberJunction project license for details.
427
+ - [@memberjunction/actions-base](../Base) -- Base classes and types used by all action packages
428
+ - [@memberjunction/actions](../Engine) -- Server-side action engine that discovers and executes actions
429
+ - [@memberjunction/core-actions](../CoreActions) -- Collection of 40+ pre-built MemberJunction actions
430
+ - [@memberjunction/actions-content-autotag](../ContentAutotag) -- Content tagging and vectorization actions
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memberjunction/actions-apollo",
3
3
  "type": "module",
4
- "version": "4.0.0",
4
+ "version": "4.2.0",
5
5
  "description": "Action classes that wrap the Apollo.io data enrichment API for contacts and accounts",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -20,11 +20,11 @@
20
20
  "typescript": "^5.9.3"
21
21
  },
22
22
  "dependencies": {
23
- "@memberjunction/actions": "4.0.0",
24
- "@memberjunction/actions-base": "4.0.0",
25
- "@memberjunction/core": "4.0.0",
26
- "@memberjunction/core-entities": "4.0.0",
27
- "@memberjunction/global": "4.0.0",
23
+ "@memberjunction/actions": "4.2.0",
24
+ "@memberjunction/actions-base": "4.2.0",
25
+ "@memberjunction/core": "4.2.0",
26
+ "@memberjunction/core-entities": "4.2.0",
27
+ "@memberjunction/global": "4.2.0",
28
28
  "axios": "^1.13.4"
29
29
  },
30
30
  "repository": {