n8n-nodes-coresignal-api 1.1.0 → 1.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # n8n-nodes-coresignal-api
2
2
 
3
- This is an [n8n](https://n8n.io/) community node for the [Coresignal API](https://coresignal.com/). It provides access to company, employee, job posting, and employee post data from Coresignal's multi-source datasets.
3
+ This is an [n8n](https://n8n.io/) community node for the [Coresignal API](https://coresignal.com/). It provides access to company, employee, job posting, and employee post data from Coresignal's multi-source datasets, and Agentic Search that turns natural language prompts into search queries.
4
4
 
5
5
  [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
6
6
 
@@ -16,6 +16,11 @@ Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes
16
16
 
17
17
  ## Operations
18
18
 
19
+ ### Agentic Search
20
+
21
+ - **Fast Search** - Turn a prompt into an Elasticsearch DSL query for a chosen entity, with rapid responses at minimal cost
22
+ - **Reasoning Search** - Turn a prompt into an Elasticsearch DSL query, with highly accurate results even for complex prompts
23
+
19
24
  ### Company Data
20
25
 
21
26
  - **Collect by ID** - Retrieve a full company profile using a company ID
@@ -64,13 +69,19 @@ Available for the Company category. Enter a company website URL to enrich with C
64
69
 
65
70
  ### Search with Elasticsearch DSL
66
71
 
67
- Use an Elasticsearch DSL query to search across any category. Enable **Preview mode** for quick testing (returns up to 20 results with essential fields only).
72
+ Use an Elasticsearch DSL query to search across any category. Regular search returns matching IDs and can page through all of them across runs. Enable **Preview mode** for quick testing (20 records per page with essential fields only).
68
73
 
69
74
  Build your queries using the Coresignal Playground:
70
75
  - [Employee Playground](https://dashboard.coresignal.com/apis/employees/playground)
71
76
  - [Company Playground](https://dashboard.coresignal.com/apis/company/playground)
72
77
  - [Jobs Playground](https://dashboard.coresignal.com/apis/jobs/playground)
73
78
 
79
+ ### Agentic Search
80
+
81
+ Turn a natural language **Prompt** into an Elasticsearch DSL query with **Fast Search** or **Reasoning Search**. The output is one item with the generated query, the entity it targets and, with **Return Data** on, a preview of up to 20 matching records.
82
+
83
+ To fetch all matching IDs, use the query in **Search with Elasticsearch DSL** on the matching category.
84
+
74
85
  ## Resources
75
86
 
76
87
  - [Coresignal API Documentation](https://docs.coresignal.com/)
@@ -1,7 +1,113 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Coresignal = void 0;
4
+ const crypto_1 = require("crypto");
4
5
  const n8n_workflow_1 = require("n8n-workflow");
6
+ const MAX_ITEMS_PER_PAGE = 1000;
7
+ const PREVIEW_PAGE_SIZE = 20;
8
+ const PREVIEW_MAX_PAGE = 50;
9
+ async function runSearch(context, itemIndex, endpoint) {
10
+ const preview = context.getNodeParameter('preview', itemIndex, false);
11
+ const options = context.getNodeParameter('options', itemIndex, {});
12
+ const esQuery = context.getNodeParameter('esQuery', itemIndex);
13
+ const request = async (qs) => (await context.helpers.httpRequestWithAuthentication.call(context, 'coresignalApi', {
14
+ method: 'POST',
15
+ url: `${endpoint}/search/es_dsl${preview ? '/preview' : ''}`,
16
+ headers: {
17
+ Accept: 'application/json',
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ qs,
21
+ body: esQuery,
22
+ returnFullResponse: true,
23
+ }));
24
+ const results = [];
25
+ const pagination = {};
26
+ if (preview) {
27
+ const page = options.page || 1;
28
+ const qs = { page };
29
+ if (options.snapshotId) {
30
+ qs.snapshot_token = options.snapshotId;
31
+ }
32
+ else if (options.snapshot === true) {
33
+ qs.snapshot = true;
34
+ }
35
+ const response = await request(qs);
36
+ results.push(...response.body);
37
+ const totalResults = Number(response.headers['x-total-results']);
38
+ const isLastPage = page * PREVIEW_PAGE_SIZE >= totalResults || page >= PREVIEW_MAX_PAGE;
39
+ pagination.totalResults = totalResults;
40
+ pagination.nextPage = isLastPage ? null : page + 1;
41
+ pagination.snapshotId = response.headers['x-snapshot-token'] || null;
42
+ }
43
+ else {
44
+ const returnAll = context.getNodeParameter('returnAll', itemIndex, false);
45
+ const limit = returnAll
46
+ ? Infinity
47
+ : context.getNodeParameter('maxResults', itemIndex);
48
+ let after = options.after || '';
49
+ while (results.length < limit) {
50
+ const qs = {
51
+ items_per_page: Math.min(limit - results.length, MAX_ITEMS_PER_PAGE),
52
+ };
53
+ if (after) {
54
+ qs.after = after;
55
+ }
56
+ const response = await request(qs);
57
+ const ids = response.body;
58
+ results.push(...ids);
59
+ after = response.headers['x-next-page-after'];
60
+ // The API sends the literal string "None" on the last page
61
+ const isLastPage = !after || after === 'None' || ids.length === 0;
62
+ pagination.totalResults = Number(response.headers['x-total-results']);
63
+ pagination.nextPageAfter = isLastPage ? null : after;
64
+ if (isLastPage)
65
+ break;
66
+ }
67
+ }
68
+ if (options.includePaginationInfo === true) {
69
+ return [{ results, pagination }];
70
+ }
71
+ return results;
72
+ }
73
+ async function runAgenticSearch(context, itemIndex, baseUrl) {
74
+ var _a;
75
+ const operation = context.getNodeParameter('operation', itemIndex);
76
+ const options = context.getNodeParameter('options', itemIndex, {});
77
+ const body = {
78
+ prompt: context.getNodeParameter('prompt', itemIndex),
79
+ return_data: context.getNodeParameter('returnData', itemIndex, true),
80
+ };
81
+ if (options.threshold !== undefined) {
82
+ body.threshold = options.threshold;
83
+ }
84
+ if (operation === 'fast') {
85
+ body.entity = context.getNodeParameter('entity', itemIndex);
86
+ }
87
+ else {
88
+ body.session_id = ((_a = options.sessionId) === null || _a === void 0 ? void 0 : _a.trim()) || (0, crypto_1.randomUUID)();
89
+ body.allow_clarification = options.allowClarification === true;
90
+ }
91
+ const response = (await context.helpers.httpRequestWithAuthentication.call(context, 'coresignalApi', {
92
+ method: 'POST',
93
+ url: `${baseUrl}/cdapi/v2/agentic_search/${operation}`,
94
+ headers: {
95
+ Accept: 'application/json',
96
+ 'Content-Type': 'application/json',
97
+ },
98
+ body,
99
+ returnFullResponse: true,
100
+ }));
101
+ const result = { ...response.body };
102
+ if (operation === 'reasoning' && result.session_id === undefined) {
103
+ result.session_id = body.session_id;
104
+ }
105
+ result.total_results =
106
+ response.headers['x-total-results'] !== undefined
107
+ ? Number(response.headers['x-total-results'])
108
+ : null;
109
+ return result;
110
+ }
5
111
  class Coresignal {
6
112
  constructor() {
7
113
  this.description = {
@@ -16,8 +122,8 @@ class Coresignal {
16
122
  defaults: {
17
123
  name: 'Coresignal',
18
124
  },
19
- inputs: ['main'],
20
- outputs: ['main'],
125
+ inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
126
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
21
127
  credentials: [
22
128
  {
23
129
  name: 'coresignalApi',
@@ -33,9 +139,9 @@ class Coresignal {
33
139
  noDataExpression: true,
34
140
  options: [
35
141
  {
36
- name: 'Employee',
37
- value: 'employee',
38
- description: 'Access employee data and profiles',
142
+ name: 'Agentic Search',
143
+ value: 'agentic_search',
144
+ description: 'Turn natural language prompts into search queries and results',
39
145
  },
40
146
  {
41
147
  name: 'Company',
@@ -43,19 +149,51 @@ class Coresignal {
43
149
  description: 'Access company data and profiles',
44
150
  },
45
151
  {
46
- name: 'Job',
47
- value: 'job',
48
- description: 'Access jobs data',
152
+ name: 'Employee',
153
+ value: 'employee',
154
+ description: 'Access employee data and profiles',
49
155
  },
50
156
  {
51
157
  name: 'Employee Post',
52
158
  value: 'post',
53
159
  description: 'Access employee posts data',
54
160
  },
161
+ {
162
+ name: 'Job',
163
+ value: 'job',
164
+ description: 'Access jobs data',
165
+ },
55
166
  ],
56
167
  default: 'employee',
57
168
  },
58
169
  // ============ OPERATION SELECTIONS ============
170
+ // Operation - Agentic Search
171
+ {
172
+ displayName: 'Operation',
173
+ name: 'operation',
174
+ type: 'options',
175
+ noDataExpression: true,
176
+ displayOptions: {
177
+ show: {
178
+ resource: ['agentic_search'],
179
+ },
180
+ },
181
+ options: [
182
+ {
183
+ name: 'Fast Search',
184
+ value: 'fast',
185
+ description: 'Generate an Elasticsearch DSL query for a chosen entity, with rapid responses at minimal cost',
186
+ action: 'Run fast agentic search',
187
+ },
188
+ {
189
+ name: 'Reasoning Search',
190
+ value: 'reasoning',
191
+ description: 'Generate an Elasticsearch DSL query, with highly accurate results even for complex prompts',
192
+ action: 'Run reasoning agentic search',
193
+ },
194
+ ],
195
+ default: 'fast',
196
+ },
59
197
  // Operation - Company
60
198
  {
61
199
  displayName: 'Operation',
@@ -68,6 +206,12 @@ class Coresignal {
68
206
  },
69
207
  },
70
208
  options: [
209
+ {
210
+ name: 'Enrich by Website',
211
+ value: 'enrich',
212
+ description: 'Enrich company data using a website URL',
213
+ action: 'Enrich company by website URL',
214
+ },
71
215
  {
72
216
  name: 'Get by ID',
73
217
  value: 'get_by_id',
@@ -80,12 +224,6 @@ class Coresignal {
80
224
  description: 'Retrieve a full company profile using a URL slug',
81
225
  action: 'Get company by URL slug',
82
226
  },
83
- {
84
- name: 'Enrich by Website',
85
- value: 'enrich',
86
- description: 'Enrich company data using a website URL',
87
- action: 'Enrich company by website URL',
88
- },
89
227
  {
90
228
  name: 'Search with Elasticsearch DSL',
91
229
  value: 'search_es_dsl',
@@ -303,8 +441,7 @@ class Coresignal {
303
441
  },
304
442
  },
305
443
  default: '',
306
- // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
307
- placeholder: 'e.g. id,full_name,headline',
444
+ placeholder: 'e.g. full_name,headline',
308
445
  description: 'Comma-separated list of fields to return (leave empty for all fields)',
309
446
  },
310
447
  // Fields Parameter - Company
@@ -319,8 +456,7 @@ class Coresignal {
319
456
  },
320
457
  },
321
458
  default: '',
322
- // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
323
- placeholder: 'e.g. id,company_name,website',
459
+ placeholder: 'e.g. company_name,website',
324
460
  description: 'Comma-separated list of fields to return (leave empty for all fields)',
325
461
  },
326
462
  // Fields Parameter - Jobs
@@ -335,8 +471,7 @@ class Coresignal {
335
471
  },
336
472
  },
337
473
  default: '',
338
- // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
339
- placeholder: 'e.g. id,title,company_name',
474
+ placeholder: 'e.g. title,company_name',
340
475
  description: 'Comma-separated list of fields to return (leave empty for all fields)',
341
476
  },
342
477
  // Fields Parameter - Posts
@@ -351,8 +486,7 @@ class Coresignal {
351
486
  },
352
487
  },
353
488
  default: '',
354
- // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
355
- placeholder: 'e.g. id,url,author_name',
489
+ placeholder: 'e.g. url,author_name',
356
490
  description: 'Comma-separated list of fields to return (leave empty for all fields)',
357
491
  },
358
492
  // Elasticsearch DSL Query - Employee
@@ -430,8 +564,230 @@ class Coresignal {
430
564
  operation: ['search_es_dsl'],
431
565
  },
432
566
  },
433
- description: 'Whether to use preview endpoint (returns up to 20 results with essential data)',
434
- hint: 'Preview mode returns up to 20 results with essential fields only. Use it for quick testing.',
567
+ description: 'Whether to use the preview endpoint (20 results per page with essential fields, up to 50 pages)',
568
+ hint: 'Preview mode returns essential fields only. Use it for quick testing.',
569
+ },
570
+ // Return All
571
+ {
572
+ displayName: 'Return All',
573
+ name: 'returnAll',
574
+ type: 'boolean',
575
+ default: false,
576
+ displayOptions: {
577
+ show: {
578
+ operation: ['search_es_dsl'],
579
+ },
580
+ hide: {
581
+ preview: [true],
582
+ },
583
+ },
584
+ description: 'Whether to return all results or only up to a given limit',
585
+ hint: 'Regular search can return millions of IDs.',
586
+ },
587
+ // Limit - regular search
588
+ {
589
+ displayName: 'Limit',
590
+ name: 'maxResults',
591
+ type: 'number',
592
+ typeOptions: {
593
+ minValue: 1,
594
+ },
595
+ default: 1000,
596
+ displayOptions: {
597
+ show: {
598
+ operation: ['search_es_dsl'],
599
+ returnAll: [false],
600
+ },
601
+ hide: {
602
+ preview: [true],
603
+ },
604
+ },
605
+ description: 'Max number of results to return',
606
+ },
607
+ // Pagination options
608
+ {
609
+ displayName: 'Options',
610
+ name: 'options',
611
+ type: 'collection',
612
+ placeholder: 'Add option',
613
+ default: {},
614
+ displayOptions: {
615
+ show: {
616
+ operation: ['search_es_dsl'],
617
+ },
618
+ },
619
+ options: [
620
+ {
621
+ displayName: 'After',
622
+ name: 'after',
623
+ type: 'string',
624
+ default: '',
625
+ displayOptions: {
626
+ hide: {
627
+ '/preview': [true],
628
+ },
629
+ },
630
+ description: 'Cursor to continue from, taken from the nextPageAfter value of a previous run with Include Pagination Info enabled',
631
+ },
632
+ {
633
+ displayName: 'Include Pagination Info',
634
+ name: 'includePaginationInfo',
635
+ type: 'boolean',
636
+ default: false,
637
+ description: 'Whether to return a single item holding all results together with pagination details (total results, next page, snapshot ID)',
638
+ },
639
+ {
640
+ displayName: 'Page',
641
+ name: 'page',
642
+ type: 'number',
643
+ typeOptions: {
644
+ minValue: 1,
645
+ maxValue: 50,
646
+ },
647
+ default: 1,
648
+ displayOptions: {
649
+ show: {
650
+ '/preview': [true],
651
+ },
652
+ },
653
+ description: 'Preview page to fetch',
654
+ },
655
+ {
656
+ displayName: 'Snapshot',
657
+ name: 'snapshot',
658
+ type: 'boolean',
659
+ default: false,
660
+ displayOptions: {
661
+ show: {
662
+ '/preview': [true],
663
+ },
664
+ },
665
+ description: 'Whether to open a snapshot so later pages stay consistent. The API only accepts it on page 1 and the snapshot expires after 300 seconds. Enable Include Pagination Info to receive the snapshot ID.',
666
+ },
667
+ {
668
+ displayName: 'Snapshot ID',
669
+ name: 'snapshotId',
670
+ type: 'string',
671
+ default: '',
672
+ displayOptions: {
673
+ show: {
674
+ '/preview': [true],
675
+ },
676
+ },
677
+ description: 'Snapshot ID from a previous run that keeps preview pages consistent',
678
+ },
679
+ ],
680
+ },
681
+ // ============ AGENTIC SEARCH ============
682
+ // Prompt
683
+ {
684
+ displayName: 'Prompt',
685
+ name: 'prompt',
686
+ type: 'string',
687
+ required: true,
688
+ typeOptions: {
689
+ rows: 3,
690
+ },
691
+ displayOptions: {
692
+ show: {
693
+ resource: ['agentic_search'],
694
+ },
695
+ },
696
+ default: '',
697
+ placeholder: 'e.g. senior Python developers in Berlin with startup experience',
698
+ description: 'Natural language description of the records to find',
699
+ },
700
+ // Entity - fast only, reasoning infers it from the prompt
701
+ {
702
+ displayName: 'Entity',
703
+ name: 'entity',
704
+ type: 'options',
705
+ displayOptions: {
706
+ show: {
707
+ resource: ['agentic_search'],
708
+ operation: ['fast'],
709
+ },
710
+ },
711
+ options: [
712
+ {
713
+ name: 'Company',
714
+ value: 'company',
715
+ },
716
+ {
717
+ name: 'Employee',
718
+ value: 'employee',
719
+ },
720
+ {
721
+ name: 'Job',
722
+ value: 'job',
723
+ },
724
+ ],
725
+ default: 'employee',
726
+ description: 'Type of records the prompt targets',
727
+ },
728
+ // Return Data
729
+ {
730
+ displayName: 'Return Data',
731
+ name: 'returnData',
732
+ type: 'boolean',
733
+ default: true,
734
+ displayOptions: {
735
+ show: {
736
+ resource: ['agentic_search'],
737
+ },
738
+ },
739
+ description: 'Whether to also return up to 20 matching records',
740
+ },
741
+ // Agentic search options
742
+ {
743
+ displayName: 'Options',
744
+ name: 'options',
745
+ type: 'collection',
746
+ placeholder: 'Add option',
747
+ default: {},
748
+ displayOptions: {
749
+ show: {
750
+ resource: ['agentic_search'],
751
+ },
752
+ },
753
+ options: [
754
+ {
755
+ displayName: 'Allow Clarification',
756
+ name: 'allowClarification',
757
+ type: 'boolean',
758
+ default: false,
759
+ displayOptions: {
760
+ show: {
761
+ '/operation': ['reasoning'],
762
+ },
763
+ },
764
+ description: 'Whether the engine may ask a clarifying question instead of returning a query',
765
+ },
766
+ {
767
+ displayName: 'Session ID',
768
+ name: 'sessionId',
769
+ type: 'string',
770
+ default: '',
771
+ displayOptions: {
772
+ show: {
773
+ '/operation': ['reasoning'],
774
+ },
775
+ },
776
+ description: 'UUID that groups follow-up prompts into one conversation. Generated when empty.',
777
+ },
778
+ {
779
+ displayName: 'Synonym Threshold',
780
+ name: 'threshold',
781
+ type: 'number',
782
+ typeOptions: {
783
+ minValue: 0,
784
+ maxValue: 1,
785
+ numberPrecision: 3,
786
+ },
787
+ default: 0.97,
788
+ description: 'How close a synonym match must be, from 0 to 1. Higher demands a closer match.',
789
+ },
790
+ ],
435
791
  },
436
792
  ],
437
793
  usableAsTool: true,
@@ -445,9 +801,14 @@ class Coresignal {
445
801
  const resource = this.getNodeParameter('resource', i);
446
802
  const dataset = 'multi_source'; // Always use multi_source
447
803
  const operation = this.getNodeParameter('operation', i);
804
+ const credentials = await this.getCredentials('coresignalApi');
805
+ const baseUrl = credentials.baseUrl;
806
+ if (resource === 'agentic_search') {
807
+ const agenticResult = await runAgenticSearch(this, i, baseUrl);
808
+ returnData.push(...this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(agenticResult), { itemData: { item: i } }));
809
+ continue;
810
+ }
448
811
  let url = '';
449
- let httpMethod = 'GET';
450
- let body;
451
812
  const qs = {};
452
813
  // Build the data source string (e.g., "company_multi_source", "employee_multi_source")
453
814
  // Posts use a different endpoint structure
@@ -469,7 +830,6 @@ class Coresignal {
469
830
  id = this.getNodeParameter('postId', i);
470
831
  }
471
832
  url = `${baseEndpoint}/collect/${id}`;
472
- httpMethod = 'GET';
473
833
  const fields = this.getNodeParameter('fields', i, '');
474
834
  if (fields) {
475
835
  qs.fields = fields.split(',').map(f => f.trim());
@@ -479,7 +839,6 @@ class Coresignal {
479
839
  case 'get_by_slug': {
480
840
  const slug = this.getNodeParameter('slug', i);
481
841
  url = `${baseEndpoint}/collect/${encodeURIComponent(slug)}`;
482
- httpMethod = 'GET';
483
842
  const fields = this.getNodeParameter('fields', i, '');
484
843
  if (fields) {
485
844
  qs.fields = fields.split(',').map(f => f.trim());
@@ -489,7 +848,6 @@ class Coresignal {
489
848
  case 'enrich': {
490
849
  const websiteUrl = this.getNodeParameter('websiteUrl', i);
491
850
  url = `${baseEndpoint}/enrich`;
492
- httpMethod = 'GET';
493
851
  qs.website = websiteUrl;
494
852
  const fields = this.getNodeParameter('fields', i, '');
495
853
  if (fields) {
@@ -498,20 +856,15 @@ class Coresignal {
498
856
  break;
499
857
  }
500
858
  case 'search_es_dsl': {
501
- const preview = this.getNodeParameter('preview', i, false);
502
- url = preview
503
- ? `${baseEndpoint}/search/es_dsl/preview`
504
- : `${baseEndpoint}/search/es_dsl`;
505
- httpMethod = 'POST';
506
- body = this.getNodeParameter('esQuery', i);
507
- break;
859
+ const searchResults = await runSearch(this, i, `${baseUrl}${baseEndpoint}`);
860
+ returnData.push(...this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(searchResults), {
861
+ itemData: { item: i },
862
+ }));
863
+ continue;
508
864
  }
509
865
  }
510
- // Get credentials to access baseUrl
511
- const credentials = await this.getCredentials('coresignalApi');
512
- const baseUrl = credentials.baseUrl;
513
866
  const requestOptions = {
514
- method: httpMethod,
867
+ method: 'GET',
515
868
  url: `${baseUrl}${url}`,
516
869
  headers: {
517
870
  'Accept': 'application/json',
@@ -522,9 +875,6 @@ class Coresignal {
522
875
  if (Object.keys(qs).length > 0) {
523
876
  requestOptions.qs = qs;
524
877
  }
525
- if (body) {
526
- requestOptions.body = body;
527
- }
528
878
  const responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'coresignalApi', requestOptions);
529
879
  // Handle both single objects and arrays from API
530
880
  const responseItems = Array.isArray(responseData) ? responseData : [responseData];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-coresignal-api",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "n8n community node for Coresignal API - access company, employee, job, and post data",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -22,7 +22,8 @@
22
22
  "dev": "n8n-node dev",
23
23
  "lint": "n8n-node lint",
24
24
  "lintfix": "n8n-node lint --fix",
25
- "prepublishOnly": "n8n-node build"
25
+ "release": "n8n-node release",
26
+ "prepublishOnly": "n8n-node prerelease"
26
27
  },
27
28
  "files": [
28
29
  "dist"