GrizzlyGrizzly

Overview

Grizzly is a phishing-detection API. Send it any URL and it classifies whether the page is a deceptive login or credential-harvesting attempt, returning a verdict plus a few human-readable reasons.

It's built for developers, security integrators, email gateways, and SOC tooling — anywhere you need to check URLs programmatically. The same classification engine powers the Grizzly web app and Chrome extension.

All requests go to https://api.grizzlysec.com. The free tier includes 10 scans per day, intended for evaluation and personal use. For higher volume, get in touch.

Quickstart

1. Get an API key

Sign in with Google or GitHub, then create an API key from your dashboard. The key is shown once and starts with gk_live_ — store it somewhere safe.

2. Make your first scan

Send a URL to the scan endpoint with your key in the Authorization header:

curl https://api.grizzlysec.com/scan \
  -H "Authorization: Bearer gk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://paypal.com.account-verify.example"}'

3. Read the response

You get back a verdict in classification and an explanation in reasons:

{
  "scan_id": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://paypal.com.account-verify.example",
  "final_url": "https://paypal.com.account-verify.example",
  "host": "paypal.com.account-verify.example",
  "classification": "phish",
  "reasons": [
    "Impersonates a trusted brand",
    "Domain registered 5 days ago"
  ],
  "target_brand": "PayPal",
  "scanned_at": "2026-06-06T15:23:45Z"
}

Authentication

Grizzly uses bearer tokens. Pass your API key in the Authorization header on every request:

Authorization: Bearer gk_live_...

Keys are issued per user and can be rotated from your dashboard. Treat your key like a password: use it only from server-side code, and never ship it in a browser or mobile app. A missing or invalid key returns 401 (see Errors).

API reference

POST https://api.grizzlysec.com/scan

Submit a URL for classification.

Request body

FieldTypeDescription
urlstring · requiredThe URL to scan. If you omit the scheme, https:// is assumed.
scan_modestring · optionalOne of auto (default), fast, or browser. auto runs a fast HTTP check and falls back to a full browser render when needed; fast skips the browser; browser forces a full render.

Response

A successful request returns 200 with a JSON object:

{
  "scan_id": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://roblox.com.glogin-secure.example",
  "final_url": "https://roblox.com.glogin-secure.example",
  "host": "roblox.com.glogin-secure.example",
  "classification": "phish",
  "reasons": [
    "Impersonates a trusted brand",
    "Domain registered 3 days ago"
  ],
  "target_brand": "Roblox",
  "scanned_at": "2026-06-06T15:23:45Z"
}
FieldTypeDescription
scan_idstringUnique scan ID. Also the path of the shareable result page: grizzlysec.com/scan/{scan_id}.
urlstringThe URL you submitted.
final_urlstringThe URL actually classified, after following redirects.
hoststringHostname of final_url.
classificationstring · nullThe verdict — see values below.
reasonsstring[]Human-readable explanations for the verdict. May be empty.
target_brandstring · optionalThe brand being impersonated, when one is identified (e.g. Roblox).
scanned_atstringISO-8601 UTC timestamp.

Verdict values

  • phish — confirmed phishing or credential harvesting.
  • suspicious — suspicious signals, but not confirmed.
  • neutral — no phishing signals detected (e.g. a non-login page).
  • valid — a verified, trusted domain.
  • null — the scan ran but couldn't reach a verdict (see Errors).

Rate limits

The free tier allows 10 scans per day, reset at 00:00 UTC. Only successful (2xx) scans count — failed requests don't consume quota. Every response includes:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1717891200

X-RateLimit-Reset is a Unix timestamp for the next reset. Exceeding the limit returns 429 with a Retry-After header.

Errors

Errors use standard HTTP status codes, and the body has the shape { "detail": ... }.

StatusMeaning
200Success. A scan that runs but can't reach a verdict also returns 200, with classification: null and an explanation in reasons.
401Unauthorized — missing or invalid API key. {"detail":"unauthorized"}
422Invalid request body (e.g. a missing url). detail lists the validation problems.
429Rate limit exceeded. {"detail":"rate_limit_exceeded"}, with a Retry-After header.
500Internal error. Usually transient — retry the request.

Terminal scan errors

When a scan completes but can't classify the page — a DNS failure, TLS error, or timeout — you still get 200 with classification: null and a human-readable reason:

{
  "scan_id": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://does-not-resolve.example",
  "final_url": "https://does-not-resolve.example",
  "host": "does-not-resolve.example",
  "classification": null,
  "reasons": ["Domain does not resolve (DNS lookup failed)"],
  "scanned_at": "2026-06-06T15:23:45Z"
}