Quick start tutorial

Screen your first domain list in two minutes

Four steps, one script, no SDK. By the end of this page you will have run a real list of hostnames against 390,000+ DNS-verified phishing domains and written the hits out to a file your blocklist can consume.

bash
$ curl "https://phishingdetectionapi.com/api/v1/check?domain=0-105.com&apikey=YOUR_KEY"

{
  "domain": "0-105.com",
  "is_phishing": true,
  "category": "phishing/malware",
  "dns_status": "resolves",
  "last_checked": "2025-07-09",
  "confidence": 0.98,
  "database_size": 212370
}

# that is the whole API. everything below is
# about running it over a list instead of one name.
1Get your API keySubscribe to a plan, copy the key
2Build a domain listOne hostname per line
3Run the scriptBatched, 100 at a time
4Act on the hitsBlock, quarantine, investigate
Before you start

Four things worth knowing first

None of them will slow you down, but all four change how you write the script.

One lookup is one domainA single check costs one lookup, and a batch costs one lookup per domain in the array. Batching saves round trips, not money.
Batches cap at 100The batch endpoint accepts a maximum of 100 domains per POST. Chunk your list; the script below does it for you.
The key is a server-side secretNever put it in browser JavaScript or a mobile binary. If a client needs a verdict, proxy the call through your own backend.
Send hostnames, not URLsStrip the scheme, the path and the query string. You will send less data and you will not waste lookups on duplicates.

What the API actually answers

Send a domain, get back whether it is present in a database of DNS-verified active phishing hosts, rebuilt every 24 hours at 04:30 UTC. A hit returns is_phishing: true with a category of phishing/malware, a dns_status of resolves and a confidence of 0.98.

A domain that is not in the database returns an ordinary 200 with is_phishing: false. That is not "this domain is safe" — it means it is not one of the live phishing hosts currently tracked. Design around that distinction and it will never surprise you.
Walkthrough

From nothing to a screened list

Follow these in order. Steps one and two take a minute between them; step three is a copy-paste.

1Step

Get your API key

Subscribe to a monthly plan on the pricing page — the Growth plan at $99/month gives you 25,000 lookups, which is more than enough to screen a first list and prove the integration. After payment, sign in and your API key is shown in the dashboard. Your key is your username from registration.

Where it livesDashboard, after sign-in.
How to pass itapikey query param or POST body field.
ValidityActive while your monthly plan is current.
Store it outside the script. Read the key from an environment variable rather than pasting it into a file you will later commit. The examples below use os.environ for exactly that reason.
2Step

Prepare the list of domains you want to screen

Put one hostname per line in a plain text file called domains.txt. Where the list comes from depends on what you are trying to prove: distinct sender and link hostnames from a day of mail logs, distinct external destinations from a day of proxy or DNS logs, or the domains already attached to an open investigation.

Sort and deduplicate before you save. A day of proxy traffic that looks like millions of events usually reduces to a few thousand distinct hostnames — and every duplicate removed is a lookup not spent.
domains.txt
0-105.com
0-amazon.weebly.com
00-ede.pages.dev
000-access-secure.web.app
google.com
example.com
mail.supplier-invoices.example.org

# build one from proxy logs:
$ awk '{print $7}' access.log | cut -d/ -f3 | sort -u > domains.txt
3Step

Run the screening script

Save the following as screen_domains.py. It reads domains.txt, chunks it into batches of 100, posts each batch to the batch endpoint, and writes every result to results.csv while printing the hits as it goes.

screen_domains.py
# pip install requests
import os, csv, time, requests

API_KEY  = os.environ["PDA_API_KEY"]          # never hard-code the key
BASE_URL = "https://phishingdetectionapi.com/api/v1"
BATCH    = 100                                # hard cap enforced by the API

def load_domains(path="domains.txt"):
    with open(path) as f:
        names = (line.strip().lower() for line in f)
        return sorted({n for n in names if n and not n.startswith("#")})

def chunks(seq, size):
    for i in range(0, len(seq), size):
        yield seq[i:i + size]

def screen(domains):
    results = []
    for group in chunks(domains, BATCH):
        r = requests.post(
            f"{BASE_URL}/batch",
            json={"apikey": API_KEY, "domains": group},
            timeout=20,
        )
        if r.status_code == 429:               # rate limit or credits exhausted
            print("429:", r.json().get("error"))
            time.sleep(2)
            continue
        r.raise_for_status()
        payload = r.json()
        results.extend(payload["results"])
        print(f"checked {payload['checked']}, "
              f"phishing_found {payload['phishing_found']}, "
              f"credits_used {payload['credits_used']}")
        time.sleep(0.15)                       # stay under 10 req/s
    return results

if __name__ == "__main__":
    domains = load_domains()
    results = screen(domains)

    with open("results.csv", "w", newline="") as out:
        w = csv.writer(out)
        w.writerow(["domain", "is_phishing", "category", "dns_status"])
        for row in results:
            w.writerow([row["domain"], row["is_phishing"],
                        row["category"], row["dns_status"]])

    hits = [r for r in results if r["is_phishing"]]
    print(f"\n{len(hits)} phishing domains out of {len(results)} checked")
    for h in hits:
        print("  ", h["domain"])
Run it with export PDA_API_KEY=your_key followed by python3 screen_domains.py. The 0.15-second pause keeps you comfortably under the documented ceiling of 10 requests per second per key.
4Step

Read the output and act on it

You now have results.csv with one row per domain and a printed summary of the hits. What happens next is the part that matters, and it should be proportionate to how expensive a wrong answer is to reverse.

results.csv
domain,is_phishing,category,dns_status
0-105.com,True,phishing/malware,resolves
0-amazon.weebly.com,True,phishing/malware,resolves
00-ede.pages.dev,True,phishing/malware,resolves
000-access-secure.web.app,True,phishing/malware,resolves
google.com,False,,
example.com,False,,
mail.supplier-invoices.example.org,False,,

$ awk -F, '$2=="True"{print $1}' results.csv > blocklist.txt
Hard blockResolver policy zone, firewall category — cheap to reverse.
QuarantineMail gateway: hold and review rather than silently delete.
WarnCustomer-facing journeys: interstitial, proceed at own risk.
Keep an allow-list in front of the check. Your own estate, your suppliers and your SaaS vendors should never reach the API at all. It saves lookups and it is the cheapest insurance against a false positive taking something down.
From script to system

What the same four steps look like in production

The script you just ran is the whole integration. Productionising it is about where you put each stage, not about adding code.

Extract hostnames
Allow-list + cache
Batch of 100
Route on result
Block or quarantine

Extraction lives where hostnames already do

An MTA hook, a proxy log shipper, or a SIEM search returning distinct external destinations for the last interval. You are not creating a new data source — you are reading one you already have.

Allow-list and cache is the stage people skip

It does two jobs at once: it removes the domains you already trust, and it collapses repeats. That is what keeps a high-volume pipeline inside a sensible monthly allowance.

Routing is where the design decisions live

Treat phishing_found as a routing key, not a verdict on the payload. An analyst handed three named domains starts work; an analyst handed "suspicious activity" starts by reproducing your query.

One exception worth naming: if your enforcement point is a DNS resolver or a firewall rather than an application, stop using per-lookup API calls for it entirely and switch to the bulk export. The economics and the latency both favour it, and there is no per-query network dependency at decision time — that path is described on the daily threat feed page.

Where to go next

Six pages that continue this one

Reference material, commercial detail and the integration shapes for specific systems.

You have the script. Now get the key.

Subscribe to a plan, export your key as PDA_API_KEY, and run the screening script against yesterday's proxy log. Most teams find something on the first pass.