n8n-nodes-apexapi 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/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # n8n-nodes-apexapi
2
+
3
+ An [n8n](https://n8n.io) community node for [ApexApi](https://apexapi.dev) — call 14 AI providers
4
+ (OpenAI, Anthropic, Google, Mistral, DeepSeek, Groq, and more) through one OpenAI-compatible API
5
+ key. Switch any model from a single dropdown.
6
+
7
+ ## Installation
8
+
9
+ **n8n (self-hosted):** Settings → Community Nodes → Install → `n8n-nodes-apexapi`.
10
+
11
+ (n8n Cloud support requires verification — in progress.)
12
+
13
+ ## Credentials
14
+
15
+ Create an ApexApi key at https://apexapi.dev/keys (starts with `ak-`). Add an **ApexApi account**
16
+ credential in n8n and paste the key. The connection is tested against `GET /v1/models`.
17
+
18
+ ## Operations
19
+
20
+ - **Chat → Message a Model** — send a prompt/messages to any chat model.
21
+ - **Image → Generate Image** — generate images.
22
+ - **Video → Generate Video** — generate a video (waits for completion by default) and **Get Result**
23
+ to poll a job by id.
24
+
25
+ Each resource's **Model** dropdown is populated live from your account's available models.
26
+
27
+ ## Templates
28
+
29
+ See the `templates/` folder for ready-to-import workflows (news → caption → post, prompt → image →
30
+ post, and an AI Agent using ApexApi as its model).
31
+
32
+ ## License
33
+
34
+ MIT
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApexApiApi = void 0;
4
+ const constants_1 = require("../shared/constants");
5
+ class ApexApiApi {
6
+ constructor() {
7
+ this.name = 'apexApiApi';
8
+ this.displayName = 'ApexApi API';
9
+ this.documentationUrl = 'https://apexapi.dev/keys';
10
+ this.properties = [
11
+ {
12
+ displayName: 'API Key',
13
+ name: 'apiKey',
14
+ type: 'string',
15
+ typeOptions: { password: true },
16
+ default: '',
17
+ required: true,
18
+ description: 'Your ApexApi key (starts with "ak-"). Create one at apexapi.dev/keys.',
19
+ },
20
+ {
21
+ displayName: 'Base URL',
22
+ name: 'baseUrl',
23
+ type: 'string',
24
+ default: constants_1.DEFAULT_BASE_URL,
25
+ description: 'ApexApi API base URL. Change only for self-hosted or staging.',
26
+ },
27
+ ];
28
+ this.authenticate = {
29
+ type: 'generic',
30
+ properties: {
31
+ headers: {
32
+ Authorization: '=Bearer {{$credentials.apiKey}}',
33
+ },
34
+ },
35
+ };
36
+ this.test = {
37
+ request: {
38
+ baseURL: '={{$credentials.baseUrl}}',
39
+ url: '/models',
40
+ },
41
+ };
42
+ }
43
+ }
44
+ exports.ApexApiApi = ApexApiApi;
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApexApi = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const chat_1 = require("../../shared/chat");
6
+ const image_1 = require("../../shared/image");
7
+ const video_1 = require("../../shared/video");
8
+ const transport_1 = require("../../shared/transport");
9
+ const loadOptions_1 = require("../../shared/loadOptions");
10
+ class ApexApi {
11
+ constructor() {
12
+ this.description = {
13
+ displayName: 'ApexApi',
14
+ name: 'apexApi',
15
+ icon: 'file:apexapi.svg',
16
+ group: ['transform'],
17
+ version: 1,
18
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
19
+ description: 'Call 14 AI providers through one OpenAI-compatible ApexApi key',
20
+ defaults: { name: 'ApexApi' },
21
+ inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
22
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
23
+ credentials: [{ name: 'apexApiApi', required: true }],
24
+ properties: [
25
+ {
26
+ displayName: 'Resource',
27
+ name: 'resource',
28
+ type: 'options',
29
+ noDataExpression: true,
30
+ options: [
31
+ { name: 'Chat', value: 'chat' },
32
+ { name: 'Image', value: 'image' },
33
+ { name: 'Video', value: 'video' },
34
+ ],
35
+ default: 'chat',
36
+ },
37
+ // ---- Chat ----
38
+ {
39
+ displayName: 'Operation',
40
+ name: 'operation',
41
+ type: 'options',
42
+ noDataExpression: true,
43
+ displayOptions: { show: { resource: ['chat'] } },
44
+ options: [{ name: 'Message a Model', value: 'message', action: 'Message a model' }],
45
+ default: 'message',
46
+ },
47
+ {
48
+ displayName: 'Model Name or ID',
49
+ name: 'model',
50
+ type: 'options',
51
+ description: 'Choose from the list of ApexApi chat models. Choose from the list, or specify an ID using an expression.',
52
+ typeOptions: { loadOptionsMethod: 'getChatModels' },
53
+ displayOptions: { show: { resource: ['chat'] } },
54
+ default: '',
55
+ required: true,
56
+ },
57
+ {
58
+ displayName: 'Prompt',
59
+ name: 'prompt',
60
+ type: 'string',
61
+ typeOptions: { rows: 4 },
62
+ displayOptions: { show: { resource: ['chat'] } },
63
+ default: '',
64
+ description: 'The user message to send to the model',
65
+ },
66
+ {
67
+ displayName: 'Options',
68
+ name: 'options',
69
+ type: 'collection',
70
+ placeholder: 'Add Option',
71
+ displayOptions: { show: { resource: ['chat'] } },
72
+ default: {},
73
+ options: [
74
+ { displayName: 'System Prompt', name: 'systemPrompt', type: 'string', default: '' },
75
+ {
76
+ displayName: 'Temperature',
77
+ name: 'temperature',
78
+ type: 'number',
79
+ typeOptions: { minValue: 0, maxValue: 2, numberPrecision: 2 },
80
+ default: 0.7,
81
+ },
82
+ { displayName: 'Max Tokens', name: 'maxTokens', type: 'number', default: 1024 },
83
+ ],
84
+ },
85
+ // ---- Image ----
86
+ {
87
+ displayName: 'Operation',
88
+ name: 'operation',
89
+ type: 'options',
90
+ noDataExpression: true,
91
+ displayOptions: { show: { resource: ['image'] } },
92
+ options: [{ name: 'Generate Image', value: 'generate', action: 'Generate an image' }],
93
+ default: 'generate',
94
+ },
95
+ {
96
+ displayName: 'Model Name or ID',
97
+ name: 'model',
98
+ type: 'options',
99
+ description: 'Choose from the list of ApexApi image models. Choose from the list, or specify an ID using an expression.',
100
+ typeOptions: { loadOptionsMethod: 'getImageModels' },
101
+ displayOptions: { show: { resource: ['image'] } },
102
+ default: '',
103
+ required: true,
104
+ },
105
+ {
106
+ displayName: 'Prompt',
107
+ name: 'prompt',
108
+ type: 'string',
109
+ typeOptions: { rows: 3 },
110
+ displayOptions: { show: { resource: ['image'] } },
111
+ default: '',
112
+ required: true,
113
+ },
114
+ {
115
+ displayName: 'Options',
116
+ name: 'options',
117
+ type: 'collection',
118
+ placeholder: 'Add Option',
119
+ displayOptions: { show: { resource: ['image'] } },
120
+ default: {},
121
+ options: [
122
+ { displayName: 'Size', name: 'size', type: 'string', default: '1024x1024' },
123
+ { displayName: 'Number of Images', name: 'n', type: 'number', default: 1 },
124
+ ],
125
+ },
126
+ // ---- Video ----
127
+ {
128
+ displayName: 'Operation',
129
+ name: 'operation',
130
+ type: 'options',
131
+ noDataExpression: true,
132
+ displayOptions: { show: { resource: ['video'] } },
133
+ options: [
134
+ { name: 'Generate Video', value: 'generate', action: 'Generate a video' },
135
+ { name: 'Get Result', value: 'getResult', action: 'Get a video job result' },
136
+ ],
137
+ default: 'generate',
138
+ },
139
+ {
140
+ displayName: 'Model Name or ID',
141
+ name: 'model',
142
+ type: 'options',
143
+ description: 'Choose from the list of ApexApi video models. Choose from the list, or specify an ID using an expression.',
144
+ typeOptions: { loadOptionsMethod: 'getVideoModels' },
145
+ displayOptions: { show: { resource: ['video'], operation: ['generate'] } },
146
+ default: '',
147
+ required: true,
148
+ },
149
+ {
150
+ displayName: 'Prompt',
151
+ name: 'prompt',
152
+ type: 'string',
153
+ typeOptions: { rows: 3 },
154
+ displayOptions: { show: { resource: ['video'], operation: ['generate'] } },
155
+ default: '',
156
+ required: true,
157
+ },
158
+ {
159
+ displayName: 'Job ID',
160
+ name: 'jobId',
161
+ type: 'string',
162
+ displayOptions: { show: { resource: ['video'], operation: ['getResult'] } },
163
+ default: '',
164
+ required: true,
165
+ description: 'The id returned by a previous Generate Video call',
166
+ },
167
+ {
168
+ displayName: 'Options',
169
+ name: 'options',
170
+ type: 'collection',
171
+ placeholder: 'Add Option',
172
+ displayOptions: { show: { resource: ['video'], operation: ['generate'] } },
173
+ default: {},
174
+ options: [
175
+ { displayName: 'Wait for Completion', name: 'waitForCompletion', type: 'boolean', default: true },
176
+ { displayName: 'Max Poll Seconds', name: 'maxPollSeconds', type: 'number', default: 300 },
177
+ { displayName: 'Poll Interval Seconds', name: 'pollIntervalSeconds', type: 'number', default: 5 },
178
+ ],
179
+ },
180
+ ],
181
+ };
182
+ this.methods = {
183
+ loadOptions: {
184
+ async getChatModels() {
185
+ return (0, loadOptions_1.loadModels)(this, 'text');
186
+ },
187
+ async getImageModels() {
188
+ return (0, loadOptions_1.loadModels)(this, 'image');
189
+ },
190
+ async getVideoModels() {
191
+ return (0, loadOptions_1.loadModels)(this, 'video');
192
+ },
193
+ },
194
+ };
195
+ }
196
+ async execute() {
197
+ const items = this.getInputData();
198
+ const returnData = [];
199
+ const resource = this.getNodeParameter('resource', 0);
200
+ const operation = this.getNodeParameter('operation', 0);
201
+ const ctx = this;
202
+ for (let i = 0; i < items.length; i++) {
203
+ try {
204
+ let json;
205
+ if (resource === 'chat') {
206
+ const options = this.getNodeParameter('options', i, {});
207
+ const body = (0, chat_1.buildChatBody)({
208
+ model: this.getNodeParameter('model', i),
209
+ prompt: this.getNodeParameter('prompt', i, ''),
210
+ systemPrompt: options.systemPrompt,
211
+ temperature: options.temperature,
212
+ maxTokens: options.maxTokens,
213
+ });
214
+ const resp = await (0, transport_1.apexApiRequest)(ctx, 'POST', '/chat/completions', body);
215
+ const parsed = (0, chat_1.parseChatResponse)(resp);
216
+ json = { content: parsed.content, response: parsed.raw };
217
+ }
218
+ else if (resource === 'image') {
219
+ const options = this.getNodeParameter('options', i, {});
220
+ const body = (0, image_1.buildImageBody)({
221
+ model: this.getNodeParameter('model', i),
222
+ prompt: this.getNodeParameter('prompt', i),
223
+ size: options.size,
224
+ n: options.n,
225
+ });
226
+ const resp = await (0, transport_1.apexApiRequest)(ctx, 'POST', '/images/generations', body);
227
+ const parsed = (0, image_1.parseImageResponse)(resp);
228
+ json = { urls: parsed.urls, response: parsed.raw };
229
+ }
230
+ else {
231
+ // video
232
+ if (operation === 'getResult') {
233
+ const jobId = this.getNodeParameter('jobId', i);
234
+ json = (await (0, transport_1.apexApiRequest)(ctx, 'GET', `/videos/generations/${jobId}`));
235
+ }
236
+ else {
237
+ const options = this.getNodeParameter('options', i, {});
238
+ const body = (0, video_1.buildVideoBody)({
239
+ model: this.getNodeParameter('model', i),
240
+ prompt: this.getNodeParameter('prompt', i),
241
+ });
242
+ json = (await (0, transport_1.generateVideo)(ctx, body, {
243
+ waitForCompletion: options.waitForCompletion ?? true,
244
+ maxPollSeconds: options.maxPollSeconds ?? 300,
245
+ pollIntervalSeconds: options.pollIntervalSeconds ?? 5,
246
+ }));
247
+ }
248
+ }
249
+ returnData.push({ json, pairedItem: { item: i } });
250
+ }
251
+ catch (error) {
252
+ if (this.continueOnFail()) {
253
+ returnData.push({ json: { error: error instanceof Error ? error.message : String(error) }, pairedItem: { item: i } });
254
+ continue;
255
+ }
256
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
257
+ }
258
+ }
259
+ return [returnData];
260
+ }
261
+ }
262
+ exports.ApexApi = ApexApi;
@@ -0,0 +1,5 @@
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="12" fill="#0B0B0F"/>
3
+ <path d="M20 44 32 20 44 44" fill="none" stroke="#5B8CFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
4
+ <line x1="25" y1="36" x2="39" y2="36" stroke="#5B8CFF" stroke-width="4" stroke-linecap="round"/>
5
+ </svg>
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildChatBody = buildChatBody;
4
+ exports.parseChatResponse = parseChatResponse;
5
+ function buildChatBody(params) {
6
+ const messages = [];
7
+ if (params.systemPrompt)
8
+ messages.push({ role: 'system', content: params.systemPrompt });
9
+ if (params.messages && params.messages.length > 0) {
10
+ messages.push(...params.messages);
11
+ }
12
+ else if (params.prompt) {
13
+ messages.push({ role: 'user', content: params.prompt });
14
+ }
15
+ const body = { model: params.model, messages, stream: false };
16
+ if (params.temperature !== undefined)
17
+ body.temperature = params.temperature;
18
+ if (params.maxTokens !== undefined)
19
+ body.max_tokens = params.maxTokens;
20
+ return body;
21
+ }
22
+ function parseChatResponse(resp) {
23
+ const r = resp;
24
+ const content = r?.choices?.[0]?.message?.content ?? '';
25
+ return { content, raw: resp };
26
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CREDENTIAL_NAME = exports.DEFAULT_BASE_URL = void 0;
4
+ exports.DEFAULT_BASE_URL = 'https://api.apexapi.dev/v1';
5
+ exports.CREDENTIAL_NAME = 'apexApiApi';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildImageBody = buildImageBody;
4
+ exports.parseImageResponse = parseImageResponse;
5
+ function buildImageBody(params) {
6
+ const body = { model: params.model, prompt: params.prompt };
7
+ if (params.size)
8
+ body.size = params.size;
9
+ if (params.n !== undefined)
10
+ body.n = params.n;
11
+ return body;
12
+ }
13
+ function parseImageResponse(resp) {
14
+ const r = resp;
15
+ const data = Array.isArray(r?.data) ? r.data : [];
16
+ const urls = data
17
+ .map((d) => d?.url)
18
+ .filter((u) => typeof u === 'string');
19
+ return { urls, raw: resp };
20
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadModels = loadModels;
4
+ const transport_1 = require("./transport");
5
+ const models_1 = require("./models");
6
+ async function loadModels(ctx, modelType) {
7
+ const resp = await (0, transport_1.apexApiRequest)(ctx, 'GET', '/models');
8
+ const data = Array.isArray(resp?.data) ? resp.data : [];
9
+ return (0, models_1.mapModelsToOptions)(data, modelType);
10
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapModelsToOptions = mapModelsToOptions;
4
+ /**
5
+ * Map the `/v1/models` `data` array to n8n dropdown options. When `modelType`
6
+ * is given, keep only models of that type (text/image/video); if that leaves
7
+ * nothing, fall back to all models rather than showing an empty dropdown.
8
+ */
9
+ function mapModelsToOptions(models, modelType) {
10
+ const filtered = modelType ? models.filter((m) => m.type === modelType) : models;
11
+ const list = filtered.length > 0 ? filtered : models;
12
+ return list
13
+ .map((m) => ({
14
+ name: m.display_name ? `${m.display_name} (${m.id})` : m.id,
15
+ value: m.id,
16
+ }))
17
+ .sort((a, b) => a.name.localeCompare(b.name));
18
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apexApiRequest = apexApiRequest;
4
+ exports.generateVideo = generateVideo;
5
+ const constants_1 = require("./constants");
6
+ const video_1 = require("./video");
7
+ async function apexApiRequest(ctx, method, path, body) {
8
+ const creds = await ctx.getCredentials(constants_1.CREDENTIAL_NAME);
9
+ const baseUrl = String(creds.baseUrl || constants_1.DEFAULT_BASE_URL).replace(/\/+$/, '');
10
+ const options = { method, url: `${baseUrl}${path}`, json: true };
11
+ if (body !== undefined)
12
+ options.body = body;
13
+ return ctx.helpers.httpRequestWithAuthentication.call(ctx, constants_1.CREDENTIAL_NAME, options);
14
+ }
15
+ const defaultDeps = {
16
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
17
+ now: () => Date.now(),
18
+ };
19
+ async function generateVideo(ctx, body, opts, deps = defaultDeps) {
20
+ const submit = await apexApiRequest(ctx, 'POST', '/videos/generations', body);
21
+ const jobId = submit?.id;
22
+ if (!opts.waitForCompletion || !jobId)
23
+ return submit;
24
+ const deadline = deps.now() + opts.maxPollSeconds * 1000;
25
+ let last = submit;
26
+ while (deps.now() < deadline) {
27
+ await deps.sleep(opts.pollIntervalSeconds * 1000);
28
+ last = await apexApiRequest(ctx, 'GET', `/videos/generations/${jobId}`);
29
+ if (typeof last?.status === 'string' && (0, video_1.isTerminalVideoStatus)(last.status)) {
30
+ return last;
31
+ }
32
+ }
33
+ return last;
34
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildVideoBody = buildVideoBody;
4
+ exports.isTerminalVideoStatus = isTerminalVideoStatus;
5
+ function buildVideoBody(params) {
6
+ return { model: params.model, prompt: params.prompt };
7
+ }
8
+ const TERMINAL_STATUSES = new Set(['completed', 'failed']);
9
+ function isTerminalVideoStatus(status) {
10
+ return TERMINAL_STATUSES.has(status);
11
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "n8n-nodes-apexapi",
3
+ "version": "0.1.0",
4
+ "description": "n8n community node for ApexApi — call 14 AI providers (OpenAI, Anthropic, Google, Mistral, and more) through one OpenAI-compatible API.",
5
+ "keywords": ["n8n-community-node-package", "apexapi", "ai", "llm", "openai", "anthropic", "gemini"],
6
+ "license": "MIT",
7
+ "homepage": "https://apexapi.dev",
8
+ "author": { "name": "ApexApi", "email": "support@apexapi.dev" },
9
+ "repository": { "type": "git", "url": "git+https://github.com/apexapi/apexapi.git" },
10
+ "main": "dist/index.js",
11
+ "scripts": {
12
+ "build": "tsc && node scripts/build-icons.mjs",
13
+ "typecheck": "tsc --noEmit",
14
+ "test": "vitest run",
15
+ "prepublishOnly": "pnpm build"
16
+ },
17
+ "files": ["dist"],
18
+ "n8n": {
19
+ "n8nNodesApiVersion": 1,
20
+ "credentials": ["dist/credentials/ApexApiApi.credentials.js"],
21
+ "nodes": ["dist/nodes/ApexApi/ApexApi.node.js"]
22
+ },
23
+ "peerDependencies": { "n8n-workflow": "*" },
24
+ "devDependencies": {
25
+ "n8n-workflow": "^1.82.0",
26
+ "typescript": "^5.9.3",
27
+ "vitest": "^3.2.4"
28
+ }
29
+ }