@nordsym/apiclaw 1.5.10 → 1.5.11

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 (48) hide show
  1. package/APILAYER_STATUS_2026-03-24.md +38 -0
  2. package/TERMINOLOGY-AUDIT.md +99 -0
  3. package/TERMINOLOGY-FIXED.md +74 -0
  4. package/VIDEO-DEMO-GUIDE.md +82 -0
  5. package/direct-test.mjs +51 -0
  6. package/dist/cli/commands/mcp-install.d.ts.map +1 -1
  7. package/dist/cli/commands/mcp-install.js +55 -40
  8. package/dist/cli/commands/mcp-install.js.map +1 -1
  9. package/dist/credentials.d.ts.map +1 -1
  10. package/dist/credentials.js +128 -0
  11. package/dist/credentials.js.map +1 -1
  12. package/dist/discovery.d.ts.map +1 -1
  13. package/dist/discovery.js +191 -82
  14. package/dist/discovery.js.map +1 -1
  15. package/dist/execute.d.ts.map +1 -1
  16. package/dist/execute.js +53 -24
  17. package/dist/execute.js.map +1 -1
  18. package/email-templates/README.md +104 -0
  19. package/email-templates/partnership-template.html +116 -0
  20. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  21. package/landing/src/app/layout.tsx +2 -2
  22. package/landing/src/app/login/page.tsx +1 -1
  23. package/landing/src/app/page.tsx +39 -18
  24. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +1 -1
  25. package/landing/src/app/providers/dashboard/login/page.tsx +1 -1
  26. package/landing/src/app/providers/dashboard/page.tsx +1 -1
  27. package/landing/src/app/providers/layout.tsx +1 -1
  28. package/landing/src/components/HeroTabs.tsx +2 -2
  29. package/landing/src/components/VideoDemo.tsx +94 -0
  30. package/landing/src/components/{ProviderDashboard.tsx → Workspace.tsx} +2 -2
  31. package/landing/src/lib/mock-data.ts +1 -1
  32. package/landing/src/lib/stats.json +1 -1
  33. package/package.json +1 -1
  34. package/src/cli/commands/mcp-install.ts +14 -4
  35. package/src/credentials.ts +136 -0
  36. package/src/discovery.ts +191 -82
  37. package/src/execute.ts +49 -22
  38. package/test-actual-handlers.ts +92 -0
  39. package/test-apilayer-all-14.ts +249 -0
  40. package/test-apilayer-fixed.ts +248 -0
  41. package/test-direct-endpoints.ts +174 -0
  42. package/test-exact-endpoints.ts +144 -0
  43. package/test-final.ts +83 -0
  44. package/test-full-routing.ts +100 -0
  45. package/test-handlers-correct.ts +217 -0
  46. package/test-numverify-key.ts +41 -0
  47. package/test-via-handlers.ts +92 -0
  48. package/test-worldnews.mjs +26 -0
@@ -183,6 +183,102 @@ const providers: Record<string, ProviderCredential> = {
183
183
  return { type: 'api_key', api_key: keys.APILAYER_EXCHANGERATE_KEY || '', ...keys } as any;
184
184
  },
185
185
  },
186
+
187
+ groq: {
188
+ type: 'bearer',
189
+ get(): APICredentials | null {
190
+ const env = loadEnvFile('groq.env');
191
+ const key = env.GROQ_API_KEY || process.env.GROQ_API_KEY;
192
+ if (key) {
193
+ return { type: 'bearer', api_key: key };
194
+ }
195
+ return null;
196
+ },
197
+ },
198
+
199
+ deepgram: {
200
+ type: 'bearer',
201
+ get(): APICredentials | null {
202
+ const env = loadEnvFile('deepgram.env');
203
+ const key = env.DEEPGRAM_API_KEY || process.env.DEEPGRAM_API_KEY;
204
+ if (key) {
205
+ return { type: 'bearer', api_key: key };
206
+ }
207
+ return null;
208
+ },
209
+ },
210
+
211
+ mistral: {
212
+ type: 'api_key',
213
+ get(): APICredentials | null {
214
+ const env = loadEnvFile('mistral.env');
215
+ const key = env.MISTRAL_API_KEY || process.env.MISTRAL_API_KEY;
216
+ if (key) {
217
+ return { type: 'api_key', api_key: key };
218
+ }
219
+ return null;
220
+ },
221
+ },
222
+
223
+ cohere: {
224
+ type: 'api_key',
225
+ get(): APICredentials | null {
226
+ const env = loadEnvFile('cohere.env');
227
+ const key = env.COHERE_API_KEY || process.env.COHERE_API_KEY;
228
+ if (key) {
229
+ return { type: 'api_key', api_key: key };
230
+ }
231
+ return null;
232
+ },
233
+ },
234
+
235
+ serper: {
236
+ type: 'api_key',
237
+ get(): APICredentials | null {
238
+ const env = loadEnvFile('serpapi.env');
239
+ const key = env.SERPAPI_API_KEY || process.env.SERPER_API_KEY || process.env.SERPAPI_API_KEY;
240
+ if (key) {
241
+ return { type: 'api_key', api_key: key };
242
+ }
243
+ return null;
244
+ },
245
+ },
246
+
247
+ stability: {
248
+ type: 'api_key',
249
+ get(): APICredentials | null {
250
+ const env = loadEnvFile('stability.env');
251
+ const key = env.STABILITY_API_KEY || process.env.STABILITY_API_KEY;
252
+ if (key) {
253
+ return { type: 'api_key', api_key: key };
254
+ }
255
+ return null;
256
+ },
257
+ },
258
+
259
+ together: {
260
+ type: 'bearer',
261
+ get(): APICredentials | null {
262
+ const env = loadEnvFile('together.env');
263
+ const key = env.TOGETHER_API_KEY || process.env.TOGETHER_API_KEY;
264
+ if (key) {
265
+ return { type: 'bearer', api_key: key };
266
+ }
267
+ return null;
268
+ },
269
+ },
270
+
271
+ assemblyai: {
272
+ type: 'api_key',
273
+ get(): APICredentials | null {
274
+ const env = loadEnvFile('assemblyai.env');
275
+ const key = env.ASSEMBLYAI_API_KEY || process.env.ASSEMBLYAI_API_KEY;
276
+ if (key) {
277
+ return { type: 'api_key', api_key: key };
278
+ }
279
+ return null;
280
+ },
281
+ },
186
282
  };
187
283
 
188
284
  /**
@@ -231,10 +327,50 @@ export function hasRealCredentials(providerId: string): boolean {
231
327
  const env = loadEnvFile('e2b.env');
232
328
  return !!(env.E2B_API_KEY || process.env.E2B_API_KEY);
233
329
  }
330
+ if (providerId === 'firecrawl') {
331
+ const env = loadEnvFile('firecrawl.env');
332
+ return !!(env.FIRECRAWL_API_KEY || process.env.FIRECRAWL_API_KEY);
333
+ }
334
+ if (providerId === 'github') {
335
+ const env = loadEnvFile('github.env');
336
+ return !!(env.GITHUB_TOKEN || process.env.GITHUB_TOKEN);
337
+ }
234
338
  if (providerId === 'apilayer') {
235
339
  const env = loadEnvFile('apilayer.env');
236
340
  return !!(env.APILAYER_EXCHANGERATE_KEY || process.env.APILAYER_EXCHANGERATE_KEY);
237
341
  }
342
+ if (providerId === 'groq') {
343
+ const env = loadEnvFile('groq.env');
344
+ return !!(env.GROQ_API_KEY || process.env.GROQ_API_KEY);
345
+ }
346
+ if (providerId === 'deepgram') {
347
+ const env = loadEnvFile('deepgram.env');
348
+ return !!(env.DEEPGRAM_API_KEY || process.env.DEEPGRAM_API_KEY);
349
+ }
350
+ if (providerId === 'mistral') {
351
+ const env = loadEnvFile('mistral.env');
352
+ return !!(env.MISTRAL_API_KEY || process.env.MISTRAL_API_KEY);
353
+ }
354
+ if (providerId === 'cohere') {
355
+ const env = loadEnvFile('cohere.env');
356
+ return !!(env.COHERE_API_KEY || process.env.COHERE_API_KEY);
357
+ }
358
+ if (providerId === 'serper') {
359
+ const env = loadEnvFile('serpapi.env');
360
+ return !!(env.SERPAPI_API_KEY || process.env.SERPER_API_KEY || process.env.SERPAPI_API_KEY);
361
+ }
362
+ if (providerId === 'stability') {
363
+ const env = loadEnvFile('stability.env');
364
+ return !!(env.STABILITY_API_KEY || process.env.STABILITY_API_KEY);
365
+ }
366
+ if (providerId === 'together') {
367
+ const env = loadEnvFile('together.env');
368
+ return !!(env.TOGETHER_API_KEY || process.env.TOGETHER_API_KEY);
369
+ }
370
+ if (providerId === 'assemblyai') {
371
+ const env = loadEnvFile('assemblyai.env');
372
+ return !!(env.ASSEMBLYAI_API_KEY || process.env.ASSEMBLYAI_API_KEY);
373
+ }
238
374
  return false;
239
375
  }
240
376
 
package/src/discovery.ts CHANGED
@@ -17,73 +17,13 @@ const apisData = JSON.parse(
17
17
  const apis: APIProvider[] = apisData.apis;
18
18
 
19
19
  // Direct Call provider specs (hardcoded handlers with params)
20
+ // Ordered: AI-first (models, LLM routing, audio), then infrastructure (code, web, search, email, SMS)
20
21
  const DIRECT_CALL_SPECS: Record<string, {
21
22
  description: string;
22
23
  auth: string;
23
24
  docs: string;
24
25
  actions: Record<string, { params: { name: string; required: boolean; desc: string }[]; desc: string }>;
25
26
  }> = {
26
- '46elks': {
27
- description: 'Swedish SMS and voice API',
28
- auth: 'basic',
29
- docs: 'https://46elks.com/docs',
30
- actions: {
31
- send_sms: {
32
- desc: 'Send SMS message',
33
- params: [
34
- { name: 'to', required: true, desc: 'Phone number (+46...)' },
35
- { name: 'message', required: true, desc: 'SMS text (max 160 chars for 1 segment)' },
36
- { name: 'from', required: false, desc: 'Sender ID (default: APIClaw)' },
37
- ],
38
- },
39
- },
40
- },
41
- twilio: {
42
- description: 'Global SMS and voice API',
43
- auth: 'basic',
44
- docs: 'https://www.twilio.com/docs',
45
- actions: {
46
- send_sms: {
47
- desc: 'Send SMS message',
48
- params: [
49
- { name: 'to', required: true, desc: 'Phone number (E.164 format)' },
50
- { name: 'message', required: true, desc: 'SMS text' },
51
- { name: 'from', required: false, desc: 'Sender phone number' },
52
- ],
53
- },
54
- },
55
- },
56
- brave_search: {
57
- description: 'Web search API',
58
- auth: 'api_key',
59
- docs: 'https://api.search.brave.com/docs',
60
- actions: {
61
- search: {
62
- desc: 'Search the web',
63
- params: [
64
- { name: 'query', required: true, desc: 'Search query' },
65
- { name: 'count', required: false, desc: 'Number of results (default: 5)' },
66
- ],
67
- },
68
- },
69
- },
70
- resend: {
71
- description: 'Email API',
72
- auth: 'bearer',
73
- docs: 'https://resend.com/docs',
74
- actions: {
75
- send_email: {
76
- desc: 'Send email',
77
- params: [
78
- { name: 'to', required: true, desc: 'Recipient email' },
79
- { name: 'subject', required: true, desc: 'Email subject' },
80
- { name: 'html', required: false, desc: 'HTML body' },
81
- { name: 'text', required: false, desc: 'Plain text body' },
82
- { name: 'from', required: false, desc: 'Sender (default: noreply@apiclaw.nordsym.com)' },
83
- ],
84
- },
85
- },
86
- },
87
27
  openrouter: {
88
28
  description: 'LLM routing (100+ models)',
89
29
  auth: 'bearer',
@@ -99,6 +39,24 @@ const DIRECT_CALL_SPECS: Record<string, {
99
39
  },
100
40
  },
101
41
  },
42
+ replicate: {
43
+ description: 'Run any AI model (images, video, audio)',
44
+ auth: 'bearer',
45
+ docs: 'https://replicate.com/docs',
46
+ actions: {
47
+ run: {
48
+ desc: 'Run a model',
49
+ params: [
50
+ { name: 'model', required: true, desc: 'Model ID (e.g., stability-ai/sdxl:...)' },
51
+ { name: 'input', required: true, desc: 'Model input parameters' },
52
+ ],
53
+ },
54
+ list_models: {
55
+ desc: 'List available models',
56
+ params: [],
57
+ },
58
+ },
59
+ },
102
60
  elevenlabs: {
103
61
  description: 'Text-to-speech',
104
62
  auth: 'api_key',
@@ -114,21 +72,23 @@ const DIRECT_CALL_SPECS: Record<string, {
114
72
  },
115
73
  },
116
74
  },
117
- replicate: {
118
- description: 'Run any AI model (images, video, audio)',
119
- auth: 'bearer',
120
- docs: 'https://replicate.com/docs',
75
+ e2b: {
76
+ description: 'Code sandbox for AI agents',
77
+ auth: 'api_key',
78
+ docs: 'https://e2b.dev/docs',
121
79
  actions: {
122
- run: {
123
- desc: 'Run a model',
80
+ run_code: {
81
+ desc: 'Execute code in sandbox',
124
82
  params: [
125
- { name: 'model', required: true, desc: 'Model ID (e.g., stability-ai/sdxl:...)' },
126
- { name: 'input', required: true, desc: 'Model input parameters' },
83
+ { name: 'code', required: true, desc: 'Code to run' },
84
+ { name: 'language', required: false, desc: 'Language (default: python)' },
127
85
  ],
128
86
  },
129
- list_models: {
130
- desc: 'List available models',
131
- params: [],
87
+ run_shell: {
88
+ desc: 'Execute shell command',
89
+ params: [
90
+ { name: 'command', required: true, desc: 'Shell command' },
91
+ ],
132
92
  },
133
93
  },
134
94
  },
@@ -206,22 +166,171 @@ const DIRECT_CALL_SPECS: Record<string, {
206
166
  },
207
167
  },
208
168
  },
209
- e2b: {
210
- description: 'Code sandbox for AI agents',
169
+ brave_search: {
170
+ description: 'Web search API',
211
171
  auth: 'api_key',
212
- docs: 'https://e2b.dev/docs',
172
+ docs: 'https://api.search.brave.com/docs',
213
173
  actions: {
214
- run_code: {
215
- desc: 'Execute code in sandbox',
174
+ search: {
175
+ desc: 'Search the web',
216
176
  params: [
217
- { name: 'code', required: true, desc: 'Code to run' },
218
- { name: 'language', required: false, desc: 'Language (default: python)' },
177
+ { name: 'query', required: true, desc: 'Search query' },
178
+ { name: 'count', required: false, desc: 'Number of results (default: 5)' },
219
179
  ],
220
180
  },
221
- run_shell: {
222
- desc: 'Execute shell command',
181
+ },
182
+ },
183
+ resend: {
184
+ description: 'Email API',
185
+ auth: 'bearer',
186
+ docs: 'https://resend.com/docs',
187
+ actions: {
188
+ send_email: {
189
+ desc: 'Send email',
223
190
  params: [
224
- { name: 'command', required: true, desc: 'Shell command' },
191
+ { name: 'to', required: true, desc: 'Recipient email' },
192
+ { name: 'subject', required: true, desc: 'Email subject' },
193
+ { name: 'html', required: false, desc: 'HTML body' },
194
+ { name: 'text', required: false, desc: 'Plain text body' },
195
+ { name: 'from', required: false, desc: 'Sender (default: noreply@apiclaw.nordsym.com)' },
196
+ ],
197
+ },
198
+ },
199
+ },
200
+ '46elks': {
201
+ description: 'Swedish SMS and voice API',
202
+ auth: 'basic',
203
+ docs: 'https://46elks.com/docs',
204
+ actions: {
205
+ send_sms: {
206
+ desc: 'Send SMS message',
207
+ params: [
208
+ { name: 'to', required: true, desc: 'Phone number (+46...)' },
209
+ { name: 'message', required: true, desc: 'SMS text (max 160 chars for 1 segment)' },
210
+ { name: 'from', required: false, desc: 'Sender ID (default: APIClaw)' },
211
+ ],
212
+ },
213
+ },
214
+ },
215
+ twilio: {
216
+ description: 'Global SMS and voice API',
217
+ auth: 'basic',
218
+ docs: 'https://www.twilio.com/docs',
219
+ actions: {
220
+ send_sms: {
221
+ desc: 'Send SMS message',
222
+ params: [
223
+ { name: 'to', required: true, desc: 'Phone number (E.164 format)' },
224
+ { name: 'message', required: true, desc: 'SMS text' },
225
+ { name: 'from', required: false, desc: 'Sender phone number' },
226
+ ],
227
+ },
228
+ },
229
+ },
230
+ apilayer: {
231
+ description: 'APILayer marketplace — currency, news, scraping, PDFs, verification & more',
232
+ auth: 'api_key',
233
+ docs: 'https://apilayer.com',
234
+ actions: {
235
+ exchange_rates: {
236
+ desc: 'Get live or historical currency exchange rates',
237
+ params: [
238
+ { name: 'base', required: false, desc: 'Base currency (default: USD)' },
239
+ { name: 'symbols', required: false, desc: 'Comma-separated target currencies' },
240
+ { name: 'date', required: false, desc: 'Historical date YYYY-MM-DD (omit for live)' },
241
+ ],
242
+ },
243
+ market_data: {
244
+ desc: 'End-of-day stock market data',
245
+ params: [
246
+ { name: 'symbols', required: true, desc: 'Stock ticker(s), comma-separated e.g. AAPL,MSFT' },
247
+ { name: 'date_from', required: false, desc: 'Start date YYYY-MM-DD' },
248
+ { name: 'date_to', required: false, desc: 'End date YYYY-MM-DD' },
249
+ ],
250
+ },
251
+ aviation: {
252
+ desc: 'Real-time flight data and tracking',
253
+ params: [
254
+ { name: 'flight_iata', required: false, desc: 'IATA flight number e.g. AA100' },
255
+ { name: 'dep_iata', required: false, desc: 'Departure airport IATA code' },
256
+ { name: 'arr_iata', required: false, desc: 'Arrival airport IATA code' },
257
+ ],
258
+ },
259
+ pdf_generate: {
260
+ desc: 'Generate PDF from URL or HTML',
261
+ params: [
262
+ { name: 'document_url', required: false, desc: 'URL to convert to PDF' },
263
+ { name: 'document_html', required: false, desc: 'HTML string to convert (alternative to URL)' },
264
+ { name: 'page_size', required: false, desc: 'Page size: A4, Letter, etc (default: A4)' },
265
+ ],
266
+ },
267
+ screenshot: {
268
+ desc: 'Capture full-page screenshot of any URL',
269
+ params: [
270
+ { name: 'url', required: true, desc: 'URL to screenshot' },
271
+ { name: 'viewport', required: false, desc: 'Viewport size e.g. 1440x900 (default)' },
272
+ { name: 'fullpage', required: false, desc: '1 for full page, 0 for viewport only (default: 0)' },
273
+ ],
274
+ },
275
+ verify_email: {
276
+ desc: 'Validate email address format and deliverability',
277
+ params: [
278
+ { name: 'email', required: true, desc: 'Email address to verify' },
279
+ ],
280
+ },
281
+ verify_number: {
282
+ desc: 'Validate and lookup phone number details',
283
+ params: [
284
+ { name: 'number', required: true, desc: 'Phone number in E.164 format e.g. +46701234567' },
285
+ ],
286
+ },
287
+ vat_check: {
288
+ desc: 'Validate EU VAT number',
289
+ params: [
290
+ { name: 'vat_number', required: true, desc: 'EU VAT number e.g. SE556012345601' },
291
+ ],
292
+ },
293
+ world_news: {
294
+ desc: 'Extract and analyze news articles from a URL',
295
+ params: [
296
+ { name: 'url', required: true, desc: 'URL of the news article to analyze' },
297
+ { name: 'analyze', required: false, desc: 'Whether to analyze the news (default: true)' },
298
+ ],
299
+ },
300
+ finance_news: {
301
+ desc: 'Latest financial and stock market news',
302
+ params: [
303
+ { name: 'tickers', required: false, desc: 'Stock tickers comma-separated e.g. AAPL,TSLA' },
304
+ { name: 'text', required: false, desc: 'Keyword filter' },
305
+ { name: 'number', required: false, desc: 'Number of results (default: 5)' },
306
+ ],
307
+ },
308
+ scrape: {
309
+ desc: 'Advanced web scraper — returns clean page content',
310
+ params: [
311
+ { name: 'url', required: true, desc: 'URL to scrape' },
312
+ ],
313
+ },
314
+ image_crop: {
315
+ desc: 'Smart crop an image to specified dimensions',
316
+ params: [
317
+ { name: 'url', required: true, desc: 'Image URL to crop' },
318
+ { name: 'width', required: false, desc: 'Target width in pixels' },
319
+ { name: 'height', required: false, desc: 'Target height in pixels' },
320
+ ],
321
+ },
322
+ skills: {
323
+ desc: 'Search 7000+ professional skills database',
324
+ params: [
325
+ { name: 'q', required: true, desc: 'Skill search query e.g. "machine learning"' },
326
+ { name: 'count', required: false, desc: 'Number of results (default: 10)' },
327
+ ],
328
+ },
329
+ form_submit: {
330
+ desc: 'Submit form data to a FormAPI endpoint',
331
+ params: [
332
+ { name: 'endpoint', required: true, desc: 'FormAPI endpoint path' },
333
+ { name: 'data', required: false, desc: 'Form data object to submit' },
225
334
  ],
226
335
  },
227
336
  },
package/src/execute.ts CHANGED
@@ -406,16 +406,17 @@ const apiEndpoints: Record<string, Record<string, { url: string; method: string;
406
406
  exchange_rates: { url: 'https://api.apilayer.com/exchangerates_data/latest', method: 'GET' },
407
407
  market_data: { url: 'http://api.marketstack.com/v1/eod', method: 'GET' },
408
408
  aviation: { url: 'http://api.aviationstack.com/v1/flights', method: 'GET' },
409
- pdf_generate: { url: 'https://api.pdflayer.com/api/convert', method: 'GET' },
409
+ pdf_generate: { url: 'https://api.pdflayer.com/api', method: 'POST' },
410
410
  screenshot: { url: 'https://api.screenshotlayer.com/api/capture', method: 'GET' },
411
411
  verify_email: { url: 'https://api.apilayer.com/email_verification/check', method: 'GET' },
412
412
  verify_number: { url: 'https://api.apilayer.com/number_verification/validate', method: 'GET' },
413
- vat_check: { url: 'http://apilayer.net/api/validate', method: 'GET' },
414
- world_news: { url: 'https://api.apilayer.com/world_news/search-news', method: 'GET' },
413
+ vat_check: { url: 'https://apilayer.net/api/validate', method: 'GET' },
414
+ world_news: { url: 'https://api.apilayer.com/world_news/extract-news', method: 'GET' },
415
415
  finance_news: { url: 'https://api.apilayer.com/financelayer/news', method: 'GET' },
416
416
  scrape: { url: 'https://api.apilayer.com/adv_scraper/scraper', method: 'GET' },
417
- image_crop: { url: 'https://api.apilayer.com/image_crop/crop', method: 'GET' },
418
- skills: { url: 'https://api.apilayer.com/skills', method: 'GET' },
417
+ image_crop: { url: 'https://api.apilayer.com/smart_crop/url', method: 'POST' },
418
+ skills: { url: 'https://api.promptapi.com/skills', method: 'GET' },
419
+ form_submit: { url: 'https://api.apilayer.com/form_api/{endpoint}', method: 'POST' },
419
420
  },
420
421
  };
421
422
 
@@ -1720,13 +1721,12 @@ const handlers: Record<string, Record<string, (params: any, creds: any) => Promi
1720
1721
  const { document_url, document_html, page_size = 'A4' } = params;
1721
1722
  if (!document_url && !document_html) return createErrorResult('apilayer', 'pdf_generate', 'Missing: document_url or document_html', ERROR_CODES.INVALID_PARAMS);
1722
1723
 
1723
- const url = new URL('https://api.pdflayer.com/api/convert');
1724
- url.searchParams.set('access_key', key);
1724
+ const url = new URL('https://api.pdflayer.com/api');
1725
1725
  url.searchParams.set('page_size', page_size);
1726
1726
  if (document_url) url.searchParams.set('document_url', document_url);
1727
1727
  if (document_html) url.searchParams.set('document_html', document_html);
1728
1728
 
1729
- const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'pdf_generate' });
1729
+ const response = await fetchWithRetry(url.toString(), { method: 'POST', headers: { 'apikey': key } }, { provider: 'apilayer', action: 'pdf_generate' });
1730
1730
  const contentType = response.headers.get('content-type') || '';
1731
1731
  if (contentType.includes('application/pdf')) {
1732
1732
  return { success: true, provider: 'apilayer', action: 'pdf_generate', data: { message: 'PDF generated', content_type: 'application/pdf', size: response.headers.get('content-length') } };
@@ -1753,6 +1753,7 @@ const handlers: Record<string, Record<string, (params: any, creds: any) => Promi
1753
1753
  return { success: true, provider: 'apilayer', action: 'screenshot', data: { message: 'Screenshot captured', content_type: contentType, url: url.toString() } };
1754
1754
  }
1755
1755
  const data = await response.json() as Record<string, unknown>;
1756
+ if (!response.ok) return createErrorResult('apilayer', 'screenshot', 'Request failed', statusToErrorCode(response.status));
1756
1757
  return { success: true, provider: 'apilayer', action: 'screenshot', data };
1757
1758
  },
1758
1759
 
@@ -1805,13 +1806,12 @@ const handlers: Record<string, Record<string, (params: any, creds: any) => Promi
1805
1806
 
1806
1807
  world_news: async (params, creds) => {
1807
1808
  const key = creds.APILAYER_WORLDNEWS_KEY || creds.api_key;
1808
- const { text, source_countries, language = 'en', number = 5 } = params;
1809
+ const { url: newsUrl, analyze = true } = params;
1810
+ if (!newsUrl) return createErrorResult('apilayer', 'world_news', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
1809
1811
 
1810
- const url = new URL('https://api.apilayer.com/world_news/search-news');
1811
- if (text) url.searchParams.set('text', text);
1812
- if (source_countries) url.searchParams.set('source-countries', source_countries);
1813
- url.searchParams.set('language', language);
1814
- url.searchParams.set('number', number.toString());
1812
+ const url = new URL('https://api.apilayer.com/world_news/extract-news');
1813
+ url.searchParams.set('url', newsUrl);
1814
+ url.searchParams.set('analyze', analyze ? 'true' : 'false');
1815
1815
 
1816
1816
  const response = await fetchWithRetry(url.toString(), {
1817
1817
  headers: { 'apikey': key },
@@ -1859,29 +1859,38 @@ const handlers: Record<string, Record<string, (params: any, creds: any) => Promi
1859
1859
  const { url: imageUrl, width, height } = params;
1860
1860
  if (!imageUrl) return createErrorResult('apilayer', 'image_crop', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
1861
1861
 
1862
- const url = new URL('https://api.apilayer.com/image_crop/crop');
1863
- url.searchParams.set('url', imageUrl);
1864
- if (width) url.searchParams.set('width', width.toString());
1865
- if (height) url.searchParams.set('height', height.toString());
1862
+ // APILayer smart_crop expects POST with form data
1863
+ const formData = new URLSearchParams();
1864
+ formData.set('url', imageUrl);
1865
+ if (width) formData.set('width', width.toString());
1866
+ if (height) formData.set('height', height.toString());
1866
1867
 
1867
- const response = await fetchWithRetry(url.toString(), {
1868
- headers: { 'apikey': key },
1868
+ const response = await fetchWithRetry('https://api.apilayer.com/smart_crop/url', {
1869
+ method: 'POST',
1870
+ headers: {
1871
+ 'apikey': key,
1872
+ 'Content-Type': 'application/x-www-form-urlencoded',
1873
+ },
1874
+ body: formData.toString(),
1869
1875
  }, { provider: 'apilayer', action: 'image_crop' });
1870
1876
  const contentType = response.headers.get('content-type') || '';
1871
1877
  if (contentType.includes('image/')) {
1872
1878
  return { success: true, provider: 'apilayer', action: 'image_crop', data: { message: 'Image cropped', content_type: contentType } };
1873
1879
  }
1874
1880
  const data = await response.json() as Record<string, unknown>;
1881
+ if (!response.ok) return createErrorResult('apilayer', 'image_crop', 'Request failed', statusToErrorCode(response.status));
1875
1882
  return { success: true, provider: 'apilayer', action: 'image_crop', data };
1876
1883
  },
1877
1884
 
1878
1885
  skills: async (params, creds) => {
1879
- const key = creds.APILAYER_SKILLAPI_KEY || creds.api_key;
1886
+ // Skills API is on PromptAPI domain, uses master key
1887
+ const key = creds.APILAYER_EXCHANGERATE_KEY || creds.api_key;
1880
1888
  const { q } = params;
1881
1889
  if (!q) return createErrorResult('apilayer', 'skills', 'Missing required param: q', ERROR_CODES.INVALID_PARAMS);
1882
1890
 
1883
- const url = new URL('https://api.apilayer.com/skills');
1891
+ const url = new URL('https://api.promptapi.com/skills');
1884
1892
  url.searchParams.set('q', q);
1893
+ if (params.count) url.searchParams.set('count', String(params.count));
1885
1894
 
1886
1895
  const response = await fetchWithRetry(url.toString(), {
1887
1896
  headers: { 'apikey': key },
@@ -1890,6 +1899,24 @@ const handlers: Record<string, Record<string, (params: any, creds: any) => Promi
1890
1899
  if (!response.ok) return createErrorResult('apilayer', 'skills', 'Request failed', statusToErrorCode(response.status));
1891
1900
  return { success: true, provider: 'apilayer', action: 'skills', data };
1892
1901
  },
1902
+
1903
+ form_submit: async (params, creds) => {
1904
+ const key = creds.APILAYER_FORMAPI_KEY || creds.api_key;
1905
+ const { endpoint, data: formData } = params;
1906
+ if (!endpoint) return createErrorResult('apilayer', 'form_submit', 'Missing required param: endpoint', ERROR_CODES.INVALID_PARAMS);
1907
+
1908
+ const response = await fetchWithRetry(`https://api.apilayer.com/form_api/${endpoint}`, {
1909
+ method: 'POST',
1910
+ headers: {
1911
+ 'apikey': key,
1912
+ 'Content-Type': 'application/json',
1913
+ },
1914
+ body: JSON.stringify(formData || {}),
1915
+ }, { provider: 'apilayer', action: 'form_submit' });
1916
+ const data = await response.json() as Record<string, unknown>;
1917
+ if (!response.ok) return createErrorResult('apilayer', 'form_submit', 'Request failed', statusToErrorCode(response.status));
1918
+ return { success: true, provider: 'apilayer', action: 'form_submit', data };
1919
+ },
1893
1920
  },
1894
1921
  };
1895
1922