Citation Optimizer tools
The Citation Optimizer tools let a connected assistant drive the full score → revise → re-score loop: pick the searches to optimize for, find the right page, score it for AI citation readiness across several assistants at once, generate an improved draft, and re-score to measure the lift — repeating until the content is publish-ready. They also cover the step before the page exists: a keyword brief that says what to write.
This is the one place in the MCP server that writes to your account — but only in a contained way. It creates your own scoring runs, draft revisions and briefs so you can iterate. It never publishes anything, never edits your live pages, and never changes your existing reports or data. For the scoring model itself, see the Citation Optimizer methodology.
Two shapes of the loop
| Audit (preferred) | Single run (legacy) | |
|---|---|---|
| Scores | One page against a query set, on every assistant your plan covers | One page against one query, on ChatGPT |
| Start with | score_citation_audit | score_citation_pipeline |
| Poll | get_citation_audit | get_pipeline_run |
| Revise | revise_citation_audit | revise_content |
The audit is what the product is about. A page that appears in the results for several related searches is far more likely to be cited than one that wins a single query, and the four pipelines disagree often enough that optimizing against one of them in isolation can cost you score on the others. The audit runs them together and harmonizes the findings into one score and one list.
The single-run tools still work unchanged, and they are the shape the free plan gets.
Which assistants you can score against depends on two things: which pipelines are live (the tools report this in scoreablePlatforms, and it can change without a release) and which ones your plan covers. The free tier is ChatGPT only; paid plans cover ChatGPT, Google AI Overviews, Google AI Mode and Claude. Asking for one outside your plan is refused with the list you can use.
What it costs
Scoring costs no credits. It is metered instead: each plan includes a number of pages a month, and a page counts once however many times you re-run it with different searches or different assistants. Re-scoring a rewrite never counts at all, so once you are in the loop on a page, the loop is free. Over the allowance, the tools refuse with the numbers (used, cap, remaining) so you can tell the user whether to wait for the reset or upgrade.
Rewrites and briefs spend credits. They draw on the plan's monthly rewrite allowance first, then credits. When neither covers it, the refusal carries the allowance and the balance so you can say exactly what is needed.
Fire-and-poll
Scoring, revising and brief-writing all run as background jobs, so those tools are fire-and-poll: they enqueue work and return an id immediately, and you poll a companion tool until it's done.
| Kick off (returns an id) | Poll until done |
|---|---|
score_citation_audit → auditId | get_citation_audit(auditId) until completed |
revise_citation_audit → revisionId | get_revision(revisionId) until completed |
rescore_revision → auditId or runId | get_citation_audit / get_pipeline_run |
generate_citation_outline → outlineId | get_citation_outline(outlineId) until completed |
score_citation_pipeline → runId | get_pipeline_run(runId) until completed / stopped |
revise_content → revisionId | get_revision(revisionId) until completed |
Step 1 — Pick what to optimize for
You need a query set and a target (a page, URL, or draft).
list_tracked_fanouts — start here
Lists a property's real tracked fan-out queries — the queries the platform's pipeline actually generated (from the property's latest AI Visibility report), highest-impact first. Starting here beats inventing keywords: you optimize for queries the platform is observed to produce. Each fan-out carries an isGap flag (the brand doesn't rank for it yet).
| Parameter | Type | Notes |
|---|---|---|
propertyId | string | From list_properties. |
platform | chatgpt | claude | google_aio | google_aim (optional) | Default chatgpt. A platform with no live pipeline returns tracked fan-outs with scoreable: false. |
limit | number (optional) | Max fan-outs (default 25, max 100). |
includeSiteScoped | boolean (optional) | Include site:-scoped fan-outs in the list. Default false — see below. |
includeCompetitorNamed | boolean (optional) | Include fan-outs that name a competitor and not this brand. Default false — see below. |
Build the audit's query set from this list: one member as the primary (the search the page is really for) plus the related fan-outs. You can also include searches the user brings — that's fully supported, it just carries a different provenance label.
Site-scoped fan-outs are evidence, never targets
A site-scoped fan-out is one the model aimed at a single site — site:example.com pricing — instead of searching the open web. Nothing can rank for a query scoped to somebody's domain, so these are evidence about which sites the model already trusts, not opportunities:
- They are excluded from
list_tracked_fanoutsby default, and from SERP gap tracking. - They are never counted in a platform's
countinavailability, so the "how much is there to work on" number can't drift. The separatesiteScopedCountreports how many exist. - A site-scoped search may be added to an audit's query set as a fan-out, where it is kept as evidence for the named-in-searches diagnostic and marked
scoreable: false. As the primary, it is refused — every recommendation the audit produced would be impossible to act on. score_citation_pipelinerejects them outright.
Pass includeSiteScoped: true only when you want to show that evidence — for example, to answer "which sites does ChatGPT consult directly when it researches us?". Each included row carries siteScoped: true, the siteScopeDomain it pointed at, a siteScopeClass of own / competitor / third_party, and a siteScopeNote.
Competitor-named fan-outs are informational too
Some fan-outs consult a specific brand without the operator — they just name it (CompetitorX pricing). A page about this brand's product cannot answer a question about a competitor's, so these are handled exactly like scoped rows:
- Excluded from
list_tracked_fanoutsby default, and from SERP gap tracking. - Never counted in a platform's
countinavailability; the separatecompetitorNamedCountreports how many exist. - Included rows carry
competitorNamed: true, thenamedCompetitorsthey matched, and acompetitorNamedNote. Don't score them.
Comparison fan-outs are not affected. A query naming this brand and a competitor (Us vs CompetitorX) is a real, rankable search — it stays in the default list and is fully scoreable.
The test runs against the property's current competitor list at read time, so removing a competitor immediately returns their queries to the rankable set. It is deliberately a whole-word name match, so a competitor named after a common word can over-exclude; that is reversible by untracking the competitor.
Find the target page
| Tool | Use it to |
|---|---|
match_pages_for_fanout | Rank a property's pages by content similarity to a query — pick the closest existing page so two of your pages don't compete for the same citation. |
list_property_pages | List / search a property's pages by path or title, to resolve a page the user names ("optimize my pricing page") into a propertyPageId. |
list_placements | List a property's PR placements (with PQS and a hasContent flag) to find one to optimize. |
get_placement | Read a placement's content so you can score it as a draft. |
Step 2 — Score the audit
score_citation_audit
Scores one page, URL, or pasted draft against a query set on several assistants at once. Runs in the background — returns an auditId; poll get_citation_audit. Provide exactly one of propertyPageId, url, or draftMarkdown.
| Parameter | Type | Notes |
|---|---|---|
propertyId | string | The property id. |
queries | array | The query set. Each member is { query, role?, provenance?, groundingSearchId? }. Exactly one member must have role: "primary"; the rest default to fanout. Two to six members is the useful range. |
platforms | array (optional) | Assistants to score against. Defaults to every one your plan covers. |
pageType | enum (optional) | product, homepage, informational, press_release, general, unknown (default informational). Drives the rewrite template. |
propertyPageId / url / draftMarkdown | string (optional) | The target. Exactly one. |
metaTitle / metaDescription | string (optional) | Optional page meta. |
outlineId | string (optional) | The brief this draft was written from. Links the score back to the brief. |
Provenance is not book-keeping
Every query carries where it came from, and the label survives into the score, the rewrite, and every screen the user sees:
provenance | Means |
|---|---|
tracked | This brand's assistants were observed running this search. Pass the groundingSearchId from list_tracked_fanouts with it. |
freetext | The user typed it. Theirs, not ours. |
generated_unverified | You or a tool suggested it. Nobody has seen an assistant run it for this brand. |
Use tracked only for a search that actually came out of list_tracked_fanouts. By the time a brief or a results screen reaches a person, there is no way left to tell a suggestion from an observation, so the label is the only thing keeping the two apart.
get_citation_audit
The poll target, and the whole picture: status, the combined score with the range it is measured within, each assistant's own score and checks, the merged recommendation list, and the readiness verdict.
| Parameter | Type | Notes |
|---|---|---|
auditId | string | The audit id. |
The range is not decoration. Citation decisions agree only about 85% of the time between identical re-runs, so the score comes with a band (at least ±8 points) and a move inside that band has not been shown to be a move. Never report a few points as an improvement.
Recommendations come grouped by kind:
| Kind | What it means | What to do |
|---|---|---|
consensus | Several assistants asked for the same thing. | Do these first — they pay off everywhere. |
platform_specific | One assistant asked alone. | Worth doing, weighted by how much that assistant matters to this brand. |
conflict | Two asks that cannot both be satisfied in the same passage. | Follow the resolution, which allocates different parts of the page to each ask. Averaging two incompatible instructions produces a rewrite that satisfies neither. |
STOP rules
readiness.recommendation is one of:
| Verdict | What it means | What to do |
|---|---|---|
revise_again | There's meaningful headroom. | Call revise_citation_audit. |
publish_ready | Every assistant that finished passes its selection checks and the combined score clears the bar. | Stop. |
plateaued | The last pass didn't move the score beyond its range. | Stop, and present the best version. |
regressed | The rewrite made at least one assistant meaningfully worse, even though the combined score held up. | Stop, and present the previous version — readiness.bestAuditId names it. |
pending | Nothing has finished scoring yet. | Keep polling. |
Whenever readiness.assistantInstruction is present, it is an explicit instruction to end the loop. Follow it. regressed is the one that is easy to miss: the rewrite objective is "raise the combined score without making any assistant worse", so a version that traded Claude for the average has not met it — and the combined number alone will not tell you.
Step 3 — Revise
revise_citation_audit
Generates a revised draft grounded in every assistant's findings at once, with the conflicts resolved by allocation rather than by averaging. Background job — returns a revisionId; poll get_revision. Only call this when readiness is revise_again.
| Parameter | Type | Notes |
|---|---|---|
auditId | string | The scored audit to revise. |
profile | harmonized | a platform key (optional) | Default harmonized, which is almost always right. Naming a single platform optimizes for that one alone and usually costs score on the others. |
Spends credits. Refused when the organization has used its monthly rewrite allowance and has no credits left to cover the next one; the refusal carries the allowance and balance.
get_revision
The poll target: when completed, returns the revised markdown, revised meta, JSON-LD, and a change log tracing each edit back to the recommendation that motivated it. auditId tells you which loop the revision belongs to.
Step 4 — Re-score (close the loop)
rescore_revision
Re-scores a completed revision to measure the improvement, and matches whatever it came from:
- a revision from an audit re-scores as a child audit — the same query set, the same pinned competitor pool, every assistant again — and returns an
auditId; - a revision from a single run returns a
runId.
Read the kind field ("audit" or "run") to know which poll target to use.
| Parameter | Type | Notes |
|---|---|---|
revisionId | string | The completed revision to re-score. |
Pinning the competitor pool is what makes the before/after comparison mean anything: the rewrite is measured against the identical competitors rather than against whatever happens to rank today. Re-scoring is always free and never counts against the monthly page allowance.
Re-weighting (free)
reweight_citation_audit
Changes how much each assistant counts toward the combined score, and returns the recomputed score and verdict. It recomputes from results already collected, so it costs nothing, uses no page from the allowance, and is instant.
| Parameter | Type | Notes |
|---|---|---|
auditId | string | The audit id. |
weights | object | Platform key → weight, e.g. { chatgpt: 40, google_aio: 30, google_aim: 20, claude: 10 }. Any non-negative numbers; they are normalized. |
Use it when the user tells you where their audience actually is ("most of our buyers still start on Google"). Naming an assistant the audit did not run is refused rather than quietly ignored. The new mix is saved on the audit — so a shared link shows the same score and the same verdict the sender saw — and becomes the property's default for its next audit.
Before the page exists — keyword briefs
generate_citation_outline
Writes a section-by-section brief for a page that hasn't been written yet, from a single keyword. Scoring answers "why was this page not cited"; the brief answers "what should the page say in the first place".
It resolves the searches to write for (this brand's tracked fan-outs first, then suggested ones clearly labeled as suggestions), reads what the pages that already rank cover and what none of them answers, and fills the page-type template into a brief: per-section target searches, word budgets, must-include terms, and the rules that decide whether a passage can be quoted.
| Parameter | Type | Notes |
|---|---|---|
propertyId | string | The property id. |
keyword | string | The search this page should be found for. A site:-scoped keyword is refused. |
pageType | homepage | product | informational | press_release | Each has its own template, so this is not cosmetic. There is no fallback — general and unknown are not brief page types. |
platforms | array (optional) | Assistants to write for. Defaults to every live pipeline. |
Spends credits, on the same allowance as a rewrite.
get_citation_outline
The poll target: the searches the brief was built for (each labeled with where it came from), the coverage gaps, and the brief itself — sections, FAQ, meta, and the opportunity nobody has covered.
When you report a brief back, keep the provenance labels. A search marked generated_unverified is one we suggested; presenting it as a search this brand's assistants were observed running is the single mistake this flow exists to prevent.
Then write the draft and score it with score_citation_audit, passing outlineId — it scores against the same searches the brief was built from, so the two screens tell one story.
The loop end-to-end
Choose the searches. list_tracked_fanouts — one primary plus its fan-outs (or searches the user brings, labeled freetext).
Find the target. match_pages_for_fanout / list_property_pages / list_placements + get_placement. Or, for a page that doesn't exist yet, generate_citation_outline → get_citation_outline and write the draft first.
Score. score_citation_audit → poll get_citation_audit until it completes.
Check readiness. If revise_again, continue. If publish_ready, plateaued or regressed, stop and follow assistantInstruction.
Revise. revise_citation_audit → poll get_revision.
Re-score. rescore_revision → poll get_citation_audit. Loop back to Check readiness.
Related
- Citation Optimizer methodology — how the checks and the readiness verdict work
- Citation Optimizer dashboard — the same loop in the app
- Scoring tools — AIPVS / PQS scoring for publishers and placements