npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aiblocklist

v1.0.0

Published

AI blocklist client for Node.js: classify any domain as an AI tool (20,000+ domains, 18 categories, daily refresh, vendor training-on-your-data verdicts) and pull the feed database for DNS sinkholes, proxies, firewalls and SOAR playbooks.

Readme

aiblocklist

Node.js client for the AI domain blocklist API. It answers one question per call, "is this domain an AI tool, and what kind?", and it pulls the full classified database for the enforcement points a network already has: DNS sinkholes, secure web gateways, proxy plugins, firewall External Dynamic Lists and SOAR playbooks.

The list behind it covers 20,000+ AI-tool domains in 18 functional categories with subcategories, rebuilt daily, extracted from a 120-million-domain web filtering infrastructure. Every record also carries the vendor's data-use position: whether the tool trains on customer input, whether an opt-out exists, whether enterprise and API tiers are exempt, and the date those terms were last checked.

Zero runtime dependencies. Node.js 14 and newer. TypeScript definitions ship with the package.


Installation

npm install aiblocklist

Quick start

const AIBlocklistClient = require('aiblocklist');

const client = new AIBlocklistClient(process.env.ATB_API_KEY);

(async () => {
  const r = await client.check('chatgpt.com');
  console.log(r.blocked);            // true
  console.log(r.primary_category);   // "Text & Language"
  console.log(r.ai_type);            // "ai_native"
  console.log(r.categoryNames);      // ["Text & Language"]
  console.log(r.subcategoryNames);   // ["General assistants & chatbots"]
  console.log(r.trains_on_data);     // "opt_out_default"
  console.log(r.terms_checked);      // "2026-09-17"
  console.log(r.quota_remaining);    // lookups left in the current cycle

  const n = await client.check('example.com');
  console.log(n.blocked);            // false
  console.log(n.categories);         // []
})();

The key is issued in the account area at aitoolsblocklist.com and is sent as the X-API-Key header on every request. One lookup is charged per check() call; the public endpoints (stats(), categories(), clause()) need no key.

With ES modules or TypeScript:

import AIBlocklistClient, { Lookup, PlanError } from 'aiblocklist';

const client = new AIBlocklistClient(process.env.ATB_API_KEY!);
const r: Lookup = await client.check('midjourney.com');
if (r.isBlocked) console.log(`${r.domain}: ${r.primaryCategory}`);

The endpoints this client wraps

| Endpoint | Key | Method on the client | |---|---|---| | GET /api/check?domain= | yes | check(domain), isBlocked(domain), checkMany(domains), dataUse(domain) | | GET /api/database/?action=status | yes, feed or database plan | feeds.status() | | GET /api/database/?action=database_info | yes, feed or database plan | feeds.databaseInfo() | | GET /api/database/?action=download_database | yes, feed or database plan | feeds.downloadDatabase(path) | | GET /api/database/?action=download_categories | yes, feed or database plan | feeds.downloadCategories(path) | | GET /api/stats.php | no | stats(), categories() | | GET /api/data-use-clause.php?d=&f= | no | clause(domain, field) |

All requests go to https://www.aitoolsblocklist.com. Pass a bare domain or any URL; the client strips scheme, path, port, credentials and a leading www. before sending. Subdomains resolve to their registrable domain on the server, so chat.openai.com returns the record for openai.com.

The lookup response

Found domains return the classification plus the data-use fields:

{
  "domain": "chatgpt.com",
  "blocked": true,
  "primary_category": "Text & Language",
  "ai_type": "ai_native",
  "categories": [
    { "category": "Text & Language", "subcategory": "General assistants & chatbots" }
  ],
  "trains_on_data": "opt_out_default",
  "opt_out_available": "yes",
  "enterprise_no_training": "yes",
  "api_no_training": "yes",
  "terms_checked": "2026-09-17",
  "quota_remaining": 9999986
}

Domains that are not AI tools return {"domain": "...", "blocked": false, "categories": [], "quota_remaining": ...}. Both cases are HTTP 200, so an integration branches on the single blocked boolean.

| Field | Meaning | |---|---| | blocked | the domain is a known AI tool | | primary_category | the category the tool is filed under first | | ai_type | ai_native for tools whose product is the AI, ai_enabled for products with an AI feature inside | | categories | every category and subcategory the tool belongs to (multi-label) | | trains_on_data | yes, no, opt_out_default or unstated | | opt_out_available | whether a user can switch training off | | enterprise_no_training | whether the enterprise tier is exempt from training | | api_no_training | whether API traffic is exempt from training | | terms_checked | date the vendor terms were last read | | quota_remaining | lookups left on the plan in the current 30-day cycle |

The Lookup object exposes the raw fields and adds accessors: isBlocked, primaryCategory, aiType, categoryNames, subcategoryNames, trainsOnData, dataUse (the five data-use fields as one object) and quotaRemaining.

Worked examples

1. A proxy decision hook

A forward proxy or secure web gateway plugin calls check() on the first request to each host, caches the verdict, and enforces per category. Here the policy blocks image and voice generators outright, flags chat assistants that train on input by default, and lets everything else through.

const AIBlocklistClient = require('aiblocklist');
const client = new AIBlocklistClient(process.env.ATB_API_KEY);

const cache = new Map();                 // host -> { verdict, expires }
const TTL = 24 * 3600 * 1000;

const BLOCK_CATEGORIES = new Set(['Image & Visual', 'Audio & Voice', 'Video']);

async function verdictFor(host) {
  const now = Date.now();
  const hit = cache.get(host);
  if (hit && hit.expires > now) return hit.verdict;

  const r = await client.check(host);
  let verdict = 'allow';
  if (r.isBlocked) {
    if (r.categoryNames.some((c) => BLOCK_CATEGORIES.has(c))) verdict = 'block';
    else if (r.trains_on_data === 'yes' || r.trains_on_data === 'opt_out_default') verdict = 'warn';
  }
  cache.set(host, { verdict, expires: now + TTL });
  return verdict;
}

// Express-style middleware in front of an egress proxy
module.exports = async function aiPolicy(req, res, next) {
  const host = req.headers.host || '';
  const verdict = await verdictFor(host);
  if (verdict === 'block') return res.status(403).send('AI tool blocked by policy');
  if (verdict === 'warn') res.setHeader('X-AI-Policy', 'trains-on-input');
  next();
};

Because the cache holds the verdict for a day, a busy gateway spends a few hundred lookups a month, not millions.

2. Refreshing a DNS sinkhole from the feed database

Feed and database plans can download the full CSV. This script pulls it nightly, filters to the categories the organisation blocks, and writes a hosts-format file that any resolver (dnsmasq, Unbound, a Pi-hole style blocker) can load as a sinkhole list.

const fs = require('fs');
const readline = require('readline');
const AIBlocklistClient = require('aiblocklist');

const client = new AIBlocklistClient(process.env.ATB_API_KEY);
const BLOCK = new Set(['Image & Visual', 'Audio & Voice', 'Companions & Social']);

async function refresh() {
  const info = await client.feeds.databaseInfo();
  console.log(`database ${info.database_file}, updated ${info.last_updated}, ${info.file_size_human}`);

  await client.feeds.downloadDatabase('/var/lib/aiblocklist/ai_tools_full.csv');

  const out = fs.createWriteStream('/etc/dnsmasq.d/ai-sinkhole.hosts.tmp');
  const rl = readline.createInterface({ input: fs.createReadStream('/var/lib/aiblocklist/ai_tools_full.csv') });
  let header = null;
  let written = 0;
  for await (const line of rl) {
    const cols = line.split(',');
    if (!header) { header = cols; continue; }
    const row = Object.fromEntries(header.map((h, i) => [h, cols[i]]));
    if (BLOCK.has(row.primary_category)) {
      out.write(`0.0.0.0 ${row.domain}\n`);
      written++;
    }
  }
  out.end();
  fs.renameSync('/etc/dnsmasq.d/ai-sinkhole.hosts.tmp', '/etc/dnsmasq.d/ai-sinkhole.hosts');
  console.log(`${written} domains written`);
}

refresh().catch((e) => { console.error(e.message); process.exit(1); });

Run it from cron at 05:30 after the daily rebuild:

30 5 * * * /usr/bin/node /opt/aiblocklist/refresh.js && systemctl reload dnsmasq

Hosted feeds in EDL, PAC, hosts and DNS formats are also available for feed plans directly from the account area, for firewalls that fetch their own lists.

3. A SOAR enrichment step

When a DLP or CASB alert names a destination host, enrich the ticket with the classification and the vendor's training terms before an analyst sees it.

const AIBlocklistClient = require('aiblocklist');
const client = new AIBlocklistClient(process.env.ATB_API_KEY);

async function enrichAlert(alert) {
  const r = await client.check(alert.destination_host);
  if (!r.isBlocked) return { ...alert, ai_tool: false };

  const clause = await client.clause(r.domain, 'trains_consumer_default');
  return {
    ...alert,
    ai_tool: true,
    ai_category: r.primaryCategory,
    ai_subcategories: r.subcategoryNames,
    ai_type: r.aiType,
    vendor_trains_on_input: r.trains_on_data,
    vendor_opt_out: r.opt_out_available,
    vendor_terms_checked: r.terms_checked,
    vendor_clause: clause ? clause.clause : null,
    vendor_clause_url: clause ? clause.url : null,
    severity: r.trains_on_data === 'yes' ? 'high' : alert.severity,
  };
}

clause() is public and returns the verbatim sentence from the vendor's terms with its URL, which is what an analyst needs to justify a block in a ticket.

4. Batch classification of an export

const AIBlocklistClient = require('aiblocklist');
const client = new AIBlocklistClient(process.env.ATB_API_KEY);

const hosts = require('fs').readFileSync('hosts.txt', 'utf8').split('\n').filter(Boolean);
const results = await client.checkMany(hosts, 50);      // 50 ms pause between calls
const found = results.filter((r) => r.isBlocked);
console.log(`${found.length} of ${new Set(hosts.map(h => h.trim())).size} unique hosts are AI tools`);

checkMany() deduplicates before calling, so a log export with a thousand repeats of one host costs one lookup. For whole DNS or proxy exports with per-user breakdowns, training verdicts and a PDF evidence pack, the hosted shadow AI audit from a DNS export does the same job without a script.

Error handling

| HTTP | Error class | When | |---|---|---| | 400 | BadRequestError | domain missing or empty after cleaning | | 401 | AuthenticationError | no key, or a key that matches no account | | 403 | QuotaError | inactive account or monthly lookup quota exhausted | | 403 | PlanError | the database endpoints on a plan that is lookup-only (the response names the plan) | | 404 | NotFoundError | a database file is not available for the account | | 503 | ServiceUnavailableError | lookup service busy; retried twice with a pause before throwing |

const { AuthenticationError, QuotaError, PlanError } = require('aiblocklist');

try {
  await client.feeds.downloadDatabase('/tmp/ai.csv');
} catch (err) {
  if (err instanceof PlanError) console.log('upgrade needed:', err.body.plan, err.message);
  else if (err instanceof QuotaError) console.log('quota:', err.message);
  else if (err instanceof AuthenticationError) console.log('key problem');
  else throw err;
}

Every error carries status and the parsed body.

Configuration

const client = new AIBlocklistClient('YOUR_API_KEY', {
  baseUrl: 'https://www.aitoolsblocklist.com', // default
  timeout: 30000,                                // ms per request; downloads use at least 300000
  maxRetries: 2,                                 // on 503 and network errors
});

The public methods work without a key:

const anon = new AIBlocklistClient();
const s = await anon.stats();
console.log(s.total_tools, s.category_count);   // 20410 18
for (const c of s.categories) console.log(c.name, c.total, c.subcategories);

Why classify AI tools at the domain level

An organisation's AI exposure is decided by network requests, not by policy documents. Every paste into a chatbot, every file dropped into a converter, every code snippet sent to an assistant is an HTTPS request to a hostname. If the hostname can be classified before or as the request happens, the existing filtering stack can enforce a policy. If it cannot, the policy is a wish.

Generic web filtering categories do not carry the distinctions the policy turns on. A district wants an approved tutor allowed and a face-swap generator blocked; a bank wants a code assistant with an enterprise no-training tier allowed and a consumer chatbot that trains on input blocked. Those decisions need a category, a subcategory, an AI type and the vendor's training terms, which is what each record in this list carries. The NIST AI Risk Management Framework asks organisations to inventory the AI systems in use and to map third-party data risks; a classified domain list is how that inventory is produced from network evidence.

DNS is the cheapest enforcement point

Every connection starts with a name resolution, which is why RFC 1035, the DNS specification, is still the most universal control surface a network has. A sinkhole list loaded into the resolver stops an AI tool for every device on the network, managed or not, with no agent and no TLS inspection. The feed database in example 2 exists for that path. Where a resolver is not under the organisation's control, the same list ships as a PAC file for browsers and as an EDL for firewalls.

Shadow AI is the current shape of shadow IT

Unsanctioned tools adopted by staff without IT involvement have a long history under the name shadow IT. The AI version is faster moving: thousands of tools launch every month, most are free to start, and the payment is often the data pasted in. CISA's guidance on securing AI puts visibility first, and MITRE ATLAS catalogues how AI systems are targeted and misused. A daily-refreshed list is the visibility layer those frameworks assume.

One record answers the data question too

Blocking is only half of governance. The other half is knowing what a permitted vendor does with input. Each record carries trains_on_data, opt_out_available, enterprise_no_training, api_no_training and terms_checked, and clause() returns the sentence behind the verdict. A security team can permit a tool, require the enterprise tier, and cite the vendor's own terms in the approval.

Where this package sits with its neighbours

This client governs what people on the network can reach. The reverse direction, what an organisation's own browsing agents may open on the web, is covered by the AI agent allow list, a page-type database of verified login, checkout, upload and settings URLs across 40 million+ domains with a per-URL allow or deny verdict. Its client is aiagentallowlist on npm and PyPI. A network that blocks AI tools for staff and enforces AI agent web access control for its agents has covered both directions.

Discovery comes before either. The shadow AI detection service takes a DNS, proxy or firewall export and returns every AI tool reached from the network, who used it, and whether the vendor trains on the data, as a dated CSV and PDF evidence pack. It runs on the same list this client queries, so a tool found in an audit is a tool check() classifies.

Frequently asked questions

What is an AI blocklist? An AI blocklist is a list of domains that belong to AI tools, classified so that a filter can block, allow or flag them by type. The AI domain blocklist behind this package holds 20,000+ domains in 18 categories with subcategories, refreshed daily, and ships as a lookup API and as feeds in EDL, PAC, hosts and DNS formats.

How do I block AI tools on a company network with this package? Either call check() from a proxy or gateway hook and enforce per category (example 1), or download the database with feeds.downloadDatabase() on a nightly cron and load the filtered result into your resolver or firewall (example 2). Feed plans can also fetch hosted EDL, PAC and hosts feeds directly, with no code.

Can I allow some AI tools and block others? Yes. Every record carries a primary category, a multi-label category list with subcategories, an AI type and the vendor's training terms. A policy can permit a coding assistant whose enterprise tier does not train on input while blocking image generators, voice cloners and companion chatbots. Paid plans also include 15 AI Policy Profiles with ready-made Block, Controls and Allow verdicts per sector.

Does this package tell me whether a vendor trains on my data? Yes. check() returns trains_on_data (yes, no, opt_out_default or unstated), whether an opt-out exists, whether enterprise and API tiers are exempt, and the date the terms were checked. clause() returns the verbatim clause and its URL for the public fields.

How often is the AI blocklist updated? Daily. The public stats() call shows the current total and the per-category counts; feeds.databaseInfo() shows the timestamp and size of the file your plan is entitled to.

Is there a bulk lookup endpoint? Lookups are one domain per call. checkMany() runs them sequentially, deduplicates first and accepts a pause between calls. For lists of thousands of hosts, download the database once and match locally.

Which plans include the database download? Blocklist Feeds and Professional Annual plans include the CSV and JSON exports; the Lookup API plan is lookup-only and receives a PlanError on the database endpoints, with the plan named in the response. Current plans are listed at aitoolsblocklist.com/pricing.php.

Who builds the AI blocklist? Alpha Quantum, the company behind the website categorization API, the web filtering database of 120M+ domains in 59 categories, the AI agent allow list and the shadow AI detection service.

Related packages

Source: github.com/explainableaixai/aiblocklist, mirror at gitlab.com/url-classifications/aiblocklist.

Links

License

MIT