discover_keywords
Discover keywords to target, from DataForSEO Labs — the question that comes before pricing a list you already have. Pick a mode: "ideas" (keywords in the…
Cost: 40 credits.
discover_keywords asks DataForSEO Labs to produce keywords you do not have yet — from one seed keyword, a list of seeds, or a domain. It answers the question that comes before research_keywords, which prices a list you already wrote: this one hands you rows you did not type. It is synchronous — everything comes back immediately, with no background job to poll.
What it does
Pick a mode. It is required and has no default, because the four modes ask DataForSEO four different questions — and take four different inputs:
mode | What comes back | What you pass |
|---|---|---|
ideas | Keywords from the same product/service categories as your seeds | seeds — a list of keywords |
suggestions | Longer search queries that contain your seed | seed — exactly one keyword |
related | The keywords Google lists under "searches related to" your seed | seed, plus optional depth |
for_site | Keywords DataForSEO considers relevant to a domain — no seed involved | target or project_id |
Each row carries DataForSEO's own search_volume, cpc, competition, competition_level, keyword_difficulty, search intent, search-volume trend and the date the vendor last refreshed the row.
Two optional filters, min_volume and max_difficulty, are applied at DataForSEO rather than after the fact. Neither is sent unless you ask for it, so by default nothing is dropped before you see it.
A field from another mode is rejected, not ignored
Pass seed with mode: "for_site" and the call is refused — it is not quietly dropped. That is deliberate: for_site looks up a domain, so a silently ignored seed would run a different lookup than the one you asked for, and bill you for it. The error names the field and says what the mode does take, so it is a one-step fix.
The same rule runs in both directions: ideas will not accept a domain, depth belongs to related alone, and include_subdomains to for_site alone.
Whose numbers these are
Every value in the output is a DataForSEO field printed under DataForSEO's own name. The vendor publishes two different competition measurements and they stay apart: competition is a 0–1 float, competition_level is an advertiser band (LOW / MEDIUM / HIGH). SeoGrep does not merge them, derive one from the other, or rename either into something friendlier.
A field the vendor did not report is printed as unreported, never as 0 — "nobody has a figure for this" and "nobody searches this" are different facts. A genuine zero the vendor did send is printed as 0. Secondary intent is the one exception in the other direction: an empty list cannot be told apart from a field the vendor never sent, so an empty one is printed as nothing at all rather than as "no secondary intent".
What it does not tell you
There is no opportunity score here, no "easy win" label and no ordering of ours: the rows come back in the one vendor order the tool asks for — by DataForSEO's own keyword_info.search_volume, highest first — and the output names that field so you know what "first" means.
keyword_difficulty is DataForSEO's own 0–100 estimate about the search results for a keyword. It is not a forecast of where your site would rank, not a promise that a low number is winnable, and neither DataForSEO nor SeoGrep can tell you what traffic any of these keywords would bring you. Which of them to target is your decision; this tool brings you the vendor's rows and says whose they are.
Example
Ask your MCP client in plain language:
Find longer search queries containing "seo software".
Or start from a domain instead of a keyword:
What keywords does DataForSEO consider relevant to example.com? Skip anything under 500 monthly searches.
Input
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | WHICH QUESTION to ask — required, with no default, because the four modes answer four different questions and take four different inputs. "ideas": keywords from the same product/service categories as your seed keywords (takes "seeds"). "suggestions": longer search queries that CONTAIN your seed keyword (takes "seed"). "related": the keywords Google lists under "searches related to" for your seed (takes "seed", and optionally "depth"). "for_site": keywords DataForSEO considers relevant to a DOMAIN, with no seed keyword involved (takes "target" or "project_id"). Passing a field that belongs to another mode is rejected, not ignored. |
seeds | string[] | No | MODE "ideas" ONLY: the seed keywords to draw ideas from (1-200, the vendor's own documented ceiling). The keywords that come back are the vendor's, not yours — none of your seeds is guaranteed to appear in the answer. |
seed | string | No | MODES "suggestions" and "related" ONLY: exactly one seed keyword. Both endpoints take a single keyword, not a list — pass one, and run the tool again for another. |
depth | integer | No | MODE "related" ONLY: how many times DataForSEO follows "searches related to" outward from your seed (0-4, the vendor's own range; default 1 when omitted). A deeper search drifts further from the seed; it does not change the price. |
target | string | No | The domain to discover keywords for, e.g. "example.com" or "https://example.com" — any public domain, including a competitor's. Pass this OR project_id, not both. |
project_id | string (uuid) | No | One of your projects (from setup_project / list_projects) — the domain is taken from it. Pass this OR target, not both. |
include_subdomains | boolean | No | MODE "for_site" ONLY: whether the domain's subdomains count as part of it (default true). Sent to DataForSEO explicitly either way, so the answer never depends on a vendor default that could move. |
limit | integer | No | How many keywords to return (1-1000, default 100). DataForSEO bills per returned row, so this is the price control, not a display preference — the flat price was signed against this ceiling. |
offset | integer | No | How many keywords to skip before this window starts (default 0). Page through a large set by advancing it; the output always states the offset and limit the rows came back under. |
min_volume | integer | No | OPTIONAL vendor filter: keep only keywords whose DataForSEO keyword_info.search_volume is at least this. Omitted by default — no filter is sent at all, so nothing is dropped on your behalf. Filtering happens at DataForSEO, so it changes which rows you are billed for. |
max_difficulty | integer | No | OPTIONAL vendor filter: keep only keywords whose DataForSEO keyword_properties.keyword_difficulty is at most this (0-100, the vendor's own scale). Omitted by default. The cut-off is YOURS: DataForSEO publishes the score but no threshold, and a low score is not a promise that a keyword is winnable. |
language_code | string | No | Language code (default 'en'). |
location_code | integer | No | DataForSEO location code (default 2840 = United States). |
Returns
A heading naming which mode ran and the DataForSEO Labs function behind it, then what that mode means in the vendor's own terms, then the locale, the vendor field the rows are ordered by, and the filters that were actually sent — printed in DataForSEO's own [field, operator, value] grammar, so what you read is what the vendor received.
The keyword list is captioned as a window: the rows you got, the offset and limit they were fetched under, and DataForSEO's whole-set count attributed to the vendor by name, followed by the sentence that stops the arithmetic — this window is a slice of that set, not a count of it. When the vendor gave no total, the caption says that instead of back-filling one from the rows in hand.
A lookup that matched nothing says so plainly, naming the window and the filters it asked for, and you are still charged for the delivered lookup — "nothing here matched" is a real answer, not an error. A missing or foreign mode field, a limit or depth outside the allowed range, a for_site call naming neither target nor project_id (or both), and a project_id that is not yours are all rejected before anything is charged; while live data is off you get a "not yet enabled" message instead — also free.
Billing
One call is one flat price, charged once, and behind it is one DataForSEO request. If it fails, the whole call fails and you are not charged.
discover_keywords needs a paid credit balance. It reads live data from a paid third-party provider, so it is not available on trial credits. Buy any credit pack and it unlocks straight away; your existing credits are untouched and keep working for crawls, audits, reports and Search Console tools.
The limit ceiling is part of the price rather than a display preference: DataForSEO bills per returned row, and that cap is what holds the flat price inside the margin it was signed against. Asking for fewer rows costs the same; asking for more than the ceiling is refused before anything is charged.
Limitations
Results are not stored. Each call returns its window to the conversation and nothing else keeps them — there is no saved keyword list, no dashboard page and no "new since last time", so run it again for a fresh read. The lookup-history table that backs the other domain tools is bound to those tools by design and does not accept this one.
The keywords are the vendor's, not yours: none of your seeds is guaranteed to appear in the answer, and a set of many thousands is normal — read the window caption for how far your slice sits from DataForSEO's whole-set count, and page through it with offset.
for_site is DataForSEO's own judgement about which keywords are relevant to a domain. It is not a list of what that site currently ranks for — that is ranked_keywords — and the two will not agree. related follows "searches related to" outward from your seed, so a deeper depth drifts further from it.
research_keywords
Look up Google search volume, CPC, competition, keyword difficulty, search intent and search-volume trend for up to 100 keywords. Synchronous — returns a…
ranked_keywords
List the Google organic keywords a domain already ranks for — organic and on-page position, monthly search volume, CPC, competition, estimated traffic…