Visibility
Citation gaps
How a gap between you and a cited competitor is detected, ranked, and actioned.

What this is for
For every prompt you track, DiscoveredBy already knows which pages the AI engines are citing right now, whether that page is yours or a competitor's (see Citations). Citation gaps takes the prompts where a competitor is winning and you are not, and turns the difference into a ranked queue of specific changes: add this table, cover this figure, add this schema type, not a generic instruction to improve your content.
What a citation gap is
A citation gap is the difference between what the pages AI engines cite for
a prompt contain and what your own page offers: the specific, missing piece
keeping you out of the answer. Ask an engine "what's the best invoice
software for a small agency?" and it synthesizes its answer from a handful
of pages it trusts; if a competitor's page gets pulled in and yours does
not, that is rarely random. The winning page usually has something concrete
yours lacks: a comparison table, an FAQ answering the follow-up question, a
statistic yours never states, a topic it covers that yours does not, cleaner
structured data, a stronger outside source, or content that reads newer.
DiscoveredBy checks a competitor's cited page against your best matching
page for exactly these signals and records what it actually finds missing
as evidence, not a guess (api/services/citation_gap.py#find_gaps_for_project).
When no page of yours exists to compare at all, the opportunity is to create
one rather than fix an existing page.
What you see
Citation gaps opens on the highest-impact opportunity still waiting on you: its impact score, the kind of fix, and why it was prioritized, linking into the detail page. Two smaller tiles count what is waiting on you and what you have shipped, which includes a fix that did not move your visibility, not only the ones that won.
Below that, a filterable table lists every opportunity: its impact score,
the kind of fix, its status (new, accepted, implemented, monitoring, won,
no lift, or dismissed), how much evidence backs it, your baseline visibility and the
competitor citation count for that prompt cluster, and the actions available
at that status: accept it, mark it done once you have made the change,
dismiss it, or open the detail page. Accepting an opportunity
(api/routers/citation_gaps.py#accept_citation_gap) also creates a candidate
topic on the Articles screen, carrying this
diagnosis with it so you do not re-type it
(api/services/citation_gap.py#create_article_topic_from_opportunity).
Nothing is written from that topic until you pick it there.
The detail page (api/routers/citation_gaps.py#get_citation_gap) repeats the
impact score, status and confidence, and adds four tiles: prompts affected,
your visibility, competitor citations, and estimated effort. Below that: the
recommended actions, the evidence behind each (the claim, what your page
does about it today, and a link to the source), the affected prompts, the
competing URLs it was diagnosed against, and, once marked done, any
visibility checks recorded afterward.
Refreshing on demand ("Refresh now") needs a plan that includes it; other
plans see the same control link to billing instead
(api/routers/citation_gaps.py#refresh_project_citation_gaps,
frontend/src/routes/(app)/citation-gaps/+page.svelte#canRefresh). See
Pricing for what each plan includes. New opportunities can
also appear on their own from the weekly on-page optimization run, for
plans that include it, without anyone pressing refresh.
How it works
A run starts from your tracked prompts. A prompt only becomes a candidate
when two things are both true over the trailing window: you appear in under
30% of that prompt's own runs, and at least one citation in that window
points at a domain that is not yours
(api/services/citation_gap.py#_OWN_VISIBILITY_THRESHOLD,
api/services/citation_gap.py#_MIN_COMPETITOR_CITATIONS). A prompt you
already win most of the time never becomes a citation gap, even if a
competitor is cited occasionally alongside you.
How many of those eligible prompts one run actually works through
depends on how the run started. The weekly optimization run, the one
with a deployed timer, works through as many as your plan allows
(api/services/optimization/service.py#_optimization_limit); see
Pricing for what each plan includes. The on-demand "Refresh
now" button and the single-prompt refresh instead always work through at
most 50, regardless of plan
(api/services/citation_gap.py#_MAX_PROMPTS_PER_RUN). Either way, the
prompts a run reaches first are the ones carrying the most competitor
citations.
The prompts a run reaches are not a fixed queue, either. Each run re-reads which prompts are currently eligible and re-ranks them by competitor citations over the trailing window, so a prompt left out of one run is not guaranteed to be picked up by the next, and it is not permanently excluded either: whether a later run reaches it depends on how it ranks against that run's eligible prompts then, a different kind of bound from the fixed, capture-time order behind the cap on Fan-out.
For each candidate prompt, DiscoveredBy reads the five most-cited competing
URLs for it (api/services/citation_gap.py#_TOP_URLS_PER_PROMPT) and looks
for the page of yours most likely to compete: an explicit mapping if one has
been set, otherwise a page of yours already cited for that prompt, otherwise
your existing page with the closest keyword overlap to it. Below a minimum
overlap, DiscoveredBy treats that as no match and recommends creating a new
page instead of pointing at a weak one (api/services/citation_gap.py#_MIN_SIMILARITY);
that threshold changes which fix gets recommended, not whether a gap is
found at all. Pages not yet fetched are queued for the scrape worker and
picked up again on a later run.
DiscoveredBy then compares your matched page, if one exists, against the
competing pages for the signals described above; every difference found
becomes one piece of evidence, and an opportunity with none is not created.
Prompts resolving to the same page and fix type are merged into one
opportunity, with its impact score reflecting the merged count. That score
weighs the competitor's citation share for the cluster, how far under your
visibility is, engine spread, evidence found, and how substantial the
competing pages are (api/services/citation_gap.py#_impact_score).
The number of new opportunities a run actually saves is capped too,
separately from the prompt-processing cap above. Clustering can turn up
more distinct opportunities than a run keeps: they are sorted by impact
score first, then cut down to a plan-derived limit, so anything dropped
is always the lowest-impact opportunity found in that run, not an
arbitrary one (api/services/citation_gap.py#_limit_for_plan,
api/services/citation_gap.py#_PLAN_LIMITS). This is the number of new
opportunities that actually show up in your queue after a run, so it is
the bound you feel more directly than the processing cap above; see
Pricing for what each plan includes.
Citation gap opportunities are generated only on plans that include the
feature; see Pricing for what each plan includes. Marking an
opportunity done sets its status to implemented, not monitoring
(api/routers/citation_gaps.py#mark_citation_gap_implemented). From there
it can advance to won, no lift, or monitoring, but only once something
checks your visibility for its prompt cluster again, at the 7, 14, and 30
day mark after you marked it done: won if visibility has risen at least
10 points from its baseline or reads 20% or higher on its own, no lift if
neither is true by the 30-day mark, monitoring if neither is true yet and
the 30-day mark has not arrived
(api/services/citation_gap.py#refresh_outcomes_for_project). Two jobs
call that check, and neither of them is this screen's own "Refresh now":
pressing Refresh here regenerates opportunities, it does not check
outcomes on ones already marked implemented. The older caller is the
project's topic-generation job
(api/tasks.py#_run_generate_topics_for_project). The newer one exists
only to run the check, and it fans out to a project only when four things
are true at once: the project holds an opportunity whose status is
implemented or monitoring, that opportunity carries the date you marked it
done, the project is active, and the account it belongs to is active. A
project with nothing left to re-read is skipped rather than queued
(api/services/citation_gap.py#project_ids_with_pending_outcomes,
api/tasks.py#_enqueue_citation_gap_outcomes_for_eligible_projects,
api/cli.py#cmd_enqueue_citation_gap_outcomes). That second job and the
daily schedule written for it,
discoveredby-cli@enqueue-citation-gap-outcomes.timer, were added to the
codebase in the same change; a schedule only starts firing once a
deployment installs it.
Publishing an article linked to the opportunity is a separate way into
monitoring: it moves the opportunity straight there on its own,
independent of either job
(api/services/articles.py#_attach_publication_monitoring). Until one of
those three things happens for your project, an opportunity stays where
you left it.
What the numbers mean
Impact score, out of 100, ranks opportunities against each other for this project; read it as a relative order, not a percentage of anything. Confidence (low, medium, high) reflects how much evidence was found and whether a specific page could be matched to the prompt, not how sure DiscoveredBy is the fix will work. Effort (low, medium, high) is a fixed estimate per fix type, not a measurement of your specific page. The prompts affected, your visibility, and competitor citations tiles are baseline measurements for the affected prompt cluster over the window the opportunity was found in, not a live number that moves as you read it. For how to read a change against a baseline in general, see How to read any number here.
Related
- Citations: what counts as a citation, and how it is attributed to you or a competitor
- Prompts: what a prompt is and how the ones you track get run
- Metrics defined: the citation rate formula behind the baseline figures here
- Troubleshooting: No citations yet: what to check before expecting a citation gap to appear
Last verified 2026-08-11