Skip to content

Findings Schema

Every audit check produces one or more AuditFinding objects. This is the core data type in the VisibilityIQ audit engine.

interface AuditFinding {
checkId: string; // e.g. "canonical.mismatch-raw-rendered"
scope: FindingScope; // where the defect lives
severity: Severity; // how bad it is if real
confidence: Confidence; // how certain we are
pageUrl: string; // the affected URL
templateId?: string; // present when scope is 'template' or 'cluster'
evidence: Record<string, unknown>; // check-specific structured data
remediation: string; // WHAT/WHERE/FIX/VERIFY/RISK operational instruction
guidance: Guidance; // structured UX payload for the guided-fix modal
affectedResources?: string[]; // related URLs or resources
firstDetected?: string; // ISO 8601 date
status: FindingStatus;
}
type FindingScope = 'page' | 'template' | 'cluster' | 'site' | 'source-reconciled';
type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
type Confidence = 'high' | 'medium' | 'low';
type FindingStatus = 'new' | 'recurring' | 'regressed' | 'fixed';
ScopeMeaning
pageDefect on a specific URL only
templateDefect shared by a URL pattern (e.g., all /blog/* pages)
clusterDefect across a group of semantically similar pages
siteSite-wide defect (e.g., missing HSTS header on all responses)
source-reconciledFinding that only emerges when comparing crawl data with GSC/GA4

The evidence object is check-specific. Every evidence payload includes the raw values that triggered the finding. Examples:

Canonical mismatch:

{
"rawCanonical": "https://example.com/page-a",
"renderedCanonical": "https://example.com/page-b",
"expectedCanonical": "https://example.com/page-a"
}

robots.txt AI bot block:

{
"userAgent": "GPTBot",
"directive": "Disallow: /",
"robotsTxtUrl": "https://example.com/robots.txt",
"rawDirectives": ["User-agent: GPTBot", "Disallow: /"],
"affectedPages": 1247
}

Missing HSTS:

{
"headerPresent": false,
"observedHeaders": {
"strict-transport-security": null
},
"recommendedValue": "max-age=31536000; includeSubDomains"
}
interface Guidance {
summary: string; // one-sentence plain-language explanation
whyItMatters: string; // business/visibility impact, no jargon
steps: GuidanceStep[]; // ordered action list
suggestedFix?: string; // copy-button-ready exact value to apply
generatedArtifact?: GeneratedArtifact;
learnMoreUrl?: string;
autoApplyEligible: boolean;
riskLevel: 'safe' | 'review' | 'destructive';
}
interface GuidanceStep {
label: string; // "Open your robots.txt"
detail: string; // what to do and what success looks like
generatesFile?: boolean;
}
interface GeneratedArtifact {
filename: string; // e.g. "robots.txt"
mimeType: string; // e.g. "text/plain"
contents: string; // the corrected, apply-ready file
placement: string; // where it goes ("site root", "<head> of /products/*")
sourceFindingIds: string[];
}
new → recurring (if same finding in next audit)
new → fixed (if not present in next audit)
fixed → regressed (if reappears after being fixed)

When fetching findings via the API, the response looks like:

{
"ok": true,
"data": {
"findings": [ /* AuditFinding[] */ ],
"total": 142,
"page": 1,
"limit": 50
}
}

Findings are paginated. Use ?limit=50&offset=50 for subsequent pages.