DocsMCP tools

MCP tools

Every tool the DispatchSEO MCP server exposes - what it does, its parameters, and what it returns.

On this page

Everything below is the same server your agent already talks to. This page exists so you can look up one tool's exact contract without asking your agent to read the source for you.

Overview

The server lives at /api/mcp over streamable HTTP - no SSE, no Redis, one endpoint for every connected site. What tells your project's data from anyone else's is the bearer token you connect with: the token is the tenant. There's no separate "pick a project" step and no way for one token to see another project's rows.

Every tool returns pretty-printed JSON as its response text, so your agent gets structured data back, not prose to parse. A call that fails comes back as an error with a plain-English message - usually naming the actual fix (a missing credential, a cooldown, a bad id) rather than a raw database error.

The MCP is a door to state, not a research or writing tool. It reads and writes the suggestions queue, tracked keywords, published pages, GSC stats, and backlink prospects - the same tables the dashboard reads and writes. It does not call DataForSEO on your behalf and it does not generate content: your agent does the thinking, using its own research tools (including DataForSEO's own MCP server, if you run one) plus the two thin research primitives below - check_serp and suggest_keywords

  • that route through whatever keyword source your project is set up with.

Parity is the rule that keeps the two faces of the product honest: anything the dashboard can do, your agent can do here, and vice versa. If a dashboard screen doesn't have a matching tool on this page, that's a gap worth reporting, not a feature you should expect to live without.

Connecting

Your dashboard's Settings screen shows a ready-to-paste connect command per agent, with your project's slug, domain, and key already filled in - copy that one rather than typing any of these by hand.

claude mcp add dispatchseo-<slug> <your-domain>/api/mcp \
  --transport http --scope local \
  --header "Authorization: Bearer <token>"

--scope local pins the connection to the repo folder you run it in, so a second connected project never leaks into an unrelated repo and starts answering for the wrong site.

Anything else that speaks streamable-HTTP MCP: point it at <your-domain>/api/mcp with an Authorization: Bearer <token> header, or ?key=<token> on the URL if it can't set headers. Same gate, same tools. See docs/AGENTS.md for where each agent stands.

Windows: use the URL variant

Claude Code on Windows has a long-lived bug where a configured --header is stored and claude mcp list reports Connected, but real tool calls go out with no Authorization header at all. The key can ride in the URL instead - <your-domain>/api/mcp?key=<token> - which survives that bug. The dashboard hands you this exact form on Windows, no --header involved.

Suggestions queue

The queue is the heart of the product: every idea - guide, tool, backlink, or update - starts here as pending, and a build workflow only ever touches what the owner (or auto-approve) moved to approved.

get_suggestions

Lists items in the queue. Defaults to approved - which is what a build workflow asks for when it wants to know what to work on next. Items come back in build order (front-placed ideas first, then oldest first), so a workflow can simply take the first one.

statusstringOptional

One of pending, approved, rejected, in_progress, done. Defaults to approved.

typestringOptional

Filter to guide, tool, backlink, or update.

Returns the matching rows, sorted in build order.

propose_suggestion

Adds one new idea to the queue, normally landing as pending for the owner to decide on the dashboard. Use it after research - one call per idea.

typestringRequired

guide, tool, backlink, or update.

titlestringRequired

The idea's title.

primary_keywordstringOptional

The keyword this idea targets.

volumenumberOptional

Monthly search volume, if known.

kdnumberOptional

Keyword difficulty, if known.

rationalestringOptional

Why it's worth doing - volume, KD, intent, the gap it fills.

specobjectOptional

A free-form brief: an outline/angle/SERP notes for a guide, the functionality for a tool, the target url for a backlink. A take grown from one specific viral post can add spec.seed_url (+ spec.seed_stats) - the guide builder writes from that source directly: credits it, pulls real quotes, embeds it, then covers what the original missed.

sourcestringOptionalHonor system

research (the default), trend-scan, or manual. Trend workflows must pass trend-scan - it's what puts the idea on the Trend radar with the owner-only approval gate. manual is only for ideas the site owner dictated in the current conversation ("add a guide about X"), never for an autonomous run.

trend_topic_iduuidOptional

Only from the trend-expand workflow - groups this take under its radar subject.

approvedbooleanOptionalManual only

Skips the pending gate and lands the idea straight in the build queue. Only takes effect when source is manual - an autonomous run passing approved:true without source:"manual" still lands pending.

positionstringOptional

front or back. front means "do this one next" - since guides ship one per morning, front means tomorrow.

buildstringOptional

now fires the tool builder immediately instead of queueing. Only has an effect for type:"tool" combined with a manual approval.

Returns the created row, or { note, suggestion } when front-placement or an instant build changed what happened.

update_suggestion

Updates a suggestion's status and/or attaches a PR url. A build workflow marks an item in_progress when it starts and done with result_pr_url when the PR opens.

Approval coercion

An agent asking for status:"approved" normally lands as pending instead, so the owner still decides on the dashboard - the response says so, and that's success, not a failure to retry. Passing decided_by:"owner" is the one thing that unlocks a real approval: it means the site owner just made the call in this conversation ("approve that one", "reject it", "restore the X idea"). Never set it from an autonomous workflow run. Owner-approving a trend-scan idea also puts it at the front of its queue, and owner-approving a rejected item restores it from History.

iduuidRequired

The suggestion to update.

statusstringOptional

pending, approved, rejected, in_progress, or done. Approving/rejecting stamps decided_at; done stamps completed_at.

result_pr_urlstringOptional

The opened PR's url, typically paired with status:"done".

decided_bystringOptionalOwner only

owner or agent. Only owner bypasses the approval coercion above.

Returns the updated row, with a note when the approval was coerced to pending or when a tool build was (or wasn't) dispatched.

reorder_queue

Owner only

Rewrites the build order of one queue - the MCP side of the dashboard's drag-to-reorder. Guides and tools are separate queues. Only use this when the site owner asked to re-prioritize in the current conversation, never from an autonomous run.

groupstringRequired

guide or tool.

ordered_idsarrayRequired

The queue's approved suggestion ids, in the exact order they should build (first id builds next). Read the current order with get_suggestions first. Ids you omit keep their place after the ordered set.

Returns { reordered: true, group, order }.

build_suggestion_now

Owner only Tools only

The dashboard's "Build now" button. Approves a tool suggestion (if it isn't already) and wakes the tool builder immediately. Guides have no instant build, by design: at most one guide ships per day so the publishing pace stays steady instead of bursty, so a guide id is instead approved and placed at the front of its queue - the next daily build picks it up, tomorrow at the latest. Only use this when the site owner explicitly asked for it in the current conversation; autonomous runs queue via propose_suggestion and wait.

iduuidRequired

The suggestion to build now.

Returns { ok: true, message } for a tool, or { ok: true, note } for a guide (explaining the front-of-queue placement instead).

get_build_brief

The guide build's opening call - the build-guide playbook says to make it first, before anything else. One response carries what a run used to collect over a dozen serial calls: the queue head (the approved guide - or refresh-detector update - to build, in the owner's exact build order, sorted by the same helper get_suggestions uses so the two can't name different items), how deep that queue is, the live pacing verdict, structural fingerprints of the last few published guides, ranked internal-link candidates when the project has back-linking on, and the site's own GSC numbers for the information-gain step. The fingerprints are the real saving: each one is a published guide's opening word-run and its heading skeleton with the topic stripped, computed by the same extractor check_sameness judges the draft with, so the run never has to read two or three published posts in full just to learn their shape.

Every section fails open on its own. Anything unavailable is named in degraded along with the fallback to use for that one section - a degraded brief is never a reason to stop a build.

keywordstringOptional

The new guide's primary keyword, used to rank internal-link candidates by topical closeness. Omit it on a first call when the queue head is what tells you the keyword.

Returns { queue_head, queue_depth, pacing, recent_guides, link_candidates, site_stats, internal_linking, degraded }. link_candidates is only populated when set_internal_linking is on. An empty approved queue arrives as a degraded line too - it's a real state, not a lookup failure, and the playbook has a backstop for it.

check_sameness

The pre-publish sameness gate: does this draft read like the guides this site already published? Pass the full guide markdown (frontmatter and code fences are ignored) plus its primary keyword, and the backend compares it against this project's most recently published guides for an identical opening, a heading skeleton that's the same once the topic is removed, and stock phrases repeated across the catalogue. A failure means rewrite the flagged elements and call again - never loosen the check. It fails open (passes, with a note) if the published corpus can't be read, so a network or database hiccup can never block a build.

markdownstringRequired

The full draft guide.

primary_keywordstringOptional

Stripped out of both the draft and the published corpus before comparing, so two guides about different topics with the same shape still get caught.

Returns pass (boolean), compared_against (how many published guides it checked against), and flags - the exact offending strings when it fails.

Articles and publishing

The other way an article reaches a site. The original route has a coding agent in your own CI write the article and open the pull request; this one takes a finished article straight from your AI and publishes it for you - to WordPress, or as a pull request our server opens in your repo. Either way your AI hands the article over and stops there.

submit_article

Hand in a finished article as plain markdown. The server runs it through the publishing gate, stores it as a draft, then formats it, inserts internal links into your existing pages, builds the meta description, FAQ schema and cover image, and publishes it on your schedule. Your agent does not publish anything itself and does not need to know how your site works.

The gate is the point. It runs here rather than inside the writing agent, because a model checking its own draft passes it - so the same 24 checks apply whether the article came from Claude Code, Codex, the Claude app, or ChatGPT. A failed submission comes back with the exact checks that failed and a fix line for each one, written to be acted on without a human: apply them and call submit_article again with the same suggestion_id and it updates the same draft. Three rejected attempts is the point to stop and tell the owner what's blocking it.

Where it ends up is the site's own setting, not something to pass here. On the WordPress route the article is posted for you; on the GitHub route our server commits it into the folder your site already keeps its articles in and opens a pull request (merged automatically if the site is on automatic mode). set_publish_target changes the door, and get_drafts says where each article got to.

suggestion_idstringOptional

The queued idea this article fulfils. Passing it marks the idea in_progress and keeps one live article per idea - a resubmission updates that draft instead of adding another. Omit it only for an article the owner asked for that was never in the queue.

titlestringRequired

The title as it should appear on the page.

slugstringRequired

Lowercase and hyphenated, no leading or trailing slash.

meta_descriptionstringOptional

Roughly 120-160 characters. Omitted means one is built from the opening.

primary_keywordstringRequired

The single search term the article targets, exactly as someone would type it.

markdownstringRequired

The full article. No frontmatter, no raw HTML, and not wrapped in a code fence.

faqarrayOptional

{ question, answer } pairs for the FAQ block and its schema. Each question ends in "?" and each answer is a couple of real sentences.

sourcesarrayOptional

{ url, title? } for sources actually used. Placeholder and example.com URLs fail the gate.

cover_briefstringOptional

One line describing the cover image's subject. Never published as text.

A site can hand in 5 articles a day and have 3 waiting to publish at once. Neither number is about our costs - the finisher calls no model, so an article costs us a fraction of a cent. They're about yours: publishing a week of content in an afternoon is the pattern search engines read as a bulk-generated site, which costs you more than the extra articles are worth. Both caps refuse by name and store nothing, a rejected article never counts against either, and a resubmission of something already in flight always goes through.

Accepted returns { draft_id, status: "submitted", score, advisory, note } - score is 0-100 across every check, and advisory lists the non-blocking ones that failed, worth fixing next time but not worth a round trip now. Rejected comes back as an error carrying draft_id, score, and blocking - one { id, title, fix } per check that must pass. An article for an idea we've already accepted is refused by name, not by a database error.

get_drafts

Every article submitted through submit_article and what happened to it - the same list the owner's Drafts screen shows. Call it after a rejection: the blocking checks and their fix lines come back here too, so a new chat can pick up where the last one left off without resubmitting blind.

The statuses, in the order an article moves through them:

StatusWhat it means
submittedPassed the gate, queued for formatting
rejectedSent back - problems says what to fix
acceptedFormatted and queued to publish
blocked_setupNowhere to publish yet; blocked_reason says what's missing
finishedPosted to WordPress, or a pull request opened in the repo - not yet confirmed live
publishedConfirmed live
discardedThe owner threw it away
statusstringOptional

Return only this status. Omit for everything.

limitnumberOptional

How many, newest first. Defaults to 20, max 50.

Returns one compact object per draft: { id, suggestion_id, title, slug, status, score, problems, published_url, pr_url, created_at, updated_at }. Only the checks that FAILED come back in problems - the full report is stored, but twenty passing checks are not worth your context. pr_url is set only on the GitHub route, and is where the owner reviews the article until the page is confirmed live.

approve_draft

Publish a finished article right now instead of at the site's usual publishing hour. Owner-gated: an accepted article already publishes on its own, so an autonomous run has no reason to call this. It's also how you release an article that parked as blocked_setup, once the owner has connected their site.

idstringRequired

The draft id from get_drafts or submit_article.

Refuses by name rather than queueing work that can't run: an article that isn't prepared yet, one that's already live, a discarded one, or a site with nowhere to publish all come back as an error saying which. When the article already has a publish queued for the site's usual hour, that job is pulled forward to now rather than left waiting - "now" means now.

discard_draft

Throw an article away so it is never published. Owner-gated. The row is kept as history rather than deleted, so "why did that article never appear" stays answerable. A rejected draft doesn't need discarding - fix it and resubmit instead.

idstringRequired

The draft id from get_drafts or submit_article.

get_site_digest

What your site already covers, read for you. Our server crawls the site when you connect it and again every week, and keeps a compact summary: the sections it has, how many pages, the newest and oldest, sample titles, the most-linked pages, typical article length.

Call it before researching or writing. It's how your AI avoids proposing an article you already have, and how it matches your site's voice and depth - and reading 150 pages instead would cost more context than the article is worth, which is the whole reason this exists.

Returns { crawled, digest, notes, page_count, source, crawled_at }. notes names anything the crawl couldn't see (a page cap it hit, pages that wouldn't load), so a partial picture says so rather than passing itself off as the whole site. A site we haven't read yet comes back as { crawled: false, note } - a real state, not an error.

The full page inventory the internal linker uses is deliberately not in here. It's an order of magnitude larger, our finisher is the only thing that reads it, and folding it in would break the context budget this summary exists to respect.

rescan_site

Queue a fresh read of the site. It runs in the background; call get_site_digest a few minutes later for the result. The site is re-read weekly anyway, so this is for when something changed that matters now - a restructure, a new section, pages published elsewhere. Calling it twice in a day queues one read and tells you so (already_queued: true) rather than pretending to queue a second.

set_publish_target

Choose where finished articles go: wordpress posts them straight to your site, github commits the article to the connected repo and opens a pull request (merged automatically in automatic mode), manual finishes the article and leaves it for you to place. Owner-gated.

targetstringRequired

github, wordpress, or manual.

It does not connect anything. The WordPress username and application password are entered on the Settings screen and never travel through the MCP - same rule as every other credential. Setting wordpress before that connection exists is allowed, and the response says plainly that articles will wait until it's made.

set_content_path_hint

Repo route only. The folder in the connected GitHub repo that holds the site's articles (for example content/blog). Leave it unset and the publisher works it out from the repo's layout; set it when get_drafts reports a draft parked on "where your articles live", or when the owner tells you the folder. An empty string clears it. Owner-gated. The same field as Settings - Where your articles live.

pathstringRequired

Repo-relative folder, plain characters only, no ... Empty string clears the hint.

save_research_notes

Store what you learned researching an idea, so a later conversation can write the article without paying to redo the research.

This is what makes the split work on a chat plan. A coding agent researches and writes in one long run and keeps the findings in its context; Claude.ai or ChatGPT has neither the window nor the continuity for that, so one chat researches, another writes, and this is the handover between them.

suggestion_idstringOptional

The queued idea this research is for. Saving again for the same id replaces the previous notes, so the writing session never has to guess which of two versions is current. Omit for research that isn't tied to a queued idea.

notesobjectRequired

Your findings as a JSON object - SERP picture, competitor gaps, the angle, an outline, numbers with their sources. Shape it however suits the work; nothing on our side parses it. Capped at 64KB, which is room for conclusions and not room for three competitor articles pasted in whole. Over that it's refused by name, and whatever was saved before stays intact.

get_research_notes

Read back research an earlier conversation saved. Call it first in a writing session: if notes exist for the idea you're about to write, the research is done. Pass suggestion_id for one idea's notes, or omit it to list the most recent unattached ones.

Nothing saved yet comes back as { found: false, note } - it means nobody has researched this, not that the lookup failed.

Keywords and rankings

The tracking set is separate from one-off research: what you track here is what the nightly rank cron checks going forward.

track_keywords

Upserts keywords into the tracking set, matched by keyword text. The rank cron checks every tracked keyword daily while it ranks in the top 30, plus a weekly full-depth sweep for everything else. Fields you omit are left as-is on existing rows.

keywordsarrayRequired

One object per keyword: keyword (required), volume, kd, cpc, intent (all optional).

Returns { upserted, keywords }.

get_rankings

Rank history for tracked keywords over the last N days. Each keyword comes back with its current position, its earliest position in the window, and the change between them (positive means it improved, moving toward #1).

Read checked and position together

checked:true with position:null means confirmed not in the top 100. checked:false means the keyword was never successfully checked in this window - unknown, not "not ranking".

keywordstringOptional

Scope to one keyword instead of every tracked one.

daysnumberOptional

Window size. Defaults to 30.

Returns each keyword with its position history, best-position-first; keywords outside the top 100 are ordered last, by search volume, so the tail reads as an opportunity list.

Published pages

log_page

Records a published page, matched by url (re-logging updates it). Call this right after a PR opens, not after it merges - the system already treats a freshly logged page as "awaiting publish" until its url first serves HTTP 200, which is verified automatically.

urlstringRequired

Must be on this project's own domain.

titlestringOptional

Page title.

typestringOptional

guide, tool, or landing.

primary_keywordstringOptional

The page's main target keyword.

pr_urlstringOptional

The PR that shipped this page.

published_atstringOptional

ISO date/datetime. Only pass this when backfilling a page that published earlier - omit it for a page shipping right now. The daily publishing pace reads this field, so a backfill stamped "now" wrongly eats today's build slot.

Returns the upserted page row.

get_pages

Lists every published page. Call this before writing new content, to pick 2-3 existing pages to link to and to avoid covering a topic twice. Takes no parameters.

Each row carries live:true|false - false means the page was logged (its PR opened) but its url hasn't served 200 yet. Don't link to not-yet-live pages.

mark_indexing_requested

Reports the outcome of a Search Console "Request indexing" browser session, so the dashboard's Get-it-on-Google card clears itself. Urls must match already-logged pages (get_pages); anything left over (daily quota hit, login wall) just stays on the card - omit it rather than guessing.

requested_urlsarrayOptional

Pages you clicked Request indexing for.

already_indexed_urlsarrayOptional

Pages the inspection showed were already on Google.

Returns { requested_marked, already_indexed_marked, unknown_urls } - unknown_urls is any url that matched no logged page, so it wasn't actually cleared.

Traffic and stats

get_site_stats

Google Search Console snapshots for the last N days, newest first, plus a trend summary (totals and first-half vs second-half deltas for clicks and impressions). For the combined dashboard-style view (traffic, rankings, and per-page numbers in one call), see get_overview under Dashboard parity instead.

daysnumberOptional

Window size. Defaults to 28.

Returns { summary, snapshots }, plus a note explaining an empty window (Search Console not connected yet, access granted but no sync yet, or an unreachable property) instead of letting zeros read as a real traffic story.

Backlink prospects (domains worth pursuing) and the curated backlink playbook (directories and paid placements) live together here - both feed the same Backlinks screen on the dashboard.

add_backlink_prospect

Adds a domain worth pursuing a link from, status new. Deduplicates by normalized domain (case-insensitive, www. stripped) - a repeat call returns the existing row with already_queued:true instead of creating a second one.

domainstringRequired

The prospect's domain.

urlstringOptional

The specific page worth targeting, if you have one.

reasonstringOptional

Why it's relevant, or where it was found.

domain_ratingnumberOptional

The prospect's own DR, if known.

Returns the new row, or the existing one with already_queued:true.

get_backlink_prospects

Lists backlink prospects, newest first.

statusstringOptional

new, contacted, acquired, or rejected.

update_backlink_prospect

Moves a prospect through its pipeline: new to contacted to acquired (or rejected). Call it after outreach actually happened or a link actually went live.

iduuidRequired

The prospect to update.

statusstringRequired

new, contacted, acquired, or rejected.

get_playbook

The curated backlink playbook: free directories and ROI-ranked paid placements, with this project's done/skipped progress. Without a slug you get the compact list; with one, the full submission brief - prefilled field copy personalized from the site profile, plain-English steps, gotchas, and a paste-ready @browser command.

slugstringOptional

An item's slug (from the compact list) for its full brief.

set_playbook_status

Marks a playbook item todo, done, or skipped - the same checkbox as the dashboard's Backlinks screen. Call it after a submission actually went through (e.g. the @browser session finished), not before.

slugstringRequired

The playbook item (from get_playbook).

statusstringRequired

todo, done, or skipped.

Research primitives

Two of these work in every mode with no credentials (suggest_keywords, check_serp when a free SerpApi key is connected); the rest need a connected SERP/DataForSEO account of some kind - your own, or the platform's bundled plan on cloud.

check_serp

Fetches live Google organic results for a keyword through the project's connected SERP provider, billed to the project's own account or quota. Use it to judge winnability before proposing content - page 1 full of Reddit threads and thin posts is winnable, page 1 full of big brands isn't. Unavailable in GSC-only mode.

Rate-capped on the bundled cloud plan

30 checks/day when billed to the platform's shared DataForSEO account, resetting at UTC midnight. Own-account and self-host projects aren't affected. Use track_keywords for anything worth ongoing monitoring instead of repeated ad-hoc checks - the rank cron checks tracked keywords daily for free. SerpApi's own free tier is separately capped at 250 searches/month.

keywordstringRequired

The keyword to check.

topnumberOptional

How many results to return, up to 100. Defaults to 10.

Returns { keyword, source, results, ai_overview } - each result has position, title, url, and domain.

get_domain_rank

The site's cached Domain Rating snapshot (0-100 DR-equivalent, referring domains, backlinks, spam score), refreshed weekly by a cron - never a live paid call. Takes no parameters. A null dr means the domain isn't indexed yet, or the cron hasn't run its first pass for this project.

get_dataforseo_usage

This project's DataForSEO billing status: who it's billed to, month-to- date spend against the platform's monthly budget (when applicable), the projected month-end spend under the rank cron's current pacing, and today's check_serp and keyword_ideas counts against their daily caps. Takes no parameters. A project on its own account (or self-host) isn't metered here - the spend fields stay zero.

The two daily caps exist for different reasons and behave differently. check_serp's is 30 and applies only to projects on the shared plan - it stops our account being used as a free SERP proxy. keyword_ideas's is 10 and applies to every project, because one call there is worth up to ten metered requests and a looping client can empty an account that isn't ours to protect but is still someone's. Both reset at UTC midnight and both refuse by name rather than returning an empty result, so a caller that hits one stops instead of retrying.

suggest_keywords

Expands a seed keyword into related searches via Google Autocomplete - free, works in every mode, no credentials needed. Returns real queries people type, without volume numbers. Pair it with get_site_stats's top queries for seeds, then check_serp on the shortlist to judge winnability.

seedstringRequired

The seed keyword to expand.

modifiersarrayOptional

Up to 8 modifier words to combine with the seed.

keyword_ideas

Expands seed keywords into related searches with real monthly search volume and keyword difficulty, via DataForSEO. Works whenever the project has DataForSEO access - its own account, or the platform's bundled plan on cloud - so it's how bundled-plan projects get the numbers the quality bar gates on. Queries the project's configured search market (US/English unless changed via set_market or Settings), so a Hebrew site set to Israel gets Hebrew keywords with Israeli volumes. Send seeds best-first: only the first 5 are expanded, at 2 metered DataForSEO calls each, and the response note names any skipped.

seedsarrayRequired

1 to 20 seed keywords.

limitnumberOptional

Max ideas returned, 1 to 200. Defaults to 100.

Returns { seeds, ideas }, sorted by volume, highest first. Returns an empty ideas list with a note (never an error) when the project has no DataForSEO access or its monthly budget is spent - fall back to check_serp and product judgment then.

AI visibility

How AI answer engines see this site - the GEO half of the product.

get_ai_visibility

Per-engine summary (queries checked, AI answers seen, citation rate), a per-day trend, the latest verbatim answers, and a gap list of domains AI cites on queries where this site isn't. Takes no parameters. Google AI Overview data arrives automatically from the weekly rank sweep; the other engines fill in only when a geo-scan workflow runs. Use the gap list to pick what to write next.

record_ai_citations

Writes geo-scan results: for each query asked of an AI answer engine, whether an answer came back, whether it cited this site, and every source it cited. Called by the geo-scan workflow after sampling each query - google_ai_overview is cron-only and can't be written here.

resultsarrayRequired

1 to 100 entries. Each has: engine (claude, chatgpt, perplexity, or gemini, required), query (required), has_ai_answer (required), cited (required), cited_url (optional), answer_excerpt (optional - a short verbatim quote so the owner can read the real answer behind the number), and citations (optional, up to 30 { domain, url, title } sources).

Returns { recorded }, the count written.

The Trend radar is two stages: a scan finds trending subjects, then the owner picks one to expand into guide-idea takes.

record_trend_scan

Stamps the project's last_trend_scan_at with now. Takes no parameters. Called by the trend-scan workflow itself at the end of every run, found something or not - it backs the Trend radar's "Scan now" cooldown, not something the owner calls directly.

propose_trend_topic

Puts a trending subject on the Trend radar - a conversation the niche is having right now, not a guide idea. Called by the trend-scan workflow, once per shortlisted subject (up to 5 per scan). Deduplicates by title across every status - if the subject is already on the radar, the existing row comes back with a note instead of a duplicate.

titlestringRequired

The trending subject.

why_nowstringRequired

The trigger event and its date, leading the explanation.

signalsarrayOptional

Up to 8 threads/launches/trend lines actually seen.

sourcesarrayOptional

Up to 8 vendor posts or threads that prove it.

seed_urlstringOptional

The single most viral piece of content driving the subject - the builder later writes from it directly (credit, quotes, embed).

seed_statsstringOptional

That content's public numbers and date, e.g. "512k views, Jul 12".

get_trend_topics

Lists radar subjects, newest first.

statusstringOptional

new, expanding, expanded, or dismissed.

update_trend_topic

Moves a subject to expanded (the trend-expand workflow's last step) or dismissed (housekeeping for subjects older than 14 days, or the owner passing on one). expanding isn't settable here - that's what expand_trend_topic does, since picking a subject to expand is the owner's move.

iduuidRequired

The subject to update.

statusstringRequired

expanded or dismissed only.

trigger_trend_scan

Owner only

The Trend radar's "Scan now" button. Wakes the project repo's trend-scan workflow; subjects appear on the radar (get_trend_topics) a few minutes later. Shares the dashboard's 30-minute cooldown - a refusal means the radar is already current, don't retry. Only use this when the site owner asked for a scan in the current conversation; the scan workflow itself must never call this (it reports back through record_trend_scan instead). Takes no parameters.

expand_trend_topic

Owner only

The radar's "Get takes" button. Wakes the trend-expand workflow for one subject; its takes land in the suggestions queue a few minutes later, waiting pending like every trend idea. Refuses dismissed subjects and repeats within a 30-minute cooldown (per subject) - a refusal means takes are already on their way. Only use this when the site owner picked the subject in the current conversation.

iduuidRequired

The radar subject to expand.

Site config and profile

get_site_profile

Reads the site profile the backlink playbook personalizes from - name, tagline, descriptions, categories, tags. Takes no parameters. Returns null if /seo-setup hasn't written it yet.

set_site_profile

Writes the site profile that prefills every directory submission and @browser command. Called by the /seo-setup command after researching the product.

namestringRequired

The site's name.

urlstringRequired

Must be a valid url.

taglinestringRequired

Up to 60 characters.

short_descriptionstringRequired

Up to 160 characters.

long_descriptionstringRequired

300-600 characters is the target directories in the playbook expect (the field itself accepts 100-700).

categoriesarrayRequired

1 to 5 categories.

tagsarrayRequired

1 to 10 tags.

set_gsc_property

Corrects which Google Search Console property this project tracks. Onboarding guesses sc-domain:<domain>, but plenty of real properties are URL-prefix (https://example.com/) - if GSC data never arrives and access checks keep failing, the guess was probably wrong.

site_urlstringRequired

The property exactly as Search Console names it.

detect_site_launch

Finds evidence of when the site actually went live - Search Console's earliest impression date (Google keeps ~16 months of history, so for older sites this is a floor, flagged at_least: true) and the Wayback Machine's first capture - and moves site_launched_at backward to the earlier of the two. Never moves the date forward and never overrides an owner's earlier correction. The launch date drives the Journey stage, publishing pace and research difficulty posture, so run this when it looks like the day the project joined DispatchSEO rather than the day the site launched. Takes no arguments.

set_market

Sets which Google country and language the project's rank checks and keyword research query. Every project starts at United States / English, which quietly measures the wrong Google for a site whose audience searches from another country or in another language. Same write as the Settings page's Search market row. The tool description lists every supported market with its location code and languages.

location_codenumberRequired

DataForSEO location code, e.g. 2840 = United States, 2376 = Israel.

language_codestringRequired

A language the chosen market supports, e.g. en, he, de.

disconnect_repo

Stops DispatchSEO running in this project's repo: the seo-* workflows are disabled and deleted, the .dispatchseo files and the SEO_MCP_API_KEY secret are removed, and the connection is cleared. Schedules stop, so the repo stops spending the owner's GitHub Actions minutes. Published guides, tools and pages are never touched, and the project keeps its keywords, rankings and history - reconnecting later re-installs the pipeline. If the repo can't be reached the connection is kept, so the attempt can be retried rather than leaving a project pointing at a half-cleaned repo. Deleting a project deliberately has no tool here; disconnecting is the reversible half.

confirmstringRequired

The connected repo as owner/name, typed exactly. Anything else is refused - it guards against a disconnect nobody asked for.

Returns { disconnected, workflows_disabled, files_removed, secret_removed, note }.

set_github_repo

Points the project at the GitHub repository its content pipeline lives in. On cloud it's validated against the project's GitHub App installation's live repo list - a repo outside the installation is refused. On self-host it's validated against the instance's stored GitHub token when one exists (the token must see the repo and read its code); before that token is saved, the write is accepted on format alone, same as project creation. Changing an already-connected repo never touches the old repo - if the pipeline was installed there, run disconnect_repo first so its workflows stop.

repostringRequired

owner/repo (a github.com URL works too). On cloud it must be part of this project's App installation.

set_agent

Chooses which coding agent runs this project's unattended builders - the scheduled GitHub Actions, or the in-stack container on a self-hosted install. It does not change which agent you are: whatever agent is connected over MCP right now keeps driving everything interactively regardless of this setting. It takes effect on the next scheduled run with no repo change, because the workflow files carry every agent and ask which to use at run time. The reply names anything the owner still has to do - chiefly adding the new agent's credential where the builders run. Ask the owner before switching: the agents bill differently (a Claude or Cursor subscription vs metered OpenAI usage), so it's their call.

agentstringRequired

claude (Claude Code, runs on a Claude subscription), codex (Codex, metered against an OpenAI API key), or cursor (Cursor, runs on your Cursor plan's API key).

set_internal_linking

Owner only

The only door to internal back-linking. With it on, the guide builder edits 2-3 of the closest already-published posts in the same PR so they link to each new guide - one sentence changed per post, nothing else touched - which is what turns a set of posts into a cluster that compounds. This is the only DispatchSEO behaviour that modifies pages the owner already published, so it's off by default, kept separate from the semi/auto automation flags, and a PR that edits published posts is never auto-merged regardless of auto_merge. Ask the owner before turning it on - never enable it on your own initiative because it would make a run tidier.

enabledbooleanRequired

true to allow editing published posts, false to stop.

Returns { project, internal_linking, note }. On a database that hasn't run migration 0045_project_internal_linking.sql yet the call fails, naming that migration.

get_project

The project this token belongs to and how it's set up: domain, mode, the effective auto-approve flags, keyword source, whether a SERP provider and Search Console are connected, whether this repo has its own DataForSEO MCP server, the content-pipeline repo, whether one-tap merge is available, where finished articles go (publish_target: github, wordpress or manual) and whether WordPress is actually connected (wordpress_connected), plus the setup wizard's answers - ai_choice (what the owner said will write: claude-web, chatgpt, claude-code, codex, cursor, or null for projects from before the adaptive wizard) and chat_app_connected (true once any request has reached this server from a chat app). Takes no parameters. Worth calling first in a session, since it tells you which of the other tools will actually work. Secrets are never returned - credentials stay dashboard-only.

Dashboard parity

These read tools call the exact same modules the dashboard's screens render from, so the two views can never drift apart.

get_overview

The dashboard Home/Analytics view in one call: 28-day traffic totals, live last-24h numbers, domain rating, the keyword ranking table, top search queries, and per-page traffic for every built guide and tool - plus the journey (which SEO stage the site is in, what to expect next) and this week's real movement. Takes no parameters. Start here for "what's going on with my SEO" - it's the whole picture. For raw daily GSC snapshots use get_site_stats; for rank history use get_rankings.

get_briefing

The dispatcher's briefing - the card at the top of Home, in the agent's own first-person voice. Takes no parameters. Returns the day's clicks and impressions, what the pipeline is building right now, and wins: the shortlist of things worth acting on today, read out of Search Console using the signals the big SEO tools lead with - striking-distance queries (position 8-20, one push from page one), page-one queries that rank but don't get clicked, high-impression zero-click queries, rising queries, searches the site has newly started appearing for, and first-ever milestones (each carrying its real-world base rate, so a first top-10 can be sized, not just checked off). action is today's one hands-on move - usually the next free backlink playbook listing, raised only when the profile is genuinely thin or stalled - and is null most days. Call it for "what should I do about SEO today". An empty wins list is a real answer on a young site; patience says why, and inventing a win to fill the gap defeats the point of the tool.

get_activity

What the SEO manager has been doing. Takes no parameters. Returns today (a granular checklist since UTC midnight, each publish/approval named) and week (the last 7 days, aggregated into counts).

get_automations

The automations registry: what runs on its own, on what schedule, and an evidence line per automation (last run, last snapshot, last build) drawn from the data it actually writes. Takes no parameters. Answers "is the nightly rank check running?" or "when does the guide builder fire?".

get_cron_health

The latest run of this project's background jobs - deploy-check, the SEO GitHub workflows, the secrets canary, and (self-host only) the platform's own instance-wide crons too. Takes no parameters. Each entry carries ok/failed, error strings, and whether the job is stale. An empty result means no job has ever logged a run; an entry with update_available:true isn't a failure, just an installed pipeline pack a version behind. repeat_failure:true means the same job also failed the real run before this one. The owner's dashboard and the alert emails only surface failures that are repeated, stale, or urgent (a broken deploy, dead credentials, an empty balance) - a one-off failed run shows here first and nowhere else, so finding one the dashboard doesn't show is the design working, not a discrepancy to report.

mark_cron_fixed

Clears a background-job alert after you've actually fixed and verified the underlying problem - logs a synthetic ok run for that job. Call this only after re-running the workflow (or hitting the endpoint) and seeing it succeed; if the problem persists, the next real failure or missed window re-raises the alert on its own.

jobstringRequired

The exact job name from get_cron_health, including any --<project> suffix. Fails if that job has no active alert.

get_next_actions

Everything waiting on a human decision: suggestions awaiting approval, approved items waiting for their build, builds in progress, open SEO PRs ready to merge, and pages waiting for a Search Console "Request indexing" click. Takes no parameters.

Returns { awaiting_approval, approved_waiting_build, building_now, open_seo_prs, indexing_queue, backlink_move } - the indexing queue comes with a paste-ready @browser command; report the outcome with mark_indexing_requested. backlink_move is the links half of the job kept specific: when the site's backlink profile is thin (under ~5 referring domains) or measured-flat for a month, it names the one free playbook listing to action next (mark it with set_playbook_status once actually submitted); it is null while the profile is healthy and growing.

merge_pr

Squash-merges an open SEO PR on the project's repo - the dashboard's one-tap merge. Only merge PRs listed by get_next_actions, and only when the user asked for it or its checks are green. Requires the server's merge token to be configured; without it, this fails and the PR page link is the fallback.

numbernumberRequired

The PR number to merge.

get_changelog

What shipped in DispatchSEO itself, newest first - the same list the dashboard shows at /changelog. This is about the product, not your site's own activity (for that, use get_activity).

limitnumberOptional

Trims the list, 1 to 50. Defaults to 10.

get_feedback

Feature requests people have asked for in DispatchSEO itself, most-voted first - the same board the dashboard shows at /feedback. Like get_changelog, this is about the product, not your site. The board is shared, not per-project: the token you connect with only says who is asking, which is what one-vote-per-account is counted by. Each request comes back with its vote count, whether this account already voted for it, and whether this account wrote it.

limitnumberOptional

How many requests to return, 1 to 200. Defaults to 50.

statusstringOptional

open, planned, in_progress, shipped, or declined.

Returns { dashboard_url, moderator, total, requests } - moderator:true means this account is the one that can also call update_feedback.

submit_feedback

Asks for something in DispatchSEO itself - a feature, a change, a thing that's missing - posted to the shared board under the account that owns this project. Check get_feedback first: voting for an existing request beats posting a duplicate. Plain text only, and the rules are enforced rather than silently cleaned up - links, email addresses and HTML are rejected outright, with the reason named. An account can post 5 requests a day, and re-sending the same title within 24 hours comes back as a double-submit rather than a second row.

titlestringRequired

The request in one line, 6 to 120 characters.

bodystringOptional

Optional detail: what you're trying to do and what gets in the way. Up to 1200 characters.

Returns { id, title, emailed, dashboard_url }. emailed:false only means the maintainer's notification didn't send - the request is on the board either way.

vote_feedback

Cloud only

Adds or removes this account's vote on a request from the board. One vote per account per request, and setting a vote it already holds is a no-op - safe to retry, since a retry can't undo the vote it just cast. A vote needs an account, so a self-hosted deployment gets an error pointing at submit_feedback instead.

idstringRequired

The request's id, from get_feedback.

votebooleanOptional

true to vote (the default), false to take the vote back.

Returns { id, voted, votes } - the settled state and the new count.

update_feedback

Moderator only

Sets a request's status or hides it from the board. Fails for everyone else; get_feedback's moderator field is what tells you whether this account qualifies. Hiding never deletes - the request stays in the database and only drops off the board. Pass status, hidden, or both; passing neither is an error.

idstringRequired

The request's id, from get_feedback.

statusstringOptional

open, planned, in_progress, shipped, or declined.

hiddenbooleanOptional

true hides it from the board, false puts it back.

Returns { id, status, hidden, updated }.

Instructions and install

The install/setup pipeline is served as content, not hardcoded into any repo - so an instructions update reaches every connected project's next run without touching a single user repo.

get_instructions

The operating instructions for one SEO workflow, personalized to this project. Automations and agents must call this before running a workflow and follow the returned markdown exactly - it's the live version of the playbook.

workflowstringRequired

install, setup, research, trend-scan, trend-expand, build-guide, build-tool, report, backlinks, or geo-scan.

Returns { project, version, workflow, summary, markdown }. The project field names who this token belongs to - confirm it matches the site you mean to operate on before following the playbook, since a mismatched token would act on another site's data. Site-specific facts live in the repo's .dispatchseo/conventions.md, which the setup workflow writes.

get_pipeline_pack

The repo-side shim files (GitHub workflows, MCP configs, slash commands), personalized to this project. Call it with no arguments for the manifest - a list of file paths only, since the full pack overflows one response - then call it again with a path to get that one file's content, writing each one at a time.

pathstringOptional

A path from the manifest. Omit for the manifest itself.

mark_install_step

A progress ticker for the owner's wizard finale - stamp one step the moment you finish it, so the checklist the owner is watching ticks in real time instead of sitting dark for 20-60 minutes. Purely informational: it unlocks nothing (mark_pipeline_installed does that), and a failure here must never stop the install.

stepstringRequired

workflows, adaptation, repo_settings, content_home, site_facts, or research. Call once per step, right after finishing it.

mark_pipeline_installed

Stamps this project as pipeline-installed. Called once, by the install workflow's final step, only after its own verification checklist passes. Takes no parameters. This call unlocks the owner's dashboard, so it fails loudly - with the specific problem named - rather than stamping an unverified install.

set_conventions

Mirrors the repo's .dispatchseo/conventions.md site facts to the backend, so the dashboard's Instructions page can show how DispatchSEO adapted to this site. Called by the setup workflow right after writing the repo file, with the complete current facts - this is a full replace, not a patch.

product_summarystringOptional

What the site/product is.

stackstringOptional

The repo's tech stack.

package_managerstringOptional

e.g. pnpm, npm.

build_commandstringOptional

How to build/typecheck the repo.

guides_dirstringOptional

Where guide content lives.

tools_wiringstringOptional

How a new tool page gets wired in.

theme_tokensarrayOptional

{ name, value } pairs - include resolved color values (hex/oklch) where the token is a color, so the dashboard can render real swatches.

fontsarrayOptional

Font names in use.

voice_rulesarrayOptional

Style/voice rules content should follow.

exemplar_guidesarrayOptional

Example guide urls or paths worth imitating.

exemplar_visualsarrayOptional

Example visual references.

tool_referencestringOptional

An existing tool page worth using as a pattern.

analyticsstringOptional

How analytics is wired on the site.

notesstringOptional

Anything else worth recording.

get_conventions

The site facts last mirrored via set_conventions, with updated_at. Takes no parameters. null data means the setup workflow hasn't run yet - the repo's own .dispatchseo/conventions.md remains the agent-facing source of truth; this copy exists for the dashboard and for agents working without the repo checked out.

Content preferences

get_content_prefs

The owner's template controls from the dashboard's Instructions page: house_rules (free text injected into every build), disabled_archetypes (guide shapes removed from rotation), and disabled_blocks (skeleton parts dropped). Takes no parameters. Build workflows don't need to call this directly - the same preferences are already rendered into get_instructions.

set_content_prefs

Owner only

Changes the owner's template controls. Use this only when the owner asked for the change in the current conversation - an autonomous build run must never adjust its own content preferences. Provided fields replace their current value wholesale; fields you omit keep their current value.

house_rulesstringOptional

Up to 2000 characters, injected into every build.

disabled_archetypesarrayOptional

Any of tutorial, comparison, data-study, opinion, reference. At least 2 must stay enabled.

disabled_blocksarrayOptional

Any of cover, tldr, comparison_table, visuals, faq. All are on by default - cover drops the generated cover image, so guides ship with no cover frontmatter and the blog card falls back to its own plate.

join_waitlist

The odd one out in this section - it doesn't touch content preferences at all, it's just the last tool registered on the server. Adds an email to the DispatchSEO Cloud waitlist, the same list the public landing page feeds. Duplicate emails are fine; re-joining is a no-op, not an error.

emailstringRequired

The email address to add.