Documentation

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)
ScoresOne page against a query set, on every assistant your plan coversOne page against one query, on ChatGPT
Start withscore_citation_auditscore_citation_pipeline
Pollget_citation_auditget_pipeline_run
Reviserevise_citation_auditrevise_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 → auditIdget_citation_audit(auditId) until completed
revise_citation_audit → revisionIdget_revision(revisionId) until completed
rescore_revision → auditId or runIdget_citation_audit / get_pipeline_run
generate_citation_outline → outlineIdget_citation_outline(outlineId) until completed
score_citation_pipeline → runIdget_pipeline_run(runId) until completed / stopped
revise_content → revisionIdget_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).

ParameterTypeNotes
propertyIdstringFrom list_properties.
platformchatgpt | claude | google_aio | google_aim (optional)Default chatgpt. A platform with no live pipeline returns tracked fan-outs with scoreable: false.
limitnumber (optional)Max fan-outs (default 25, max 100).
includeSiteScopedboolean (optional)Include site:-scoped fan-outs in the list. Default false — see below.
includeCompetitorNamedboolean (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_fanouts by default, and from SERP gap tracking.
  • They are never counted in a platform's count in availability, so the "how much is there to work on" number can't drift. The separate siteScopedCount reports 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_pipeline rejects 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_fanouts by default, and from SERP gap tracking.
  • Never counted in a platform's count in availability; the separate competitorNamedCount reports how many exist.
  • Included rows carry competitorNamed: true, the namedCompetitors they matched, and a competitorNamedNote. 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

ToolUse it to
match_pages_for_fanoutRank 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_pagesList / search a property's pages by path or title, to resolve a page the user names ("optimize my pricing page") into a propertyPageId.
list_placementsList a property's PR placements (with PQS and a hasContent flag) to find one to optimize.
get_placementRead 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.

ParameterTypeNotes
propertyIdstringThe property id.
queriesarrayThe 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.
platformsarray (optional)Assistants to score against. Defaults to every one your plan covers.
pageTypeenum (optional)product, homepage, informational, press_release, general, unknown (default informational). Drives the rewrite template.
propertyPageId / url / draftMarkdownstring (optional)The target. Exactly one.
metaTitle / metaDescriptionstring (optional)Optional page meta.
outlineIdstring (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:

provenanceMeans
trackedThis brand's assistants were observed running this search. Pass the groundingSearchId from list_tracked_fanouts with it.
freetextThe user typed it. Theirs, not ours.
generated_unverifiedYou 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.

ParameterTypeNotes
auditIdstringThe 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:

KindWhat it meansWhat to do
consensusSeveral assistants asked for the same thing.Do these first — they pay off everywhere.
platform_specificOne assistant asked alone.Worth doing, weighted by how much that assistant matters to this brand.
conflictTwo 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:

VerdictWhat it meansWhat to do
revise_againThere's meaningful headroom.Call revise_citation_audit.
publish_readyEvery assistant that finished passes its selection checks and the combined score clears the bar.Stop.
plateauedThe last pass didn't move the score beyond its range.Stop, and present the best version.
regressedThe 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.
pendingNothing 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.

ParameterTypeNotes
auditIdstringThe scored audit to revise.
profileharmonized | 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.

ParameterTypeNotes
revisionIdstringThe 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.

ParameterTypeNotes
auditIdstringThe audit id.
weightsobjectPlatform 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.

ParameterTypeNotes
propertyIdstringThe property id.
keywordstringThe search this page should be found for. A site:-scoped keyword is refused.
pageTypehomepage | product | informational | press_releaseEach has its own template, so this is not cosmetic. There is no fallback — general and unknown are not brief page types.
platformsarray (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.

How-to guides for setting up Spyglasses to track your AI traffic