# How to Interpret OpenSEO Site Audit Report Results: A Complete Guide

> Unlock insights from your OpenSEO site audit report. Learn to interpret the audit, pages, lighthouse, and issues sections for a full technical SEO analysis.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-08-15

---

**An OpenSEO site audit report returns a JSON payload with four main sections—`audit`, `pages`, `lighthouse`, and `issues`—that map directly to database tables and provide a complete picture of your site's technical health.**

The **every-app/open-seo** repository provides a multi-phase auditing workflow that crawls your site, gathers Lighthouse performance data, and identifies broken links and orphan pages. When you call the `getAuditResults` endpoint, the system aggregates data from multiple database tables into a single coherent document. Understanding how to read each section allows you to prioritize fixes based on actual technical impact rather than raw issue counts.

## Understanding the Four Main Sections of an OpenSEO Audit Report

The JSON response from `getAuditResults` (defined in [`src/serverFunctions/audit.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/audit.ts)) contains four top-level keys that correspond to physical database tables: `audit`, `pages`, `lighthouse`, and `issues`.

### The Audit Metadata Section

The `audit` object contains run metadata including `id`, `startUrl`, `status`, `pagesCrawled`, `pagesTotal`, and timestamps. It also includes the parsed configuration such as `lighthouseStrategy` and `maxPages`.

**Why this matters:** This section tells you whether the audit `status` is `"completed"`, `"running"`, or `"error"`. If `pagesCrawled` is less than `pagesTotal`, the crawl stopped early due to hitting the `maxPages` limit or encountering a fatal error. You can cross-reference `error` statuses with error codes defined in [`src/server/lib/errors.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/errors.ts).

### The Pages Section

The `pages` array lists every URL discovered during the crawl with its HTTP `statusCode`, `contentLength`, `mimeType`, and flags like `blocked` (indicating robots.txt exclusion).

**Why this matters:** This inventory reveals which URLs were actually reachable. Look for 4xx/5xx responses here to identify server errors or soft 404s that need immediate attention.

### The Lighthouse Performance Section

The `lighthouse` array contains scores and metrics for sampled pages tested on both mobile and desktop. Each entry includes numeric scores for `performance`, `accessibility`, `bestPractices`, and `seo`, plus an `errorMessage` field if the fetch failed.

**Why this matters:** Lighthouse results provide a quantitative health check for page speed and SEO best practices. The count `lighthouseTotal` should equal twice the number of sampled pages (mobile + desktop). If `lighthouseFailed` is greater than zero, inspect the corresponding entries for specific `errorMessage` values.

### The Issues Section

The `issues` array contains detected problems with an `issueType` (e.g., `broken-internal-link`, `orphan-page`), the affected `pageId`/`pageUrl`, a `dedupeKey`, and type-specific `details`.

**Why this matters:** This is your actionable todo list. Common issue types include:
- **`broken-internal-link`** — A link on a crawled page returned a non-2xx response.
- **`orphan-page`** — A page discovered in the crawl but not linked from any other page.
- **`missing-meta`** or **`slow-page`** — Generated by additional multipage check modules in [`src/server/lib/audit/issues/multipage.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/issues/multipage.ts).

Each issue's `details` object contains the exact URL, HTTP status, and remediation context.

## How OpenSEO Generates Your Audit Data

Understanding the report structure requires knowing how the data is built. The workflow in [`src/server/workflows/siteAuditWorkflowPhases.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/siteAuditWorkflowPhases.ts) orchestrates five distinct phases:

1. **Discovery Phase** (`runDiscoveryPhase`) — Parses [`robots.txt`](https://github.com/every-app/open-seo/blob/main/robots.txt) and extracts sitemaps, storing seeds in a scratch-pad data object.
2. **Crawl Phase** (`runCrawlPhase`) — Fetches URLs and persists them to the `AuditRepository`.
3. **Lighthouse Phase** — Selects sample pages via `selectLighthousePages` and fetches Lighthouse data via DataForSEO (stored via `storeLighthouseResult`).
4. **Multipage Checks** (`runMultipageChecks` and `runScratchpadLinkChecks`) — Runs after crawling to detect broken links and orphan pages.
5. **Finalize** (`finalizeAudit`) — Inserts all issues, sets status to completed, and clears the scratch-pad.

The `getAuditResults` endpoint simply reads these persisted rows via `AuditRepository.getAuditResultsForProject` and returns the fully joined view.

## Reading Key Status Fields and Metrics

When interpreting the report, focus on these specific fields:

- **`status`** — `"completed"` means all phases in [`siteAuditWorkflowPhases.ts`](https://github.com/every-app/open-seo/blob/main/siteAuditWorkflowPhases.ts) succeeded; `"error"` contains an `errorCode` for debugging.
- **`pagesTotal` vs. `pagesCrawled`** — A discrepancy indicates the crawl halted early, usually due to the `maxPages` configuration limit.
- **`lighthouseTotal`** — Should be twice your sampled page count. If `lighthouseFailed` is non-zero, check the `errorMessage` in the Lighthouse array entries.
- **`dedupeKey`** — Found in issue objects; allows you to group identical issues across multiple pages without double-counting.

## Querying and Displaying Audit Results

You can consume the audit data via React hooks, direct HTTP requests, or server-side service calls.

### Fetching Results in a React Component

Use the generated server function with TanStack Query to display audit data:

```tsx
import { useQuery } from '@tanstack/react-query';
import { getAuditResults } from '@/routes/api/audit';

function AuditReport({ auditId }: { auditId: string }) {
  const { data, isLoading, error } = useQuery(['audit', auditId], () =>
    getAuditResults({ auditId })
  );

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Failed to load audit.</p>;

  const { audit, pages, lighthouse, issues } = data!;

  return (
    <>
      <h2>Audit – {audit.startUrl}</h2>
      <p>Status: {audit.status}</p>

      <section>
        <h3>Crawled Pages ({audit.pagesCrawled}/{audit.pagesTotal})</h3>
        <ul>
          {pages.map(p => (
            <li key={p.id}>
              {p.url} – {p.statusCode}
            </li>
          ))}
        </ul>
      </section>

      <section>
        <h3>Lighthouse Scores</h3>
        <ul>
          {lighthouse.map(l => (
            <li key={l.id}>
              {l.url}: {l.performance} / 100
              {l.errorMessage && <em> – Error: {l.errorMessage}</em>}
            </li>
          ))}
        </ul>
      </section>

      <section>
        <h3>Issues</h3>
        {issues.map(i => (
          <div key={i.pageId} style={{ marginBottom: '0.5rem' }}>
            <strong>{i.issueType}</strong> on {i.pageUrl}
            {i.details && <pre>{JSON.stringify(i.details, null, 2)}</pre>}
          </div>
        ))}
      </section>
    </>
  );
}

```

### Raw API Access via CLI

For automation or external integrations, call the endpoint directly:

```bash
curl -X POST https://your-openseo.example.com/api/audit/getAuditResults \
  -H "Content-Type: application/json" \
  -d '{"auditId":"c1d2e3f4-5678-90ab-cdef-1234567890ab"}'

```

Filter the JSON response with `jq` to isolate specific issues:

```bash

# Show only broken internal links

jq '.issues[] | select(.issueType=="broken-internal-link")' response.json

```

### Server-Side Service Integration

For custom backends or background jobs, use the `AuditService` directly:

```ts
import { AuditService } from '@/server/features/audit/services/AuditService';

async function printSummary(auditId: string, projectId: string) {
  const result = await AuditService.getResults(auditId, projectId);
  console.log(`Audit for ${result.audit.startUrl}`);
  console.log(`Pages crawled: ${result.audit.pagesCrawled}/${result.audit.pagesTotal}`);
  console.log(`Lighthouse failures: ${result.audit.lighthouseFailed}`);
  console.log('Issues:');
  for (const i of result.issues) {
    console.log(`- ${i.issueType} on ${i.pageUrl}`);
  }
}

```

The `AuditService.getResults` method in [`src/server/features/audit/services/AuditService.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/audit/services/AuditService.ts) handles the database joins and returns the same structured data used by the API endpoint.

## Summary

- **OpenSEO audit reports** contain four main sections (`audit`, `pages`, `lighthouse`, `issues`) that map to database tables in the every-app/open-seo architecture.
- **Always check `audit.status`** to confirm the workflow completed successfully before analyzing other sections.
- **Compare `pagesCrawled` to `pagesTotal`** to identify if the crawl stopped early due to limits or errors.
- **Review the `issues` array** for actionable items like `broken-internal-link` and `orphan-page`, using the `details` object for remediation specifics.
- **Query results** via the React hook, raw HTTP POST, or server-side `AuditService` depending on your integration needs.

## Frequently Asked Questions

### What does it mean if `pagesCrawled` is less than `pagesTotal`?

This indicates the audit stopped before discovering all potential URLs. According to the workflow logic in [`src/server/workflows/siteAuditWorkflowPhases.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/siteAuditWorkflowPhases.ts), this typically occurs when the crawl hits the `maxPages` limit defined in your audit configuration or encounters a fatal network error. Check the `audit.status` field—if it shows `"completed"`, the crawl simply reached its configured cap; if it shows `"error"`, inspect the `errorCode` for the specific failure reason.

### How do I identify which pages failed Lighthouse testing?

Examine the `lighthouseFailed` count in the `audit` metadata section, then iterate through the `lighthouse` array to find entries containing an `errorMessage` field. The `lighthouseTotal` should equal twice the number of sampled pages (accounting for both mobile and desktop tests). Failed entries will have null scores and a descriptive error explaining why DataForSEO could not retrieve the metrics.

### Can I query the audit data directly from the database instead of using the API?

Yes. The JSON sections returned by `getAuditResults` correspond directly to the tables `audit`, `audit_pages`, `audit_lighthouse`, and `audit_issues`. If you need deeper diagnostics or want to run custom SQL analysis, you can query these tables directly. However, the API response in [`src/serverFunctions/audit.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/audit.ts) provides a fully-joined view that handles the relationships between pages and their associated issues automatically.

### What is the difference between `broken-internal-link` and `orphan-page` issues?

A `broken-internal-link` issue (generated in [`src/server/lib/audit/issues/multipage.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/issues/multipage.ts)) indicates that a crawled page contains a hyperlink returning a 4xx or 5xx status code. An `orphan-page` issue means the page was discovered (usually via sitemap or direct inclusion) but no other page in the crawl links to it. Broken links represent navigation problems for users, while orphan pages represent architectural isolation that may prevent proper indexing by search engines.