Validate a URL
URLs look simple until a user pastes one with a stray space, a missing scheme, mismatched casing or sneaky Unicode lookalikes, and your database ends up with three versions of what should be one link. This endpoint parses a URL into its real components, checks it's actually well-formed, and returns a normalised version you can safely store and compare.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
The messiness hiding behind a familiar format
A URL looks like one string but is actually several parts glued together — scheme, host, port, path, query and fragment — each with its own rules about what characters are allowed and how they should be encoded. Users, copy-paste, browser autofill, and even other software routinely produce URLs that are technically broken or inconsistently formatted, and catching that before it reaches your database or your outbound request logic saves real headaches much later on.
What the check actually verifies
The response breaks the URL into its components, confirms whether it conforms to the standard URL syntax, and returns a normalised form — consistent casing for scheme and host, resolved relative segments, and canonical encoding — so that two URLs that mean exactly the same thing end up looking the same. It tells you the URL is structurally valid; it doesn't fetch the page or confirm the destination is actually reachable.
A syntax older than most of the web running on it
The URL format traces back to the early specification of uniform resource locators that made the web navigable in the first place, later formalized and refined further as the web grew to handle internationalized domains and a much wider range of characters than originally anticipated. Normalisation exists precisely because that same format has always allowed multiple valid-looking ways to write the same address.
Where duplicate and malformed links cause real damage
Storing unnormalised URLs quietly duplicates records, breaks deduplication logic, and lets look-alike links slip past filters meant to catch one specific domain. A consistent, validated form applied upstream is what makes downstream logic — deduplication, allow-lists, redirect handling — actually reliable instead of merely approximately reliable most of the time.
How it fits an automated pipeline
Send the raw URL, get a task_id back, and receive the parsed, validated, normalised result via your signed webhook or a signed link valid for 24 hours, whichever suits your integration. It's built to run inline wherever URLs enter your system — user submissions, imported feeds, referral links — rather than as a one-off manual check someone remembers to run.
What you can do with it
User-submitted link cleanup
Normalise links pasted into profile fields or comments so duplicates and malformed entries don't pile up in your database.
Feed and import sanitisation
Validate URLs coming from imported feeds or third-party data before they're stored or displayed to users.
Redirect and allow-list logic
Normalise incoming URLs before comparing them against an allow-list, so encoding tricks can't slip past a domain filter.
Analytics deduplication
Canonicalise tracked URLs so the same destination isn't split across multiple rows due to formatting differences.
FAQ
What exactly does this url validation api check?
It parses the URL into its components — scheme, host, port, path, query and fragment — confirms the structure is valid, and returns a normalised, canonical form of the address.
Does it check if the destination page actually exists?
No. It validates structure and syntax only; it doesn't fetch the URL or confirm the server responds, so it's not a dead-link checker.
What does normalising a URL mean in practice?
It means resolving things like inconsistent casing in the scheme or host, redundant path segments, and different-but-equivalent encodings into one consistent, comparable form.
Does it support internationalized domains and Unicode?
Yes, the parser handles internationalized domain names and Unicode characters as part of validating and normalising the URL.
Is it free to use?
The tool above runs free in your browser. The API is paid — each call draws from your prepaid ForHosting KIT balance: top up from $10.00 (it never expires), pay each request's published price, and a call with no balance returns HTTP 402. No subscription, no tokens, and a failed task is never charged.
Can I validate a batch of URLs at once?
Each call validates one URL; for larger sets, send one request per URL and let the asynchronous model process them in parallel.
How do I receive the result?
You get a task_id immediately, and the parsed result arrives via your signed webhook or a signed link valid for 24 hours.
Is submitted URL data kept afterward?
No, it's deleted once the retention period ends and is never used for training, and a failed check is retried up to three times without being billed.
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, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/verify/url \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"items":["valor-1","valor-2"]}'const res = await fetch("https://api.kit.forhosting.com/verify/url", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"items": [
"valor-1",
"valor-2"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/verify/url",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"items": [
"valor-1",
"valor-2"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/verify/url", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"items":["valor-1","valor-2"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"items":["valor-1","valor-2"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/verify/url", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"items": [
"valor-1",
"valor-2"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "verify.url",
"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. |