Batch slugify a list of titles with unique URL slugs
Batch Slugify converts an ordered list of page titles, article names, product labels, or other headings into URL-friendly slugs in one deterministic request.
Run — free
It lowercases text, removes common Latin accents, replaces punctuation and spacing with hyphens, and preserves digits. When two titles produce the same base slug, the later result receives an incrementing numeric suffix, so every returned value is unique. The original order remains unchanged, making the output easy to align with an import file, content queue, migration plan, or publishing workflow.
Create a clean slug for every title
Provide the titles in the exact order in which you want results returned. Each title is normalized into a compact URL component: uppercase letters become lowercase, common Latin diacritical marks are removed, and each run of spaces or punctuation becomes one hyphen. Leading and trailing separators disappear. Digits remain available, which is useful for release names, numbered chapters, years, and product editions. The operation expects actual strings rather than silently converting objects, booleans, or numbers, because accidental coercion can create plausible but incorrect URLs. A title must also produce at least one ASCII letter or digit after normalization. This makes failures visible when an input contains only punctuation, whitespace, or characters outside the supported transliteration range. The response contains a slugs array in the same order as the titles array and a count, so callers can confirm that every submitted row has a corresponding result. Nothing is fetched, stored, reordered, or inferred from external sources; identical input always produces identical output.
Resolve collisions without losing order
Different titles can normalize to the same slug. Capitalization, accents, repeated spaces, and punctuation distinctions all disappear during slugification, so “Cafe News,” “Café News,” and “cafe-news” share the base cafe-news. The first occurrence keeps that base. Later occurrences receive -2, -3, and further increasing suffixes as needed. Uniqueness is checked against every slug already emitted, not merely against a count of identical source strings. That detail matters when a natural title already ends in a number or when several different spellings collapse to one form. Processing remains left to right, which gives the input order clear precedence and makes reruns reproducible. If editorial priority matters, place the preferred title first so it receives the unsuffixed slug. The capability does not inspect an existing website or database, so it cannot know which slugs are already published there. Include relevant existing titles in the submitted sequence, or compare the returned array with your own reserved-slug index before writing routes.
Use the result safely in publishing workflows
Batch output is particularly useful at the boundary between editorial data and a content management system. Keep each returned slug beside the source row rather than sorting the slugs independently; positional correspondence is the simple contract that lets a spreadsheet import, migration script, or static-site generator attach the right route to the right record. Validate the entire request before persisting changes, because one invalid title rejects the operation instead of returning a partial result that could leave a migration half applied. The algorithm intentionally targets conservative ASCII URL components. It does not perform language-specific transliteration for every writing system, choose keywords, shorten long titles, check trademarks, or guarantee uniqueness against records omitted from the request. Those are separate editorial or storage concerns. For automation, one request costs $0.002, while the same deterministic logic can be exercised in the browser interface. Save the source titles along with the resulting slugs when redirects or future title changes must be audited, and avoid regenerating established public routes unless your redirect policy is ready.
What you can do with it
Prepare a CMS import
Generate one unique route component for every ordered title before inserting a batch of articles or landing pages.
Migrate a content archive
Normalize legacy headings consistently and identify collision suffixes while retaining row-for-row alignment with the source export.
Build static site routes
Create deterministic filenames or route segments for a collection during a repeatable build process.
FAQ
What happens when two titles create the same slug?
The first keeps the base slug. Later collisions receive incrementing suffixes such as -2, -3, and -4.
Does the output preserve input order?
Yes. Each slug occupies the same array position as its source title, and the count reports the number produced.
What does a request cost?
The API price is $0.002 per request, regardless of whether titles collide.
Does it check slugs already used on my website?
No. It guarantees uniqueness only within the submitted list and does not access your website, CMS, or database.
Why was a title rejected?
Every item must be a string and must yield at least one ASCII letter or digit after accent removal and separator cleanup.
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/data/slugify-batch \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"titles":["Getting Started","Getting Started","API Reference"]}'const res = await fetch("https://api.kit.forhosting.com/data/slugify-batch", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"titles": [
"Getting Started",
"Getting Started",
"API Reference"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/data/slugify-batch",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"titles": [
"Getting Started",
"Getting Started",
"API Reference"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/data/slugify-batch", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"titles":["Getting Started","Getting Started","API Reference"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"titles":["Getting Started","Getting Started","API Reference"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/data/slugify-batch", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"titles": [
"Getting Started",
"Getting Started",
"API Reference"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "data.slugify_batch",
"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_items | 10000 |
max_chars_per_title | 10000 |
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. |