Audit Limits in OpenSEO and How Metered Tasks Are Tracked Across Batches

OpenSEO enforces tier-based audit limits via hard-coded constants in src/shared/audit-limits.ts and tracks DataForSEO credit consumption per batch using the meter wrapper in src/server/lib/dataforseo/client.ts.

The every-app/open-seo repository implements a dual-layer constraint system: hard limits on crawl scope and concurrent capacity per tier, plus a granular metering layer for external API costs. This architecture ensures that free-plan users cannot abuse compute resources while accurately attributing DataForSEO credit spend to the correct organization and feature.

Understanding OpenSEO Audit Limits

Hard-Coded Page Bounds

OpenSEO defines four immutable constants that govern audit granularity. These values live in [src/shared/audit-limits.ts](https://github.com/every-app/open-seo/blob/main/src/shared/audit-limits.ts) and are referenced by both client-side forms and server-side validation:

  • MIN_AUDIT_PAGES (10): The absolute floor for any audit request.
  • DEFAULT_AUDIT_PAGES (50): Fallback value when users omit a page limit.
  • FREE_MAX_AUDIT_PAGES (50): Hard ceiling for free-tier audits.
  • PAID_MAX_AUDIT_PAGES (10,000): Ceiling for paid and self-hosted deployments.

These constants prevent drift between the launch form’s UI constraints and the workflow’s execution parameters.

Tier-Specific Capacity Constraints

Beyond per-audit page caps, OpenSEO enforces organizational limits via the AUDIT_LIMITS record in [src/server/features/audit/services/audit-capacity.ts](https://github.com/every-app/open-seo/blob/main/src/server/features/audit/services/audit-capacity.ts):

Tier maxPagesPerAudit maxCapacityUnits maxRunningAudits
free 50 2,000 5
paid 10,000 100,000 Unlimited
self_hosted 10,000 Unlimited Unlimited

Free organizations hit a hard stop at five concurrent audits and 2,000 total capacity units, while paid plans scale to 100,000 units with unlimited concurrency. Self-hosted installations bypass capacity and concurrency checks entirely.

Normalizing User Input with clampAuditMaxPages

When a client submits a custom page limit, the server normalizes it through clampAuditMaxPages (also in audit-capacity.ts). This utility clamps the input to the inclusive range [MIN_AUDIT_PAGES, PAID_MAX_AUDIT_PAGES] and substitutes DEFAULT_AUDIT_PAGES if the value is null or undefined.

import { clampAuditMaxPages } from "@/server/features/audit/services/audit-capacity";

const userPages = 300; // Potentially from a query parameter
const pagesToCrawl = clampAuditMaxPages(userPages);
// Result: 300 (within the allowed 10–10,000 range)

How Metered Tasks Are Tracked Across Batches

The Metering Layer Architecture

OpenSEO integrates with DataForSEO for credit-metered data such as keyword ideas, backlinks, and Lighthouse results. The metering logic resides in [src/server/lib/dataforseo/client.ts](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/client.ts) and follows this flow:

  1. createDataforseoClient instantiates a namespaced client backed by a billing customer context.
  2. The meter function wraps every endpoint call, returning a proxy that invokes meterDataforseoCall upon execution.
  3. meterDataforseoCall executes the underlying fetcher, then records the cost via trackDataforseoCost if running in hosted mode.
  4. Special error handling catches DataforseoChargedTaskError to log spend even when the request was malformed but still billed.

Batch-Level Charging for DataForSEO

For endpoints accepting array inputs—such as postRankCheckTasks—OpenSEO writes a single billing entry for the entire batch. As documented in client.ts, “one metered charge covers the whole batch (DataForSEO bills task_post at post time, collection is free).” This means posting 100 keywords incurs one charge, not 100 individual charges.

import { createDataforseoClient } from "@/server/lib/dataforseo/client";
import { getOrCreateOrganizationCustomer } from "@/server/billing/subscription";

async function runRankCheckBatch(customerCtx, keywordBatch) {
  const billingCustomer = await getOrCreateOrganizationCustomer(customerCtx);
  const dfClient = createDataforseoClient(billingCustomer);

  // Posts up to 100 queued tasks; one metered charge covers the whole batch
  await dfClient.serp.rankCheckTaskPost({ keywords: keywordBatch });
}

Non-Metered vs. Metered Operations

The [siteAuditWorkflowCrawl.ts](https://github.com/every-app/open-seo/blob/main/src/server/workflows/siteAuditWorkflowCrawl.ts) workflow demonstrates the boundary between free and metered compute. The actual crawl runs on Cloudflare Workers and consumes zero DataForSEO credits. Only downstream calls—such as fetching Lighthouse metrics or keyword data—trigger the metering layer. This design ensures that partial crawl failures do not generate orphaned credit charges.

Summary

  • Audit limits in OpenSEO are enforced through constants in src/shared/audit-limits.ts, with tier-specific capacity rules defined in audit-capacity.ts.
  • Free plans are capped at 50 pages per audit, 2,000 capacity units, and 5 concurrent runs; paid and self-hosted plans scale to 10,000 pages with higher or unlimited thresholds.
  • The clampAuditMaxPages function normalizes user input to prevent out-of-bounds requests.
  • Metered tasks are tracked via the meter wrapper in src/server/lib/dataforseo/client.ts, which records credit usage per batch rather than per individual task.
  • Crawl operations use Workers compute and remain un-metered; only DataForSEO API calls incur credit charges.

Frequently Asked Questions

What are the OpenSEO audit limits for free versus paid plans?

Free-tier organizations are limited to 50 pages per audit, 2,000 total capacity units, and a maximum of five running audits simultaneously. Paid plans allow up to 10,000 pages per audit, 100,000 capacity units, and unlimited concurrent audits. Self-hosted installations inherit the 10,000-page limit but bypass capacity and concurrency restrictions entirely.

How does OpenSEO handle metered task tracking for batch operations?

OpenSEO tracks metered tasks through a wrapper function called meter in src/server/lib/dataforseo/client.ts. When you post a batch of tasks—such as 100 keyword checks—the system records a single billing entry for the entire batch. DataForSEO bills at post time, making the collection phase free, and the meterDataforseoCall function handles special cases like malformed requests that still incur charges.

What happens if a user requests more pages than the OpenSEO audit limit allows?

The clampAuditMaxPages function in src/server/features/audit/services/audit-capacity.ts automatically clamps the requested value to the range of 10 to 10,000 pages. If the user submits a number below the minimum, it defaults to 10; if above the maximum, it caps at 10,000. If no value is provided, the system falls back to 50 pages.

Are website crawl operations in OpenSEO metered against DataForSEO credits?

No. The crawl phase executed in src/server/workflows/siteAuditWorkflowCrawl.ts runs on Workers compute and does not consume DataForSEO credits. Only subsequent calls to DataForSEO endpoints—such as retrieving Lighthouse scores or backlink data—trigger the metering layer and deduct credits from the organization’s balance.

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 →