Check slug uniqueness and format
Check a proposed URL slug before it reaches a CMS, deployment pipeline, or routing table.
Run — free
This tool first enforces a predictable format made from lowercase letters, digits, and single hyphens, then compares the valid proposal with the existing slugs you provide. Invalid formatting produces an error even when the same text also appears in the list, so callers never mistake a malformed value for an ordinary naming collision. Valid results clearly report whether the slug is unique and whether an exact collision was found.
Validate the slug before checking availability
A uniqueness result is useful only when the proposed value can safely become part of a URL. Enter the candidate in the slug field and provide the slugs already used by your site, application, or content store. The checker accepts lowercase English letters from a to z, digits from 0 to 9, and hyphens placed between nonempty groups. It rejects uppercase letters, spaces, underscores, punctuation, leading or trailing hyphens, and repeated hyphens. This strict order matters: format validation happens before membership testing. If an invalid candidate also exists in the supplied list, the response is still an invalid-input error, not a normal collision result. That behavior prevents a publishing workflow from approving malformed routes merely because it knows they have been used before. It also gives integrations one stable rule to enforce at form submission, during bulk imports, and immediately before deployment. Keep the candidate under the documented length limit and send the current collection as an array of strings; no hidden normalization changes what you submitted.
Interpret exact collision results correctly
For a correctly formatted candidate, the response includes the submitted slug, confirms that its format is valid, and reports two complementary booleans: unique and collision. A collision means the exact candidate string occurs at least once in the supplied existing-slug array. Unique is the logical opposite, making the result convenient for interfaces that enable a publish button as well as systems that branch on conflicts. Comparison is deliberately exact rather than approximate. The checker does not remove prefixes, decode URL escapes, trim list entries, singularize words, or decide that similar phrases represent the same page. For example, product-guide and product-guides remain different valid routes. Exact behavior keeps the answer deterministic and lets your application remain responsible for business rules such as reserved names, locale prefixes, redirects, or case-insensitive database constraints. Duplicate values inside the existing array do not change the outcome. The list is treated as the caller's current source of truth, so refresh it before checks when several editors or deployment jobs can claim routes concurrently.
Use the check in publishing and deployment workflows
Run the check as close as practical to the point where a route is reserved. In a content editor, you can validate after a title-derived slug is generated and repeat the check when the editor changes it. In a static-site build, collect output paths first, then test each proposed route against the paths already accepted. In an API or migration, treat invalid-input errors as data-quality failures that require correction, while treating a collision result as a naming decision that may be resolved with a suffix or a different phrase. The operation is deterministic, uses no network service, and stores nothing, so the same input always gives the same answer. That makes it appropriate for tests and repeatable build gates. However, it is a checker rather than a reservation service: another process can claim a slug after your list was read. Systems with concurrent writers should still enforce a unique database constraint or perform an atomic reservation when saving. Use this result for early feedback and clean errors, then rely on your authoritative store for the final guarantee.
What you can do with it
Prevent CMS route collisions
Validate an editor's proposed path and warn when an existing article already uses the exact slug.
Gate static-site builds
Check generated page slugs against accepted output paths before writing or deploying files.
Clean migration inputs
Separate malformed legacy slugs from valid names that merely collide during a content migration.
FAQ
What slug format is accepted?
One or more lowercase letters or digits, optionally separated by single hyphens. Hyphens cannot appear first, last, or twice in a row.
What happens if an invalid slug is also in the existing list?
The checker returns an invalid-input error because format validation always takes precedence over collision checking.
Is collision matching case-insensitive?
No. Matching is exact. Uppercase characters are already invalid in the proposed slug, and existing list values are not normalized.
Does a unique result reserve the slug?
No. It checks the list supplied with that request. Use an atomic write or unique database constraint to prevent concurrent claims.
How much does the API request cost?
The base price is $0.002 per request. The same deterministic logic can run in the generated browser experience.
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/web/slug-uniqueness-check \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"slug":"summer-sale-2026","existing_slugs":["spring-sale-2026","clearance"]}'const res = await fetch("https://api.kit.forhosting.com/web/slug-uniqueness-check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"slug": "summer-sale-2026",
"existing_slugs": [
"spring-sale-2026",
"clearance"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/slug-uniqueness-check",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"slug": "summer-sale-2026",
"existing_slugs": [
"spring-sale-2026",
"clearance"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/slug-uniqueness-check", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"slug":"summer-sale-2026","existing_slugs":["spring-sale-2026","clearance"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"slug":"summer-sale-2026","existing_slugs":["spring-sale-2026","clearance"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/slug-uniqueness-check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"slug": "summer-sale-2026",
"existing_slugs": [
"spring-sale-2026",
"clearance"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.slug_uniqueness_check",
"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.
Limits
max_existing_slugs | 10000 |
max_slug_length | 200 |
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. |