<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Furqan Ashraf</title>
    <description>The latest articles on DEV Community by Furqan Ashraf (@furqan_ashraf).</description>
    <link>https://dev.to/furqan_ashraf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3417494%2Fa4ca94c7-b065-4a18-b8cf-400a8ac45cf7.png</url>
      <title>DEV Community: Furqan Ashraf</title>
      <link>https://dev.to/furqan_ashraf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/furqan_ashraf"/>
    <language>en</language>
    <item>
      <title>Detect Account Takeover in Node.js: A Free Login Location Check</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Wed, 19 Aug 2026 22:20:33 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/detect-account-takeover-in-nodejs-a-free-login-location-check-1g22</link>
      <guid>https://dev.to/furqan_ashraf/detect-account-takeover-in-nodejs-a-free-login-location-check-1g22</guid>
      <description>&lt;p&gt;Someone logs into an account from New York. Forty minutes later, someone logs into the same account from Lagos, roughly 8,400 km away. Nobody makes that trip in forty minutes. Whoever just logged in from Lagos almost certainly isn't the same person who logged in from New York, and there's a decent chance the app in question has no idea that just happened.&lt;/p&gt;

&lt;p&gt;That's the whole pitch for impossible travel detection, and if you spend any time on dev.to's security tag, you've probably already read an explanation of it. The concept isn't new and it isn't complicated: geolocate the login, measure the distance from the last one, divide by the time between them, check if the result is physically possible. What's rarer is finding the tested, installable version of that logic instead of a snippet you're expected to adapt yourself. So I built one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;impossible-travel-guard&lt;/code&gt; is free, open source, and has zero runtime dependencies. Install it, call one function after your login check passes, done. This post covers what it actually catches, where it falls apart, and how to feed it real coordinates without writing your own geolocation client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is worth shipping, not just explaining
&lt;/h2&gt;

&lt;p&gt;Account takeover isn't a fringe problem. The FBI's IC3 unit logged close to 4,700 ATO complaints in 2025 with $359.7 million in reported losses, and their own numbers show that once an attacker actually gets into an account, fifty or more transactions across different banks can fire off within minutes (&lt;a href="https://www.ic3.gov/AnnualReport/Reports/2025_IC3Report.pdf" rel="noopener noreferrer"&gt;source&lt;/a&gt;). That's not someone clicking around by hand, that's a script doing damage fast once it's past your login screen. Anything that slows down the "getting in" part earns its keep.&lt;/p&gt;

&lt;p&gt;What makes this particular signal useful is that it doesn't need either IP address to look suspicious on its own. No VPN flag, no bad reputation score, nothing wrong with either login individually. The only thing wrong is the relationship between them. Most account security tooling checks a login against a blacklist or a reputation score. This checks a login against the user's own history, so it still catches an attacker coming from a completely clean IP, because they didn't think they needed to hide it.&lt;/p&gt;

&lt;p&gt;That also means it will occasionally flag things that are completely fine. More on that below, because it's the part that decides whether this is actually useful in production or just a fun demo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjsbit1idl77y9x5s5zd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjsbit1idl77y9x5s5zd7.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;impossible-travel-guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five lines gets you a working check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TravelGuard&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;impossible-travel-guard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TravelGuard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// in-memory store, fine for local dev&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;6.5244&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.3792&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flagged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// require MFA, alert the user, or hold the session for manual review&lt;/span&gt;
  &lt;span class="c1"&gt;// do not hard-block on this signal alone, more on that below&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first login for any user is never flagged. There's nothing to compare it to yet, so it just becomes the baseline everything else gets measured against.&lt;/p&gt;

&lt;p&gt;That in-memory store is fine for poking at this locally, and it forgets everything on restart. For anything real, implement the two-method &lt;code&gt;LoginStore&lt;/code&gt; interface against whatever you already run. Redis is a natural fit since this is one small read and one small write per login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoginStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;impossible-travel-guard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedisStore&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;LoginStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getLastLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoginEvent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`last_login:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;saveLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`last_login:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TravelGuard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RedisStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Picking the threshold
&lt;/h2&gt;

&lt;p&gt;Two numbers control how sensitive this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TravelGuard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;maxPlausibleSpeedKmh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// default&lt;/span&gt;
  &lt;span class="na"&gt;minDistanceKm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// default&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1000 km/h isn't a number I picked out of the air. Commercial jets cruise between roughly 830 and 1,050 km/h, and the fastest passenger aircraft flying today tops out around 1,136 km/h. 1000 sits above normal cruise speed with margin for geolocation being a little imprecise, without being loose enough to let a real cross-continent jump slide under it. &lt;code&gt;minDistanceKm&lt;/code&gt; exists for a dumber reason: IP geolocation jitters within the same city all the time, and without a floor you'd flag someone whose ISP handed them a slightly different IP on the same Wi-Fi network.&lt;/p&gt;

&lt;p&gt;Turn the speed threshold down if your users rarely fly internationally. Turn it up if you'd rather only catch the most extreme jumps and tolerate more noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The false positives you'll actually hit
&lt;/h2&gt;

&lt;p&gt;I ran this against enough test scenarios before publishing to know where the flags actually come from in practice, and it's rarely the interesting case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F904vx7tlt03ig8eigmf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F904vx7tlt03ig8eigmf6.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Why it trips the check&lt;/th&gt;
&lt;th&gt;What to do about it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Corporate VPN or privacy relay&lt;/td&gt;
&lt;td&gt;Traffic exits through a server nowhere near the user&lt;/td&gt;
&lt;td&gt;If the device is recognized, log it and move on. If not, step up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile carrier CGNAT&lt;/td&gt;
&lt;td&gt;The carrier's gateway can sit hundreds of km from the actual phone&lt;/td&gt;
&lt;td&gt;Keep &lt;code&gt;minDistanceKm&lt;/code&gt; above typical carrier drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same-city IP reissue&lt;/td&gt;
&lt;td&gt;A new DHCP lease or repeat lookup lands a few km away&lt;/td&gt;
&lt;td&gt;This is what &lt;code&gt;minDistanceKm&lt;/code&gt; is for, it shouldn't reach you at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An actual compromised login&lt;/td&gt;
&lt;td&gt;Attacker logging in from their own, unmasked location&lt;/td&gt;
&lt;td&gt;The reason this library exists. Step up first, block if it stacks with other signals&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the reason to build this at all. VPN and IP-reputation checks catch the attackers sloppy enough to route through something flagged. This catches the ones who don't bother hiding, because a stolen password from a data breach doesn't come with instructions to use a VPN.&lt;/p&gt;

&lt;p&gt;The practical takeaway from that table: never wire a flag straight to a hard block. Log it first, watch what comes through for a week, and only start requiring MFA on a flag once you've seen your own users' actual travel patterns. Reserve an outright block for when a flag stacks with something else, a new device, a new browser, a password reset requested minutes earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this won't catch
&lt;/h2&gt;

&lt;p&gt;This is one signal, not a fraud engine. It has no opinion on VPNs, proxies, device fingerprints, or IP reputation, pair it with those if you want that coverage, I didn't try to make this library do five jobs at once.&lt;/p&gt;

&lt;p&gt;It also can't tell you why a trip is impossible, only that it is. A shared corporate VPN egress bouncing between two data centers looks exactly the same to this library as a real account takeover does. iPhones behind Apple's Private Relay cause the same kind of noise. None of that is specific to this package, it's true of any impossible-travel check anyone builds, and the honest fix is the boring one: log enough context on every flag that you can review a handful by hand until you trust the threshold you've picked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting coordinates without building your own geolocation client
&lt;/h2&gt;

&lt;p&gt;This library never calls a geolocation API itself. That's deliberate, so it works with whatever provider you already have. If you don't have one, &lt;a href="https://ipgeolocation.io/ip-location-api.html" rel="noopener noreferrer"&gt;IPGeolocation.io's IP Location API&lt;/a&gt; covers exactly what this needs on its free tier, latitude, longitude, city, and country for an IP, no card required for a key. The repo's &lt;code&gt;examples/ipgeolocation-adapter.ts&lt;/code&gt; has a working version of that wiring, and &lt;code&gt;examples/live-test.ts&lt;/code&gt; runs the whole flow against two real IP addresses so you're looking at actual coordinates instead of the hardcoded ones in the quick-start snippet above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;IPGEO_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key-here"&lt;/span&gt;
npx tsx examples/live-test.ts 8.8.8.8 197.210.28.1 40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That resolves both IPs, runs the exact check &lt;code&gt;guard.check()&lt;/code&gt; runs, and prints the distance, elapsed time, and implied speed. One thing worth saying plainly: never call a geolocation API directly from a browser with your key in the request. That belongs on your backend, full stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things people ask
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this replace VPN or IP reputation checks?&lt;/strong&gt; No, and I'd be skeptical of anything that claims one signal does. It catches a different kind of attacker, one whose IP has no reputation problem at all. Run it alongside VPN and proxy detection if you want broader coverage, not instead of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What data does this actually need?&lt;/strong&gt; Latitude, longitude, and a timestamp per login. That's it. Nothing paid, nothing beyond what any geolocation provider's free tier already returns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the library itself free?&lt;/strong&gt; Yes, MIT licensed, no paid tier of its own. The only cost that could show up is whichever geolocation provider you pick, and a free tier is enough for what this needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will this lock out real users?&lt;/strong&gt; Not if you respond to a flag with step-up verification instead of a hard block, which is what the rest of this post has been arguing for. Treat a flag as "ask one more question," not "deny access."&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it before you install it
&lt;/h2&gt;

&lt;p&gt;There's a &lt;a href="https://furqan-ashraf.github.io/impossible-travel-guard/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; that runs this exact logic in your browser. Pick two cities, set a time gap, watch it flag or clear. I built that after realizing a passing test suite that only I'd seen wasn't proof of anything to anyone else, and it's a faster way to get a feel for the threshold than reading about it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Furqan-Ashraf/impossible-travel-guard" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; has the full source, the test suite, and both example scripts above. Issues and pull requests are genuinely welcome, if you hit a false positive or false negative worth handling differently, open one with the two real login events involved. Real numbers make it easy to reason about.&lt;/p&gt;

</description>
      <category>node</category>
      <category>security</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building a Typosquat Detector with Python and a WHOIS API: A Technical Walkthrough</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Tue, 18 Aug 2026 14:37:49 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/building-a-typosquat-detector-with-python-and-a-whois-api-a-technical-walkthrough-1623</link>
      <guid>https://dev.to/furqan_ashraf/building-a-typosquat-detector-with-python-and-a-whois-api-a-technical-walkthrough-1623</guid>
      <description>&lt;p&gt;I open sourced a small tool recently for flagging suspicious domains, and a few people asked how it actually works under the hood rather than just what it does. So here's the technical side: the design decisions, the tradeoffs, and why I built it this way instead of a few obvious alternatives.&lt;/p&gt;

&lt;p&gt;Repo is here if you want to skip ahead: &lt;a href="https://github.com/Furqan-Ashraf/Typosquat-detector" rel="noopener noreferrer"&gt;https://github.com/Furqan-Ashraf/Typosquat-detector&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with checking domains one at a time
&lt;/h2&gt;

&lt;p&gt;If you've ever tried to script anything around WHOIS data, you've probably hit the same wall I did. Raw WHOIS responses aren't structured. They're plain text, and the format changes depending on the registrar and the TLD. Some responses have a &lt;code&gt;Creation Date&lt;/code&gt; field. Others have &lt;code&gt;created&lt;/code&gt;. Others bury it three lines deep in a block of legal text you have to skip past first.&lt;/p&gt;

&lt;p&gt;Writing a parser that handles even a handful of TLDs reliably takes real effort, and it breaks the moment you hit a registry you didn't test against. This is the main reason I used a WHOIS API (WhoisFreaks, specifically) instead of building a raw WHOIS parser from scratch. It returns normalized JSON, so the code doesn't care whether it's looking at a &lt;code&gt;.com&lt;/code&gt;, a &lt;code&gt;.io&lt;/code&gt;, or a &lt;code&gt;.dev&lt;/code&gt; domain. Same fields, same structure, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two independent checks, combined
&lt;/h2&gt;

&lt;p&gt;The core logic is intentionally simple: two checks, each cheap to compute, combined into one risk flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check one: registration age.&lt;/strong&gt; This one's a straightforward date diff once you have the creation date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_age_days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_date_str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT%H:%M:%SZ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d %H:%M:%S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_date_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple date formats are handled because different registries return dates differently even through a normalized API. Better to try a few formats than crash on the one registry that's slightly different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check two: string similarity against a watchlist.&lt;/strong&gt; This is Levenshtein distance, implemented without any external dependency since it's a small enough algorithm not to warrant pulling in a library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feylrvac9uawb6szlnipo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feylrvac9uawb6szlnipo.png" alt="Typosquatting detection workflow using a WHOIS API: collect domains, check domain age, compare spelling, flag high risk domains" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the classic dynamic programming approach, O(n*m) where n and m are the string lengths. For domain names, which are short strings, this runs instantly even across a large watchlist.&lt;/p&gt;

&lt;p&gt;One bug worth mentioning, since it's the kind of thing that only shows up once you test against real-world typosquat patterns: comparing the full base string against the watch domain misses cases where the attacker adds extra text, like &lt;code&gt;paypa1-secure.com&lt;/code&gt; against &lt;code&gt;paypal.com&lt;/code&gt;. The full-string edit distance there is large enough to fall outside any sane threshold, even though it's an obvious typosquat to a human. Fixed it by also sliding a same-length window across the candidate string and taking whichever comparison, full string or windowed, gives the smaller distance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;closest_watch_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;watch_domains&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;strip_tld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;best_domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best_distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;watch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;watch_domains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;watch_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;strip_tld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;full_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;watch_base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;window_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;full_dist&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;watch_base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;window_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;watch_base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window_dist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;best_distance&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;best_distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;best_domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best_distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;best_domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best_distance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small fix, but it's the difference between catching &lt;code&gt;paypa1-secure.com&lt;/code&gt; and missing it entirely, which matters a lot more than the character-swap case since real phishing domains almost always pad the name with something like &lt;code&gt;-secure&lt;/code&gt;, &lt;code&gt;-support&lt;/code&gt;, or &lt;code&gt;-login&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why combine them instead of scoring separately
&lt;/h2&gt;

&lt;p&gt;An earlier version of this scored age and similarity separately and ranked domains by a weighted total. I dropped that pretty quickly. Weighted scoring sounds more sophisticated, but in practice it just makes the threshold harder to reason about, and harder to explain to anyone else looking at the flagged list. A boolean AND, new domain and close lookalike, both true, is easy to tune (two thresholds, both adjustable as plain variables at the top of the script) and easy to explain to someone reviewing the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;risk_flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_new&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_lookalike&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire decision. Everything before it in the script is just gathering the two inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling the API layer
&lt;/h2&gt;

&lt;p&gt;A few things mattered here beyond just making the request:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting courtesy.&lt;/strong&gt; There's a small sleep between requests (&lt;code&gt;REQUEST_DELAY_SECONDS = 0.3&lt;/code&gt;). Nothing fancy, just enough to avoid hammering the API on a large batch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graceful degradation.&lt;/strong&gt; If a lookup fails for one domain (timeout, malformed response, whatever), that shouldn't kill the whole batch. Errors get caught, logged into the result object, and the script moves on to the next domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;query_whoisfreaks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dataclasses for structured results.&lt;/strong&gt; Each domain check produces a &lt;code&gt;DomainResult&lt;/code&gt; dataclass rather than a loose dictionary. Makes the CSV export trivial (&lt;code&gt;asdict()&lt;/code&gt; handles the conversion) and keeps the fields self-documenting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd add next
&lt;/h2&gt;

&lt;p&gt;The repo's open for contributions, and a few extensions are on my list:&lt;/p&gt;

&lt;p&gt;DNS and nameserver cross-referencing would add a third signal, since a lot of phishing infrastructure reuses the same nameservers across multiple campaigns even when the domain names differ. SSL certificate issuance date is another one worth adding, mainly because a domain with no cert, or one issued the same day as registration, tends to correlate with the same short-lived infrastructure pattern.&lt;/p&gt;

&lt;p&gt;Async requests would help too. Right now it's sequential with a delay, which is fine for a few hundred domains but slow if you're trying to batch-check thousands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to get it
&lt;/h2&gt;

&lt;p&gt;The full script, README, and usage instructions are on GitHub: &lt;a href="https://github.com/Furqan-Ashraf/Typosquat-detector" rel="noopener noreferrer"&gt;https://github.com/Furqan-Ashraf/Typosquat-detector&lt;/a&gt;. If you're running something similar, or you've layered in other signals (SSL, DNS, ASN data) that work well for this kind of detection, I'd be curious to hear about it.&lt;/p&gt;

&lt;p&gt;If you want the fuller story behind why I built this in the first place, I wrote that up separately here: &lt;a href="https://medium.com/@furqanashraf/i-built-a-script-to-catch-phishing-domains-before-they-hit-my-inbox-whois-api-python-38adf6f1d240" rel="noopener noreferrer"&gt;I Built a Script to Catch Phishing Domains Before They Hit My Inbox&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Someone Registered a Fake Version of Your Domain. Here's How to Detect It Before Your Customers Get Scammed</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Thu, 06 Aug 2026 13:44:40 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/someone-registered-a-fake-version-of-your-domain-heres-how-to-detect-it-before-your-customers-get-1pem</link>
      <guid>https://dev.to/furqan_ashraf/someone-registered-a-fake-version-of-your-domain-heres-how-to-detect-it-before-your-customers-get-1pem</guid>
      <description>&lt;p&gt;Picture this. Your company owns &lt;code&gt;paypal.com&lt;/code&gt;. Somewhere, someone just registered &lt;code&gt;paypa1.com&lt;/code&gt;, swapping the letter L for the number 1. At a glance, in most fonts, nobody can tell the difference. That new domain isn't live yet. Maybe it never will be. Or maybe in three weeks it's hosting a fake login page, harvesting your customers' passwords, and quietly ruining your brand's reputation.&lt;/p&gt;

&lt;p&gt;This happens constantly, and most companies never know it's happening until a customer complains, or worse, until it shows up in the news.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this keeps happening
&lt;/h3&gt;

&lt;p&gt;Registering a lookalike domain costs almost nothing, often less than the price of a coffee. Attackers use a few common tricks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Character typos&lt;/strong&gt;: swapping, dropping, or duplicating a letter (&lt;code&gt;gooogle.com&lt;/code&gt;, &lt;code&gt;googel.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard slips&lt;/strong&gt;: substituting a letter with the one next to it on a QWERTY layout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homoglyphs&lt;/strong&gt;: using characters that look nearly identical, like a capital "I" instead of a lowercase "l"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compound squats&lt;/strong&gt;: adding a word to the brand name, like &lt;code&gt;yourbrand-login.com&lt;/code&gt; or &lt;code&gt;yourbrand-support.net&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any one of these can be used for phishing, credential theft, fake customer support pages, or straight-up brand impersonation. And here's the part most teams miss: even if you know typosquatting exists, actually finding which lookalike domains are currently registered against your brand is a different problem entirely, and knowing whether a domain you found is actually dangerous is a third problem on top of that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: Finding the fake domains in the first place
&lt;/h3&gt;

&lt;p&gt;You can't manually guess every possible misspelling of your brand across 1,500+ domain extensions. Free permutation tools exist, but most of them work by generating typo candidates locally and then resolving each one with a DNS lookup from your own machine. That misses compound squats, wildcard patterns, and anything the generator didn't think to produce. It also means you're the one running potentially thousands of DNS queries just to check.&lt;/p&gt;

&lt;p&gt;A more direct approach is to search the domains that are actually registered, instead of generating guesses and hoping one resolves. WhoisFreaks' &lt;a href="https://whoisfreaks.com/products/domain-typosquats-api" rel="noopener noreferrer"&gt;Domain Typosquats API&lt;/a&gt; works this way. You send it a brand keyword, and it searches across 931 million plus registered domains and 1,529+ TLDs for fuzzy matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.whoisfreaks.com/v3.0/domain/typos?keyword=yourbrand&amp;amp;apiKey=YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response comes back with every matching registered domain, along with its creation date, expiry date, and when it was last seen active. Here's a trimmed version, the real response also includes pagination fields for stepping through large result sets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalRecords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"domains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"domainName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"y0urbrand.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"createDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"expiryDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2027-06-02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"lastSeen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isDropped"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also search with a wildcard pattern instead of a plain keyword, which catches prefix, suffix, and hyphenated variants that a simple typo match would miss, things like &lt;code&gt;yourbrand-support.com&lt;/code&gt; or &lt;code&gt;login-yourbrand.net&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: Not every lookalike domain is actually dangerous
&lt;/h3&gt;

&lt;p&gt;Here's the catch. Once you run a scan, you'll likely get back a list of dozens, sometimes hundreds, of matching domains. Most of them are harmless. Some are parked and doing nothing. Some belong to unrelated small businesses that happen to share a similar name. A handful might be actively serving a phishing page right now.&lt;/p&gt;

&lt;p&gt;Treating every result the same way, either ignoring all of them or trying to escalate all of them, isn't practical. What you actually need is a way to separate "genuinely dangerous" from "just similar looking."&lt;/p&gt;

&lt;p&gt;This is where a second check comes in. WhoisFreaks' &lt;a href="https://whoisfreaks.com/products/domain-reputation-api" rel="noopener noreferrer"&gt;Domain Reputation API&lt;/a&gt; takes a single domain and returns a risk verdict, a trust score from 0 to 100, and the evidence behind it. Under the hood, it runs live WHOIS, DNS, SSL, and page content checks, plus a match against WhoisFreaks' own threat intelligence feeds, all in one request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.whoisfreaks.com/v1/domain/security?domainName=y0urbrand.com&amp;amp;apiKey=YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified version of what comes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risk_category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"suspicious"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"primary_threat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phishing"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trust_signals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trust_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trust_band"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"indicators"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_newly_registered"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"has_dmarc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"uses_free_ssl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"domain_age_days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"intelligence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"recommended_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"block"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A domain that's a few weeks old, has no email authentication set up, is running on a free SSL certificate, and redirects somewhere unexpected checks almost every box that real phishing infrastructure tends to check. A trust score in the low band plus a "block" recommendation tells you this one deserves immediate attention, unlike a similar-looking domain that's been sitting parked and unused for three years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting both checks together
&lt;/h3&gt;

&lt;p&gt;Used separately, each API answers half a question. Used together, they form a simple, repeatable workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Brand keyword
     ↓
Typosquats API → list of registered lookalike domains
     ↓
Domain Reputation API → trust score + verdict for each one
     ↓
Result: block, monitor, or ignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice, this means running a keyword or pattern scan against your brand on a schedule (daily or weekly works for most teams), then automatically feeding every new result through a reputation check. Anything that comes back with a low trust score or a "malicious" verdict gets flagged for your security team immediately. Everything else can wait for a routine review, or get ignored entirely if it's clearly unrelated or parked.&lt;/p&gt;

&lt;p&gt;This is a small amount of glue code, a scheduled job and two API calls, but it replaces what used to require someone manually checking a list of domains one at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who actually needs this
&lt;/h3&gt;

&lt;p&gt;This workflow is most useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SaaS companies&lt;/strong&gt;, where a fake login page can lead directly to account takeover&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Banks and fintech&lt;/strong&gt;, where impersonation directly threatens customer trust and money&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E-commerce&lt;/strong&gt;, where fake storefronts scam customers out of payment details&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOC and security teams&lt;/strong&gt;, who need this fed into existing SIEM or SOAR pipelines rather than checked by hand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSSPs and anti-phishing vendors&lt;/strong&gt;, who need to offer this kind of detection as a feature to their own clients&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The bigger point
&lt;/h3&gt;

&lt;p&gt;Most companies never check for this until something already went wrong, a customer forwards a suspicious email, or a fake site gets reported after it's already scammed people. Finding lookalike domains early, before they turn into active phishing infrastructure, is the difference between a quiet defensive registration and a real incident response.&lt;/p&gt;

&lt;p&gt;The two checks aren't complicated on their own. What makes the difference is running them consistently, and actually acting on what they tell you.&lt;/p&gt;

</description>
      <category>security</category>
      <category>api</category>
      <category>cybersecurity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Guessing Why Your SQL Query Is Slow: A Simple Guide to EXPLAIN</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Wed, 05 Aug 2026 09:00:21 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/stop-guessing-why-your-sql-query-is-slow-a-simple-guide-to-explain-3dpj</link>
      <guid>https://dev.to/furqan_ashraf/stop-guessing-why-your-sql-query-is-slow-a-simple-guide-to-explain-3dpj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0892104ytifhmfdprh3j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0892104ytifhmfdprh3j.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;
If you are just getting started with SQL, you have probably written a query that worked fine while testing, but felt slow once the table had more data in it. This happens to almost everyone, and it does not mean you did something wrong. It usually just means the database is not being used in the most efficient way yet, and that is a normal part of learning.&lt;/p&gt;

&lt;p&gt;This article walks through how to understand why a query might be slow, in simple terms, without needing to be an expert.&lt;/p&gt;
&lt;h3&gt;
  
  
  First, understand what the database is actually doing
&lt;/h3&gt;

&lt;p&gt;When you write a query, you might imagine the database just "looks through the table" to find the data. In a way, that is true, but there is more to it. Before running your query, the database makes a small decision internally: how should it go through the table to find what you asked for? Should it check every single row, or is there a faster shortcut it can use?&lt;/p&gt;

&lt;p&gt;That decision is what really controls how fast or slow your query feels. Two queries that look almost the same can behave very differently depending on this.&lt;/p&gt;

&lt;p&gt;The good news is you do not have to guess. Most databases let you ask them directly what they plan to do, before actually running the query. In MySQL and PostgreSQL, you do this by adding the word &lt;code&gt;EXPLAIN&lt;/code&gt; in front of your query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will not return your actual data. Instead, it shows you the plan, almost like a summary of what the database is about to do. You do not need to understand every detail of this output right away. Just look for one simple thing: is the database checking every row in the table, or is it jumping straight to the rows that match?&lt;/p&gt;

&lt;p&gt;If your table only has a few hundred rows, checking every row is usually fine and you will not notice any delay. But once a table grows to thousands or millions of rows, checking every single row becomes slow, and that is usually the real reason behind a slow query.&lt;/p&gt;

&lt;h3&gt;
  
  
  What an index actually does (in simple terms)
&lt;/h3&gt;

&lt;p&gt;You have probably heard the word "index" mentioned around database performance. An index works a bit like the index page at the back of a textbook. Instead of flipping through every page to find a topic, you check the index page, and it tells you exactly where to look.&lt;/p&gt;

&lt;p&gt;A database index works the same way. Instead of scanning the whole table, the database can jump straight to the matching rows if there is an index on the right column.&lt;/p&gt;

&lt;p&gt;For example, if you often search orders by their status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_status&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the database to keep a quick lookup list for that column, so future searches on &lt;code&gt;status&lt;/code&gt; do not need to scan the entire table.&lt;/p&gt;

&lt;p&gt;One important tip: an index only helps if your query searches using that exact column in a simple way. If you wrap the column inside a function, like checking only the year from a date column, the database usually cannot use the index anymore, even if one exists. It is better to compare the raw column directly whenever possible.&lt;/p&gt;

&lt;p&gt;Also, indexes are not something you should add to every column "just in case." They do help with reading data faster, but they slightly slow down adding or updating data, since the database has to update the index too. For now, it is enough to know this trade-off exists. You will get a feel for when to use indexes as you write more queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  A few simple habits that make a real difference
&lt;/h3&gt;

&lt;p&gt;You do not need advanced tricks to write faster queries. A few small habits go a long way, especially while you are still learning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only ask for the columns you actually need.&lt;/strong&gt; It is common to write &lt;code&gt;SELECT *&lt;/code&gt; out of convenience. This asks the database for every single column, even ones you might not use. Once you know which columns you actually need, it is better to name them directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is lighter for the database and easier to read too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid pulling more rows than you will show.&lt;/strong&gt; If you are only displaying twenty results on a page, there is no need to ask the database for thousands of rows and filter them later in your code. Use &lt;code&gt;LIMIT&lt;/code&gt; to ask the database to only return what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Be careful with pagination on large tables.&lt;/strong&gt; A common approach when starting out is something like &lt;code&gt;LIMIT 50000, 20&lt;/code&gt;, meaning "skip fifty thousand rows, then give me the next twenty." The problem is the database still has to go through those fifty thousand rows first, even though you do not want them. As your data grows, this gets noticeably slower.&lt;/p&gt;

&lt;p&gt;A simpler and faster habit is to filter using the last row you already saw, instead of skipping by a number. For example, instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;track the last ID you saw on the previous page, then do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database can jump straight to &lt;code&gt;id &amp;gt; 5000&lt;/code&gt; using the index, instead of counting through 50,000 rows first just to skip them. The same idea works with a timestamp column too, if you are paginating by date instead of ID.&lt;/p&gt;

&lt;p&gt;None of these habits require deep expertise. They are just small choices that matter more as your tables grow bigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query speed is something you check now and then, not just once
&lt;/h3&gt;

&lt;p&gt;When you are still new to this, it is easy to think performance is something you fix one time and forget about. In reality, a query that runs fine today might feel slow months later, simply because the table has grown. This is completely normal, and experienced developers deal with the same thing.&lt;/p&gt;

&lt;p&gt;A good habit going forward is this: whenever a query feels slower than expected, run &lt;code&gt;EXPLAIN&lt;/code&gt; on it first before changing anything. It takes the guesswork out of the process and shows you exactly where the slowdown is coming from.&lt;/p&gt;

&lt;p&gt;If you are still building your SQL foundations and want a simple, step by step place to start, APIFreaks has a free &lt;a href="https://apifreaks.com/resources/tutorials/sql" rel="noopener noreferrer"&gt;SQL tutorial series&lt;/a&gt; that covers the basics in order, from what a table is, all the way through joins, subqueries, and views. It is a good next stop before diving deeper into topics like performance and optimization.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>mysql</category>
      <category>beginners</category>
      <category>database</category>
    </item>
    <item>
      <title>7 APIs Every Developer Should Know About (But Probably Doesn't)</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Tue, 09 Jun 2026 12:14:06 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/7-apis-every-developer-should-know-about-but-probably-doesnt-bkp</link>
      <guid>https://dev.to/furqan_ashraf/7-apis-every-developer-should-know-about-but-probably-doesnt-bkp</guid>
      <description>&lt;p&gt;Most developers know about Stripe, Twilio, and Google Maps.&lt;/p&gt;

&lt;p&gt;Those are the popular ones. Everyone uses them. Everyone writes about them.&lt;/p&gt;

&lt;p&gt;But there's a whole layer of APIs that quietly power real-world apps, and most developers never hear about them until they need them badly.&lt;/p&gt;

&lt;p&gt;This list is for those APIs.&lt;/p&gt;

&lt;p&gt;Each one solves a specific, practical problem. Each one has a free tier. And together, they can power a surprisingly large chunk of a real product.&lt;/p&gt;

&lt;p&gt;Let's get into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. IP Geolocation API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Tells you where an IP address is coming from.&lt;/p&gt;

&lt;p&gt;Not just the country. Think city, timezone, ISP, currency, language, and even whether the request is coming through a VPN or proxy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why developers need it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show the right currency based on location&lt;/li&gt;
&lt;li&gt;Block traffic from certain regions&lt;/li&gt;
&lt;li&gt;Detect suspicious logins from unexpected locations&lt;/li&gt;
&lt;li&gt;Redirect users to the right regional version of your app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real use case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A SaaS app detects that a user suddenly logged in from a different country than usual. Was it a trip or a compromised account? The IP geolocation API gives you the first clue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to get it:&lt;/strong&gt; &lt;a href="https://ipgeolocation.io" rel="noopener noreferrer"&gt;IPGeolocation.io&lt;/a&gt; has a generous free tier and solid documentation. One API call returns everything: location, timezone, currency, VPN/proxy status, and more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8.8.8.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"United States"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mountain View"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time_zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/Los_Angeles"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"security"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"is_vpn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"is_proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. WHOIS and Domain Intelligence API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Lets you look up everything about a domain name programmatically.&lt;/p&gt;

&lt;p&gt;Registration date, registrar, expiry date, name servers, historical WHOIS records, reverse WHOIS lookups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why developers need it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fraud prevention: Is this domain newly registered? Big red flag.&lt;/li&gt;
&lt;li&gt;Competitor research tools&lt;/li&gt;
&lt;li&gt;Security tools that track domain ownership changes&lt;/li&gt;
&lt;li&gt;Due diligence before purchasing a domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real use case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A fintech app lets users pay via business email. Before processing, it checks if the domain in the email was registered in the last 7 days. If yes, it flags the transaction. This is a classic fraud signal that WHOIS data can catch instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to get it:&lt;/strong&gt; &lt;a href="https://whoisfreaks.com" rel="noopener noreferrer"&gt;WhoisFreaks.com&lt;/a&gt; covers WHOIS lookup, DNS lookup, reverse WHOIS, and historical records. All accessible via API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET https://api.whoisfreaks.com/v1.0/whois?apiKey&lt;span class="o"&gt;=&lt;/span&gt;YOUR_KEY&amp;amp;whois&lt;span class="o"&gt;=&lt;/span&gt;live&amp;amp;domainName&lt;span class="o"&gt;=&lt;/span&gt;example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Currency Exchange Rate API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Returns live and historical currency exchange rates for 150+ currencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why developers need it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;E-commerce apps showing prices in local currency&lt;/li&gt;
&lt;li&gt;Finance dashboards tracking portfolio value&lt;/li&gt;
&lt;li&gt;Invoice tools that bill in multiple currencies&lt;/li&gt;
&lt;li&gt;Any app that touches money across borders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real use case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A freelancer invoicing tool needs to show EUR, GBP, and PKR rates relative to USD. Instead of scraping random websites or using outdated hardcoded values, you hit one endpoint and get accurate, up-to-date rates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to get it:&lt;/strong&gt; &lt;a href="https://currencyfreaks.com" rel="noopener noreferrer"&gt;CurrencyFreaks.com&lt;/a&gt; provides live rates, historical rates, and currency conversion in one place. Clean API, free plan available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"GBP"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PKR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;278.50&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Commodity Price API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Returns live and historical prices for commodities like gold, silver, oil, wheat, natural gas, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why developers need it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fintech and investment tracking apps&lt;/li&gt;
&lt;li&gt;Agricultural platforms&lt;/li&gt;
&lt;li&gt;Economic data dashboards&lt;/li&gt;
&lt;li&gt;Any app that needs real-world asset pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real use case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A small inventory app for a jewelry business wants to calculate the melt value of gold items in stock. Instead of manually updating gold prices each morning, it calls the commodity API and calculates values automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to get it:&lt;/strong&gt; &lt;a href="https://commoditypriceapi.com" rel="noopener noreferrer"&gt;CommodityPriceAPI.com&lt;/a&gt; covers metals, energy, agriculture, and more with live and historical data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl https://api.commoditypriceapi.com/v2/usage &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: YOUR_API_KEY"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. DNS Lookup API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Lets you query DNS records for any domain: A, MX, TXT, CNAME, NS, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why developers need it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email validation tools (check if the domain has valid MX records)&lt;/li&gt;
&lt;li&gt;Security research and threat intelligence&lt;/li&gt;
&lt;li&gt;Infrastructure monitoring&lt;/li&gt;
&lt;li&gt;Domain verification workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real use case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A SaaS app lets users connect a custom domain. Before accepting it, the app checks the DNS records to confirm the CNAME is pointing correctly. Manual checking does not scale. An API call does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to get it:&lt;/strong&gt; &lt;a href="https://whoisfreaks.com" rel="noopener noreferrer"&gt;WhoisFreaks.com&lt;/a&gt; also covers DNS lookup as part of its API suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Multi-Category API Hub
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; One platform. 60+ production-ready APIs across geolocation, domain intelligence, DNS, finance, weather, automation, and utilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why developers need it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of managing 10 different API providers, 10 different API keys, 10 different billing relationships, and 10 different rate limits, you work with one platform.&lt;/p&gt;

&lt;p&gt;This matters more as your app grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real use case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A developer building a fraud detection tool needs geolocation, WHOIS, DNS, and email validation in one flow. Going to four separate providers adds complexity at every step. A single hub keeps it clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to get it:&lt;/strong&gt; &lt;a href="https://apifreaks.com" rel="noopener noreferrer"&gt;APIFreaks.com&lt;/a&gt; is an API hub with 60+ APIs across categories. Not a marketplace. Production-ready APIs maintained by the same team.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. AI Writing and Content API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Lets you add AI-powered writing and content generation to your app without building a model from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why developers need it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-generating product descriptions&lt;/li&gt;
&lt;li&gt;Summarizing long documents&lt;/li&gt;
&lt;li&gt;Drafting email responses&lt;/li&gt;
&lt;li&gt;Content creation tools for non-technical users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real use case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A blog CMS wants to give writers a "generate first draft" button. Instead of integrating raw LLM APIs and handling prompts, tokens, and outputs yourself, a purpose-built writing API handles the complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to get it:&lt;/strong&gt; &lt;a href="https://netus.ai" rel="noopener noreferrer"&gt;Netus.AI&lt;/a&gt; provides AI content tools with an API layer for developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;None of these APIs are flashy. None of them go viral on Twitter.&lt;/p&gt;

&lt;p&gt;But they solve real problems that real apps run into. And the developers who know about them ship faster, build smarter, and spend less time reinventing the wheel.&lt;/p&gt;

&lt;p&gt;The next time you find yourself thinking "I wish there was an API for this," there probably is. You just have not found it yet.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Today’s digital users expect personalized experiences, and if you don’t deliver, someone else will.</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Sun, 17 May 2026 07:44:44 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/todays-digital-users-expect-personalized-experiences-and-if-you-dont-deliver-someone-else-will-2cp8</link>
      <guid>https://dev.to/furqan_ashraf/todays-digital-users-expect-personalized-experiences-and-if-you-dont-deliver-someone-else-will-2cp8</guid>
      <description>&lt;p&gt;Personalize or Perish?&lt;/p&gt;

&lt;p&gt;Today’s digital users expect personalized experiences, and if you don’t deliver, someone else will.&lt;/p&gt;

&lt;p&gt;Studies show that content localization can increase conversion rates by up to 70%.&lt;/p&gt;

&lt;p&gt;That’s where ipgeolocation.io steps in. Our IP Location API empowers you to:&lt;/p&gt;

&lt;p&gt;✅ Auto-detect a visitor’s country, city, postal code, language, currency, and even time zone&lt;br&gt;
✅ Localize content instantly show prices in €, ¥, or $, display the nearest store, or auto-switch languages&lt;br&gt;
✅ Serve region-specific experiences without ever asking the user to select location or language&lt;/p&gt;

&lt;p&gt;Imagine displaying the perfect content for a user in Tokyo, Paris, or São Paulo, all in real time. That’s the power of geotargeting.&lt;/p&gt;

&lt;p&gt;Global companies use IP intelligence not just to track but to convert. Because the more relevant the experience, the higher the engagement.&lt;/p&gt;

&lt;p&gt;Deliver the right content to the right user at the right time automatically.&lt;/p&gt;

&lt;p&gt;Try the IP Location API today: &lt;a href="https://ipgeolocation.io/ip-location-api.html" rel="noopener noreferrer"&gt;https://ipgeolocation.io/ip-location-api.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>What Is an API Key? Everything You Need to Know Before Using an API</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Mon, 26 Jan 2026 08:27:30 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/what-is-an-api-key-everything-you-need-to-know-before-using-an-api-5ak2</link>
      <guid>https://dev.to/furqan_ashraf/what-is-an-api-key-everything-you-need-to-know-before-using-an-api-5ak2</guid>
      <description>&lt;p&gt;If you’ve ever tried to use an API and got stuck at the part where it asks for an API key, you’re not alone.&lt;/p&gt;

&lt;p&gt;Most beginners hit this wall:&lt;br&gt;
“I understand what an API does, but what is this key thing, and why do I need it?”&lt;/p&gt;

&lt;p&gt;Let’s clear that up without jargon, without buzzwords, and without pretending this is more complicated than it actually is.&lt;/p&gt;

&lt;h3&gt;
  
  
  First: What Is an API (Quick Recap)
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;API (Application Programming Interface)&lt;/strong&gt; is simply a way for one program to talk to another.&lt;/p&gt;

&lt;p&gt;Think of it like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app asks a question&lt;/li&gt;
&lt;li&gt;Another service answers with data&lt;/li&gt;
&lt;li&gt;The API is the agreed-upon way they communicate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, an API might be used&amp;nbsp;by a security tool to pull DNS records for analysis, by a website to check whether a domain exists, or&amp;nbsp;by a weather app to ask for today’s temperature.&lt;/p&gt;

&lt;p&gt;APIs are built for machines, not humans. That’s why the responses usually come back as JSON or XML instead of pretty web pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  So… What Is an API Key?
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;API key&lt;/strong&gt; is a &lt;strong&gt;unique string of characters&lt;/strong&gt; that tells an API that:&lt;br&gt;
“This request is coming from someone who’s allowed to be here.”&lt;/p&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;It’s not encryption by itself. It’s basically an &lt;strong&gt;ID badge&lt;/strong&gt; for your app or project.&lt;/p&gt;

&lt;p&gt;Example of what an API key might look like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;1f9ba190-c513-471b-a573-b8d008bb52fe&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When your app sends a request, it includes this key. The API checks it and decides whether to allow or deny access.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Simplest Analogy (That Actually Works)
&lt;/h3&gt;

&lt;p&gt;Imagine a restaurant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Menu&lt;/strong&gt; : the API (what you’re allowed to ask for)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waiter&lt;/strong&gt; : the API endpoint handling your request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kitchen&lt;/strong&gt; : the server/database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Membership card&lt;/strong&gt; : the API key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyone can look at the menu. But only members can place certain orders.&lt;/p&gt;

&lt;p&gt;Your API key proves you’re a member.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why APIs Use Keys in the First Place
&lt;/h3&gt;

&lt;p&gt;You might wonder:&lt;br&gt;
“Why not just make everything public?”&lt;/p&gt;

&lt;p&gt;Some APIs do;&amp;nbsp;those are called &lt;strong&gt;keyless APIs&lt;/strong&gt;. But most don’t, for a few important reasons:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Abuse prevention
&lt;/h4&gt;

&lt;p&gt;Without API keys, there would be nothing stopping anyone from sending unlimited requests, scraping massive amounts of data, or deliberately overloading the system. This kind of unrestricted access can quickly lead to abuse, performance issues, and higher operational costs for the API provider.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Usage tracking
&lt;/h4&gt;

&lt;p&gt;API keys allow providers to track how many requests each user or application is making, apply rate limits to prevent excessive usage, and offer different pricing tiers based on consumption. This helps API owners manage resources fairly while scaling access for different types of users.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Access control
&lt;/h4&gt;

&lt;p&gt;Some data exposed through APIs is paid, sensitive, or intentionally limited to specific use cases. This might include proprietary datasets, security-related information, or resources that should only be accessed under certain conditions or agreements.&lt;/p&gt;

&lt;p&gt;API keys make sure only approved users get access.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Keys vs Passwords (Important Difference)
&lt;/h3&gt;

&lt;p&gt;People often say:&lt;/p&gt;

&lt;p&gt;“An API key is just a password.”&lt;/p&gt;

&lt;p&gt;That’s kind of true, but incomplete.&lt;/p&gt;

&lt;p&gt;Key differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys usually identify a &lt;strong&gt;project or application&lt;/strong&gt;, not a person&lt;/li&gt;
&lt;li&gt;They’re meant for &lt;strong&gt;machine-to-machine communication&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They often have &lt;strong&gt;restricted permissions&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why API keys should always be treated as sensitive credentials. Exposing them in frontend code or committing them to public GitHub repositories can allow anyone to misuse your API access, potentially leading to abuse, unexpected costs, or even account suspension.&lt;/p&gt;

&lt;h3&gt;
  
  
  How API Keys Are Usually Used
&lt;/h3&gt;

&lt;p&gt;Most APIs expect the key in one of these places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP headers&lt;/li&gt;
&lt;li&gt;Query parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example (header-based):&lt;br&gt;
&lt;code&gt;Authorization: Bearer YOUR_API_KEY&lt;/code&gt;&lt;br&gt;
The exact format depends on the API, which always checks the documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Examples of DNS APIs and API Keys
&lt;/h3&gt;

&lt;p&gt;Let’s make this concrete.&lt;/p&gt;

&lt;p&gt;If you’re working with DNS data,&amp;nbsp;things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS records&lt;/li&gt;
&lt;li&gt;Domain history&lt;/li&gt;
&lt;li&gt;Infrastructure changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’re dealing with data that &lt;strong&gt;needs control and monitoring&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Platforms like &lt;strong&gt;WhoisFreaks.com&lt;/strong&gt; and &lt;strong&gt;APIFreaks.com&lt;/strong&gt; provide DNS APIs that require API keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why DNS APIs require keys
&lt;/h3&gt;

&lt;p&gt;Because DNS data is often used for security investigations, threat intelligence workflows, infrastructure mapping, and abuse detection, it needs to be protected from misuse. Unrestricted access to this kind of information could expose sensitive infrastructure details or enable malicious activity.&lt;/p&gt;

&lt;p&gt;An API key ensures that you are an authorized user whose requests are rate-limited and usage is tied to your account.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WhoisFreaks.com DNS APIs&lt;/strong&gt; can help you programmatically analyze DNS-related domain information&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;APIFreaks.com DNS API&lt;/strong&gt; lets developers query DNS data as part of a unified API platform&lt;/p&gt;

&lt;p&gt;In both cases, the API key is what unlocks that access.&lt;/p&gt;

&lt;p&gt;How Do You Get an API Key?&lt;/p&gt;

&lt;p&gt;Usually, the process looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up on the API provider’s website&lt;/li&gt;
&lt;li&gt;Go to the developer dashboard&lt;/li&gt;
&lt;li&gt;Generate an API key&lt;/li&gt;
&lt;li&gt;Copy it and store it securely&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some platforms allow multiple keys, key rotations, and permission scopes.&lt;/p&gt;

&lt;p&gt;All good signs of a well-designed API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Takeaway
&lt;/h3&gt;

&lt;p&gt;If you remember only one thing, remember this:&lt;/p&gt;

&lt;p&gt;An API is how software talks to software. An API key is how the API knows it can trust you.&lt;/p&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;Once this clicks, working with APIs becomes much less intimidating, whether you’re pulling DNS data, weather info, or anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQs: Common Questions About API Keys
&lt;/h3&gt;

&lt;p&gt;Is an API key the same as OAuth or a token?&lt;br&gt;
No. An API key is a simple identifier used to allow access and track usage. OAuth and access tokens are more advanced authorization mechanisms designed for user-level permissions, delegated access, and higher security scenarios.&lt;/p&gt;

&lt;p&gt;Can I share my API key with others?&lt;br&gt;
You generally should not. Sharing an API key means sharing your usage limits, billing, and permissions. If someone misuses it, the responsibility usually falls on the key owner.&lt;/p&gt;

&lt;p&gt;Where should I store an API key safely?&lt;br&gt;
API keys should be stored in environment variables, server-side configuration files, or secret managers. They should never be hardcoded into frontend code or exposed in public repositories.&lt;/p&gt;

&lt;p&gt;What happens if my API key is leaked?&lt;br&gt;
If an API key is exposed, revoke it immediately and generate a new one. Most platforms allow you to rotate keys to minimize damage without downtime.&lt;/p&gt;

&lt;p&gt;Do all APIs require an API key?&lt;br&gt;
No. Some APIs are keyless and publicly accessible, especially for non-sensitive or open data. However, most production APIs require keys to prevent abuse and manage access.&lt;/p&gt;

&lt;p&gt;Can I use one API key for multiple projects?&lt;br&gt;
You can, but it’s usually not recommended. Creating separate API keys per project makes monitoring, limiting, and revoking access much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Last Advice:
&lt;/h3&gt;

&lt;p&gt;If you still feel unsure about API keys after reading this, that’s normal. Once you start using one in a real request, the concept usually clicks very quickly.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>APIs Power Almost Everything You Build. Here’s Why That Matters</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Mon, 22 Dec 2025 12:36:08 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/apis-power-almost-everything-you-build-heres-why-that-matters-2c8a</link>
      <guid>https://dev.to/furqan_ashraf/apis-power-almost-everything-you-build-heres-why-that-matters-2c8a</guid>
      <description>&lt;p&gt;If you’ve built any modern application recently, you’ve already relied on APIs—probably without even thinking about it.&lt;/p&gt;

&lt;p&gt;Sending an email?&lt;br&gt;
Checking a user’s location?&lt;br&gt;
Fetching weather data, DNS records, screenshots, or currency rates?&lt;/p&gt;

&lt;p&gt;All of that happens through APIs.&lt;/p&gt;

&lt;p&gt;APIs quietly do the heavy lifting behind the scenes. They connect services, move data, and let developers build complex systems without reinventing the wheel. In this article, I’ll break down why APIs matter so much today, what developers actually need from them, and how a unified API platform like APIFreaks fits into real-world development workflows.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why APIs Are So Important Today
&lt;/h2&gt;

&lt;p&gt;Modern software isn’t built as a single, isolated system anymore. It’s a collection of services working together.&lt;/p&gt;

&lt;p&gt;APIs make this possible.&lt;/p&gt;

&lt;p&gt;Instead of building everything from scratch, developers use APIs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate workflows&lt;/li&gt;
&lt;li&gt;Pull real-time data&lt;/li&gt;
&lt;li&gt;Integrate third-party services&lt;/li&gt;
&lt;li&gt;Scale applications faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A SaaS app might use a DNS API to monitor domains&lt;/li&gt;
&lt;li&gt;A security tool might rely on WHOIS or IP data&lt;/li&gt;
&lt;li&gt;A monitoring service might need screenshots of web pages&lt;/li&gt;
&lt;li&gt;A dashboard might pull weather or currency data in real time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without APIs, all of this would be slower, more expensive, and harder to maintain.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Real Problem Developers Face With APIs
&lt;/h2&gt;

&lt;p&gt;APIs are powerful, but using too many different providers creates friction.&lt;/p&gt;

&lt;p&gt;Most developers run into the same issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different API styles and response formats&lt;/li&gt;
&lt;li&gt;Separate dashboards and billing systems&lt;/li&gt;
&lt;li&gt;Inconsistent documentation quality&lt;/li&gt;
&lt;li&gt;Multiple API keys to manage&lt;/li&gt;
&lt;li&gt;Extra time spent just on integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of focusing on building features, you end up managing vendors.&lt;/p&gt;

&lt;p&gt;That’s where a unified API approach becomes useful.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Developers Actually Need From an API Platform
&lt;/h2&gt;

&lt;p&gt;From a developer’s perspective, a good API platform should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be easy to integrate&lt;/li&gt;
&lt;li&gt;Have consistent request/response structures&lt;/li&gt;
&lt;li&gt;Offer reliable uptime&lt;/li&gt;
&lt;li&gt;Cover multiple use cases&lt;/li&gt;
&lt;li&gt;Provide clear documentation&lt;/li&gt;
&lt;li&gt;Scale as your product grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t want ten APIs from ten vendors when one well-designed platform can do the job.&lt;/p&gt;
&lt;h2&gt;
  
  
  Introducing &lt;a href="https://apifreaks.com/" rel="noopener noreferrer"&gt;APIFreaks&lt;/a&gt;: One Platform, Multiple Developer APIs
&lt;/h2&gt;

&lt;p&gt;APIFreaks.com is designed around this exact problem.&lt;/p&gt;

&lt;p&gt;Instead of chasing different providers, APIFreaks brings multiple commonly used APIs into a single, developer-friendly platform.&lt;/p&gt;

&lt;p&gt;It offers APIs for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS lookup&lt;/li&gt;
&lt;li&gt;WHOIS data&lt;/li&gt;
&lt;li&gt;IP intelligence&lt;/li&gt;
&lt;li&gt;Website screenshots&lt;/li&gt;
&lt;li&gt;Weather data&lt;/li&gt;
&lt;li&gt;Currency and commodity data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All APIs follow a consistent structure, making integration simpler and faster, especially if you’re building SaaS products, security tools, or automation workflows.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Quick Example: DNS Lookup API in Action
&lt;/h2&gt;

&lt;p&gt;Let’s look at a simple Python example using the &lt;a href="https://apifreaks.com/api/dns-lookup" rel="noopener noreferrer"&gt;DNS Lookup API from APIFreaks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This example fetches DNS records for a domain, which can be useful for monitoring, security analysis, or troubleshooting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install requests

import requests 

url = "https://api.apifreaks.com/v1.0/domain/dns/live?host-name=example.com&amp;amp;type=all"

payload = {}
headers = {
    'X-apiKey': 'API-KEY'
}

response = requests.request("GET", url, headers=headers, data=payload)

print(response.text)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a single request, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect DNS records&lt;/li&gt;
&lt;li&gt;Track configuration changes&lt;/li&gt;
&lt;li&gt;Detect misconfigurations or suspicious updates&lt;/li&gt;
&lt;li&gt;Automate domain monitoring tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of task that would otherwise require custom scripts or multiple tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;Here’s how developers are using APIs like these in practice:&lt;/p&gt;

&lt;h3&gt;
  
  
  SaaS Platforms
&lt;/h3&gt;

&lt;p&gt;Automate domain checks, DNS monitoring, and IP intelligence as part of onboarding, analytics, or security workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cybersecurity Tools
&lt;/h3&gt;

&lt;p&gt;Combine DNS, WHOIS, and IP data to detect suspicious infrastructure and risky domains early.&lt;/p&gt;

&lt;h3&gt;
  
  
  DevOps &amp;amp; Monitoring
&lt;/h3&gt;

&lt;p&gt;Track DNS changes, capture website screenshots, and monitor uptime using API-driven automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal Tools &amp;amp; Automation
&lt;/h3&gt;

&lt;p&gt;Build internal dashboards that pull real-time data without manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  APIs Are the Building Blocks of Modern Software
&lt;/h2&gt;

&lt;p&gt;APIs aren’t just integrations, they’re core building blocks of modern applications.&lt;/p&gt;

&lt;p&gt;When you choose the right API platform, you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build faster&lt;/li&gt;
&lt;li&gt;Reduce complexity&lt;/li&gt;
&lt;li&gt;Scale more easily&lt;/li&gt;
&lt;li&gt;Spend more time on real product development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A unified platform like &lt;strong&gt;APIFreaks&lt;/strong&gt; helps keep your architecture clean and your integrations manageable, especially as your project grows.&lt;/p&gt;

&lt;p&gt;If you’re building something that relies on external data or automation, APIs aren’t optional anymore, they’re essential.&lt;/p&gt;

</description>
      <category>api</category>
      <category>developers</category>
      <category>saas</category>
      <category>automation</category>
    </item>
    <item>
      <title>Fraudsters Can't Hide Anymore. Here's How to Spot Them.</title>
      <dc:creator>Furqan Ashraf</dc:creator>
      <pubDate>Thu, 14 Aug 2025 15:13:40 +0000</pubDate>
      <link>https://dev.to/furqan_ashraf/fraudsters-cant-hide-anymore-heres-how-to-spot-them-4e7b</link>
      <guid>https://dev.to/furqan_ashraf/fraudsters-cant-hide-anymore-heres-how-to-spot-them-4e7b</guid>
      <description>&lt;p&gt;When a user or customer claims to be in London but their IP location shows Lagos, that's a red flag. In the world of e-commerce and online platforms, these inconsistencies are often the first sign of fraud. The ability to &lt;strong&gt;spot and block these fake users in real time&lt;/strong&gt; is no longer a luxury; it's a must.&lt;br&gt;
This guide will show you how to integrate &lt;strong&gt;IPgeolocation.io's IP Security API&lt;/strong&gt; to get real-time threat intelligence, allowing you to &lt;strong&gt;spot fraud before it happens&lt;/strong&gt; and &lt;a href="https://dev.to/furqan_ashraf_07df2e7ef3a/apis-power-almost-everything-you-build-heres-why-that-matters-2c8a"&gt;build a more secure platform&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Threat Intelligence You Need, Instantly
&lt;/h2&gt;

&lt;p&gt;The IP Security API goes beyond simple location data. It provides a full list of threat intelligence, giving you a complete picture of who is interacting with your platform and whether they're a potential risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threat Scoring&lt;/strong&gt;: The API gives a risk score from &lt;strong&gt;0 to 100&lt;/strong&gt;. This score is measurable, so you can instantly check a user's risk level and build rules around it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anonymizer Detection&lt;/strong&gt;: Stop fraudsters from using common tricks. The API can detect if an IP is associated with &lt;strong&gt;a VPN, a proxy, Tor usage, or a known bot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider Details&lt;/strong&gt;: Get specifics on the hiding tool, including the proxy type (e.g., residential, data center) and the provider's name (e.g., NordVPN).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detailed Data&lt;/strong&gt;: For a full view, the API adds to its threat data with detailed geolocation, ASN, and ISP/company information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Up-to-Date Network&lt;/strong&gt;: The API is backed by an &lt;strong&gt;always-updated global IP information network&lt;/strong&gt;, making sure you're always using the latest details to protect your users.&lt;/p&gt;
&lt;h2&gt;
  
  
  Putting It to the Test: A Code Example
&lt;/h2&gt;

&lt;p&gt;Let's look at a quick example using Python to show how easy it is to integrate the API into your work. This script will check a given IP address for a security threat and print the results. You can easily change this to fit your specific application's needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input&lt;/strong&gt;&lt;br&gt;
This is the Python script used to make the API call. The IP_ADDRESS variable is set to a known Tor exit node to show how the API identifies a security threat.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests

# Replace with your actual API key from Ipgeolocation.io
API_KEY = 'YOUR_API_KEY'

# The IP address to check.
IP_ADDRESS = '85.239.127.126'

# The API endpoint for IP geolocation and security data
URL = f'https://api.ipgeolocation.io/ipgeo?apiKey={API_KEY}&amp;amp;ip={IP_ADDRESS}&amp;amp;security=1'

def check_ip_threat(ip_address):
    """
    Checks an IP address for security threats using the IPgeolocation.io API.
    """
    try:
        response = requests.get(URL.replace(IP_ADDRESS, ip_address))
        response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)

        data = response.json()

        print(f"--- IP Security Report for {ip_address} ---")
        print(f"Country: {data.get('country_name', 'N/A')}")
        print(f"City: {data.get('city', 'N/A')}")
        print(f"ISP: {data.get('isp', 'N/A')}")
        print("-" * 35)

        security_data = data.get('security', {})
        if security_data:
            print(f"Threat Score: {security_data.get('threat_score', 'N/A')}")
            print(f"Is VPN: {security_data.get('is_vpn', False)}")
            print(f"Is Proxy: {security_data.get('is_proxy', False)}")
            print(f"Is TOR: {security_data.get('is_tor', False)}")
            print(f"Is Bot: {security_data.get('is_threat', False)}")
            print(f"Provider: {security_data.get('proxy_provider', 'N/A')}")
        else:
            print("Security data not available for this IP.")

    except requests.exceptions.RequestException as e:
        print(f"An error occurred: {e}")
    except ValueError as e:
        print(f"Error parsing JSON response: {e}")

if __name__ == '__main__':
    check_ip_threat(IP_ADDRESS)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;API Response&lt;/strong&gt;&lt;br&gt;
This is a sample of the JSON response you would receive from the &lt;strong&gt;ipgeolocation.io API&lt;/strong&gt; for the &lt;strong&gt;IP address 85.239.127.126&lt;/strong&gt;. Notice the &lt;strong&gt;high threat_score&lt;/strong&gt; and the &lt;strong&gt;is_tor: true flag&lt;/strong&gt;, which indicates that this &lt;strong&gt;IP is a security risk&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ip": "85.239.127.126",
  "location": {
    "continent_name": "Europe",
    "country_name": "Germany",
    "city": "Frankfurt am Main"
  },
  "network": {
    "asn": {
      "organization": "Server-Service"
    },
    "isp": "Server-Service"
  },
  "security": {
    "threat_score": 90,
    "is_tor": true,
    "is_proxy": false,
    "proxy_type": "TOR",
    "proxy_provider": "",
    "is_anonymous": true,
    "is_known_attacker": false,
    "is_spam": false,
    "is_bot": false,
    "is_cloud_provider": false,
    "cloud_provider": ""
  },
  "time_zone": {
    "name": "Europe/Berlin"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;The IP Security API provides security that scales with your platform. Here’s how you can &lt;strong&gt;implement&lt;/strong&gt; it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E-commerce&lt;/strong&gt;: Use the API in your checkout process. If a high risk score is found or a user is on a known proxy, you can &lt;strong&gt;flag the transaction for a closer look or block it&lt;/strong&gt; to stop payment fraud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fintech&lt;/strong&gt;: During user sign-up, implement the API to check a user's location and connection type. This helps with confirming who the user is and &lt;strong&gt;spotting suspicious new accounts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SaaS Platforms&lt;/strong&gt;: Prevent bots from creating fake accounts. By checking the is_threat or is_bot flags during sign-up, you can &lt;strong&gt;greatly reduce spam and keep your data clean&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;IP information is a &lt;strong&gt;powerful set of tools&lt;/strong&gt; for your security. By integrating the IP Security API, you move from handling fraud after it happens to a &lt;strong&gt;forward-thinking, real-time security model&lt;/strong&gt;. It's a simple, effective way to get deep insights into your users and &lt;strong&gt;protect your platform from a wide range of threats&lt;/strong&gt;.&lt;br&gt;
Ready to get started? Sign up for your &lt;strong&gt;&lt;a href="https://ipgeolocation.io/ip-security-api.html" rel="noopener noreferrer"&gt;Free API Key&lt;/a&gt;&lt;/strong&gt; and secure your platform today.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ipgeolocation</category>
      <category>security</category>
      <category>fruad</category>
    </item>
  </channel>
</rss>
