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

> Learn to run an SEO audit with OpenSEO. This guide details how to use Cloudflare workflows, Lighthouse checks, and the AuditService for comprehensive site analysis.

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

---

**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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/audit.ts).
2. **Limit enforcement** – `AuditService.resolveAuditLimitTier` checks your plan constraints before proceeding.
3. **Workflow creation** – `AuditRepository.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`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/SiteAuditWorkflow.ts)) executes `runAuditPhases` from [`src/server/workflows/siteAuditWorkflowPhases.ts`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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

```typescript
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

```typescript
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

```typescript
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

```typescript
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:

- **[`src/serverFunctions/audit.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/audit.ts)** – Exposes the public API surface (`startAudit`, `getAuditStatus`, `getAuditResults`, `deleteAudit`, `getHistory`, `getCrawlProgress`).

- **[`src/server/features/audit/services/AuditService.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/audit/services/AuditService.ts)** – The singleton service handling limit enforcement (`resolveAuditLimitTier`), workflow orchestration, and database interactions.

- **[`src/server/workflows/SiteAuditWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/SiteAuditWorkflow.ts)** – Durable Object implementation that manages the lifecycle of `runAuditPhases` execution with automatic retry logic.

- **[`src/server/workflows/siteAuditWorkflowPhases.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/siteAuditWorkflowPhases.ts)** – Contains the discrete phase implementations (`discoverUrls`, `runLighthouse`, `runMultipageChecks`) that perform the actual SEO analysis.

- **[`src/server/features/audit/repositories/AuditRepository.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/audit/repositories/AuditRepository.ts)** – Persistence layer for audit records, crawled pages, Lighthouse results, and detected issues.

- **[`src/server/lib/audit/progress-kv.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/progress-kv.ts)** – KV store abstraction for real-time progress updates consumed by the polling interface.

## 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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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.