Keyword Research
Volume, CPC, competition and a 12-month trend for up to 100 keywords in one call.
curl -X POST https://seonio.com/api/v1/keywords/global \ -H "Content-Type: application/json" \ -H "X-API-Key: $SEONIO_API_KEY" \ -d '{"keywords": ["keyword research", "seo tools", "domain authority checker"]}'
{
"keywords": [
{
"text": "keyword research",
"volume": 550000,
"cpc": 37,
"low_cpc": 37,
"high_cpc": 212,
"competition": 18,
"monthly_trend": [
{
"month": "2026-08",
"value": 450000
},
{
"month": "2026-07",
"value": 201000
},
{
"month": "2026-06",
"value": 27100
}
],
"source": "google_ads"
},
{
"text": "seo tools",
"volume": 110000,
"cpc": 86,
"low_cpc": 86,
"high_cpc": 455,
"competition": 53,
"monthly_trend": [
{
"month": "2026-08",
"value": 301000
},
{
"month": "2026-07",
"value": 90500
},
{
"month": "2026-06",
"value": 90500
}
],
"source": "google_ads"
},
{
"text": "domain authority checker",
"volume": 135000,
"cpc": 41,
"low_cpc": 41,
"high_cpc": null,
"competition": 76,
"monthly_trend": [
{
"month": "2026-08",
"value": 110000
},
{
"month": "2026-07",
"value": 110000
},
{
"month": "2026-06",
"value": 110000
}
],
"source": "google_ads_estimated_months"
}
]
}What to send and what comes back
Send a list of keywords and get back the core metrics most SEO and PPC tools are built on. Ideal for keyword explorers, content planners and bulk research jobs.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
keywords |
string[] | required | 1–100 keywords to look up. Billed per keyword. |
Response
| Field | Type | Description |
|---|---|---|
keywords[].text |
string | The keyword. A keyword with no data is left out of the list (still billed). |
keywords[].volume |
integer | Average monthly search volume worldwide (Google Ads). |
keywords[].cpc |
integer | Cost per click in US cents: the same value as low_cpc, kept for existing clients. |
keywords[].low_cpc |
integer | Google Ads' low top-of-page bid in US cents. When source is google_ads_estimated_months it comes from a second source, which can differ. |
keywords[].high_cpc |
integer | null | Google Ads' high top-of-page bid in US cents; null when source is google_ads_estimated_months (that source has no high bid). |
keywords[].competition |
integer | Google Ads competition index (0–100). |
keywords[].monthly_trend |
object[] | Up to 12 months of search volume, newest to oldest, each as { month: YYYY-MM, value }, labelled with the Google Ads month each value belongs to. With source google_ads the months are Google's own, from data updated since Google's latest monthly release; with google_ads_estimated_months they are dated by rule (the newest is two months before the request) and can be one month off. Until October 2026 these months were labelled one month later than the data; the values were right. |
keywords[].source |
string | "google_ads": our stored Google Ads history, exact months, served only while it is current. "google_ads_estimated_months": the same Google Ads metrics from a second source, months estimated. |
Copy, paste, add your key
The same request in cURL, Python and Node.js.
curl -X POST https://seonio.com/api/v1/keywords/global \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"keywords": ["keyword research", "seo tools", "domain authority checker"]}'
import requests
resp = requests.post(
"https://seonio.com/api/v1/keywords/global",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"keywords": ["keyword research", "seo tools", "domain authority checker"]},
)
print(resp.json())
const resp = await fetch("https://seonio.com/api/v1/keywords/global", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "YOUR_API_KEY",
},
body: JSON.stringify({"keywords": ["keyword research", "seo tools", "domain authority checker"]}),
});
console.log(await resp.json());
Run a real request
Send this request from your browser with your API key, or leave the key empty to see the sample response.
A successful run costs $0.001 / keyword. With no key, you see the sample response and nothing is charged.
{
"keywords": [
{
"competition": 18,
"cpc": 37,
"high_cpc": 212,
"low_cpc": 37,
"monthly_trend": [
{
"month": "2026-08",
"value": 450000
},
{
"month": "2026-07",
"value": 201000
},
{
"month": "2026-06",
"value": 27100
}
],
"source": "google_ads",
"text": "keyword research",
"volume": 550000
},
{
"competition": 53,
"cpc": 86,
"high_cpc": 455,
"low_cpc": 86,
"monthly_trend": [
{
"month": "2026-08",
"value": 301000
},
{
"month": "2026-07",
"value": 90500
},
{
"month": "2026-06",
"value": 90500
}
],
"source": "google_ads",
"text": "seo tools",
"volume": 110000
},
{
"competition": 76,
"cpc": 41,
"high_cpc": null,
"low_cpc": 41,
"monthly_trend": [
{
"month": "2026-08",
"value": 110000
},
{
"month": "2026-07",
"value": 110000
},
{
"month": "2026-06",
"value": 110000
}
],
"source": "google_ads_estimated_months",
"text": "domain authority checker",
"volume": 135000
}
]
}
What developers build with it
- Bulk keyword explorers and research dashboards.
- Content briefs that need volume and trend at a glance.
- Prioritising a backlog by demand and competition.
The same contract on every endpoint
Error responses are JSON with a "detail" field. It is a message string unless the row above says otherwise.
| Code | When |
|---|---|
200 | Success. The lookup completed and was charged, even if the response is empty. |
401 | Missing or unknown API key. |
402 | Not enough balance. "detail" is an object with "error", "message", "balance_required" and "balance_available". Requests still running hold their price until they finish, so a retry can succeed once they do. |
403 | The account is not verified yet, or it is locked. |
422 | Invalid request (wrong shape or types, a keyword over 200 characters, a URL the page checker cannot audit, a domain that does not exist, or a bare public suffix such as co.uk that groups many websites) — never charged. "detail" is usually a list of validation errors; an answer that retrying cannot change (such as a domain without a DNS record) is a message string with an X-Outcome header naming it: missing, unsupported, too_large or refused_input. |
429 | Rate limit exceeded, never charged. Requests per second per account by plan: 5 on a free account, 10 on Starter, 15 on Pro and 20 on Business; retry after the Retry-After header. Also the page checker's provider limit. |
503 | A data provider failed or timed out (X-Outcome: temporary). Nothing was charged; retry after the seconds in the Retry-After header. |
Questions, answered
Simple answers for developers using the API.
Every endpoint has a published USD price or formula. Requests draw from the same testing, included and Extra Usage balance as website and MCP actions: testing first, then included usage, then Extra Usage expiring soonest.
API actions require a verified account with a testing balance or an active subscription balance. Create and manage API keys from your account, then send the key in the X-API-Key header.
Yes. Your account shows API usage and remaining balance, so you can track costs while building and running automated workflows.
REST API and MCP requests per second depend on your plan: 5 on a free account, 10 on Starter, 15 on Pro and 20 on Business. Website data requests from the same account count toward the same second. Requests above the limit return HTTP 429 with a Retry-After header and are not charged; wait a second and retry.
Turn search insights into measurable growth
Start your SEO research in seconds.