How to Run an SEO Audit Using OpenSEO: Complete Developer Guide

OpenSEO runs SEO audits as durable Cloudflare workflows that crawl target sites, execute Lighthouse performance checks, and persist structured results to the database, orchestrated through the startAudit server function and AuditService singleton.

Running an SEO audit using open-seo requires interacting with its durable workflow architecture designed for Cloudflare Workers. The system validates requests against strict schemas, enforces billing limits, and executes crawls through fault-tolerant durable objects. This guide explains the complete execution flow from initiation to result retrieval, referencing the actual implementation in the every-app/open-seo repository.

Prerequisites for Running an OpenSEO Audit

Before triggering an audit, your environment must satisfy three core requirements:

  • DataForSEO API key – Required for all crawling operations. Store this as DATAFORSEO_API_KEY in your environment variables (see docs/DATAFORSEO_API_KEY.md).
  • Deployment target – Either a Docker container or Cloudflare Workers deployment with Durable Objects enabled (see docs/SELF_HOSTING_DOCKER.md).
  • Authentication token – A valid MCP session token or API key passed in the Authorization header for all requests.

Step-by-Step Audit Execution Flow

OpenSEO implements a five-phase execution model orchestrated by the AuditService (src/server/features/audit/services/AuditService.ts). Each phase corresponds to specific functions and durable workflow steps.

Phase 1: Audit Initiation via startAudit

The entry point resides in src/serverFunctions/audit.ts. When you call startAudit, the system performs three critical operations:

  1. Schema validation – The request body is validated against src/types/schemas/audit.ts.
  2. Limit enforcementAuditService.resolveAuditLimitTier checks your plan constraints before proceeding.
  3. Workflow creationAuditRepository.createAudit persists an audit record, then env.SITE_AUDIT_WORKFLOW.create launches the durable object.

The function returns a unique auditId that tracks the audit through its entire lifecycle.

Phase 2: Crawl and Analysis via SiteAuditWorkflow

Once initiated, the SiteAuditWorkflow class (src/server/workflows/SiteAuditWorkflow.ts) executes runAuditPhases from src/server/workflows/siteAuditWorkflowPhases.ts. This durable workflow runs the following steps sequentially:

  • discoverUrls – Crawls the start URL and discovers linked pages up to maxPages.
  • runLighthouse – Executes Lighthouse performance audits (unless lighthouseStrategy: "none").
  • runMultipageChecks – Detects SEO issues across the crawled page set.

Each step updates the audit_progress KV store via AuditProgressKV (src/server/lib/audit/progress-kv.ts) for real-time status tracking.

Phase 3: Real-Time Progress Monitoring

While the workflow executes, clients poll getAuditStatus (exposed in src/serverFunctions/audit.ts). This endpoint queries AuditService.getStatus and returns:

  • status: running, failed, or completed
  • pagesCrawled and pagesTotal counts
  • Lighthouse completion percentage
  • Error codes if phases fail

Phase 4: Retrieving Audit Results

When status === "completed", call getAuditResults to fetch the complete payload. This invokes AuditService.getResults, which calls AuditRepository.getAuditResultsForProject to return:

  • Audit metadata (start URL, timestamps, configuration)
  • Complete list of crawled pages
  • Lighthouse metrics and scores
  • Detected SEO issues with severity classifications

Phase 5: Optional Cleanup

Remove completed or failed audits using deleteAudit, which triggers AuditService.remove. This terminates the durable object workflow and deletes associated scratch-pad data.

Code Examples for Running OpenSEO Audits

Starting an Audit

const response = await fetch('https://your-openseo-instance.com/api/audit/start', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <YOUR_MCP_TOKEN>',
  },
  body: JSON.stringify({
    startUrl: 'https://example.com',
    maxPages: 100,
    lighthouseStrategy: 'auto',  // Use "none" to skip Lighthouse
  }),
});

if (!response.ok) throw new Error('Failed to start audit');
const { auditId } = await response.json();
console.log('Audit started – ID:', auditId);

Polling Audit Status

async function pollStatus(auditId: string) {
  while (true) {
    const res = await fetch('https://your-openseo-instance.com/api/audit/status', {
      method: 'POST',
      headers: { 
        'Content-Type': 'application/json', 
        'Authorization': 'Bearer <YOUR_MCP_TOKEN>' 
      },
      body: JSON.stringify({ auditId }),
    });
    
    const status = await res.json();
    console.log(`Status: ${status.status} (${status.pagesCrawled}/${status.pagesTotal})`);

    if (status.status !== 'running') break;
    await new Promise(r => setTimeout(r, 2000));
  }
}

Retrieving Final Results

const resultRes = await fetch('https://your-openseo-instance.com/api/audit/results', {
  method: 'POST',
  headers: { 
    'Content-Type': 'application/json', 
    'Authorization': 'Bearer <YOUR_MCP_TOKEN>' 
  },
  body: JSON.stringify({ auditId }),
});

const results = await resultRes.json();
console.log('Pages crawled:', results.pages.length);
console.log('Lighthouse data:', results.lighthouse);
console.log('Issues detected:', results.issues);

Deleting an Audit

await fetch('https://your-openseo-instance.com/api/audit/delete', {
  method: 'POST',
  headers: { 
    'Content-Type': 'application/json', 
    'Authorization': 'Bearer <YOUR_MCP_TOKEN>' 
  },
  body: JSON.stringify({ auditId }),
});

Key Architectural Components

Understanding these core files helps debug and extend audit functionality:

Summary

  • OpenSEO audits run as durable Cloudflare workflows through the SiteAuditWorkflow class, ensuring fault-tolerant execution even during network interruptions.
  • The AuditService singleton (src/server/features/audit/services/AuditService.ts) enforces billing limits and orchestrates all audit operations.
  • DataForSEO API integration is mandatory for crawling; configure via DATAFORSEO_API_KEY.
  • Real-time progress tracking uses KV storage (AuditProgressKV) while the workflow executes phases defined in siteAuditWorkflowPhases.ts.
  • Final results include comprehensive Lighthouse metrics, crawled page data, and structured SEO issue detection accessible via getAuditResults.

Frequently Asked Questions

How do I check my audit limits before starting a crawl?

Call AuditService.resolveAuditLimitTier internally or check your project settings via the API before invoking startAudit. The system automatically validates limits during the initiation phase and returns a 402 error if you've exceeded your tier's page crawl quota.

Can I run an audit without Lighthouse performance checks?

Yes. Set lighthouseStrategy: "none" in the request body when calling startAudit. The workflow will skip the runLighthouse phase in siteAuditWorkflowPhases.ts, reducing execution time and focusing solely on technical SEO crawling and issue detection.

What happens if an audit fails during the crawl phase?

The SiteAuditWorkflow durable object implements automatic retries for transient failures. If a phase permanently fails (such as an invalid URL or DataForSEO API outage), the workflow updates the status to failed in the KV store and persists error details to the database. You can retrieve failure codes via getAuditStatus and restart the audit with a new auditId.

How long does OpenSEO retain audit results?

According to the repository's AuditRepository implementation, results persist indefinitely in the database until explicitly deleted via deleteAudit. However, the temporary scratch-pad Durable Object used during active crawling is automatically cleaned up when the workflow completes or fails.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →