POST
/v1/phishing/checkphishCheckPhish URL Scan
Scan URLs against CheckPhish.ai for phishing detection, brand impersonation, and malware hosting. Uses a submit-and-poll pattern with SSRF protection.
Credits
2
Per request
Max URLs
5
Per request
Timeout
60s
Polling up to 30s
Plans
All
Free, Starter, Professional, Enterprise
Disposition Types
| Disposition | Description |
|---|---|
| clean | URL is not flagged for any threats |
| phishing | URL identified as a phishing page attempting to steal credentials or data |
| malware | URL is hosting or distributing malicious software |
| suspicious | URL exhibits suspicious characteristics but is not definitively malicious |
| unknown | CheckPhish could not determine the disposition of the URL |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| urls | string[] | Yes | Array of 1-5 URLs to scan. Must use http:// or https:// scheme. Duplicates are removed before scanning. |
How It Works
- URLs are validated, deduplicated, and checked against SSRF protection (private/reserved IPs are blocked).
- Each URL is submitted to CheckPhish.ai sequentially (rate limit friendly).
- The API polls CheckPhish for results (up to 30s per URL) using a submit-and-poll pattern.
- 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
| Field | Type | Description |
|---|---|---|
| url | string | The scanned URL |
| state | complete | unknown | Whether a completed assessment was verified |
| risk | malicious | suspicious | no_known_threats | unknown | Risk distinct from scan completion |
| jobID | string | CheckPhish job identifier |
| status | "done" | "pending" | "error" | Scan completion status |
| disposition | "clean" | "phishing" | "malware" | "suspicious" | "unknown" | Threat classification |
| brand | string | Impersonated brand detected (e.g. PayPal, Microsoft) |
| insights | object | Withheld (null) to avoid exposing provider-hosted artifacts |
| screenshot | null | Withheld to avoid exposing provider-hosted artifacts |
| resolved | boolean | Whether 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.