Skip to content

CanonicalObservation

CanonicalObservation reconciles canonical signals from multiple sources for a single URL: the crawl data (raw HTML tag, HTTP Link header, rendered DOM), and external truth from Google Search Console.

This is the model that powers canonical mismatch findings, including the harder ones where your site declares one canonical but Google selects a different one.

interface CanonicalObservation {
url: string;
projectId: string;
observedAt: string;
// Signals from crawl
rawHtmlCanonical: string | null; // <link rel="canonical"> in raw HTML
httpHeaderCanonical: string | null; // Link: rel=canonical HTTP header
renderedCanonical: string | null; // canonical in rendered DOM (may differ)
// Computed effective canonical (what the crawl engine would use)
crawlEffectiveCanonical: string | null;
// GSC data (if connected)
gscSelectedCanonical: string | null; // Google's chosen canonical for this URL
gscIndexedStatus: 'indexed' | 'duplicate' | 'excluded' | 'unknown';
gscLastVerified: string | null; // ISO timestamp of last GSC inspection
// Findings (populated during audit check run)
hasConflict: boolean; // any signal disagrees with another
conflictType: CanonicalConflictType | null;
targetIsRedirect: boolean;
targetStatusCode: number | null;
loopDetected: boolean;
}
type CanonicalConflictType =
| 'raw-vs-rendered' // raw HTML canonical ≠ rendered DOM canonical
| 'html-vs-http-header' // <link rel=canonical> ≠ Link header
| 'crawl-vs-gsc' // crawl effective canonical ≠ GSC selected
| 'self-canonical-missing' // no canonical declared at all
| 'loop' // canonical chain forms a cycle
| 'chain' // 3+ hops to reach final canonical
| 'to-redirect' // canonical target is a redirect
| 'to-non-200' // canonical target returns error
| 'cross-domain'; // canonical points to different domain

When multiple canonical signals exist, the audit engine uses this precedence:

  1. HTTP Link header (highest precedence, per Google’s documented behavior)
  2. <link rel="canonical"> in HTML
  3. Rendered DOM canonical (if it differs from #2, that’s a parity finding)

The crawlEffectiveCanonical is populated with whichever source wins under these rules.

When GSC is connected, gscSelectedCanonical shows which URL Google has actually chosen as canonical — which may differ from what you declared. Google can override your declared canonical when:

  • It finds higher-quality duplicate content it prefers
  • Your declared canonical itself has redirect or error issues
  • The canonical declaration is inconsistent across signals

A crawl-vs-gsc conflict is a High severity finding because it means Google is ignoring your canonical declaration.

Terminal window
# Get canonical observations for a project
curl "https://visibilityiq365.com/api/v1/projects/proj_abc123/pages?type=canonical&conflict=true" \
-H "Authorization: Bearer vis_live_YOUR_KEY_HERE"

Canonical conflicts are also surfaced in the standard issues endpoint with checkId=canonical.*.