Documentation
Everything you need to use keywordclusters.com — product guide and full REST API reference.
Getting Started
keywordclusters.com turns a seed keyword into an organised, clustered keyword list in seconds. Here's how to run your first research:
- Create an account (Free plan, no credit card).
- From the Search page, type a seed keyword (e.g. "keyword research tools").
- Choose one or more search engines: Google, Bing, YouTube, or Amazon.
- Select your locale (e.g.
en-US) and click Search. - Review the autocomplete suggestions with volume, CPC, competition, and keyword difficulty (KD) metrics.
- Keyword clusters are generated automatically — use them to plan content or ad groups.
- Export results as CSV or XLSX, or save keywords to a Project.
API Reference
The REST API lets Agency plan subscribers run keyword searches programmatically and poll for results. All requests go to https://keywordclusters.com.
Base URL
https://keywordclusters.comAuthentication
Every API request must include your API key in the x-api-key header. Keys are tied to an Agency plan account and have a 500-request daily limit.
Getting an API key
- Upgrade to the Agency plan on the Billing page ($149/month).
- Go to Settings and find the API Keys section.
- Click Create API Key. Your new token is shown once — copy and store it securely.
- To revoke a key, return to Settings → API Keys and click Delete.
Tokens are shown once. If you lose a key, delete it and create a new one. The platform never stores the plaintext token.
Sending the header
Pass the header on every request:
x-api-key: kc_your_token_hereRequests without a valid key — or with a key that has exceeded its daily limit — return 401 or 429.
Full examples by language
curl
curl -X POST https://keywordclusters.com/api/v1/search \
-H "x-api-key: kc_your_token_here" \
-H "Content-Type: application/json" \
-d '{"term":"keyword research","locale":"en-US","engines":["google"]}'JavaScript / fetch
const response = await fetch("https://keywordclusters.com/api/v1/search", {
method: "POST",
headers: {
"x-api-key": "kc_your_token_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
term: "keyword research",
locale: "en-US",
engines: ["google"],
}),
});
const { jobId } = await response.json();Python (requests)
import requests
resp = requests.post(
"https://keywordclusters.com/api/v1/search",
headers={"x-api-key": "kc_your_token_here"},
json={
"term": "keyword research",
"locale": "en-US",
"engines": ["google"],
},
)
data = resp.json()
job_id = data["jobId"]POST /api/v1/search
Starts a keyword research job. The job runs asynchronously — this endpoint returns immediately with a jobId you can poll. One request consumes one unit of your 500/day API limit.
Request
| Field | Type | Required | Description |
|---|---|---|---|
| term | string | Yes | Seed keyword or phrase to research. |
| locale | string | No | IETF locale tag. Default: en-US. E.g. en-GB, de-DE. |
| language | string | No | ISO 639-1 language code. Default: en. |
| engines | string[] | No | Which engines to query. Default: ["google"]. Allowed values: "google", "bing", "youtube", "amazon". |
POST /api/v1/search
x-api-key: kc_your_token_here
Content-Type: application/json
{
"term": "seo tools",
"locale": "en-US",
"language": "en",
"engines": ["google", "bing"]
}Response — 200 OK
{
"jobId": 1842,
"status": "pending",
"message": "Search started"
}Error responses
| Status | Body | Cause |
|---|---|---|
| 401 | { "message": "Invalid or missing API key" } | Key absent, malformed, or revoked. |
| 400 | { "message": "Missing required fields" } | term missing or engines is empty. |
| 429 | { "message": "API key daily limit reached", "limit": 500, "used": 500 } | Daily limit exhausted. Resets at midnight UTC. |
GET /api/v1/search/:jobId
Polls the status of a search job. When query.status is "completed", the keywords, clusters, and trendPredictions fields are populated. While pending they return as empty arrays / objects. Polling does not consume your daily limit.
Request
GET /api/v1/search/1842
x-api-key: kc_your_token_hereResponse — pending
{
"query": {
"id": 1842,
"term": "seo tools",
"locale": "en-US",
"language": "en",
"engines": ["google", "bing"],
"status": "pending",
"createdAt": "2026-04-22T09:00:00.000Z",
"completedAt": null
},
"keywords": [],
"clusters": [],
"trendPredictions": {}
}Response — completed
{
"query": {
"id": 1842,
"term": "seo tools",
"locale": "en-US",
"language": "en",
"engines": ["google", "bing"],
"status": "completed",
"createdAt": "2026-04-22T09:00:00.000Z",
"completedAt": "2026-04-22T09:00:04.123Z"
},
"keywords": [
{
"id": 9001,
"queryId": 1842,
"phrase": "best seo tools",
"engine": "google",
"volume": 14800,
"cpc": 3.2,
"competition": 72,
"kd": 58,
"trend": "up",
"clusterId": 201,
"createdAt": "2026-04-22T09:00:04.000Z"
}
],
"clusters": [
{
"id": 201,
"queryId": 1842,
"label": "SEO Tool Comparisons",
"keywordCount": 12,
"createdAt": "2026-04-22T09:00:04.000Z"
}
],
"trendPredictions": {
"9001": {
"id": 55,
"phrase": "best seo tools",
"locale": "en-US",
"engine": "google",
"currentVolume": 14800,
"predictedVolume": 16200,
"growthRate": 946,
"trendDirection": "up",
"confidence": 78,
"predictionPeriod": 30
}
}
}Error responses
| Status | Cause |
|---|---|
| 401 | Key absent, malformed, or revoked. |
| 404 | No job with that ID exists. |
| 403 | The job belongs to a different account. |
Polling Pattern
Searches typically complete in 3–10 seconds. Poll every 2 seconds until status is "completed" or "failed".
JavaScript — full round-trip
const API_KEY = "kc_your_token_here";
const BASE = "https://keywordclusters.com";
async function search(term, engines = ["google"], locale = "en-US") {
// 1. Create the job
const createRes = await fetch(`${BASE}/api/v1/search`, {
method: "POST",
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ term, engines, locale }),
});
if (!createRes.ok) throw new Error(`Create failed: ${createRes.status}`);
const { jobId } = await createRes.json();
// 2. Poll until completed
while (true) {
await new Promise((r) => setTimeout(r, 2000));
const pollRes = await fetch(`${BASE}/api/v1/search/${jobId}`, {
headers: { "x-api-key": API_KEY },
});
if (!pollRes.ok) throw new Error(`Poll failed: ${pollRes.status}`);
const data = await pollRes.json();
if (data.query.status === "completed") return data;
if (data.query.status === "failed") throw new Error("Search failed on server");
}
}
// Usage
const result = await search("seo tools", ["google", "bing"]);
console.log(result.keywords.length, "keywords,", result.clusters.length, "clusters");Python — full round-trip
import time
import requests
API_KEY = "kc_your_token_here"
BASE = "https://keywordclusters.com"
HEADERS = {"x-api-key": API_KEY}
def search(term, engines=None, locale="en-US"):
engines = engines or ["google"]
# 1. Create the job
r = requests.post(
f"{BASE}/api/v1/search",
headers={**HEADERS, "Content-Type": "application/json"},
json={"term": term, "engines": engines, "locale": locale},
)
r.raise_for_status()
job_id = r.json()["jobId"]
# 2. Poll until completed
while True:
time.sleep(2)
r = requests.get(f"{BASE}/api/v1/search/{job_id}", headers=HEADERS)
r.raise_for_status()
data = r.json()
status = data["query"]["status"]
if status == "completed":
return data
if status == "failed":
raise RuntimeError("Search failed on server")
result = search("seo tools", engines=["google", "bing"])
print(len(result["keywords"]), "keywords,", len(result["clusters"]), "clusters")Rate Limits
| Limit | Value | Notes |
|---|---|---|
| Requests per key per day | 500 | Each POST /api/v1/search counts as 1. Poll requests don't count. |
| Reset cadence | Daily, midnight UTC | Counter resets to 0 each day. |
| Keys per account | Unlimited | Each key gets its own 500/day limit. |
When a key is exhausted the API returns:
HTTP/1.1 429 Too Many Requests
{
"message": "API key daily limit reached",
"limit": 500,
"used": 500
}Your app should check for 429, surface an informative error, and retry after reset.
Response Shapes
Field-by-field reference for every object returned by GET /api/v1/search/:jobId.
query
| Field | Type | Notes |
|---|---|---|
| id | number | Numeric job ID. |
| term | string | The seed keyword you submitted. |
| locale | string | IETF locale, e.g. "en-US". |
| language | string | ISO 639-1 code, e.g. "en". |
| engines | string[] | Engines queried: google, bing, youtube, amazon. |
| status | string | "pending" | "processing" | "completed" | "failed" |
| errorMessage | string | null | Set if status is "failed". |
| createdAt | ISO 8601 string | When the job was created. |
| completedAt | ISO 8601 string | null | When the job finished. |
keywords[]
| Field | Type | Notes |
|---|---|---|
| id | number | Unique keyword ID. |
| phrase | string | The keyword string. |
| engine | string | Which engine returned this keyword. |
| volume | number | null | Monthly search volume. |
| cpc | number | null | Cost-per-click in local currency, as a decimal (e.g. 3.2 = $3.20). |
| competition | number | null | Competition score 0–100. |
| kd | number | null | Keyword difficulty 0–100. |
| trend | string | null | "up" | "down" | "stable" |
| clusterId | number | null | References clusters[].id, or null if unclustered. |
clusters[]
| Field | Type | Notes |
|---|---|---|
| id | number | Cluster ID referenced by keywords[].clusterId. |
| label | string | Human-readable cluster name (ML-generated). |
| keywordCount | number | Number of keywords in this cluster. |
trendPredictions
A map of keyword_id → prediction. Only present when a prediction exists for that keyword.
| Field | Type | Notes |
|---|---|---|
| currentVolume | number | null | Current monthly search volume. |
| predictedVolume | number | null | Predicted volume after predictionPeriod days. |
| growthRate | number | Growth percentage × 100 (e.g. 946 = +9.46%). |
| trendDirection | string | "up" | "down" | "stable" | "emerging" |
| confidence | number | Confidence score 0–100. |
| predictionPeriod | number | Days ahead the prediction covers (default 30). |
Product Docs
- Enter a seed keyword and choose engines
- Volume: monthly average searches
- CPC: cost-per-click in local currency
- Competition: paid ad competition 0–100
- KD: organic ranking difficulty 0–100
- Trend: up / down / stable vs prior period
- Clusters are generated automatically using ML
- Each cluster groups semantically related keywords
- Use clusters to plan content topics or ad groups
- View clusters in the Results page, tab-separated
- Export clusters as separate sheets in XLSX
- View top 10 results for a keyword
- See title, URL, snippet, and domain
- Understand what content is currently ranking
- Identify content gaps and opportunities
- Available on Starter, Pro, and Agency plans
- Create projects to organise keywords by campaign
- Save keywords from any search to a project
- Export as CSV for Excel/Google Sheets
- Export as XLSX for multi-sheet workbooks
- Export as PDF for client-ready reports
Plans & Billing
| Plan | Price | Credits/month | Keywords/search | API |
|---|---|---|---|---|
| Free | $0 | 50 | 50 | No |
| Starter | $29/mo | 200 | 500 | No |
| Pro | $79/mo | 400 | 2,500 | No |
| Agency | $199/mo | 700 | 5,000 | Yes — 500 req/day per key |
Upgrade, downgrade, or cancel anytime from Settings → Billing. Stripe handles all payment processing — we never store card details.
Account settings, API key management, and password changes are available at Settings.