Find content gaps
Two halves under one name, labelled per row.
Tool name: find_content_gaps
Cost: Free by default, paid on includeUncovered: true - half A is a free read, while half B makes paid calls (one LLM expansion plus one keyword-volume request per engine), capped per workspace per day and confirmed before it runs.
Read-only, but asks before the paid step
This tool changes nothing. One of its modes is opt-in and calls a paid external API, so the server asks you to confirm before running that part - and the backend caps it per workspace per day regardless of your answer. Everything else runs without a prompt.
Two halves under one name, labelled per row. Half A (rows, always returned, free): visible queries (impressions above the threshold) for which no page on the site ranks well (worse than positionThreshold, or not ranking at all). Half B (uncovered, only on includeUncovered=true): topics the workspace's confirmed products and audiences imply and the site has no page for. Half B exists because half A is structurally blind to it - both engines list a query only if the site already drew an impression for it, so a topic you have no page for produces no row in any feed, and "this query is absent" is never evidence of anything.
Half B costs money on every call: one LLM expansion of the workspace's content graph plus one keyword-volume request per engine. It is capped per workspace per day and asks for confirmation first. Read uncoveredStatus before concluding anything from an empty uncovered: not_requested means you did not ask for it, unavailable means the workspace has no confirmed products or audiences to imply a topic from, and failed means the expansion could not run - none of the three means the site has no uncovered demand. Half-B rows are clustered by topic with query variants nested, and recommendedAction is the whole answer: create (nothing of yours is near it), refresh (a page of yours is adjacent - expand pageToRefresh rather than publishing a second page against it), or verify_inventory (we searched fewer pages than the site has, so the absence is a limit and not a finding). Read coverage.nearestDistance alongside the verdict: the thresholds are declared, not calibrated.
Every half-A row carries a cannibalizationStatus, because "no page ranks well for this" and "several of our pages are splitting it" look identical here and want opposite advice. "confirmed" re-labels the row gapType: "cannibalized" with competingPages listed: consolidate (see detect_keyword_cannibalization), do not strengthen one of them. "possible" names an unread sitemap URL in suspectedPages - open it before optimizing, since the two pages may be competing. "none" is the row this tool actually cleared: one page ranks and nothing else in the site's sitemap targets the query, so optimizing that page is the right move. "unknown" means the site has no usable sitemap inventory, so the row is a candidate to optimize rather than a cleared one. See unsupportedInputs. Google stores only about 5,000 rows per day per single-dimension breakdown once a day is more than roughly three days old, so on a large site these rows are a top-N by impressions rather than a complete list. When that applies to the window you asked for, the response says so in unsupportedInputs with the dates - there is nothing to fetch, so read a missing row as 'not in the top ~5,000', never as 'zero'.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
site | string | Yes | The site to look up, as a bare domain ("example.com") or a URL - scheme, www., path and trailing slash are all stripped. Must resolve to a workspace the caller owns; call list_sites first if unsure of the exact domain. |
windowDays | integer | No | Length of the current window, in days. Defaults to 28. |
to | string | No | Last day of the current window (YYYY-MM-DD). Defaults to the most recent date either engine has data for. |
positionThreshold | number | No | A query ranking worse than this position counts as a gap. Defaults to 15. |
minImpressions | integer | No | Minimum current-window impressions for a query to count as visible. Defaults to 10. |
limit | integer | No | Maximum rows to return, sorted by relevance. Defaults to 50. |
includeUncovered | boolean | No | Run half B: expand the workspace's confirmed product/audience/pain-point graph into candidate search topics and report the ones it has no page for. Paid - one LLM expansion plus one keyword-volume request per engine - capped per workspace per day, and confirmed with the user first. Off by default, so half A stays a free read. Defaults to false. |
uncoveredLimit | integer | No | Maximum half-B topics to return, highest demand first. Defaults to 20. |
searchVertical | "web" | "image" | "video" | No | Which Google search vertical to read: web (default), image, or video. Google-only - Bing Webmaster Tools does not split verticals on any traffic endpoint, so anything other than 'web' returns Google alone, with Bing excluded and declared in unsupportedInputs. A null or absent Bing figure then means 'not measurable', never 'zero'. Defaults to "web". |
Full input JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"site": {
"type": "string",
"minLength": 1,
"description": "The site to look up, as a bare domain (\"example.com\") or a URL - scheme, www., path and trailing slash are all stripped. Must resolve to a workspace the caller owns; call list_sites first if unsure of the exact domain."
},
"windowDays": {
"default": 28,
"description": "Length of the current window, in days.",
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 365
},
"to": {
"description": "Last day of the current window (YYYY-MM-DD). Defaults to the most recent date either engine has data for.",
"type": "string"
},
"positionThreshold": {
"default": 15,
"description": "A query ranking worse than this position counts as a gap.",
"type": "number",
"minimum": 1
},
"minImpressions": {
"default": 10,
"description": "Minimum current-window impressions for a query to count as visible.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"limit": {
"default": 50,
"description": "Maximum rows to return, sorted by relevance.",
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 500
},
"includeUncovered": {
"default": false,
"description": "Run half B: expand the workspace's confirmed product/audience/pain-point graph into candidate search topics and report the ones it has no page for. **Paid** - one LLM expansion plus one keyword-volume request per engine - capped per workspace per day, and confirmed with the user first. Off by default, so half A stays a free read.",
"type": "boolean"
},
"uncoveredLimit": {
"default": 20,
"description": "Maximum half-B topics to return, highest demand first.",
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 100
},
"searchVertical": {
"default": "web",
"description": "Which Google search vertical to read: web (default), image, or video. Google-only - Bing Webmaster Tools does not split verticals on any traffic endpoint, so anything other than 'web' returns Google alone, with Bing excluded and declared in `unsupportedInputs`. A null or absent Bing figure then means 'not measurable', never 'zero'.",
"type": "string",
"enum": ["web", "image", "video"]
}
},
"required": ["site"]
}Response
Full output JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"site": {
"type": "string"
},
"workspaceId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"searchVertical": {
"type": "string",
"enum": ["web", "image", "video"]
},
"currentWindow": {
"type": "object",
"properties": {
"start": {
"type": "string"
},
"end": {
"type": "string"
}
},
"required": ["start", "end"],
"additionalProperties": false
},
"rows": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"google": {
"anyOf": [
{
"type": "object",
"properties": {
"impressions": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"clicks": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"position": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"impressions",
"clicks",
"position"
],
"additionalProperties": {}
},
{
"type": "null"
}
]
},
"bing": {
"anyOf": [
{
"type": "object",
"properties": {
"impressions": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"clicks": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"position": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"impressions",
"clicks",
"position"
],
"additionalProperties": {}
},
{
"type": "null"
}
]
},
"gapType": {
"type": "string"
},
"cannibalizationStatus": {
"type": "string"
},
"competingPages": {
"type": "array",
"items": {
"type": "string"
}
},
"suspectedPages": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"label",
"google",
"bing",
"gapType",
"cannibalizationStatus",
"competingPages",
"suspectedPages"
],
"additionalProperties": {}
}
},
"uncovered": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gapType": {
"type": "string"
},
"topic": {
"type": "string"
},
"seed": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"seedKind": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"mergedFrom": {
"type": "array",
"items": {
"type": "string"
}
},
"queries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"query": {
"type": "string"
},
"searchVolume": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"demandSource": {
"type": "string"
},
"competition": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"competitionIndex": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"cpc": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"nearestDistance": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"query",
"searchVolume",
"demandSource",
"competition",
"competitionIndex",
"cpc",
"nearestDistance"
],
"additionalProperties": {}
}
},
"coverage": {
"type": "object",
"properties": {
"verdict": {
"type": "string"
},
"test": {
"type": "string"
},
"nearestPage": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"nearestDistance": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"nearestExcerpt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"searchedPages": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"siteKnownPages": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"siteKnownPagesReliable": {
"type": "boolean"
},
"inventoryComplete": {
"type": "boolean"
},
"coveredMaxDistance": {
"type": "number"
},
"adjacentMaxDistance": {
"type": "number"
}
},
"required": [
"verdict",
"test",
"nearestPage",
"nearestDistance",
"nearestExcerpt",
"searchedPages",
"siteKnownPages",
"siteKnownPagesReliable",
"inventoryComplete",
"coveredMaxDistance",
"adjacentMaxDistance"
],
"additionalProperties": {}
},
"demand": {
"type": "object",
"properties": {
"searchVolume": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"source": {
"type": "string"
},
"variantsPriced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"variantsTotal": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
}
},
"required": [
"searchVolume",
"source",
"variantsPriced",
"variantsTotal"
],
"additionalProperties": {}
},
"cannibalizationStatus": {
"type": "string"
},
"pageToRefresh": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"recommendedAction": {
"type": "string"
}
},
"required": [
"gapType",
"topic",
"seed",
"seedKind",
"mergedFrom",
"queries",
"coverage",
"demand",
"cannibalizationStatus",
"pageToRefresh",
"recommendedAction"
],
"additionalProperties": {}
}
},
"uncoveredStatus": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"reason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"topicsProposed": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"topicsReturned": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"topicsCovered": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"topicsVisibleInSearch": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"queriesExpanded": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"costUsd": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"status",
"reason",
"topicsProposed",
"topicsReturned",
"topicsCovered",
"topicsVisibleInSearch",
"queriesExpanded",
"costUsd"
],
"additionalProperties": {}
},
"unsupportedInputs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string"
},
"engine": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"reason": {
"type": "string"
}
},
"required": ["field", "reason"],
"additionalProperties": {}
}
},
"lastSyncedAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"site",
"workspaceId",
"searchVertical",
"currentWindow",
"rows",
"uncovered",
"uncoveredStatus",
"unsupportedInputs"
],
"additionalProperties": false
}