OpenSEO Error Codes and Debugging Common Failures: Complete Guide
OpenSEO uses a strict, type-safe error code system defined in error-codes.ts to categorize every predictable failure, with helper functions like shouldCaptureAppErrorCode controlling telemetry capture and isErrorCode enabling runtime validation.
The OpenSEO codebase implements a centralized error handling architecture that makes debugging predictable and telemetry noisy-free. Every error that can surface from the backend or client libraries is enumerated explicitly, with clear rules about which failures warrant alerting and which represent expected user-side issues.
The Complete OpenSEO Error Code Reference
All error codes live in a single source of truth: [src/shared/error-codes.ts](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts). The ERROR_CODES constant enumerates every possible failure state.
| Error Code | Typical Cause | Immediate Debug Action |
|---|---|---|
| UNAUTHENTICATED | Missing or invalid auth token | Verify API key or session token exists and hasn't expired |
| AUTH_CONFIG_MISSING | Required configuration absent | Check .env or runtime config for required variables like DATAFORSEO_API_KEY |
| PAYMENT_REQUIRED | Account lacks paid plan | Review billing status in dashboard; upgrade if necessary |
| INSUFFICIENT_CREDITS | API quota exhausted | Monitor credits UI; add credits or wait for quota reset |
| FORBIDDEN | Insufficient permissions | Confirm user access rights to project or domain |
| NOT_FOUND | Requested entity doesn't exist | Double-check IDs, URLs, and slugs for typos |
| AUDIT_CAPACITY_REACHED | System-wide audit queue full | Retry later or contact support for capacity issues |
| AUDIT_PAGE_LIMIT_EXCEEDED | Audit exceeds page count limits | Reduce page scope or split into smaller audits |
| AUDIT_ALREADY_RUNNING | Duplicate audit in progress | Wait for existing audit completion; implement deduplication |
| VALIDATION_ERROR | Input fails schema validation | Inspect Zod validation details in response body |
| CRAWL_TARGET_BLOCKED | Target blocks crawler | Review target's robots.txt and firewall rules |
| BACKLINKS_BILLING_ISSUE | Billing problem specific to backlinks | Resolve in Backlinks billing section |
| AI_SEARCH_BILLING_ISSUE | Billing problem specific to AI search | Resolve in AI Search billing section |
| DATAFORSEO_AUTH_FAILED | Invalid DataForSEO credentials | Re-enter credentials; test with direct API call |
| RATE_LIMITED | Too many requests | Implement exponential backoff; respect Retry-After header |
| UPSTREAM_UNAVAILABLE | Third-party service down | Check upstream status page; implement circuit breaker |
| CONFLICT | Resource conflict or duplicate | Adjust request payload to resolve collision |
| INTERNAL_ERROR | Unexpected server failure | Capture stack trace; report to telemetry if reportable |
How Error Codes Drive Telemetry and UX
OpenSEO distinguishes between reportable errors (unexpected failures needing attention) and non-reportable errors (expected user-side issues). The NON_REPORTABLE_ERROR_CODES set in [src/shared/error-codes.ts](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts) includes codes like UNAUTHENTICATED and NOT_FOUND that don't indicate system problems.
The shouldCaptureAppErrorCode Gate
The shouldCaptureAppErrorCode function determines whether an error should reach PostHog:
// src/shared/error-codes.ts
export function shouldCaptureAppErrorCode(
code: ErrorCode | null | undefined,
): boolean {
// Report only unexpected errors
return code == null || !NON_REPORTABLE_ERROR_CODES.has(code);
}
This single function controls error reporting across three critical integration points:
- [
src/middleware/errorHandling.ts](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts) — Catches thrownAppErrorinstances and gates PostHog capture - [
src/server/mcp/instrumentation.ts](https://github.com/every-app/open-seo/blob/main/src/server/mcp/instrumentation.ts) — Filters metrics to keep dashboards actionable - [
src/client/lib/error-messages.ts](https://github.com/every-app/open-seo/blob/main/src/client/lib/error-messages.ts) — Determines which errors surface user-facing messages versus silent handling
Runtime Validation with isErrorCode
Type safety doesn't stop at compile time. The isErrorCode type guard validates unknown strings against the schema:
// src/shared/error-codes.ts (excerpt)
export const errorCodeSchema = z.enum(ERROR_CODES);
export type ErrorCode = z.infer<typeof errorCodeSchema>;
export function isErrorCode(value: unknown): value is ErrorCode {
return errorCodeSchema.safeParse(value).success;
}
Use this defensively when processing API responses or external data that might carry malformed error codes.
Practical Debugging Patterns
Pattern 1: Safe API Response Handling
import { isErrorCode, shouldCaptureAppErrorCode } from "@/shared/error-codes";
async function fetchProject(id: string) {
const res = await fetch(`/api/projects/${id}`);
const data = await res.json();
if (!res.ok && isErrorCode(data.error?.code)) {
const code = data.error.code;
switch (code) {
case "NOT_FOUND":
console.warn("Project not found — verify the ID");
return null;
case "RATE_LIMITED": {
const waitSeconds = Number(res.headers.get("Retry-After")) || 5;
await new Promise(r => setTimeout(r, waitSeconds * 1000));
return fetchProject(id); // retry
}
case "UNAUTHENTICATED":
// Expected user error — don't spam telemetry
redirectToLogin();
return;
default:
if (shouldCaptureAppErrorCode(code)) {
posthog.capture("api_error", {
code,
endpoint: `/api/projects/${id}`,
trace: data.error?.stack
});
}
throw new Error(`API failed: ${code}`);
}
}
return data;
}
Pattern 2: Server-Side Error Construction
import { errorCodeSchema, type ErrorCode } from "@/shared/error-codes";
export class AppError extends Error {
constructor(
public readonly code: ErrorCode,
message?: string
) {
super(message ?? errorCodeSchema.Enum[code]);
this.name = "AppError";
}
toJSON() {
return {
code: this.code,
message: this.message,
// Omit stack in production responses
...(process.env.NODE_ENV === "development" && { stack: this.stack })
};
}
}
// Route handler usage
app.get("/api/audits/:id", async (req, res, next) => {
const audit = await getAudit(req.params.id);
if (!audit) {
throw new AppError("NOT_FOUND", `Audit ${req.params.id} not found`);
}
if (audit.userId !== req.user.id) {
throw new AppError("FORBIDDEN");
}
if (await isAuditCapacityReached()) {
throw new AppError("AUDIT_CAPACITY_REACHED");
}
res.json(audit);
});
Pattern 3: Telemetry Integration in Error Middleware
// src/middleware/errorHandling.ts (conceptual)
import { shouldCaptureAppErrorCode } from "@/shared/error-codes";
export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
if (err instanceof AppError) {
const shouldCapture = shouldCaptureAppErrorCode(err.code);
if (shouldCapture) {
posthog.captureException(err, {
distinct_id: req.user?.id,
properties: {
code: err.code,
path: req.path,
method: req.method
}
});
}
// Always log to structured logs regardless of capture decision
logger.log(shouldCapture ? "error" : "info", "Request failed", {
code: err.code,
message: err.message,
reportable: shouldCapture
});
return res.status(mapCodeToStatus(err.code)).json(err.toJSON());
}
// Unhandled errors always captured
posthog.captureException(err);
next(err);
}
Debugging Workflow for Common Failures
-
Extract the code — Inspect
error.codefrom the JSON response body, not just HTTP status -
Validate with
isErrorCode— Defensively confirm the string is a recognized member of the enum before branching logic -
Consult the reportability check — Call
shouldCaptureAppErrorCode(code)to understand if this failure indicates a system issue worth escalating -
Apply targeted remediation:
- Billing errors (
PAYMENT_REQUIRED,*_BILLING_ISSUE) → Dashboard billing section - Quota errors (
INSUFFICIENT_CREDITS,RATE_LIMITED) → Backoff and retry with headers - Validation errors → Parse
error.detailsfor field-level Zod failures - Upstream errors → Check DataForSEO status page, implement circuit breaker
- Billing errors (
-
Verify with the test suite — Run [
src/shared/error-codes.test.ts](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.test.ts) to confirm error code behavior matches expectations after any code changes
Key Files and Their Roles
| File | Purpose | Direct Link |
|---|---|---|
src/shared/error-codes.ts |
Central enum, schema, and helper functions | View source |
src/shared/error-codes.test.ts |
Unit tests for enum coverage and reportability logic | View source |
src/middleware/errorHandling.ts |
Express middleware normalizing errors and gating telemetry | View source |
src/server/mcp/instrumentation.ts |
Metrics instrumentation using shouldCaptureAppErrorCode |
View source |
src/client/lib/error-messages.ts |
Client-side error message resolution | View source |
src/server/lib/errors.ts |
Server-side error class definitions | View source |
Summary
- OpenSEO error codes are exhaustively enumerated in
error-codes.tswith 18 distinct failure categories shouldCaptureAppErrorCodeseparates user errors from system problems, keeping PostHog alerts actionableisErrorCodeprovides runtime type safety for defensive programming- Debugging follows a four-step flow: extract → validate → check reportability → apply targeted fix
- The test suite in
error-codes.test.tsprevents regression in error handling behavior
Frequently Asked Questions
How do I add a new error code to OpenSEO?
Add the string literal to the ERROR_CODES array in [src/shared/error-codes.ts](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts), then decide its reportability by adding or omitting it from NON_REPORTABLE_ERROR_CODES. Update [error-codes.test.ts](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.test.ts) with assertions for the new code, and regenerate any TypeScript types if needed. The Zod schema automatically includes new enum members.
Why am I seeing INTERNAL_ERROR in my logs instead of a specific code?
INTERNAL_ERROR is the catch-all for unhandled exceptions that don't originate from explicit AppError throws. This typically indicates a bug or unexpected state. Check the stack trace, ensure all error paths construct AppError with appropriate codes, and verify your middleware is catching and re-throwing with proper categorization.
How can I suppress specific errors from appearing in PostHog?
Add the error code to NON_REPORTABLE_ERROR_CODES in error-codes.ts. The shouldCaptureAppErrorCode function will automatically exclude it from telemetry across all instrumentation points. Be selective—only suppress errors that represent normal user behavior, not system degradation.
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 →