n8n-nodes-periskop 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Periskop
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # n8n-nodes-periskop
2
+
3
+ An [n8n](https://n8n.io) community node for **Periskop** — product discovery for
4
+ AI agents and workflows.
5
+
6
+ Periskop turns natural-language shopping intent into structured product results:
7
+ recommendations, ranked alternatives, merchant product URLs, price/currency when
8
+ available, caveats, no-match results, request IDs, and machine-readable errors.
9
+
10
+ > **Discovery only.** Periskop does not buy products, process payments, create
11
+ > merchant carts or checkout, reserve stock, or place orders, and it does not
12
+ > guarantee live price or availability. Your users complete the purchase on the
13
+ > merchant website. Every response includes an explicit `purchase_boundary`.
14
+
15
+ ## Node
16
+
17
+ - **Node:** `Periskop`
18
+ - **Operation:** `Run Shopping Discovery`
19
+ - **Endpoint:** `POST https://mcp.periskop.ai/v1/mcp/shopping/discover`
20
+
21
+ ### Fields
22
+
23
+ | Field | Required | Notes |
24
+ |---|---|---|
25
+ | Prompt | yes | Natural-language shopping intent |
26
+ | Response Format | no | `simple` (default) or `full` |
27
+ | Country | no | ISO country code, e.g. `PT` |
28
+ | Currency | no | ISO currency code, e.g. `EUR` |
29
+ | Language | no | BCP 47 tag, e.g. `en` |
30
+ | Mode | no | `auto` (default), `browse`, `recommend`, `best`, `bundle` |
31
+ | Max Results | no | 1–50 (default `3`) |
32
+ | Store | no | Store id or natural store hint |
33
+ | External User ID | no | Your own analytics id |
34
+ | Previous Result ID | no | Optional prior result id for follow-up discovery |
35
+ | Constraints (JSON) | no | e.g. `{"max_price": 200, "optimization": "best_quality"}` |
36
+
37
+ The node returns the Periskop JSON response as-is (one output item per input
38
+ item), so you can reference `result_id`, `request_id`, `items`, `caveats`,
39
+ `purchase_boundary`, and `errors` directly in downstream nodes.
40
+
41
+ ## Credential
42
+
43
+ - **Name:** `Periskop API`
44
+ - **Field:** `API Key` (your `dp_...` key — create one at
45
+ <https://periskop.ai/developer/keys>)
46
+ - The node sends `Authorization: Bearer <API Key>` automatically — do **not**
47
+ type the word `Bearer`.
48
+ - **Credential test:** the credential is verified against `GET /v1/mcp/stores`,
49
+ an auth-safe, **non-billable** endpoint — testing credentials never burns
50
+ credits.
51
+ - An optional **Base URL** field defaults to `https://mcp.periskop.ai` and only
52
+ needs changing for sandbox/self-hosted environments.
53
+
54
+ ## Installation
55
+
56
+ ### In n8n (GUI)
57
+
58
+ Install via **Settings → Community Nodes → Install** and enter
59
+ `n8n-nodes-periskop`.
60
+
61
+ ### Local development
62
+
63
+ ```bash
64
+ # from integrations/n8n-nodes-periskop
65
+ npm install
66
+ npm run build # tsc + copies the SVG icon into dist/
67
+ npm run lint # eslint-plugin-n8n-nodes-base checks
68
+
69
+ # link into a local n8n install for manual testing
70
+ npm link
71
+ cd ~/.n8n/custom # create this folder if it does not exist
72
+ npm link n8n-nodes-periskop
73
+ # restart n8n; the Periskop node now appears in the node panel
74
+ ```
75
+
76
+ See the n8n docs on
77
+ [running community nodes locally](https://docs.n8n.io/integrations/creating-nodes/test/run-node-locally/)
78
+ for the authoritative, version-specific steps.
79
+
80
+ ## Example workflow
81
+
82
+ 1. **Manual Trigger**
83
+ 2. **Periskop → Run Shopping Discovery**
84
+ - Prompt: `best desk chair under 60€`
85
+ - Response Format: `simple`
86
+ 3. Inspect the output: `result_id`, `request_id`, `items[]`, `caveats`,
87
+ `purchase_boundary`.
88
+
89
+ ## Pricing
90
+
91
+ Billed per successful request in EUR. Errors, rate limits, and hard no-match
92
+ responses are not billed. See <https://periskop.ai/developer> for current
93
+ pricing and any introductory credits.
94
+
95
+ ## Known limitations
96
+
97
+ - One operation (`Run Shopping Discovery`) in this first version. Additional
98
+ operations may be added later.
99
+ - A genuine no-result is a `200` with `answer_type: "no_match"`, not an error;
100
+ infrastructure failures surface in `errors[]` and/or as HTTP `4xx/5xx` with a
101
+ machine-readable `code`.
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PeriskopApi = void 0;
4
+ class PeriskopApi {
5
+ constructor() {
6
+ this.name = 'periskopApi';
7
+ this.displayName = 'Periskop API';
8
+ this.documentationUrl = 'https://periskop.ai/developer';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: { password: true },
15
+ default: '',
16
+ required: true,
17
+ description: 'Your Periskop API key (starts with dp_). Create one at https://periskop.ai/developer/keys. The node adds the "Bearer " prefix for you.',
18
+ },
19
+ {
20
+ displayName: 'Base URL',
21
+ name: 'baseUrl',
22
+ type: 'string',
23
+ default: 'https://mcp.periskop.ai',
24
+ description: 'Periskop API base URL. Change only for sandbox/self-hosted environments.',
25
+ },
26
+ ];
27
+ this.authenticate = {
28
+ type: 'generic',
29
+ properties: {
30
+ headers: {
31
+ Authorization: '=Bearer {{$credentials.apiKey}}',
32
+ },
33
+ },
34
+ };
35
+ this.test = {
36
+ request: {
37
+ baseURL: '={{$credentials.baseUrl}}',
38
+ url: '/v1/mcp/stores',
39
+ method: 'GET',
40
+ },
41
+ };
42
+ }
43
+ }
44
+ exports.PeriskopApi = PeriskopApi;
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Periskop = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ class Periskop {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: 'Periskop',
9
+ name: 'periskop',
10
+ icon: 'file:periskop.svg',
11
+ group: ['transform'],
12
+ version: 1,
13
+ subtitle: '={{ "Run Shopping Discovery" }}',
14
+ description: 'Product discovery for AI agents and workflows — turn a shopping prompt into structured product results. Discovery only: no checkout, payments, or stock reservation.',
15
+ defaults: {
16
+ name: 'Periskop',
17
+ },
18
+ inputs: ['main'],
19
+ outputs: ['main'],
20
+ credentials: [
21
+ {
22
+ name: 'periskopApi',
23
+ required: true,
24
+ },
25
+ ],
26
+ requestDefaults: {
27
+ baseURL: '={{$credentials.baseUrl}}',
28
+ headers: {
29
+ 'Content-Type': 'application/json',
30
+ },
31
+ },
32
+ properties: [
33
+ {
34
+ displayName: 'Operation',
35
+ name: 'operation',
36
+ type: 'options',
37
+ noDataExpression: true,
38
+ options: [
39
+ {
40
+ name: 'Run Shopping Discovery',
41
+ value: 'runShoppingDiscovery',
42
+ action: 'Run a shopping discovery from a prompt',
43
+ description: 'Turn a natural-language shopping prompt into structured product results',
44
+ },
45
+ ],
46
+ default: 'runShoppingDiscovery',
47
+ },
48
+ {
49
+ displayName: 'Prompt',
50
+ name: 'prompt',
51
+ type: 'string',
52
+ typeOptions: { rows: 2 },
53
+ default: '',
54
+ required: true,
55
+ placeholder: 'best desk chair under 60€',
56
+ description: 'Natural-language shopping intent. The only required field.',
57
+ displayOptions: {
58
+ show: { operation: ['runShoppingDiscovery'] },
59
+ },
60
+ },
61
+ {
62
+ displayName: 'Response Format',
63
+ name: 'responseFormat',
64
+ type: 'options',
65
+ options: [
66
+ { name: 'Simple', value: 'simple' },
67
+ { name: 'Full', value: 'full' },
68
+ ],
69
+ default: 'simple',
70
+ description: 'Simple returns a compact, easy-to-render shape (recommended). Full returns the complete contract.',
71
+ displayOptions: {
72
+ show: { operation: ['runShoppingDiscovery'] },
73
+ },
74
+ },
75
+ {
76
+ displayName: 'Additional Fields',
77
+ name: 'additionalFields',
78
+ type: 'collection',
79
+ placeholder: 'Add Field',
80
+ default: {},
81
+ displayOptions: {
82
+ show: { operation: ['runShoppingDiscovery'] },
83
+ },
84
+ options: [
85
+ {
86
+ displayName: 'Constraints (JSON)',
87
+ name: 'constraints',
88
+ type: 'json',
89
+ default: '',
90
+ description: 'Optional constraints, e.g. {"max_price": 200, "optimization": "best_quality"}',
91
+ },
92
+ {
93
+ displayName: 'Country',
94
+ name: 'country',
95
+ type: 'string',
96
+ default: '',
97
+ placeholder: 'PT',
98
+ description: 'ISO country code. Defaults to the account default.',
99
+ },
100
+ {
101
+ displayName: 'Currency',
102
+ name: 'currency',
103
+ type: 'string',
104
+ default: '',
105
+ placeholder: 'EUR',
106
+ description: 'ISO currency code. Defaults to the account default.',
107
+ },
108
+ {
109
+ displayName: 'External User ID',
110
+ name: 'externalUserId',
111
+ type: 'string',
112
+ default: '',
113
+ description: 'Your end-user identifier, for your own analytics',
114
+ },
115
+ {
116
+ displayName: 'Language',
117
+ name: 'language',
118
+ type: 'string',
119
+ default: '',
120
+ placeholder: 'en',
121
+ description: 'BCP 47 language tag',
122
+ },
123
+ {
124
+ displayName: 'Max Results',
125
+ name: 'maxResults',
126
+ type: 'number',
127
+ typeOptions: { minValue: 1, maxValue: 50 },
128
+ default: 3,
129
+ description: 'Maximum number of products to return (1-50)',
130
+ },
131
+ {
132
+ displayName: 'Mode',
133
+ name: 'mode',
134
+ type: 'options',
135
+ options: [
136
+ { name: 'Auto', value: 'auto' },
137
+ { name: 'Best', value: 'best' },
138
+ { name: 'Browse', value: 'browse' },
139
+ { name: 'Bundle', value: 'bundle' },
140
+ { name: 'Recommend', value: 'recommend' },
141
+ ],
142
+ default: 'auto',
143
+ description: 'Discovery mode. Leave on Auto to let Periskop infer it.',
144
+ },
145
+ {
146
+ displayName: 'Previous Result ID',
147
+ name: 'previousResultId',
148
+ type: 'string',
149
+ default: '',
150
+ description: 'Optional prior result ID for follow-up discovery',
151
+ },
152
+ {
153
+ displayName: 'Store',
154
+ name: 'store',
155
+ type: 'string',
156
+ default: '',
157
+ description: 'Store ID or natural store hint to scope the search',
158
+ },
159
+ ],
160
+ },
161
+ ],
162
+ };
163
+ }
164
+ async execute() {
165
+ const items = this.getInputData();
166
+ const returnData = [];
167
+ for (let i = 0; i < items.length; i++) {
168
+ try {
169
+ const operation = this.getNodeParameter('operation', i);
170
+ if (operation !== 'runShoppingDiscovery') {
171
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
172
+ itemIndex: i,
173
+ });
174
+ }
175
+ const prompt = this.getNodeParameter('prompt', i).trim();
176
+ if (!prompt) {
177
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Prompt is required.', { itemIndex: i });
178
+ }
179
+ const responseFormat = this.getNodeParameter('responseFormat', i);
180
+ const additional = this.getNodeParameter('additionalFields', i, {});
181
+ const body = {
182
+ prompt,
183
+ response_format: responseFormat,
184
+ };
185
+ if (additional.country)
186
+ body.country = additional.country;
187
+ if (additional.currency)
188
+ body.currency = additional.currency;
189
+ if (additional.language)
190
+ body.language = additional.language;
191
+ if (additional.mode && additional.mode !== 'auto')
192
+ body.mode = additional.mode;
193
+ if (additional.maxResults)
194
+ body.max_results = additional.maxResults;
195
+ if (additional.store)
196
+ body.store = additional.store;
197
+ if (additional.externalUserId)
198
+ body.external_user_id = additional.externalUserId;
199
+ if (additional.previousResultId)
200
+ body.previous_result_id = additional.previousResultId;
201
+ if (additional.constraints) {
202
+ let constraints = additional.constraints;
203
+ if (typeof constraints === 'string' && constraints.trim() !== '') {
204
+ try {
205
+ constraints = JSON.parse(constraints);
206
+ }
207
+ catch (error) {
208
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Constraints must be valid JSON.', { itemIndex: i });
209
+ }
210
+ }
211
+ if (constraints && typeof constraints === 'object') {
212
+ body.constraints = constraints;
213
+ }
214
+ }
215
+ const options = {
216
+ method: 'POST',
217
+ url: '/v1/mcp/shopping/discover',
218
+ body,
219
+ json: true,
220
+ };
221
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'periskopApi', options);
222
+ returnData.push({
223
+ json: response,
224
+ pairedItem: { item: i },
225
+ });
226
+ }
227
+ catch (error) {
228
+ if (this.continueOnFail()) {
229
+ returnData.push({
230
+ json: { error: error.message },
231
+ pairedItem: { item: i },
232
+ });
233
+ continue;
234
+ }
235
+ throw error;
236
+ }
237
+ }
238
+ return [returnData];
239
+ }
240
+ }
241
+ exports.Periskop = Periskop;
@@ -0,0 +1,18 @@
1
+ {
2
+ "node": "n8n-nodes-periskop.periskop",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["AI", "Marketing", "Miscellaneous"],
6
+ "resources": {
7
+ "credentialDocumentation": [
8
+ {
9
+ "url": "https://periskop.ai/developer"
10
+ }
11
+ ],
12
+ "primaryDocumentation": [
13
+ {
14
+ "url": "https://periskop.ai/developer/docs"
15
+ }
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2
+ <rect width="64" height="64" rx="14" fill="#0B0B0F"/>
3
+ <circle cx="29" cy="29" r="14" fill="none" stroke="#6EE7F9" stroke-width="4"/>
4
+ <line x1="39.5" y1="39.5" x2="52" y2="52" stroke="#6EE7F9" stroke-width="5" stroke-linecap="round"/>
5
+ <circle cx="29" cy="29" r="5" fill="#6EE7F9"/>
6
+ </svg>
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // n8n loads nodes and credentials from the paths declared in package.json's
2
+ // "n8n" field (dist/...). This file exists only so the package has a valid
3
+ // "main" entry; it intentionally exports nothing.
4
+ module.exports = {};
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "n8n-nodes-periskop",
3
+ "version": "0.1.0",
4
+ "description": "n8n community node for Periskop — product discovery for AI agents and workflows.",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "periskop",
8
+ "product-discovery",
9
+ "shopping",
10
+ "shopping-discovery",
11
+ "ai-agents",
12
+ "workflow-automation"
13
+ ],
14
+ "license": "MIT",
15
+ "homepage": "https://github.com/Periskop-ai/n8n-nodes-periskop#readme",
16
+ "author": {
17
+ "name": "Periskop",
18
+ "url": "https://periskop.ai"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Periskop-ai/n8n-nodes-periskop.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/Periskop-ai/n8n-nodes-periskop/issues"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.10"
29
+ },
30
+ "main": "index.js",
31
+ "scripts": {
32
+ "build": "tsc && gulp build:icons",
33
+ "dev": "tsc --watch",
34
+ "format": "prettier nodes credentials --write",
35
+ "lint": "eslint nodes credentials",
36
+ "lintfix": "eslint nodes credentials --fix",
37
+ "prepublishOnly": "npm run build && npm run lint -- --quiet"
38
+ },
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "n8n": {
43
+ "n8nNodesApiVersion": 1,
44
+ "credentials": [
45
+ "dist/credentials/PeriskopApi.credentials.js"
46
+ ],
47
+ "nodes": [
48
+ "dist/nodes/Periskop/Periskop.node.js"
49
+ ]
50
+ },
51
+ "devDependencies": {
52
+ "@typescript-eslint/parser": "^7.18.0",
53
+ "eslint": "^8.57.0",
54
+ "eslint-plugin-n8n-nodes-base": "^1.16.3",
55
+ "gulp": "^5.0.0",
56
+ "n8n-workflow": "*",
57
+ "prettier": "^3.3.3",
58
+ "typescript": "^5.5.4"
59
+ },
60
+ "peerDependencies": {
61
+ "n8n-workflow": "*"
62
+ }
63
+ }