Cluster keywords by intent
A raw keyword export is a wall of near-duplicates, not a plan. This endpoint takes a large keyword list and groups it by shared search intent, turning a spreadsheet nobody wants to open into a set of clusters you can hand straight to a content calendar.
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.
The mess this replaces
Anyone who has exported a keyword list from a research tool knows the problem: thousands of rows, dozens of them meaning almost the same thing, and no obvious way to tell which thirty keywords should actually become one page versus thirty separate ones. Sorting that by hand is tedious in a way that invites shortcuts, and shortcuts here mean either duplicate content competing with itself or thin pages that split traffic they should have shared.
How the grouping works
The endpoint groups keywords by the intent behind the search, not just surface-level word overlap — 'best hiking boots' and 'top hiking boots 2026' cluster together despite different wording, while 'hiking boots review' sits in a separate cluster from 'buy hiking boots' because a reviewer and a buyer want different pages. Each cluster comes back with its member keywords and a suggested primary term, ready to map onto a single URL.
Why topic clusters became the standard approach
Search results shifted years ago from rewarding pages stuffed with keyword variants to rewarding sites that cover a topic comprehensively across a small number of well-organized pages — the 'pillar and cluster' model that most SEO teams now plan around. This endpoint automates the tedious part of that model: the initial sort that decides which keywords belong together before anyone starts mapping pillar pages.
Where it saves the most time
The value compounds with scale — clustering 50 keywords by hand is annoying, clustering 5,000 by hand is close to impossible without cutting corners. Because the endpoint runs asynchronously, a full keyword export from a new market or a competitor gap analysis can be submitted as one batch and processed while the team works on something else.
What still needs a strategist
Clustering tells you what belongs together; it doesn't tell you which cluster to prioritize, what to name the page, or how to position it against competitors — that judgment stays with the person who understands the business. Output arrives by signed webhook or a 24-hour signed link, and submitted keyword lists are deleted after retention and never used to train anything.
What you can do with it
New site information architecture
A team launching a new content site clusters 2,000 seed keywords into 60 topic groups and uses those groups as the starting sitemap.
Consolidating cannibalizing pages
An existing site finds several pages ranking for the same cluster and merges them after clustering confirms the overlap wasn't accidental.
Competitor gap analysis
After exporting keywords a competitor ranks for, a team clusters the list to see which whole topics they haven't covered yet, not just which single keywords.
Localized content expansion
A business expanding into a new region clusters a fresh keyword list for that market to plan pages instead of guessing at direct translations.
FAQ
How does the keyword clustering API group keywords?
It groups by shared search intent rather than surface wording alone, so keywords phrased differently but seeking the same answer land in the same cluster.
How many keywords can I submit at once?
You can submit large batches in a single request; pricing scales per batch, so it's built for bulk lists rather than one-off pairs of keywords.
Is there a free tier?
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.
What's the pricing?
$0.002 per request plus $0.002 per batch processed.
What do I get back for each cluster?
Each cluster returns its grouped keywords and a suggested primary term to map onto a single page.
How is this different from just grouping by shared words?
Word-overlap grouping misses synonyms and splits identical intents that are phrased differently; intent-based clustering catches both, which keyword-matching alone cannot.
How do I receive results for a large batch?
Asynchronously — a task_id returns immediately, and clusters are delivered by signed webhook or a signed link valid for 24 hours.
Are failed clustering requests charged?
No. A failed task retries up to three times automatically and is never billed if it doesn't succeed.
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/seo/keyword-cluster \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"…"}'const res = await fetch("https://api.kit.forhosting.com/seo/keyword-cluster", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"input": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/seo/keyword-cluster",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"input": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/seo/keyword-cluster", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"input":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"input":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/seo/keyword-cluster", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"input": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "seo.keyword_cluster",
"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. |