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_KEYin your environment variables (seedocs/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
Authorizationheader 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:
- Schema validation – The request body is validated against
src/types/schemas/audit.ts. - Limit enforcement –
AuditService.resolveAuditLimitTierchecks your plan constraints before proceeding. - Workflow creation –
AuditRepository.createAuditpersists an audit record, thenenv.SITE_AUDIT_WORKFLOW.createlaunches 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 tomaxPages.runLighthouse– Executes Lighthouse performance audits (unlesslighthouseStrategy: "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, orcompletedpagesCrawledandpagesTotalcounts- 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:
-
src/serverFunctions/audit.ts– Exposes the public API surface (startAudit,getAuditStatus,getAuditResults,deleteAudit,getHistory,getCrawlProgress). -
src/server/features/audit/services/AuditService.ts– The singleton service handling limit enforcement (resolveAuditLimitTier), workflow orchestration, and database interactions. -
src/server/workflows/SiteAuditWorkflow.ts– Durable Object implementation that manages the lifecycle ofrunAuditPhasesexecution with automatic retry logic. -
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– Persistence layer for audit records, crawled pages, Lighthouse results, and detected issues. -
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
SiteAuditWorkflowclass, 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 insiteAuditWorkflowPhases.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →