# Ubersuggest MCP Server — Documentation

Ubersuggest's SEO tools (keyword research, domain analysis, backlinks, site
audits, projects, AI search visibility) exposed over the Model Context
Protocol. 58 tools, Streamable HTTP transport, OAuth 2.0
authentication with your Ubersuggest account.

- MCP endpoint: `/mcp` on this host (e.g. `https://ubersuggest-mcp.neilpatelapi.com/mcp`)
- Human docs: `/docs` on this host
- Transport: Streamable HTTP (POST). No SSE stream — GET on /mcp returns 405.
- Auth: OAuth 2.0; unauthenticated requests get 401 with a WWW-Authenticate
  header pointing to the OAuth metadata.

## Connecting

- **Claude (claude.ai / desktop)**: Settings → Connectors → Add custom connector → paste the endpoint URL.
- **Claude Code**: `claude mcp add --transport http ubersuggest <endpoint>`
- **Cursor** (`~/.cursor/mcp.json`): `{"mcpServers": {"ubersuggest": {"url": "<endpoint>"}}}`
- **VS Code**: `code --add-mcp '{"name":"ubersuggest","type":"http","url":"<endpoint>"}'`

## Plans & tool availability

Every MCP tool is available on every Ubersuggest plan, including free accounts — what changes with your plan is how much data you get back. Check the pricing page for a full plan comparison.

- Project tools (list_projects, get_project, create_project, add_project_keywords, add_project_competitors, project_position_info, seo_opportunities) and AI Search Visibility tools (brand_config, brand_visibility_overview, brand_prompts) operate on data tied to your account, so they require a logged-in account with those features configured.
- Content Studio tools (project_business_summary, article_title_suggestions, generate_article, get_article) require a logged-in account with a project. Writing an article spends 100 monthly credits, so it needs a plan whose monthly credit allowance covers that — free and entry-level plans do not.
- Use the auth_status tool to see the tier of the connected account.
- Full plan comparison and pricing: https://app.neilpatel.com/en/pricing

## Usage & rate limits

MCP calls consume the same quotas as the Ubersuggest web app — there are no separate MCP limits.

- Daily searches (reports): each distinct report subject (e.g. a keyword or domain you analyze) counts one report per day against your plan's daily report limit. Repeated calls for the same subject on the same day do not consume extra reports.
- Monthly credits: some data (e.g. keyword metrics updates) draws from monthly credit pools defined by your plan.
- When a quota is exhausted, tool calls return an isError result with the backend's message; quotas reset daily (reports) or monthly (credits).
- Check your current usage and limits in Account & Billing → Usage at app.neilpatel.com.

## Errors

### HTTP status codes

| Code | Meaning | Triggered by |
| --- | --- | --- |
| 401 Unauthorized | Missing, invalid or expired access token. | The Authorization header is absent, or the OAuth token was rejected/revoked. The response carries a WWW-Authenticate header pointing to the OAuth metadata — MCP clients use it to re-authenticate automatically. |
| 404 Not Found | Unknown path. | Requesting any route other than /mcp, /docs, /health or the OAuth endpoints. |
| 405 Method Not Allowed | Unsupported HTTP method on /mcp. | GET on /mcp (the server is stateless and does not offer an SSE stream — use POST), or any method other than POST/GET/DELETE. |
| 500 Internal Server Error | Unexpected server failure. | An unhandled error while processing the MCP request. Safe to retry. |

### Tool-level errors

Errors inside a tool call do not use HTTP status codes. Following the MCP spec, the tool result comes back with isError: true and a text content of the form "Error: <message>".

| Error | Triggered by |
| --- | --- |
| Error: … 403 / limit reached | The Ubersuggest backend refused the request — usually the daily report quota or a plan restriction. Check auth_status and your plan usage. |
| Error: … timed out; report still pending | Asynchronous reports (competitors, pagespeed_audit, project_position_info) exceeded the server-side polling window. Retry after a short wait. |
| Error: 502/503/504 … | Transient backend failure. The server already retries these internally for some tools; retrying the call again usually succeeds. |

Still stuck, or think something is wrong on our side? Open a support ticket and our team will help you out. https://ubersuggest.zendesk.com/hc/en-us/requests/new

## Tool reference

## Authentication

### auth_status

*Authentication Status*

Check current authentication status and account tier.

**Parameters**: none.

**Response**

| Field | Type | Description |
| --- | --- | --- |
| (plain text) | string | Not JSON: 'Logged in as <email> / Tier: <tier>' or a not-logged-in message. |

## Domain Analysis

### domain_overview

*Domain Overview*

Get a comprehensive overview of a domain including traffic, organic keywords count, domain authority, and backlinks summary.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to analyze (e.g. 'example.com') |
| language | string | no | Language code (e.g. 'en', 'pt', 'es') Default: "en". |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Omit for global data. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Traffic, organic keyword count, domain authority and backlinks summary for the domain.

### domain_keywords

*Domain Keywords*

Get the organic or paid keywords ranking for a domain, with search volume, position, and difficulty.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to analyze (e.g. 'example.com') |
| language | string | no | Language code (e.g. 'en', 'pt', 'es') Default: "en". |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Omit for global data. |
| searchType | "organic" \| "paid" | no | Type of keywords Default: "organic". |
| limit | number | no | Max results to return Default: 50. |
| previousKey | number | no | Pagination offset |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| [] | array | Array of keyword rows. |
| [].position | number | Ranking position; null when not in top results. |
| [].keyword | string | Keyword phrase. |
| [].search_intent | string \| null | Primary intent (Informational, Navigational, Commercial, Transactional). |
| [].volume | number | Monthly search volume. |
| [].cpc | number | CPC in US dollars. |
| [].sd | number | SEO difficulty. |
| [].pd | number | Paid difficulty. |
| [].traffic | number | Estimated monthly traffic from this keyword. |
| [].url | string | Ranking URL. |

### domain_top_pages

*Domain Top Pages*

Get the top pages of a domain ranked by estimated traffic.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to analyze (e.g. 'example.com') |
| language | string | no | Language code (e.g. 'en', 'pt', 'es') Default: "en". |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Omit for global data. |
| limit | number | no | Max results to return Default: 50. |
| offset | number | no | Pagination cursor — pass back the `nextKey` value from a previous response to get the next page. Defaults to 0. Default: 0. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Top pages by estimated traffic. Supports limit/offset pagination.

### domain_top_countries

*Domain Top Countries*

Get the top countries where a domain gets organic traffic.

Pass one or more language+location pairs in 'lang_locs' (format: 'languageCode:locationId', e.g. 'en:2840' for US-English, 'pt:2076' for Brazil-Portuguese). Use 'location_suggest' to find location IDs.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to analyze (e.g. 'example.com') |
| lang_locs | string[] | yes | List of 'langCode:locId' pairs (e.g. ['en:2840','pt:2076']). Each pair is one country to query. |
| path | string | no | Optional page path to restrict analysis to a single URL. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Traffic distribution of the domain across countries.

### competitors

*Domain Competitors*

Find the main organic competitors of a domain.

The backend runs this as an async report: the tool starts the job and polls every 5s for up to ~40s, so a ready report comes back in one call. If the response has 'pendingData: true' the report is still building — call the tool again in a moment.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to analyze (e.g. 'example.com') |
| language | string | no | Language code (e.g. 'en', 'pt', 'es') Default: "en". |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Unlike other tools this one has no global mode: omitting it means the US, and each location only covers some languages (the US covers en and es), so pass the locId that matches the language you asked for. |
| competitors | string[] | no | Optional list of specific competitor domains to analyze instead of auto-detected ones. |
| limit | integer | no | Max competitors to return. Defaults to 20. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| [] | array | Array of competitor objects. |
| [].traffic | number | Headline monthly traffic estimate. |
| [].* | varies | Additional competitor metrics passed through from the API. |

The report is computed asynchronously; the server polls up to ~2 minutes before returning.

### page_overview

*Page Overview*

Get an overview of a specific page including its organic keywords and traffic.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| page | string | yes | Full URL of the page (must start with http/https) |
| language | string | no | Language code (e.g. 'en', 'pt', 'es') Default: "en". |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Omit for global data. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Organic keywords and traffic estimates for a specific page.

### page_keywords

*Page Keywords*

Get the keywords that a specific page ranks for.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| page | string | yes | Full URL of the page |
| language | string | no | Language code (e.g. 'en', 'pt', 'es') Default: "en". |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Omit for global data. |
| limit | number | no | Max results to return Default: 50. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Keywords a specific page ranks for, with per-keyword metrics.

### traffic_value

*Estimated Traffic Value*

Get the estimated monthly value in USD of a domain's organic traffic (the equivalent Google Ads spend). Only available for domains tracked as a project in this account — requires login.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to analyze. Must be one of the account's project domains (see list_projects). |
| project_id | string | no | Project ID (from 'list_projects') to read the metric from. Optional — omit it and the domain is matched against the account's projects. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Estimated monetary value (USD) of the organic traffic of a domain tracked as a project.

## Keyword Research

### keyword_overview

*Keyword Overview*

Get search volume, CPC, SEO difficulty, and paid difficulty for a keyword.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| keyword | string | yes | Keyword to analyze |
| language | string | no | Language code. Only applied together with `locId` — a global lookup (no locId) is not language-filtered. Default: "en". |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Omit for global data. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| keyword | string | The analyzed keyword. |
| search_volume | number | Monthly search volume. |
| cpc | number | CPC in US dollars. |
| seo_difficulty | number | SEO difficulty (0-100). |
| paid_difficulty | number | Paid difficulty (0-100). |
| competition | number | Competition score. |
| search_intent | string \| null | Primary search intent label. |
| language | string \| null | Language display name. |
| location | string \| null | Resolved location name. |
| monthly_searches | array | Historical monthly volume series. |
| monthly_searches[].period | string | Month key (YYYY-MM). |
| monthly_searches[].search_volume | number | Volume for that month. |

When there is no data for the keyword, the raw payload is returned with noData: true.

### keyword_suggestions

*Keyword Suggestions*

Get related keyword suggestions with metrics for seed keywords, as a flat list. For paginated or custom-sorted keyword research, use match_keywords instead.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| keywords | string[] | yes | Seed keywords (1-3) |
| language | string | no |  Default: "en". |
| locId | number | no | Location ID; accepts a country, a state or a city. Suggestions are localized to it. Omit for the backend default. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Suggestion groups: related keywords, questions, prepositions and comparisons.

### keyword_metrics

*Keyword Metrics*

Recalculate a specific metric for a keyword: search difficulty or search intent. Runs an async report on the backend and waits for the result (may take ~30s; search_difficulty consumes the monthly keyword metrics update quota).

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| keyword | string | yes |  |
| language | string | yes | Language code |
| locId | number | no | Google location ID. Must be a real ID from `location_suggest` — never an ISO country number or a guessed value. Common ones: 2840 US, 2826 UK, 2076 Brazil, 2124 Canada, 2276 Germany, 2392 Japan. Omit for global data. Default: 2840. |
| metric | "search_difficulty" \| "search_intent" | yes | Metric to retrieve |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Returns search_difficulty or search_intent data depending on the requested metric.

### serp_analysis

*SERP Analysis*

Analyze the SERP (Search Engine Results Page) for a keyword, showing top ranking URLs with metrics.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| keyword | string | yes |  |
| language | string | no |  Default: "en". |
| locId | number | no |  |
| limit | number | no | Number of SERP results Default: 20. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| location | string \| null | Resolved human-readable location name. |
| language | string \| null | Language display name. |
| (SERP entries) | array | Top ranking URLs with per-URL metrics, passed through from the API. |

### match_keywords

*Match Keywords*

Find keywords matching seed terms with volume, difficulty, and CPC data. Great for keyword research.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| keywords | string[] | yes | Seed keyword(s) or phrase(s) to expand, e.g. ["beauty ecommerce"]. This is the only required input — keyword research works from these alone, no domain needed. |
| language | string | no |  Default: "en". |
| locId | number | no |  |
| sortby | string | no | Sort field (prefix with - for desc) Default: "-search_volume". |
| limit | number | no | Approximate maximum — the API applies server-side filtering after fetching, so it may return more or fewer rows than requested. Default: 50. |
| offset | number | no | Pagination cursor — pass back the `nextKey` value from a previous response to get the next page. Defaults to 0. Default: 0. |
| domain | string | no | Optional. A real website domain (e.g. "example.com") used only to filter results by Search Difficulty achievable for that site. Leave it empty for plain keyword research. NEVER put seed keywords or phrases here — those go in `keywords`. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| searched_keywords[] | array | Metrics for each seed keyword. |
| suggestions[] | array | Matching keyword suggestions. |
| …[].keyword | string | Keyword phrase. |
| …[].search_intent | string \| null | Primary intent label. |
| …[].volume | number | Monthly search volume. |
| …[].cpc | number | CPC in US dollars. |
| …[].sd | number | SEO difficulty. |
| …[].pd | number | Paid difficulty. |
| …[].competition | number | Competition score. |
| nextKey | number | Pagination cursor — pass back as offset. |

### google_suggestions

*Google Autocomplete Suggestions*

Expand keywords into Google autocomplete suggestions, grouped the way the Ubersuggest web app groups them.

Each seed keyword is fanned out into ~60 autocomplete queries (question words, prepositions, comparison words, alphabet and digit suffixes), so this returns hundreds of real long-tail variations per keyword rather than only what autocomplete shows for the bare term. At most 10 keywords are expanded per call.

These are raw autocomplete phrases with no metrics attached — use 'keyword_overview' or 'match_keywords' to get search volume, CPC and difficulty for the ones worth pursuing.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| keywords | string[] | yes | Keywords to get suggestions for (max 10 are expanded) |
| language | string | no |  Default: "en". |
| country | string | no | Country code (e.g. 'us', 'br') |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Google Autocomplete suggestions for the given keywords.

### estimate_serp_clicks

*Estimate SERP Clicks*

Estimate monthly click-through traffic for each SERP result, given its search volume, position, and result type.

This is a calculator — it does NOT look up rankings. For each entry you already know (volume + current position + type), it returns the projected clicks. Use it to model scenarios like "if I ranked #3 for kw X (10k volume), how many clicks would I get?". To discover positions for a keyword, use 'serp_analysis' or 'domain_keywords' first.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| serps | object[] | yes | List of SERP entries to estimate. Each needs searchVolume, position, and type. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Projected clicks per submitted SERP entry based on position, type and ads.

## Keyword Lists

### keyword_lists

*Keyword Lists*

List the user's saved keyword lists with their aggregates. Requires login. Use keyword_list with an id to read the keywords in one list. A free or downgraded account only reaches its three newest lists; 'hidden_by_plan' counts the ones an upgrade would unlock.

**Parameters**: none.

**Response**

| Field | Type | Description |
| --- | --- | --- |
| keyword_lists[] | array | The user's saved lists, newest first. |
| keyword_lists[].id | string | Pass it to the other keyword-list tools. |
| keyword_lists[].keyword_count | number \| null | Keywords stored in the list. |
| keyword_lists[].total_volume | number \| null | Summed monthly search volume. |
| keyword_lists[].avg_cpc | number \| null | Average cost per click. |
| keyword_lists[].avg_sd | number \| null | Average SEO difficulty (0-100); avg_pd is the paid one. |
| hidden_by_plan | number | Lists left out because the account's plan does not open them (free and downgraded accounts reach their three newest). |

### keyword_list

*Keyword List*

Read one saved keyword list: its keywords with volume, CPC and difficulty. Requires login. Get the id from keyword_lists. A free account reads only as many keywords as its plan allows; 'hidden_by_plan' counts the rest, which an upgrade unlocks.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| list_id | string | yes | Keyword list id, from keyword_lists |
| limit | integer | no | How many keywords to return, highest volume first Default: 100. |
| offset | integer | no | Keywords to skip, for paging through a large list Default: 0. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| keyword_count | number \| null | Keywords in the whole list, not in this page. |
| hidden_by_plan | number | Keywords left out because the account's plan does not open them, mirroring the cap the Ubersuggest site applies. |
| returned | number | Keywords in this response (see `limit`/`offset`). |
| keywords[] | array | Highest search volume first. |
| keywords[].language | string \| null | null together with loc_id for a global keyword. |
| keywords[].sd | number \| null | SEO difficulty (0-100); pd is the paid one. |
| keywords[].updated_at | number \| null | Unix timestamp of the last difficulty update. |

### create_keyword_list

*Create Keyword List*

Create a saved keyword list, optionally filling it with keywords. Requires login. The account's plan caps how many lists it may have.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| name | string | yes | Name of the new list |
| keywords | object[] | no | Keywords to put in the list right away |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| id | string | The new list's id. |
| keywords_added | number | Keywords accepted from the `keywords` input. |

Aggregates are null until the list holds keywords. Plan-capped: creating past the account's list limit fails with 'Exceeded maximum number of keyword lists for a single user.'

### add_keywords_to_list

*Add Keywords to List*

Add keywords to a saved keyword list. Requires login. Only the new keywords are needed — existing ones stay, and duplicates are ignored. The account's plan caps how many keywords one list may hold.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| list_id | string | yes | Keyword list id, from keyword_lists |
| keywords | object[] | yes | Keywords to add |

**Response**


Returns the list's aggregates after the write, without its keywords — read those with keyword_list. Keywords already in the list are ignored. Plan-capped per list.

### remove_keywords_from_list

*Remove Keywords from List*

Remove keywords from a saved keyword list. Requires login. Match the language and loc_id shown by keyword_list — the same phrase in another location is a different entry.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| list_id | string | yes | Keyword list id, from keyword_lists |
| keywords | object[] | yes | Keywords to remove |

**Response**


Returns the list's aggregates after the write. Keywords that are not in the list are ignored.

### rename_keyword_list

*Rename Keyword List*

Rename a saved keyword list. Requires login. Its keywords are untouched.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| list_id | string | yes | Keyword list id, from keyword_lists |
| name | string | yes | New name |

**Response**


Returns the list's aggregates under the new name.

### delete_keyword_list

*Delete Keyword List*

Delete a saved keyword list and every keyword in it. Requires login. This cannot be undone — confirm with the user first.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| list_id | string | yes | Keyword list id, from keyword_lists |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| deleted | boolean | True once the list is gone. |

## Backlinks

### backlinks_overview

*Backlinks Overview*

Get a backlinks summary for a domain: total backlinks, referring domains, domain authority.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to analyze |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Totals for the target: backlinks, referring domains, domain authority.

### backlinks

*Backlinks List*

List individual backlinks pointing to a domain or page.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain or URL to analyze |
| mode | "domain" \| "url" \| "host" \| "page" | no | Scope of the target: 'domain' (whole domain, includes subdomains), 'url' (that exact page only — pass the full URL in `domain`), or 'host' (exact host, excludes subdomains). 'page' is a deprecated alias of 'url'. Default: "domain". |
| limit | number | no |  Default: 20. |
| offset | number | no | Pagination cursor — pass back the `previousKey` value from a previous response to get the next page. Defaults to 0. Default: 0. |
| one_per_domain | boolean | no | If true, returns at most one backlink per referring domain. Defaults to false. |
| order_by | "domain_inlink_rank" \| "page_inlink_rank" \| "first_seen" \| "last_seen" | no | Sort field. Defaults to 'domain_inlink_rank'. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Backlink rows for the target. Response includes previousKey — pass it back as offset to paginate.

### anchor_texts

*Anchor Texts*

Get the most common anchor texts used in backlinks to a domain.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes |  |
| mode | "domain" \| "url" \| "host" \| "page" | no | Scope of the target: 'domain' (whole domain, includes subdomains), 'url' (that exact page only — pass the full URL in `domain`), or 'host' (exact host, excludes subdomains). 'page' is a deprecated alias of 'url'. Default: "domain". |
| limit | number | no | Max results per page. Upstream caps this at 25; higher values are reduced to 25. Default: 20. |
| offset | number | no | Pagination cursor — pass back the `previousKey` value from a previous response to get the next page. Defaults to 0. Default: 0. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Anchor text distribution. Response includes previousKey — pass it back as offset to paginate.

### linking_domains

*Linking Domains*

Get referring domains that a target domain recently gained or lost.

'filter_by' selects the set: 'new' (gained, default) or 'lost'. There is no "full live list" option — for totals use 'backlinks_overview' instead.

Date range ('begin_date'/'end_date') narrows the gained/lost window (the data source limits this to roughly the last 60 days). For "domains acquired in May 2026", use filter_by='new' with that month's begin/end dates.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Target domain or URL to analyze. |
| mode | "domain" \| "url" \| "host" | no | Scope of the target: 'domain' (whole domain, includes subdomains), 'url' (that exact page only — pass the full URL in `domain`), or 'host' (exact host, excludes subdomains). Default: "domain". |
| filter_by | "new" \| "lost" | no | 'new' = recently gained, 'lost' = recently lost. Default: "new". |
| begin_date | string | no | Optional ISO date (YYYY-MM-DD). |
| end_date | string | no | Optional ISO date (YYYY-MM-DD). |
| limit | number | no |  Default: 20. |
| offset | number | no | Pagination cursor — pass back the `previousKey` value from a previous response. Defaults to 0. Default: 0. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Referring domains. Response includes previousKey — pass it back as offset to paginate.

### backlink_opportunity

*Backlink Opportunities*

Find backlink opportunities: referring domains that link to your competitors ('positive_targets') but not to you ('negative_targets').

Typical flow: run 'competitors' first to pick competitor domains, put them in 'positive_targets' each with scope='domain', and put your own domain in 'negative_targets'. The response lists referring domains linking to the positive_targets but not to the negative_targets.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| positive_targets | object[] | yes | Competitor domains/URLs you want referrers of (e.g. [{target: 'competitor.com', scope: 'domain'}]). |
| negative_targets | object[] | no | Your own domain(s) to exclude (referrers that link here are filtered out). E.g. [{target: 'yourdomain.com', scope: 'domain'}]. |
| limit | number | no |  Default: 20. |
| offset | number | no | Pagination cursor — pass back the `previousKey` value from a previous response. Defaults to 0. Default: 0. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| backlinks[] | array | Referring-domain opportunity rows. |
| backlinks[].competitors_to | string | Comma-separated competitor domains linked to. |
| backlinks[].* | varies | Additional row metrics passed through from the API. |
| previousKey | number | Pagination cursor — pass back as offset. |

## Content

### content_ideas

*Content Ideas*

Get content ideas for keywords: top-performing pages by social shares, estimated visits, and backlinks.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| keywords | string[] | yes | Keywords to find content ideas for |
| language | string | no |  Default: "en". |
| locId | number | no |  |
| sortby | "-facebookShares" \| "facebookShares" \| "-estVisits" \| "estVisits" \| "-totalShares" \| "totalShares" \| "-redditShares" \| "redditShares" \| "-pinterestShares" \| "pinterestShares" | no | Sort field. Prefix with '-' for descending. Valid fields: facebookShares, estVisits, totalShares, redditShares, pinterestShares. Backlinks/refdomains cannot be used to sort — they're enriched after pagination. Default: "-facebookShares". |
| limit | number | no |  Default: 25. |
| offset | number | no | Pagination cursor — pass back the `previousKey` value from a previous response to get the next page. Defaults to 0. |
| filters | object | no | Optional filters object (e.g. { minShares: 100, language: 'en' }). |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Pages with social shares, estimated visits and backlinks. Response includes previousKey for pagination.

### page_shares

*Page Social Shares*

Get social media share counts + backlink/traffic metrics for a batch of page URLs. Supports multiple URLs in a single call.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| page_urls | string[] | yes | Page URLs to query (e.g. ['https://example.com/post-1', 'https://example.com/post-2']). Domains alone also accepted. |
| language | string | no | Language code (e.g. 'en', 'pt'). Defaults to 'en'. |
| locId | number | no | Location ID (e.g. 2840 for US). Defaults to 2840. |
| mode | "domain" \| "url" \| "host" | no | Scope for backlink metrics lookup. Defaults to 'url'. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Social share counts plus backlink/traffic metrics per URL.

## Site Audit

### site_audit

*Run Site Audit*

Starts (or re-starts) a site audit crawl for a domain AND returns the initial crawl status. Requires a paid account.

This is STEP 1 of the site audit flow. Internally the tool:
  a) calls the backend to register the crawl task (without this step, later status calls fail with "Task has not been set");
  b) immediately reads 'site_audit_status' once so the response tells you whether the crawl just started, is already in progress, or a cached report is already available.

Response is the same shape as 'site_audit_status':
  result.done === true  -> a report is ready (cached or freshly finished). Inspect 'result.report'.
  result.done === false -> crawl is running. Start polling 'site_audit_status' every ~5 seconds with the same 'domain' / 'path' / 'crawlMaxPages' until 'result.done' is true.

Then, to list URLs affected by a specific issue id from 'result.report.issues_per_category.{errors|warnings|recommendations}[].id', call 'site_audit_results'.

Use 'path' only when auditing a single URL (page audit) rather than the whole domain. Set 'recrawl' to true to force a fresh crawl ignoring any cached result.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Root domain to audit (e.g. 'example.com'). For URL-mode audits pass the domain here and the page path in 'path'. |
| path | string | no | Optional path for URL-mode audit (e.g. '/pricing'). Omit to audit the whole domain. |
| crawlMaxPages | integer | no | Max pages to crawl. Defaults to 150 (free tier). Paid plans allow more; pass a higher value if the user's plan permits. |
| recrawl | boolean | no | If true, forces a fresh crawl and bypasses the cache. Defaults to false. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| result.done | boolean | True when the crawl has finished. |
| result.crawl_count | number | Pages crawled so far. |
| result.crawl_max_pages | number | Total pages the crawl will visit. |
| result.report.overview | object | Issue totals and site health score. |
| result.report.issues_per_category | object | errors / warnings / recommendations with counts. |
| result.extended_status | string | 'no_errors' on success; otherwise the crawl failed. |

Starts (or refreshes) the crawl and returns the current status — same shape as site_audit_status. Poll site_audit_status until result.done.

### site_audit_status

*Site Audit Status*

Checks the progress/result of a site audit previously started with 'site_audit'. Requires a paid account.

This is STEP 2 of the site audit flow. Poll this tool repeatedly (every ~5 seconds) until 'result.done' is true.

Response shape:
  result.done            -> false while crawling, true when finished
  result.crawl_count     -> pages crawled so far
  result.crawl_max_pages -> total pages the crawl will visit
  result.report          -> partial (while crawling) or final (when done) audit report, including:
      overview           -> totals + health score
      issues_per_category: { errors, warnings, recommendations } each with list of issue ids + counts
  result.extended_status -> 'no_errors' on success, otherwise the crawl failed

When 'result.done' is true, stop polling and inspect 'result.report.issues_per_category' for issue ids. To list the affected URLs for a given issue id, call 'site_audit_results'.

Pass the same 'domain' / 'path' / 'crawlMaxPages' you used when calling 'site_audit'.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Root domain being audited (e.g. 'example.com'). |
| path | string | no | Page path for URL-mode audits. Must match what was passed to 'site_audit'. |
| crawlMaxPages | integer | no | Max pages used when starting the audit. Defaults to 150. Must match what was passed to 'site_audit'. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| result.done | boolean | False while crawling, true when finished. |
| result.crawl_count | number | Pages crawled so far. |
| result.crawl_max_pages | number | Total pages the crawl will visit. |
| result.report | object | Partial or final audit report. |
| result.extended_status | string | 'no_errors' on success; otherwise the crawl failed. |

### site_audit_results

*Site Audit Results*

Gets the list of pages affected by a specific SEO issue from a completed site audit. Requires a paid account.

This is STEP 3 of the site audit flow — call it AFTER 'site_audit_status' returned 'result.done === true'. You do not need this tool to get the issue summary (that already lives in the status response's 'result.report.issues_per_category').

Pick the 'issue' id from 'result.report.issues_per_category.{errors|warnings|recommendations}[].id' in the status response (e.g. 'seo_missing_h1', 'seo_broken_links', 'seo_duplicate_titles').

Response contains:
  result.breakdown -> affected URLs with status + recommendation
  result.ignored   -> URLs the user previously ignored
  result.diff      -> change vs. previous audit

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain that was audited. |
| issue | string | yes | Issue id from the status report's issues_per_category (e.g. 'seo_missing_h1'). Not a free-text label. |
| path | string | no | Page path for URL-mode audits. Must match what was passed to 'site_audit'. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| result.breakdown[] | array | Affected URLs with status and recommendation. |
| result.ignored[] | array | URLs the user previously ignored. |
| result.diff | object | Change versus the previous audit. |

### site_audit_pages

*Site Audit Pages*

Lists every URL that was crawled during a completed site audit, with HTTP status and index state. Requires a paid account.

Use this after 'site_audit_status' returns 'result.done === true' when the user wants the full list of discovered pages (not issue breakdown). For issue-specific URLs use 'site_audit_results' instead.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain that was audited. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Crawled URLs with HTTP status and index state.

### pagespeed_audit

*PageSpeed Audit*

Run a PageSpeed audit on a domain to check Core Web Vitals and performance.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to audit (e.g. 'example.com'). |
| forceUpdate | boolean | no | Bypass the cached result and re-run the audit. Defaults to false. |
| devices | string | no | Comma-separated device strategies. Defaults to 'DESKTOP,MOBILE'. Values: 'DESKTOP', 'MOBILE'. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| report.<device> | object | Per-device report (DESKTOP / MOBILE). |
| report.<device>.status | string | Device audit status. |
| report.<device>.field_data | object | Core Web Vitals metric → category (e.g. FAST). |
| report.<device>.lab_metrics | object | Lab metric → formatted value. |
| report.<device>.opportunities[] | array | Improvement opportunities with estimated_savings and size_reduction_kb. |

The server polls up to ~2 minutes for the audit to complete.

## Projects

### list_projects

*List Projects*

List all your tracked projects/domains. Requires login.

**Parameters**: none.

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

The user's Ubersuggest projects. Requires login.

### get_project

*Get Project*

Get details of a specific project including tracked keywords and settings. Requires login.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Project details including tracked keywords and settings. Requires login.

### project_position_info

*Project Keyword Position Tracking*

Get ranking positions for the tracked keywords of a project (rank tracking report). Requires login.

Returns the full report. How to read the response:
- "done": true — the report is FINAL. Treat it as a definitive answer; do not retry expecting different data.
- A keyword's "status": "ok" with "old_position.position": null and "new_position.position": null means the domain does NOT rank in the top 100 for that keyword. This is a final answer ("not ranking"), NOT a "still loading" state. The "binned.not_ranking" bucket counts these.
- A keyword's "status": "pending" only appears for brand-new projects whose first SERP collection hasn't run yet (no "updated_at"). In that case, ranking data typically appears 5-60 minutes after project creation; the caller should retry then.
- "average_positions.positions": [] simply means there's no historical ranking series to plot — consistent with "not ranking".

Polls briefly (HTTP 200/202) for the rare cached-report regeneration path. If the backend errors persistently the tool throws — retry in a few minutes. Filter by lang/location/device; pick one combo you track in the project.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID (from 'list_projects' or 'create_project'). |
| startDate | string | yes | Required. ISO date (YYYY-MM-DD). Start of the date range for the report. Use today minus 30 days if no specific range is given. |
| endDate | string | yes | Required. ISO date (YYYY-MM-DD). End of the date range for the report. Use today's date if no specific end is given. |
| locId | number | no | Location ID to filter results (e.g. 2840 US). Must match one tracked in the project. |
| language | string | no | Language code (e.g. 'en', 'pt'). Must match one tracked in the project. |
| device | "desktop" \| "mobile" | no | Device to filter results. Defaults to 'desktop'. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| done | boolean | Report is final when true. |
| keywords[].status | string | 'ok' or 'pending' per tracked keyword. |
| keywords[].old_position.position | number \| null | Previous rank; null = not in top 100. |
| keywords[].new_position.position | number \| null | Current rank; null = not in top 100. |
| binned.not_ranking | number | Count of non-ranking keywords. |
| average_positions.positions | array | Historical ranking series. |
| updated_at | string | Last SERP collection time; absent for new projects. |

The server retries transient backend errors (502/503/504) and polls while the report is being computed.

### seo_opportunities

*SEO Opportunities*

Get SEO improvement opportunities for a project. Requires login.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes |  |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

SEO opportunities detected for the project. Requires login.

### create_project

*Create Project*

Create a new tracked project for a domain. Requires login. Use location_suggest to find valid loc_id values.

For a first-time setup, prefer 'onboard_project': it analyses the business, then suggests competitors, keywords, topics and prompts, and sets up AI Search Visibility too.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to track (e.g. 'example.com') |
| title | string | no | Project title. Defaults to the domain — the API rejects an empty title, so one is always sent. |
| locations | object[] | yes | Locations to track rankings for |
| keywords | object | no | Keywords to track. Map of keyword phrase to array of {lang, loc_id}. Example: {"seo tools": [{"lang": "en", "loc_id": 2840}]} |
| competitors | object | no | Competitors to track. Map of competitor domain to array of {lang, loc_id}. Example: {"competitor.com": [{"lang": "en", "loc_id": 2840}]} |
| business_summary | object | no | What the business does. Content Studio needs it, and passing it here saves a later call to 'project_business_summary'. |
| project_type | "standard" \| "pitch" | no | Project type. 'pitch' is a time-limited prospect project and needs a pitch slot on the plan. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

The created project object. Requires login.

### onboard_project

*Set Up a Project (Onboarding)*

Run the full first-time setup for a domain: analyse the business, suggest competitors, generate AI Search Visibility topics and prompts, generate keywords, then create the project and its brand. Requires login.

**Call it repeatedly.** The flow takes minutes, so one call does as much as fits and returns 'done': false with a 'stage' and everything gathered so far. To resume, call again with the same 'domain' and 'locations' — the generation jobs are keyed by them — and pass every returned field straight back, so nothing is recomputed.

**It stops at 'stage': "review" and creates nothing.** At that point show the user the business summary, competitors, topics with their prompts, and keywords, and ask what to change. Only call again with 'confirm': true once they have answered. To apply edits, send the corrected 'competitors', 'topics' or 'keywords' along with 'confirm' — what you pass wins over what was generated.

Reviewing matters: saving the brand spends one of the account's monthly 'brand_operations' credits, and prompts can only be changed by resending the whole list, so a fix afterwards costs another credit.

Lists are trimmed to what the plan allows before anything is created; 'notes' says what was dropped. Resolve 'locations' with 'location_suggest' first — guessing a loc_id silently tracks the wrong country.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | Domain to set up, e.g. 'example.com'. |
| locations | object[] | no | Markets to track, first one is primary. Defaults to English / United States (loc_id 2840) — pass it explicitly for anywhere else. |
| confirm | boolean | no | Set true only after the user has reviewed the generated setup. False stops at the review stage without creating anything. Default: false. |
| title | string | no | Project title. Defaults to the business name. |
| brand_name | string | no | Brand name for AI Search Visibility. Defaults to the business name. |
| project_type | "standard" \| "pitch" | no | Project type. 'pitch' requires a pitch slot on the plan. |
| skip_brand | boolean | no | Create the project without AI Search Visibility tracking. Default: false. |
| business_summary | object | no | Carry-over, or the summary to use when the website cannot be analysed. Ask the user for the facts rather than inventing them. |
| competitors | object[] | no | Carry-over, or the edited competitor list to use. |
| topics | object[] | no | Carry-over, or the edited topics and prompts to use. |
| keywords | string[] | no | Carry-over, or the edited keyword list to track. |
| brand_competitors | object[] | no | Competitor brands for AI Search Visibility. Defaults to the project competitors. |
| project_id | string | no | Carry-over once the project exists, so a resumed call goes straight to the brand step. |

### add_project_keywords

*Add Keywords to Project*

Add keywords to an existing project. Requires login. First use get_project to see current keywords, then include ALL existing keywords plus the new ones.

The plan limit counts one entry per keyword **per tracked location**, so N keywords across M locations cost N*M against 'keywords_per_project' (see 'user_limits'). Going over returns a bare "Invalid project parameter: keywords".

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes |  |
| keywords | object | yes | Full keywords map (existing + new). Map of keyword phrase to array of {lang, loc_id}. Example: {"seo tools": [{"lang": "en", "loc_id": 2840}]} |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

The updated project object. Requires login.

### add_project_competitors

*Add Competitors to Project*

Add competitors to an existing project. Requires login. First use get_project to see current competitors, then include ALL existing competitors plus the new ones.

The plan limit counts one entry per competitor **per tracked location**, so N competitors across M locations cost N*M against 'competitors_per_project' (see 'user_limits'). Going over returns a bare "Invalid project parameter: competitors". Each key must be a bare domain, e.g. 'competitor.com'.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes |  |
| competitors | object | yes | Full competitors map (existing + new). Map of competitor domain to array of {lang, loc_id}. Example: {"competitor.com": [{"lang": "en", "loc_id": 2840}]} |
| competitors_locations | object[] | no | Locations for competitor tracking. If not provided, uses project locations. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

The updated project object. Requires login.

## AI Search Visibility

### brand_config

*AI Search Visibility — Brand Config*

Get the AI Search Visibility (AISV) brand setup for a project: tracked topics and prompts, competitors, alias groups, update frequency and limits. Requires login.

Use this to understand WHAT is being tracked before interpreting the visibility numbers from 'brand_visibility_overview' / 'brand_prompts'.

To find the project_id: call 'list_projects' and pick a project with "has_brand": true. An error mentioning "No brand found" means the project has no AISV brand configured — the user must set one up in the app first.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID (from 'list_projects'; pick one with has_brand: true). |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Brand tracking configuration: topics, prompts, competitors, alias groups, update frequency. Requires login; 404 means no brand configured.

### brand_visibility_overview

*AI Search Visibility — Overview*

Get the headline AI Search Visibility (AISV) metrics for a project's brand: how often the brand appears in AI assistant answers (visibility %), average rank, share of voice, total mentions and sentiment — overall and broken down by provider — plus the competitive brand ranking and aggregated search intents. Requires login.

To find the project_id: call 'list_projects' and pick a project with "has_brand": true. Use 'brand_prompts' for the per-prompt breakdown, and 'brand_config' to see what is tracked.

If the response is prefixed with a pending_update note, the report is still computing — ask the user to retry in a few minutes.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID (from 'list_projects'; pick one with has_brand: true). |
| start_date | string | no | ISO date (YYYY-MM-DD). Start of the range. Defaults to 30 days ago. Clamped to the brand's creation date. |
| end_date | string | no | ISO date (YYYY-MM-DD). End of the range. Defaults to today. |
| provider | "openai" \| "gemini" \| "google_aio" | no | Filter to a single AI provider. Omit for all providers. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| overview | object | Headline visibility metrics for the user's brand. |
| brand_aggregations[] | array | Competitive brand ranking (visibility, rank, mentions, sentiment) — capped at 25 brands. |
| aggregated_intents | object | Aggregated search intents. |

If the report is still computing, the JSON is prefixed with a plain-text NOTE line. Requires login.

### brand_prompts

*AI Search Visibility — Prompts*

Get the per-prompt AI Search Visibility (AISV) breakdown for a project's brand: for each tracked prompt, how the user's brand ranks, which brands were found, sentiment, sentiment keywords (positive/negative) and search intents. Requires login.

To find the project_id: call 'list_projects' and pick a project with "has_brand": true. Use 'brand_visibility_overview' for the headline metrics and competitive ranking, and 'brand_config' to see the tracked topics/prompts.

If the response is prefixed with a pending_update note, the report is still computing — ask the user to retry in a few minutes.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID (from 'list_projects'; pick one with has_brand: true). |
| start_date | string | no | ISO date (YYYY-MM-DD). Start of the range. Defaults to 30 days ago. Clamped to the brand's creation date. |
| end_date | string | no | ISO date (YYYY-MM-DD). End of the range. Defaults to today. |
| provider | "openai" \| "gemini" \| "google_aio" | no | Filter to a single AI provider. Omit for all providers. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| prompt_aggregations[] | array | One entry per tracked prompt. |
| prompt_aggregations[].prompt_text | string | The tracked prompt. |
| prompt_aggregations[].user_brand_data | object | User brand metrics for this prompt. |
| prompt_aggregations[].sentiment | string | Prompt-level sentiment. |
| prompt_aggregations[].positive_keywords | array | Top positive sentiment keywords (max 5). |
| prompt_aggregations[].negative_keywords | array | Top negative sentiment keywords (max 5). |
| prompt_aggregations[].brands_found[] | array | Top brands in AI answers (max 8): name, visibility %, rank, mentions, sentiment. |

If the report is still computing, the JSON is prefixed with a plain-text NOTE line. Requires login.

### configure_brand

*AI Search Visibility — Configure Brand*

Create or update the AI Search Visibility (AISV) brand for a project: the topics and prompts tracked across ChatGPT, Gemini and Google AI Overviews, plus the competitor brands compared against. Requires login.

Creates the brand when the project has none, otherwise updates it. Creating one also starts the tracking.

Before calling:
  - 'brand_config' to read what is tracked today. **'topics' fully replaces the stored list** — there is no way to edit a single topic or prompt, so resend every topic you want to keep, including its prompts.
  - 'user_limits' for 'prompts_per_brand' (the cap on total prompts) and 'brands' (how many brands the plan allows).

This spends one of the account's monthly 'brand_operations' credits whenever topics or prompts change, so confirm the list with the user first rather than saving twice.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID (from 'list_projects' or 'create_project'). |
| brand_name | string | yes | The brand as customers name it, e.g. 'Ubersuggest'. |
| topics | object[] | yes | Full list of topics to track, each with its prompts. Replaces the stored list entirely. |
| competitors | object[] | no | Competitor brands to compare visibility against. |

### industry_detect

*AI Search Visibility — Detect Industry*

Work out which industry a brand belongs to, and suggest related topics worth tracking in AI Search Visibility. Requires login.

Use it when adding topics to a brand that already exists. For a brand-new project, 'onboard_project' already derives topics from the business summary.

Feed the topics you pick into 'industry_prompts' to get prompts, then save both with 'configure_brand'.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | yes | The brand's domain, e.g. 'example.com'. |
| brand_name | string | yes | The brand as customers name it. |
| loc_id | number | yes | Location ID from 'location_suggest'. Do not guess it. |
| language | string | yes | Language code, e.g. 'en'. |

### industry_prompts

*AI Search Visibility — Generate Prompts*

Generate the prompts to track for a brand's topics — the questions real users would ask an AI assistant about that topic. Requires login.

Use it when adding topics to a brand that already exists; 'onboard_project' covers a brand-new project.

The prompts come back per topic, ready to hand to 'configure_brand'. Show them to the user before saving: the wording decides what the visibility numbers actually measure.

Spends one of the account's monthly 'brand_operations' credits.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| brand_name | string | yes | The brand as customers name it. |
| domain | string | yes | The brand's domain, e.g. 'example.com'. |
| topics | object[] | yes | Topics to generate prompts for. |
| project_id | string | no | Project the brand belongs to, when it already exists. |

## Content Studio

### project_business_summary

*Project Business Summary*

Makes sure a project has the business summary that Content Studio requires, and returns it. Requires login. Free of charge.

Call this before 'article_title_suggestions' / 'generate_article', or whenever one of them fails with "business summary is missing or incomplete".

Behaviour:
  - If the project already has a complete summary, it is returned as-is ('already_complete' = true) and nothing is written.
  - Otherwise the project's website is analysed (this takes up to ~1 minute) and the resulting summary is saved on the project. If the analysis is still running the tool returns 'done' = false — just call it again.
  - Pass 'business_summary' yourself to write it directly, e.g. when the automatic analysis cannot read the site. Ask the user for the facts instead of inventing them.
  - Omit 'project_id' and pass 'domain' to just analyse a site and get the summary back without storing it — useful before the project exists.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | no | Project ID (from 'list_projects' or 'create_project'). Omit it to analyse a domain without storing anything — pass `domain` instead. |
| domain | string | no | Domain to analyse. Defaults to the project's own domain. Required when `project_id` is omitted. |
| language | string | no | Language code the summary should be written in. Default: "en". |
| business_summary | object | no | Write this summary to the project instead of analysing the website. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| project_id | string | Project the summary belongs to. |
| done | boolean | False while the website analysis is still running — call the tool again. |
| already_complete | boolean | True when the project already had a complete summary, so nothing was written. |
| business_summary | object \| null | Summary stored on the project: business_name, business_type, about[], target_customers[], key_features[], and optionally unique_value_proposition, geographic_focus, suggested_topics[], sitemaps[]. |

Requires login. Free of charge. Writes to the project unless already_complete is true.

### article_title_suggestions

*Article Title Suggestions*

Suggests article titles plus a content angle for a project, from a keyword or a free-form prompt. Requires login. Free of charge.

This is STEP 1 of writing an article with Content Studio:
  1. 'article_title_suggestions' -> pick one 'titles' entry and keep 'content_idea';
  2. 'generate_article' with that title + content_idea (costs 100 monthly credits);
  3. 'get_article' until the article is ready.

The project must have a business summary, which is what grounds the suggestions in the user's business. If this fails with "business summary is missing or incomplete", call 'project_business_summary' for the same project_id first, then retry.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID (from 'list_projects' or 'create_project'). |
| source_type | "keyword" \| "prompt" | no | What the article is seeded from: 'keyword' (default, uses `keyword`) or 'prompt' (uses `prompt`). Default: "keyword". |
| keyword | string | no | Target keyword. Required when source_type is 'keyword'. |
| prompt | string | no | Free-form description of what to write about. Required when source_type is 'prompt'. |
| language | string | no | Language code for the article (e.g. 'en', 'pt', 'es'). |
| locId | number | no | Google location ID the content targets. Must be a real ID from `location_suggest`. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| content_idea | string | Content angle the titles share; pass it verbatim to generate_article. |
| why_relevant | string | Why the angle fits the project's business. |
| titles | array | Suggested article titles. |

Requires login and a project with a business summary. Free of charge.

### generate_article

*Generate Article*

Starts writing a full SEO article for a project and returns its 'article_id'. Requires login and a paid plan.

COST: 100 credits from the account's monthly credit pool ('monthly_keyword_metrics_updates'). Free and lowest-tier plans do not have enough allowance, so confirm with the user before calling.

ASYNCHRONOUS: the response only means the job was queued ('status' = "queued"). The article itself takes several minutes.
  -> Poll 'get_article' with the returned 'article_id' every ~15 seconds.
  -> Stop when 'status' is "generated" / "delivered" / "delivery_failed" (content is ready) or "failed" / "cancelled" (credits are refunded automatically).

'title' and 'content_idea' should come from 'article_title_suggestions'; pass the same seed ('source_type' + 'keyword'/'prompt') used there. The project must have a business summary — on "business summary is missing or incomplete", call 'project_business_summary' first.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID (from 'list_projects' or 'create_project'). |
| title | string | yes | Article title, normally one of the 'titles' returned by 'article_title_suggestions'. |
| content_idea | string | yes | The content angle to write about — pass the 'content_idea' from 'article_title_suggestions' verbatim. |
| source_type | "keyword" \| "prompt" | no | What the article is seeded from: 'keyword' (default, uses `keyword`) or 'prompt' (uses `prompt`). Default: "keyword". |
| keyword | string | no | Target keyword. Required when source_type is 'keyword'. |
| prompt | string | no | Free-form description of what to write about. Required when source_type is 'prompt'. |
| language | string | no | Language code for the article (e.g. 'en', 'pt', 'es'). |
| locId | number | no | Google location ID the content targets. Must be a real ID from `location_suggest`. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| article_id | string | ID to poll with get_article. |
| status | string | Initial status — "queued" when the job was accepted. |
| limits | object | Monthly credit pool after the charge: monthly_keyword_metrics_updates_limits and monthly_keyword_metrics_updates_used. |

Requires login. Costs 100 monthly credits and is asynchronous — poll get_article until the article is ready.

### get_article

*Get Article*

Reads a Content Studio article and its generation status. Requires login. Free of charge — this is the polling endpoint for 'generate_article'.

'status' values:
  "queued" / "generating" -> still being written; 'content' is null. Poll again in ~15 seconds.
  "generated" / "delivered" / "delivery_failed" -> the article is ready; read 'content' (markdown), 'outline' and 'meta_description'.
  "failed" / "cancelled" -> generation stopped; 'error' explains why and the 100 credits were refunded.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| project_id | string | yes | Project ID the article belongs to. |
| article_id | string | yes | Article ID returned by 'generate_article'. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| article_id | string | The article's ID. |
| status | string | queued \| generating \| generated \| delivered \| delivery_failed \| failed \| cancelled. |
| title | string \| null | Article title. |
| content | string \| null | Article body in Markdown; null until generation finishes. |
| meta_description | string \| null | SEO meta description. |
| outline | string \| null | Heading outline of the article. |
| featured_image_url | string \| null | URL of the generated featured image. |
| last_saved_at | string \| null | When the article was last saved (ISO 8601). |
| error | string \| null | Failure reason when status is failed or cancelled. |
| limits | object \| null | Monthly credit pool; null while generation is running. |

Requires login. Free of charge — this is the polling endpoint for generate_article. Credits are refunded automatically on failed/cancelled.

## Utilities

### validate_site

*Validate Site*

Validate if a domain or URL is reachable and can be analyzed by Ubersuggest.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| site | string | yes | Domain (e.g. 'example.com') or full URL to validate. |
| is_domain | boolean | no | True if 'site' is a bare domain, false if it's a full URL. Defaults to true. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Whether the site is reachable and analyzable.

### location_suggest

*Location Autocomplete*

Search for location IDs by name. Useful to find the locId parameter for other tools. Matches countries as well as cities and states.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| query | string | yes | Location name to search (e.g. 'Brazil', 'New York', 'London') |
| lang | string | no | Language of the response labels (e.g. 'en', 'pt'). Defaults to 'en'. |
| limit | integer | no | Max number of matches to return (default 8). Default: 8. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| [].id | number | Location ID. |
| [].name | string | Location name. |

### location_details

*Location Details*

Get details (name, type, parent hierarchy) for one or more location IDs, countries included. Accepts a list so you can resolve several at once.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| location_ids | string,number[] | yes | One or more location IDs (e.g. [2840] for US). |
| lang | string | no | Language of the response labels (e.g. 'en', 'pt'). Defaults to 'en'. |

**Response**

Returns the raw Ubersuggest API payload for this report (fields defined by the backend).

Name, type and parent hierarchy for each location ID.

### user_limits

*Plan Limits*

Get the account's plan allowances. Requires login. Free of charge.

Call this before setting a project up, so lists are trimmed to what the plan accepts. The backend rejects an over-limit request with a bare "invalid_parameter" that does not say which limit was hit.

Useful keys: 'projects', 'keywords_per_project', 'competitors_per_project', 'locations_per_project', 'brands' (AI Search Visibility slots), 'prompts_per_brand', 'topics_per_brand', 'keyword_lists', 'keywords_per_keyword_list', and the monthly 'brand_operations_limits'/'brand_operations_used' pool that brand edits draw on.

**Parameters**: none.

## Blog

### search_neilpatel_blog

*Search Neil Patel Blog*

Search articles from Neil Patel's blog. Filter by category server-side and/or refine with query keywords. Returns matching articles with full content as markdown. Provide at least one of: query, category.

**Parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| query | string | no | Keywords to rank results client-side (e.g. 'technical seo', 'link building'). Required if no category is provided. |
| category | "seo" \| "content-marketing" \| "social-media" \| "paid-ads" \| "data-analytics" \| "lead-gen" \| "news-trends" \| "email-marketing" \| "marketing" \| "ecommerce" | no | Optional WordPress category slug to scope results server-side. Pick the closest topic — invalid slugs return no articles. |
| limit | integer | no | Maximum number of articles to return (1-5). Default: 3. |
| full_content | boolean | no | If true, include the full article content as markdown. If false, only the excerpt. Default: true. |

**Response**

| Field | Type | Description |
| --- | --- | --- |
| [].title | string | Article title. |
| [].url | string | Article URL. |
| [].date | string | Publication date; may be absent. |
| [].author | string | Author; may be absent. |
| [].categories | array | Category names. |
| [].excerpt | string | Short excerpt (HTML stripped). |
| [].content | string | Full article as Markdown; only when full_content=true. |
| [].score | number | Relevance score; only when a query is provided. |
