Screen a link for danger
Every link your app renders, stores or forwards is a liability the moment someone else controls the destination.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
This endpoint runs a fast structural screen of the URL itself and flags the red flags attackers lean on — link shorteners that hide the destination, an @ used to disguise the real host, a bare IP-address host, punycode lookalike domains — so you can hold, warn or take a closer look before a link reaches a user's screen. It reads the URL string; it does not follow redirects or look the domain up in a threat database.
Who ends up needing this
Teams that accept user-submitted links are the obvious case: chat apps, forum software, ticketing systems, comment sections, browser extensions. Less obvious but just as common are email platforms rewriting links at send time, ad networks vetting creative URLs, and internal tools that ingest URLs from spreadsheets or CRM exports nobody has looked at closely. Anywhere a URL crosses a trust boundary, a cheap structural screen has a job to do.
What actually happens to a URL
Submit a URL to POST /verify/url-safe and the task screens the string itself for the structural patterns attackers lean on: a known link shortener (bit.ly, tinyurl, goo.gl and the like) that hides where the link really goes, an @ in the URL used to make a hostile host look like a trusted one, a host that is a bare IP address instead of a domain, and punycode (xn--) sequences used to build lookalike domains. It returns a clear suspicious-or-not verdict with the flag that fired, not a bare score. It does not resolve the link, follow its redirects, or check it against a reputation feed — it is a structural screen, fast and cheap enough to run on every link.
A short history of a long problem
URL-based attacks have outlived nearly every other web threat category because the fix is structurally hard: a URL is just text until something resolves it, and by then the damage window has often opened. Shortened links made it worse by hiding destinations entirely, and lookalike domains built with punycode or a disguising @ now routinely slip past a hurried glance. Eyeballing a URL stopped being a reliable defense years ago, which is exactly why an automated first-pass screen for the obvious structural tricks is worth running on every link before anyone clicks.
Fitting it into a pipeline
Because the task is asynchronous, you fire the request at the moment a URL is submitted or ingested and move on; the verdict lands on your webhook, or you fetch it from a signed link valid for 24 hours if you prefer to poll. Failed checks are never billed, three retries happen automatically, and a request that still can't complete returns a clear error rather than a silent gap, which matters when the check is standing between a user and a click they cannot take back.
What it deliberately does not do
This endpoint screens the structure of the URL string; it does not follow redirects to a final destination, fetch the page, or look the domain up in a threat or reputation database. That means a malicious page on a clean-looking, un-shortened domain will pass this screen, and a shortened link is flagged for being shortened rather than resolved to what it hides. Pair it with verify.phishing when you need a content-level read on a landing page or an email; keeping the two separate is what keeps this check fast and cheap enough to run on every single link at submission time.
What you can do with it
Chat and messaging apps
Screen links the moment a user pastes them into a conversation, so a shortened or disguised link gets flagged before anyone else in the thread taps it.
Support and ticketing tools
Screen URLs attached to inbound tickets automatically, since agents handling high volume are prime phishing targets via crafted links using shorteners or lookalike hosts.
Comment sections and forums
Hold posts containing a flagged link for a beat, catching the obvious structural red flags of link spam before they reach public view.
Bulk link audits
Sweep a spreadsheet of URLs collected from old campaigns or scraped sources to flag the ones using shorteners, raw-IP hosts or lookalike punycode that deserve a closer manual look.
FAQ
Is there a free tier for the URL screener API?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
How much does a URL check cost?
$0.002 per request, billed only on success. Retries on a failed check happen automatically and you are never charged for a check that didn't complete.
How do I get the result?
By signed webhook, which we recommend for production pipelines, or by fetching a signed link that stays valid for 24 hours if you'd rather poll.
Does it follow redirect chains?
No. It screens the URL string as submitted; it flags a known link shortener because a shortener hides the destination, rather than following it. To evaluate where a shortened link actually lands, resolve it yourself first and then screen the destination.
Can I check URLs in bulk?
Yes, call the endpoint per URL as fast as your integration allows; each call is an independent async task with its own task_id.
Does this replace phishing content analysis?
No. This endpoint screens the URL's structure for common red flags; for scoring a page or email's phishing signals, use verify.phishing alongside it.
What response times should I expect?
The task runs asynchronously and returns a task_id immediately; the check is a fast structural screen, and the webhook fires as soon as the verdict is ready.
Is my submitted URL data kept?
No, data is deleted after the retention window and is never used for training, regardless of the verdict returned.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/verify/url-safe \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://bit.ly/xyz"}'const res = await fetch("https://api.kit.forhosting.com/verify/url-safe", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"url": "https://bit.ly/xyz"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/verify/url-safe",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"url": "https://bit.ly/xyz"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/verify/url-safe", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"url":"https://bit.ly/xyz"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"url":"https://bit.ly/xyz"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/verify/url-safe", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"url": "https://bit.ly/xyz"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "verify.url_safe",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |