@builtbyecho/public-api-finder 0.5.5 → 0.5.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builtbyecho/public-api-finder",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Find free/public APIs for agents and prototypes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+
4
+ const cases = [
5
+ { q: 'crypto prices', args: ['--no-auth','--https','--cors','Yes'], topCategory: /cryptocurrency/i, top3Category: /cryptocurrency/i, mustName: /dexscreener|dexpaprika|defillama|geckoterminal|coinpaprika/i, forbid: /weather|joke|tvmaze|jobs/i },
6
+ { q: 'stock prices', args: ['--no-auth','--https','--cors','Yes'], topCategory: /finance/i, top3Category: /finance/i, forbid: /weather|joke|tvmaze|jobs/i },
7
+ { q: 'free stock quote API no auth', args: ['--no-auth','--https'], topCategory: /finance/i, mustName: /stooq|portfolio|alpha|finnhub|twelve/i, forbid: /weather|joke|anime|tv/i },
8
+ { q: 'solana token price market cap dex no key', args: ['--no-auth','--https'], topCategory: /cryptocurrency/i, top3Category: /cryptocurrency/i, mustName: /dexscreener|dexpaprika|geckoterminal|defillama/i },
9
+ { q: 'wallet balance transactions erc20 transfers api', args: [], topCategory: /cryptocurrency|openapi/i, mustName: /etherscan|alchemy|moralis|covalent|block/i },
10
+ { q: 'weather forecast no auth cors', args: ['--no-auth','--https','--cors','Yes'], topCategory: /weather/i, top3Category: /weather/i, mustName: /open-meteo|national weather|pirate/i, forbid: /joke|crypto|stocks/i },
11
+ { q: 'historical weather api', args: [], topCategory: /weather/i, mustName: /open-meteo|weather/i },
12
+ { q: 'weather alerts us api', args: ['--no-auth'], topCategory: /weather/i, mustName: /national weather|weather/i },
13
+ { q: 'geocoding api no auth', args: ['--no-auth','--https'], topCategory: /geocoding|location/i, mustName: /nominatim|geocod|zippopotam|ipinfo/i },
14
+ { q: 'reverse geocoding api', args: [], topCategory: /geocoding|location/i, mustName: /nominatim|geocod|mapbox|google/i },
15
+ { q: 'maps routing api', args: [], topCategory: /geocoding|location/i, mustName: /graphhopper|mapbox|google|here|tomtom/i, forbid: /linkedin/i },
16
+ { q: 'public transit api', args: [], topCategory: /transport|transit/i, mustName: /transitland|transport|mbta/i },
17
+ { q: 'jobs api remote', args: [], topCategory: /jobs|agents/i, mustName: /graphql jobs|adzuna|usajobs|search.gov|linkedin jobs/i },
18
+ { q: 'government jobs api', args: [], topCategory: /jobs|government/i, mustName: /usajobs|search.gov/i },
19
+ { q: 'company enrichment api', args: [], topCategory: /business|marketing|openapi/i, mustName: /clearbit|brandfetch|opencorporates|company/i },
20
+ { q: 'email validation api', args: [], topCategory: /email/i, mustName: /abstract|email|mail/i },
21
+ { q: 'phone number lookup api', args: [], topCategory: /communication|telecom|security|openapi/i, mustName: /numverify|twilio|phone|abstract/i },
22
+ { q: 'sms api', args: [], topCategory: /communication|messaging|telecom|openapi/i, mustName: /twilio|telnyx|vonage|sms|message/i },
23
+ { q: 'webhook testing api', args: [], mustName: /webhook|requestbin|pipedream|beeceptor|alchemy/i },
24
+ { q: 'payment processing api', args: [], topCategory: /payments|financial|openapi/i, mustName: /stripe|paypal|checkout|payment/i },
25
+ { q: 'subscriptions billing api', args: [], topCategory: /payments|openapi/i, mustName: /stripe|billing|subscription|chargebee/i },
26
+ { q: 'oauth identity api', args: [], topCategory: /authentication|security|development|openapi/i, mustName: /auth0|clerk|okta|oauth|openid/i },
27
+ { q: 'passwordless auth api', args: [], topCategory: /authentication|security|openapi/i, mustName: /auth0|clerk|magic|stytch|passwordless/i },
28
+ { q: 'screenshot api', args: [], topCategory: /development|media|documents|openapi/i, mustName: /urlbox|microlink|screenshot/i },
29
+ { q: 'link preview api', args: [], topCategory: /development|media|utility|openapi/i, mustName: /microlink|linkpreview|metadata|open graph/i },
30
+ { q: 'website metadata api', args: [], topCategory: /development|media|utility|openapi/i, mustName: /microlink|metadata|open graph|urlbox/i },
31
+ { q: 'pdf generation api', args: [], topCategory: /documents|development|utility|openapi/i, mustName: /pdf|document/i },
32
+ { q: 'ocr api', args: [], topCategory: /machine learning|ai|documents|openapi/i, mustName: /ocr|vision|mindee/i },
33
+ { q: 'image generation api', args: [], topCategory: /ai|machine learning|media|openapi/i, mustName: /stability|openai|replicate|image/i },
34
+ { q: 'text to speech api', args: [], topCategory: /ai|audio|machine learning|openapi/i, mustName: /elevenlabs|speech|voice|tts/i },
35
+ { q: 'speech to text api', args: [], topCategory: /ai|audio|machine learning|openapi/i, mustName: /assemblyai|deepgram|whisper|speech/i },
36
+ { q: 'vulnerability database api', args: [], topCategory: /security|development|open data/i, mustName: /osv|nvd|cve|vulnerab/i },
37
+ { q: 'cve lookup api', args: [], topCategory: /security|open data/i, mustName: /nvd|cve|osv/i },
38
+ { q: 'npm package metadata api', args: ['--no-auth'], topCategory: /development|open data/i, mustName: /npm|libraries.io|package/i },
39
+ { q: 'github repo stats api', args: [], topCategory: /development|open data/i, mustName: /github|gitlab/i },
40
+ { q: 'domain dns lookup api', args: [], topCategory: /security|development|openapi/i, mustName: /dns|google dns|whois|ssl/i },
41
+ { q: 'open data demographics api', args: [], topCategory: /government|open data/i, mustName: /census|data.gov|demographics/i },
42
+ { q: 'fake ecommerce api for testing', args: ['--no-auth','--https'], topCategory: /test data|shopping/i, mustName: /fake store|dummyjson|jsonplaceholder/i },
43
+ { q: 'joke api', args: ['--no-auth'], topCategory: /entertainment|jokes/i, mustName: /joke/i, forbid: /weather|crypto|stock/i },
44
+ { q: 'cat images random dog facts no auth', args: ['--no-auth'], mustName: /cat|dog|thecatapi|dog ceo/i, forbid: /weather|stock|crypto/i },
45
+ { q: 'food barcode ingredients allergens nutrition no auth cors', args: ['--no-auth','--https'], topCategory: /food/i, mustName: /open food facts/i },
46
+ { q: 'recipe from pantry ingredients avoid allergens api', args: [], topCategory: /food/i, mustName: /spoonacular|edamam|recipe|meal/i },
47
+ { q: 'air quality by coordinates pollutant measurements', args: [], topCategory: /environment|science|weather/i, mustName: /openaq|air quality|aqicn/i },
48
+ { q: 'historical currency exchange rates no auth cors', args: ['--no-auth','--https'], topCategory: /currency exchange/i, mustName: /frankfurter|currency/i, forbid: /crypto|bitcoin|dex/i },
49
+ { q: 'public holidays by country no auth', args: ['--no-auth','--https'], topCategory: /calendar|date/i, mustName: /nager|holiday|calendarific/i },
50
+ { q: 'vehicle vin decode recall safety data no auth', args: ['--no-auth'], topCategory: /transportation|government|open data/i, mustName: /nhtsa|vin|vehicle/i },
51
+ { q: 'real estate property value rent estimate api', args: [], topCategory: /real estate|property|openapi|finance/i, mustName: /rentcast|attom|zillow|real estate|property/i },
52
+ { q: 'flight status airport arrivals departures api', args: [], topCategory: /transportation|travel|openapi/i, mustName: /aviation|flight|airport|amadeus/i },
53
+ { q: 'hotel search booking availability api', args: [], topCategory: /travel|commerce|openapi/i, mustName: /hotel|booking|amadeus/i },
54
+ { q: 'brand colors fonts logo company domain enrichment', args: [], topCategory: /business|marketing|development|openapi/i, mustName: /brandfetch|clearbit|logo|brand/i },
55
+ ];
56
+
57
+ let failures = 0;
58
+ for (const [i, c] of cases.entries()) {
59
+ const res = spawnSync(process.execPath, ['src/cli.js', c.q, ...c.args, '--limit', '5', '--json'], { encoding: 'utf8' });
60
+ const rows = res.status === 0 ? JSON.parse(res.stdout || '[]') : [];
61
+ const top = rows[0];
62
+ const checks = [];
63
+ if (!top) checks.push('no results');
64
+ if (top && c.topCategory && !c.topCategory.test(top.category || '')) checks.push(`top category ${top.category} !~ ${c.topCategory}`);
65
+ if (top && c.top3Category) {
66
+ const bad = rows.slice(0, 3).filter(r => !c.top3Category.test(r.category || '')).map(r => `${r.name} (${r.category})`);
67
+ if (bad.length) checks.push(`top3 off-domain: ${bad.join(', ')}`);
68
+ }
69
+ if (c.mustName && !rows.slice(0, 3).some(r => c.mustName.test(`${r.name} ${r.description} ${r.url}`))) checks.push(`top3 missing ${c.mustName}`);
70
+ if (c.forbid && rows.slice(0, 5).some(r => c.forbid.test(`${r.name} ${r.category} ${r.description}`))) checks.push(`forbidden result ${c.forbid}`);
71
+ const ok = checks.length === 0;
72
+ if (!ok) failures++;
73
+ console.log(`${ok ? 'PASS' : 'FAIL'} ${i + 1}. ${c.q}`);
74
+ rows.slice(0, 3).forEach((r, idx) => console.log(` ${idx + 1}. ${r.name} | ${r.category} | auth=${r.auth} | cors=${r.cors} | score=${r.score}`));
75
+ if (!ok) console.log(` Reasons: ${checks.join('; ')}`);
76
+ }
77
+ console.log(`\n${cases.length - failures}/${cases.length} hardening cases passed`);
78
+ process.exitCode = failures ? 1 : 0;
package/src/cli.js CHANGED
@@ -9,10 +9,11 @@ const SOURCES = {
9
9
  publicApiLists: 'https://public-api-lists.github.io/public-api-lists/api/all.json',
10
10
  publicApisReadme: 'https://raw.githubusercontent.com/public-apis/public-apis/master/README.md',
11
11
  apisGuru: 'https://api.apis.guru/v2/list.json',
12
+ apiMegaList: 'https://raw.githubusercontent.com/cporter202/API-mega-list/main/README.md',
12
13
  };
13
14
  const CACHE_PATH = process.env.PUBLIC_API_FINDER_CACHE || join(homedir(), '.cache', 'public-api-finder', 'all.json');
14
15
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
15
- const DATA_VERSION = 14;
16
+ const DATA_VERSION = 16;
16
17
 
17
18
  const ENRICHMENT_FIELDS = [
18
19
  'tags',
@@ -53,6 +54,15 @@ const TARGETED_BOOSTS = [
53
54
  [/\b(sanctions|ofac|pep|kyc|aml)\b/, /\b(opensanctions|ofac|sanctions|pep|kyc|aml|chainalysis)\b/i, 135],
54
55
  [/\b(wallet risk|crypto sanctions|blockchain wallet risk)\b/, /\b(chainalysis|trm|elliptic|sanctions|wallet risk)\b/i, 95],
55
56
  [/\b(crypto token metadata|token metadata|token logos|coin logos|logos contract addresses)\b/, /\b(coinmarketcap|coingecko|coinpaprika|coinbase|token logos|coin metadata|coin images)\b/i, 90],
57
+ [/\b(stock|stocks|stock quote|stock prices|equity|equities|ticker|tickers)\b/, /\b(stooq|portfolio optimizer|alpha vantage|polygon|finnhub|twelve data|stock|stocks|equity|ticker|quote)\b/i, 90],
58
+ [/\b(reverse geocoding|geocoding|maps routing|routing api|distance matrix|places)\b/, /\b(nominatim|geocod\.io|graphhopper|mapbox|foursquare|geocoding|routing|distance matrix|places)\b/i, 120],
59
+ [/\b(webhook testing|webhook debug|request bin|mock api)\b/, /\b(webhook\.site|beeceptor|requestbin|webhook|mock api|request bin)\b/i, 180],
60
+ [/\b(oauth identity|passwordless auth|openid|social auth|login oauth)\b/, /\b(auth0|clerk|stytch|magic|openid|oauth|passwordless|authentication|identity)\b/i, 190],
61
+ [/\b(cve lookup|vulnerability database|vulnerability data|package security)\b/, /\b(osv|nvd|cve|cvss|vulnerability|security advisories)\b/i, 190],
62
+ [/\b(cve lookup|cve api|cve search)\b/, /\b(osv|nvd|cve)\b/i, 260],
63
+ [/\b(open data demographics|demographics|census data)\b/, /\b(census|data\.gov|demographics|open data|government data)\b/i, 145],
64
+ [/\b(joke|jokes|meme|memes|random quote)\b/, /\b(official joke|joke api|jokes|quotable|quote)\b/i, 110],
65
+ [/\b(github repo|repo stats|stars issues commits)\b/, /\b(github rest|github|gitlab|repositories|stars|issues|commits)\b/i, 100],
56
66
  [/\b(zipcode|zip code|postal code)\b/, /\b(zippopotam|zip|postal|census)\b/i, 80],
57
67
  [/\b(real estate|property value|rent estimate)\b/, /\b(rentcast|attom|zillow|real estate|property|rent estimate)\b/i, 135],
58
68
  [/\b(mortgage|mortgage rates|loan calculator|loan rate|home loan)\b/, /\b(mortgage|home loan|loan rate)\b/i, 230],
@@ -387,9 +397,13 @@ const CURATED_APIS = [
387
397
  { name: 'GitHub REST API', url: 'https://docs.github.com/en/rest', description: 'GitHub repositories, stars, issues, commits, pull requests, releases, users, and org data API.', auth: 'No', https: true, cors: 'Yes', category: 'Development', source: 'curated', sourceWeight: 5 },
388
398
  { name: 'npm Registry API', url: 'https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md', description: 'No-auth npm package metadata, versions, downloads, dist-tags, registry documents, and package data.', auth: 'No', https: true, cors: 'Yes', category: 'Development', source: 'curated', sourceWeight: 5 },
389
399
  { name: 'Docker Hub', url: 'https://docs.docker.com/docker-hub/api/latest/', description: 'Docker image repositories, tags, registry metadata, namespaces, vulnerabilities, and container image data API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Development', source: 'curated', sourceWeight: 5 },
400
+ { name: 'Webhook.site', url: 'https://webhook.site/', description: 'Webhook testing, inspect HTTP requests, debug callbacks, request bins, and temporary webhook endpoints.', auth: 'No', https: true, cors: 'Yes', category: 'Development', source: 'curated', sourceWeight: 5 },
401
+ { name: 'Beeceptor', url: 'https://beeceptor.com/docs/', description: 'Webhook testing, mock APIs, request bin inspection, HTTP debugging, and endpoint simulation.', auth: 'No', https: true, cors: 'Unknown', category: 'Development', source: 'curated', sourceWeight: 5 },
390
402
 
391
403
  { name: 'Auth0', url: 'https://auth0.com/docs/api', description: 'OAuth, OpenID Connect, login, user profiles, social auth, authentication, and identity APIs.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
392
404
  { name: 'Clerk', url: 'https://clerk.com/docs/reference/backend-api', description: 'Authentication, user profiles, organizations, sessions, OAuth, social login, and identity APIs.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
405
+ { name: 'Stytch', url: 'https://stytch.com/docs/api', description: 'Passwordless login, magic links, OTP, OAuth, passkeys, sessions, and user authentication API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
406
+ { name: 'Magic', url: 'https://magic.link/docs/api', description: 'Passwordless authentication, magic links, wallets, login, users, and identity API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
393
407
  { name: 'Mail.tm', url: 'https://docs.mail.tm/', description: 'No-auth temporary email inboxes, disposable mail accounts, receive messages, and testing email API.', auth: 'No', https: true, cors: 'Yes', category: 'Email', source: 'curated', sourceWeight: 5 },
394
408
  { name: 'Twilio Verify', url: 'https://www.twilio.com/docs/verify/api', description: 'SMS OTP verification, phone verification, one-time passcodes, factors, and verification checks.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Telecom', source: 'curated', sourceWeight: 5 },
395
409
  { name: 'numverify', url: 'https://numverify.com/documentation', description: 'Phone number validation, carrier, country, location, line type, and international number lookup API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Telecom', source: 'curated', sourceWeight: 5 },
@@ -683,7 +697,7 @@ Usage:
683
697
 
684
698
  Options:
685
699
  --category <name> Filter by category substring
686
- --source <name> Filter by source: public-api-lists, public-apis, apis-guru, curated
700
+ --source <name> Filter by source: public-api-lists, public-apis, apis-guru, api-mega-list, curated
687
701
  --no-auth Only APIs with Auth = No
688
702
  --https Only HTTPS APIs
689
703
  --cors <value> Filter by CORS: Yes, No, Unknown
@@ -835,6 +849,37 @@ function intentPenalty(entry, queryText) {
835
849
  if (/\b(favicon|website preview|open graph|link preview|screenshot)\b/.test(queryText) && !/\b(microlink|urlbox|favicon|website metadata|open graph|link preview|screenshot)\b/.test(text)) penalty += 120;
836
850
  }
837
851
 
852
+ if (/\b(school district|school boundary|district boundary|reverse geocoding|maps routing|routing api|open data demographics|demographics|census data)\b/.test(queryText) && /\b(linkedin|jobs scraper|lead|sales|recruiting|amazon product scraper|tiktok profile scraper)\b/.test(text)) {
853
+ penalty += 120;
854
+ }
855
+ if (/\b(stock|stocks|stock quote|stock prices|equity|equities|ticker|tickers)\b/.test(queryText) && !/finance|financial|stock|stocks|equity|ticker|market data|portfolio|stooq|finnhub|polygon|alpha vantage|twelve data/.test(text)) {
856
+ penalty += 80;
857
+ }
858
+ if (/\b(stock|stocks|stock quote|stock prices|equity|equities|ticker|tickers)\b/.test(queryText) && /\b(quotable|joke|entertainment|food|weather|test data|placeholder)\b/.test(text)) {
859
+ penalty += 140;
860
+ }
861
+ if (/\bcrypto\b/.test(queryText) && /\bnot stocks?\b/.test(queryText) && /\b(finance|financial|stock|stocks|equity|ticker|portfolio|stooq|finnhub|polygon|sec edgar|predscope|valueray|econdb)\b/.test(text)) {
862
+ penalty += 240;
863
+ }
864
+ if (/\b(oauth identity|passwordless auth|openid|social auth|login oauth)\b/.test(queryText) && !/authentication|auth0|clerk|stytch|magic|openid|identity|passwordless|social auth/.test(text)) {
865
+ penalty += 160;
866
+ }
867
+ if (/\b(oauth identity|passwordless auth|openid|social auth|login oauth)\b/.test(queryText) && /\b(calendar|nager|holiday|open food|plaid|clearbit logo)\b/.test(text)) {
868
+ penalty += 180;
869
+ }
870
+ if (/\b(webhook testing|webhook debug|request bin|mock api)\b/.test(queryText) && !/webhook|beeceptor|requestbin|mock api|pipedream/.test(text)) {
871
+ penalty += 80;
872
+ }
873
+ if (/\b(cve lookup|vulnerability database|vulnerability data|package security)\b/.test(queryText) && !/osv|nvd|cve|cvss|vulnerability|security advisories/.test(text)) {
874
+ penalty += 95;
875
+ }
876
+ if (/\b(cve lookup|cve api|cve search)\b/.test(queryText) && !/osv|nvd|cve/.test(text)) {
877
+ penalty += 180;
878
+ }
879
+ if (/\b(joke api|jokes?|memes?)\b/.test(queryText) && !/joke|meme|quote/.test(text)) {
880
+ penalty += 70;
881
+ }
882
+
838
883
  if (!cat.includes('cryptocurrency') && /\b(wallet address|identicon|avatar|profile picture)\b/.test(queryText) && /\b(avatar|identicon|profile picture)\b/.test(text)) {
839
884
  penalty -= 20;
840
885
  }
@@ -926,6 +971,42 @@ function parsePublicApisReadme(readme) {
926
971
  return entries;
927
972
  }
928
973
 
974
+
975
+ function parseApiMegaList(readme) {
976
+ const entries = [];
977
+ let category = '';
978
+ for (const raw of readme.split('\n')) {
979
+ const heading = raw.match(/^##\s+(.+?)\s*$/) || raw.match(/^###\s+(.+?)\s*$/);
980
+ if (heading) {
981
+ const text = heading[1].replace(/[#*_`]/g, '').trim();
982
+ if (text && !/table of contents|repository stats|star this|join my|contributing|license/i.test(text)) category = normalizeCategory(text.replace(/^\d+\.\s*/, ''));
983
+ continue;
984
+ }
985
+ if (!raw.startsWith('| [')) continue;
986
+ const cells = raw.split('|').slice(1, -1).map(c => c.trim());
987
+ if (cells.length < 2) continue;
988
+ if (/^-+$/.test(cells[0]) || /^api name$/i.test(cells[0])) continue;
989
+ const link = cells[0].match(/\[([^\]]+)\]\(([^)]+)\)/);
990
+ if (!link) continue;
991
+ const name = link[1].replace(/<[^>]+>/g, '').trim();
992
+ const url = link[2].trim();
993
+ const description = cleanDescription(cells[1] || `${name} API`);
994
+ if (!name || !/^https?:\/\//i.test(url)) continue;
995
+ entries.push({
996
+ name,
997
+ url,
998
+ description,
999
+ auth: 'Unknown',
1000
+ https: /^https:/i.test(url),
1001
+ cors: 'Unknown',
1002
+ category: category || 'Unknown',
1003
+ source: 'api-mega-list',
1004
+ sourceWeight: 1,
1005
+ });
1006
+ }
1007
+ return entries;
1008
+ }
1009
+
929
1010
  function parseApisGuru(data) {
930
1011
  const entries = [];
931
1012
  for (const [providerName, item] of Object.entries(data || {})) {
@@ -950,10 +1031,11 @@ function parseApisGuru(data) {
950
1031
  }
951
1032
 
952
1033
  async function buildData() {
953
- const [pal, publicApisReadme, guru] = await Promise.allSettled([
1034
+ const [pal, publicApisReadme, guru, megaList] = await Promise.allSettled([
954
1035
  fetchJson(SOURCES.publicApiLists),
955
1036
  fetchText(SOURCES.publicApisReadme),
956
1037
  fetchJson(SOURCES.apisGuru),
1038
+ fetchText(SOURCES.apiMegaList),
957
1039
  ]);
958
1040
  const entries = [];
959
1041
  const sourceStatus = {};
@@ -971,6 +1053,11 @@ async function buildData() {
971
1053
  sourceStatus['apis-guru'] = rows.length;
972
1054
  entries.push(...rows);
973
1055
  } else sourceStatus['apis-guru'] = `error: ${guru.reason.message}`;
1056
+ if (megaList.status === 'fulfilled') {
1057
+ const rows = parseApiMegaList(megaList.value);
1058
+ sourceStatus['api-mega-list'] = rows.length;
1059
+ entries.push(...rows);
1060
+ } else sourceStatus['api-mega-list'] = `error: ${megaList.reason.message}`;
974
1061
  sourceStatus.curated = ENRICHED_CURATED_APIS.length;
975
1062
  entries.push(...ENRICHED_CURATED_APIS);
976
1063
  const deduped = dedupe(entries).map(enrichApiMetadata);