n8n-nodes-coresignal-api 1.1.1 → 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 = {
@@ -32,6 +138,11 @@ class Coresignal {
32
138
  type: 'options',
33
139
  noDataExpression: true,
34
140
  options: [
141
+ {
142
+ name: 'Agentic Search',
143
+ value: 'agentic_search',
144
+ description: 'Turn natural language prompts into search queries and results',
145
+ },
35
146
  {
36
147
  name: 'Company',
37
148
  value: 'company',
@@ -56,6 +167,33 @@ class Coresignal {
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',
@@ -426,8 +564,230 @@ class Coresignal {
426
564
  operation: ['search_es_dsl'],
427
565
  },
428
566
  },
429
- description: 'Whether to use preview endpoint (returns up to 20 results with essential data)',
430
- 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
+ ],
431
791
  },
432
792
  ],
433
793
  usableAsTool: true,
@@ -441,9 +801,14 @@ class Coresignal {
441
801
  const resource = this.getNodeParameter('resource', i);
442
802
  const dataset = 'multi_source'; // Always use multi_source
443
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
+ }
444
811
  let url = '';
445
- let httpMethod = 'GET';
446
- let body;
447
812
  const qs = {};
448
813
  // Build the data source string (e.g., "company_multi_source", "employee_multi_source")
449
814
  // Posts use a different endpoint structure
@@ -465,7 +830,6 @@ class Coresignal {
465
830
  id = this.getNodeParameter('postId', i);
466
831
  }
467
832
  url = `${baseEndpoint}/collect/${id}`;
468
- httpMethod = 'GET';
469
833
  const fields = this.getNodeParameter('fields', i, '');
470
834
  if (fields) {
471
835
  qs.fields = fields.split(',').map(f => f.trim());
@@ -475,7 +839,6 @@ class Coresignal {
475
839
  case 'get_by_slug': {
476
840
  const slug = this.getNodeParameter('slug', i);
477
841
  url = `${baseEndpoint}/collect/${encodeURIComponent(slug)}`;
478
- httpMethod = 'GET';
479
842
  const fields = this.getNodeParameter('fields', i, '');
480
843
  if (fields) {
481
844
  qs.fields = fields.split(',').map(f => f.trim());
@@ -485,7 +848,6 @@ class Coresignal {
485
848
  case 'enrich': {
486
849
  const websiteUrl = this.getNodeParameter('websiteUrl', i);
487
850
  url = `${baseEndpoint}/enrich`;
488
- httpMethod = 'GET';
489
851
  qs.website = websiteUrl;
490
852
  const fields = this.getNodeParameter('fields', i, '');
491
853
  if (fields) {
@@ -494,20 +856,15 @@ class Coresignal {
494
856
  break;
495
857
  }
496
858
  case 'search_es_dsl': {
497
- const preview = this.getNodeParameter('preview', i, false);
498
- url = preview
499
- ? `${baseEndpoint}/search/es_dsl/preview`
500
- : `${baseEndpoint}/search/es_dsl`;
501
- httpMethod = 'POST';
502
- body = this.getNodeParameter('esQuery', i);
503
- 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;
504
864
  }
505
865
  }
506
- // Get credentials to access baseUrl
507
- const credentials = await this.getCredentials('coresignalApi');
508
- const baseUrl = credentials.baseUrl;
509
866
  const requestOptions = {
510
- method: httpMethod,
867
+ method: 'GET',
511
868
  url: `${baseUrl}${url}`,
512
869
  headers: {
513
870
  'Accept': 'application/json',
@@ -518,9 +875,6 @@ class Coresignal {
518
875
  if (Object.keys(qs).length > 0) {
519
876
  requestOptions.qs = qs;
520
877
  }
521
- if (body) {
522
- requestOptions.body = body;
523
- }
524
878
  const responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'coresignalApi', requestOptions);
525
879
  // Handle both single objects and arrays from API
526
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.1",
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",