POST/v1/phishing/checkphish

CheckPhish URL Scan

Scan URLs against CheckPhish.ai for phishing detection, brand impersonation, and malware hosting. Uses a submit-and-poll pattern with SSRF protection.

Try this endpoint in the playground →

Credits

2

Per request

Max URLs

5

Per request

Timeout

60s

Polling up to 30s

Plans

All

Free, Starter, Professional, Enterprise

Disposition Types

DispositionDescription
cleanURL is not flagged for any threats
phishingURL identified as a phishing page attempting to steal credentials or data
malwareURL is hosting or distributing malicious software
suspiciousURL exhibits suspicious characteristics but is not definitively malicious
unknownCheckPhish could not determine the disposition of the URL
Pending, error, unresolved, and unknown results are inconclusive. Preserve positive threat evidence, but never treat a missing disposition as clean. This active scan submits the URL to CheckPhish and may visit its destination. Do not submit private sign-in or document-access links without an approved data-processing policy; retention and provider access are separate from the verdict.

Request Body

FieldTypeRequiredDescription
urlsstring[]YesArray of 1-5 URLs to scan. Must use http:// or https:// scheme. Duplicates are removed before scanning.

How It Works

  1. URLs are validated, deduplicated, and checked against SSRF protection (private/reserved IPs are blocked).
  2. Each URL is submitted to CheckPhish.ai sequentially (rate limit friendly).
  3. The API polls CheckPhish for results (up to 30s per URL) using a submit-and-poll pattern.
  4. Results are returned with disposition, brand detection, insights, and completion state. Provider-hosted screenshots and raw insights are withheld.

Code Examples

cURL

curl -X POST https://api.dfir-lab.ch/v1/phishing/checkphish \
  -H "Authorization: Bearer sk-dfir-your-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://suspicious-site.com",
      "https://another-url.com"
    ]
  }'

Python

import requests

response = requests.post(
    "https://api.dfir-lab.ch/v1/phishing/checkphish",
    headers={
        "Authorization": "Bearer sk-dfir-your-key-here",
        "Content-Type": "application/json",
    },
    json={
        "urls": [
            "https://suspicious-site.com",
            "https://another-url.com",
        ]
    },
    timeout=90,  # Allow for polling time
)

response.raise_for_status()
data = response.json()
for result in data["data"]["results"]:
    disposition = result["disposition"]
    brand = result.get("brand", "N/A")
    if disposition in ("phishing", "malware", "suspicious"):
        print(f"FLAGGED {result['url']}: {disposition} (brand: {brand})")
    elif result.get("state") == "complete" and result.get("status") == "done" and result.get("resolved") is True and not result.get("error") and disposition == "clean":
        print(f"NO KNOWN THREATS {result['url']} — safety unverified")
    else:
        print(f"UNKNOWN {result['url']} — scan incomplete or inconclusive")

TypeScript

const response = await fetch(
  "https://api.dfir-lab.ch/v1/phishing/checkphish",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer sk-dfir-your-key-here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      urls: [
        "https://suspicious-site.com",
        "https://another-url.com",
      ],
    }),
  }
);

if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { data, meta } = await response.json();

console.log(`Credits used: ${meta.credits_used}`);
console.log(`Summary: ${data.summary.flagged} flagged / ${data.summary.total} total`);

for (const result of data.results) {
  const flagged = ["phishing", "malware", "suspicious"].includes(result.disposition);
  const completed = result.state === "complete" && result.status === "done" && result.resolved === true && !result.error;
  const status = flagged ? "FLAGGED"
    : completed && result.disposition === "clean" ? "NO KNOWN THREATS" : "UNKNOWN";
  console.log(`[${status}] ${result.url} - ${result.disposition}`);
}

Example Response

{
  "data": {
    "results": [
      {
        "url": "https://suspicious-site.com",
        "jobID": "abc123",
        "status": "done",
        "state": "complete",
        "risk": "malicious",
        "disposition": "phishing",
        "brand": "PayPal",
        "insights": null,
        "screenshot": null,
        "resolved": true
      },
      {
        "url": "https://another-url.com",
        "jobID": "def456",
        "status": "done",
        "state": "complete",
        "risk": "no_known_threats",
        "disposition": "clean",
        "brand": "",
        "insights": null,
        "screenshot": null,
        "resolved": true
      }
    ],
    "summary": {
      "total": 2,
      "clean": 1,
      "flagged": 1,
      "pending": 0
    }
  },
  "meta": {
    "request_id": "req_abc",
    "credits_used": 2,
    "credits_remaining": 98,
    "processing_time_ms": 5000
  }
}

Response Fields

FieldTypeDescription
urlstringThe scanned URL
statecomplete | unknownWhether a completed assessment was verified
riskmalicious | suspicious | no_known_threats | unknownRisk distinct from scan completion
jobIDstringCheckPhish job identifier
status"done" | "pending" | "error"Scan completion status
disposition"clean" | "phishing" | "malware" | "suspicious" | "unknown"Threat classification
brandstringImpersonated brand detected (e.g. PayPal, Microsoft)
insightsobjectWithheld (null) to avoid exposing provider-hosted artifacts
screenshotnullWithheld to avoid exposing provider-hosted artifacts
resolvedbooleanWhether the URL resolved successfully

Results are powered by CheckPhish.ai by Bolster. The submit-and-poll pattern means responses may take longer than other endpoints (up to 60 seconds for 5 URLs).

SSRF protection blocks URLs that resolve to private or reserved IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.). Each URL is scanned sequentially to respect upstream rate limits.

A clean disposition means CheckPhish did not flag the URL at the time of the scan. It is not a guarantee of safety.