TypeScript/JavaScript client for the ipfastcheck.com IP reputation API — detect proxies, VPNs, Tor exit nodes and datacenter ranges, plus geolocation and ASN data.
- No API key. Nothing to sign up for, no secret binding to configure, no token to rotate.
- Zero runtime dependencies. Built on the platform
fetch. - Runs at the edge. Node 18+, Cloudflare Workers, Deno, Bun, browsers.
- Caching built in, including a Workers KV backend.
- Fails open by design — a third-party outage must not take your signup flow down.
- Fully typed, IPv4 and IPv6.
npm install ipfastcheckimport { Client } from 'ipfastcheck';
const client = new Client();
const result = await client.check('185.220.101.1');
result.country; // 'Germany'
result.asn; // 'AS60729'
result.risk; // 100
result.isTor; // true
result.isDatacenter; // trueMost callers want an action, not raw flags. decide() applies a policy and returns allow / review / deny:
const decision = await client.decide(clientIp);
switch (decision.action) {
case 'deny': return reject();
case 'review': return requireEmailVerification(); // step up, don't slam the door
case 'allow': return proceed();
}
decision.reasons; // ['risk_100_ge_90', 'proxy', 'datacenter_or_hosting']review exists because blocking is usually the wrong response. Corporate VPNs, shared CGNAT and mobile carrier NAT all produce "suspicious" addresses for entirely legitimate users. Step-up verification costs an abuser real effort and a real user one click.
import { Client, Policy } from 'ipfastcheck';
const client = new Client({
policy: new Policy({
denyRisk: 90, // risk >= 90 -> deny
reviewRisk: 50, // risk >= 50 -> review
denyTor: true,
reviewDatacenter: true,
reviewWhenDegraded: true, // see below
onError: 'allow', // fail open
}),
});The whole reason this client uses fetch and an async cache interface. Note that a keyless API means there is no secret to bind — the Worker below is the complete deployment.
import { Client, KVCache, Policy } from 'ipfastcheck';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const client = new Client({
cache: new KVCache(env.IPFC), // shared across every isolate in the colo
policy: new Policy({ denyTor: true }),
timeoutMs: 2000, // keep edge latency bounded
});
const ip = request.headers.get('cf-connecting-ip');
if (!ip) return fetch(request);
const decision = await client.decide(ip);
if (decision.action === 'deny') {
return new Response('Forbidden', { status: 403 });
}
return fetch(request);
},
};# wrangler.toml
kv_namespaces = [{ binding = "IPFC", id = "<your-namespace-id>" }]Caching is not optional here: a cold lookup costs roughly 450–600 ms, which is far too much to sit inline on every request. KVCache raises any TTL below 60 s to Workers KV's own minimum.
Two things are worth knowing before you gate anything on this API.
Don't branch on isVpn alone. It under-reports. A live example — an address on AS9009 (M247), well-known VPN infrastructure:
{ "flags": { "vpn": false, "proxy": true, "hosting": true }, "risk": 82 }A naive if (result.isVpn) block lets that straight through. Use the composite signal — risk, netType and isHosting together — which is what Policy does.
Watch for degraded responses. When the upstream reputation backend doesn't answer, the API falls back to a geolocation-only estimate. The response still reports ok: true with a plausible risk score; the only tell is sources.risk === 'geo-only', where vpn is always false and fraudScore is always null.
This client exposes that as result.degraded, caches such answers for two minutes rather than hours, and by default escalates an otherwise-clean degraded result to review instead of allow:
const result = await client.check(ip);
if (result.degraded) {
// "we didn't get an answer" — not "it's clean"
}import { Client, MemoryCache, KVCache, NullCache } from 'ipfastcheck';
new Client(); // bounded in-process LRU, 10k entries
new Client({ cache: new MemoryCache(50_000) });
new Client({ cache: new KVCache(env.IPFC) }); // Cloudflare Workers KV
new Client({ cache: new NullCache() }); // disabledTTLs are picked per result, because volatility differs by orders of magnitude between classes:
| Result | TTL |
|---|---|
Degraded (geo-only) |
2 minutes |
| Tor exit node | 15 minutes |
| Proxy / VPN / datacenter / risk ≥ 50 | 1 hour |
| Clean residential | 24 hours |
Implement the Cache interface for Redis, Deno KV or anything else:
import type { Cache } from 'ipfastcheck';checkSelf() needs no argument and no key, which makes it a complete health check for "is my egress address flagged?" — worth knowing before your transactional email starts silently vanishing on a freshly provisioned host.
const self = await client.checkSelf();
if (self.risk > 75) {
alert(`Our egress IP ${self.ip} is flagged: risk=${self.risk}`);
}There is no bulk endpoint, so checkMany() is a polite serial loop. It de-duplicates first, which matters on access logs — the same address recurs constantly, and cache hits are free while fresh lookups are not.
const results = await client.checkMany(addresses, { delayMs: 200 });
for (const [ip, result] of results) {
if (result.ok && result.risk >= 75) console.log(ip, result.risk, result.asn);
}| Option | Default | Purpose |
|---|---|---|
baseUrl |
https://ipfastcheck.com |
Endpoint override |
timeoutMs |
5000 |
Per-request timeout |
retries |
2 |
Extra attempts after the first failure |
cache |
MemoryCache |
Any Cache implementation |
policy |
Policy |
Default thresholds for decide() |
fetchImpl |
globalThis.fetch |
Injectable, for tests |
Retries use exponential backoff with jitter. A 4xx other than 429 is never retried.
| Method | Returns |
|---|---|
check(ip, { useCache }) |
Promise<Result> |
checkSelf() |
Promise<Result> — this host's egress address, never cached |
myIp() |
Promise<string | null> |
country(ip) |
Promise<string | null> |
decide(ip, policy?) |
Promise<Decision> |
checkMany(ips, { delayMs }) |
Promise<Map<string, Result>> |
Network failures never throw — check result.ok. Only malformed input throws (InvalidAddressError), since that is a caller bug rather than a runtime condition.
ip · country · countryCode · city · asn · isp · netType · risk · fraudScore · flags · isTor · isProxy · isVpn · isHosting · isDatacenter · isResidential · isMobile · degraded · ageSeconds · fromCache · ok · error · raw
Comparable free tiers, as of August 2026:
| Service | API key | HTTPS on free tier | VPN/proxy flags free | Commercial use |
|---|---|---|---|---|
| ipfastcheck | not required | yes | yes | yes |
| ip-api.com | not required | no | partial | non-commercial only |
| AbuseIPDB | required | yes | no (abuse score only) | yes |
| ipinfo.io | required | yes | paid add-on | yes |
| MaxMind GeoLite2 | account + license key | local database | no | yes |
An HTTP-only endpoint cannot be called from a Cloudflare Worker at all, since fetch there requires TLS. That single fact rules out the leading keyless alternative for edge use.
npm install
npm test # builds, then runs 35 tests on node:test — no network accessTypeScript is the only development dependency; the published package has none.
- ipfastcheck-python — same API, same policy model
- ansible-role-fail2ban-ipfastcheck — reputation-tiered fail2ban bans
- API documentation
MIT