# OpenSEO Error Codes and Debugging Common Failures: Complete Guide

> Master OpenSEO error codes and debug common failures with this comprehensive guide. Learn to identify and resolve issues efficiently using OpenSEO's type-safe error system.

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

---

**OpenSEO uses a strict, type-safe error code system defined in [`error-codes.ts`](https://github.com/every-app/open-seo/blob/main/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)](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`](https://github.com/every-app/open-seo/blob/main/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)](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:

```typescript
// 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)](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts)** — Catches thrown `AppError` instances and gates PostHog capture
- **[[`src/server/mcp/instrumentation.ts`](https://github.com/every-app/open-seo/blob/main/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)](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:

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

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

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

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

1. **Extract the code** — Inspect `error.code` from the JSON response body, not just HTTP status

2. **Validate with `isErrorCode`** — Defensively confirm the string is a recognized member of the enum before branching logic

3. **Consult the reportability check** — Call `shouldCaptureAppErrorCode(code)` to understand if this failure indicates a system issue worth escalating

4. **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.details` for field-level Zod failures
   - **Upstream errors** → Check DataForSEO status page, implement circuit breaker

5. **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)](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`](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts) | Central enum, schema, and helper functions | [View source](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts) |
| [`src/shared/error-codes.test.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.test.ts) | Unit tests for enum coverage and reportability logic | [View source](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.test.ts) |
| [`src/middleware/errorHandling.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts) | Express middleware normalizing errors and gating telemetry | [View source](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts) |
| [`src/server/mcp/instrumentation.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/instrumentation.ts) | Metrics instrumentation using `shouldCaptureAppErrorCode` | [View source](https://github.com/every-app/open-seo/blob/main/src/server/mcp/instrumentation.ts) |
| [`src/client/lib/error-messages.ts`](https://github.com/every-app/open-seo/blob/main/src/client/lib/error-messages.ts) | Client-side error message resolution | [View source](https://github.com/every-app/open-seo/blob/main/src/client/lib/error-messages.ts) |
| [`src/server/lib/errors.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/errors.ts) | Server-side error class definitions | [View source](https://github.com/every-app/open-seo/blob/main/src/server/lib/errors.ts) |

---

## Summary

- OpenSEO error codes are **exhaustively enumerated** in [`error-codes.ts`](https://github.com/every-app/open-seo/blob/main/error-codes.ts) with 18 distinct failure categories
- **`shouldCaptureAppErrorCode`** separates user errors from system problems, keeping PostHog alerts actionable
- **`isErrorCode`** provides 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.ts`](https://github.com/every-app/open-seo/blob/main/error-codes.test.ts)** prevents 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)](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/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`](https://github.com/every-app/open-seo/blob/main/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.