> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dacard.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Unified error catalog. Every code returned by the Dacard.ai API, with HTTP status, retryability, and recommended action.

The API returns errors in a single shape. Every response with a non-2xx status carries a JSON body with `code`, `message`, `action`, and `retryable`. Match the `code` field, not the message string. Messages are written for end users and may change without notice.

## Response shape

```json theme={null}
{
  "error": {
    "code": "PLAN_LIMIT_REACHED",
    "message": "You're at the top of your plan.",
    "action": "Move up a plan for more reads.",
    "retryable": false
  }
}
```

Some legacy routes return `{ "error": "string" }` only. Treat the HTTP status as authoritative when no `code` is present.

## Retry posture

| Retryable | Meaning                                                                                                                    |
| --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `true`    | The condition is transient. Back off, then retry. Use exponential backoff for 5xx and rate limit errors.                   |
| `false`   | The condition is permanent for this caller, this resource, or this plan. Retrying without changing inputs will fail again. |

For `429` rate limit errors, see [Rate Limits](/api-reference/rate-limits).

## Code catalog

| Code                       | HTTP | Retryable | What it means                                                                                                                                     | What to do                                                                                                                                              |
| -------------------------- | ---- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_REQUIRED`            | 401  | No        | The request has no valid Clerk session or API key.                                                                                                | Sign in, mint an API key, or attach `Authorization: Bearer <token>` and retry.                                                                          |
| `FORBIDDEN`                | 403  | No        | The caller is authenticated but does not own the resource or lacks the required role.                                                             | Use an account that has access. Check RBAC scope on `/api-reference/api-keys`.                                                                          |
| `ONBOARDING_REQUIRED`      | 403  | No        | The user has not completed the activation contract (Day 1 interview + Week 1 org map).                                                            | Send the user through `/onboarding/welcome`.                                                                                                            |
| `PLAN_LIMIT_REACHED`       | 402  | No        | The account has hit a plan ceiling (products, members, or feature gate).                                                                          | Move up a plan. See `/api-reference/billing/create-checkout-session`.                                                                                   |
| `CREDIT_EXHAUSTED`         | 402  | No        | The monthly credit pool is empty for this billing cycle.                                                                                          | Move up a plan, or wait for the next reset. Check `/api-reference/user/get-usage-and-quota`.                                                            |
| `FEATURE_GATED`            | 403  | No        | The feature exists on a higher tier than the caller's current plan.                                                                               | Move up a plan to unlock.                                                                                                                               |
| `SCORING_FAILED`           | 500  | Yes       | The scoring engine could not produce a result. The site may be too large or scripted.                                                             | Retry with a simpler URL, or point the read at a different page.                                                                                        |
| `SCORING_TIMEOUT`          | 504  | Yes       | The scoring run exceeded the time budget.                                                                                                         | Retry. If it persists, point the read at a leaner page.                                                                                                 |
| `SCORING_RATE_LIMITED`     | 429  | Yes       | More than 5 score calls in the trailing 60-second window.                                                                                         | Back off 60 seconds, then retry. See [Rate Limits](/api-reference/rate-limits).                                                                         |
| `CRAWL_FAILED`             | 502  | Yes       | The public-web crawl could not reach the URL.                                                                                                     | Confirm the URL is public and reachable. Retry.                                                                                                         |
| `CRAWL_TIMEOUT`            | 504  | Yes       | The crawler hit the per-page time budget.                                                                                                         | Retry, or point at a faster page.                                                                                                                       |
| `EMPTY_CRAWL`              | 422  | Yes       | The crawler reached the URL but found no scoreable content.                                                                                       | Point at the main product page, not a marketing redirect.                                                                                               |
| `INTEGRATION_DISCONNECT`   | 401  | No        | The integration's OAuth token expired or was revoked.                                                                                             | Reconnect the integration in `Settings > Integrations`.                                                                                                 |
| `TEAM_INVITE_EXPIRED`      | 410  | No        | The invite token has aged out.                                                                                                                    | Ask an admin to resend the invite.                                                                                                                      |
| `SIGNAL_VALIDATION_FAILED` | 400  | Yes       | A signal-card submission missed required fields.                                                                                                  | Complete the assessment, then retry.                                                                                                                    |
| `SIGNAL_RATE_LIMITED`      | 429  | Yes       | A signal card was generated for the same target within the last hour.                                                                             | Wait an hour, then retry.                                                                                                                               |
| `READ_ONLY_MODE_ACTIVE`    | 403  | No        | The Free account hit the day-14 activation cliff with one or both onboarding milestones still incomplete. Write actions are blocked until rescue. | Finish the day-1 interview AND the week-1 org map (rescue path), or move up to Pro. See [Path C+ activation contract](/knowledge-base/getting-started). |
| `NETWORK_ERROR`            | 502  | Yes       | An upstream dependency was unreachable from the API.                                                                                              | Retry with backoff.                                                                                                                                     |
| `INTERNAL_ERROR`           | 500  | Yes       | Unhandled exception inside the API. The trace was sent to Sentry.                                                                                 | Retry once. If it persists, ping support with the request ID.                                                                                           |

## Where each code shows up

Most codes are not endpoint-specific. The list below covers the ones a developer can predict from the call site.

* **`AUTH_REQUIRED` / `FORBIDDEN`**, every authenticated route returns these. Check before calling anything other than `/api/health` or `/api/score/anonymous`.
* **`PLAN_LIMIT_REACHED` / `CREDIT_EXHAUSTED`**, `/api/score`, `/api/chat`, `/api/agents/run`, `/api/teams` (POST).
* **`FEATURE_GATED`**, Business-and-up routes: `/api/teams`, `/api/settings/byom`, `/api/intelligence/portfolio`.
* **`ONBOARDING_REQUIRED`**, `/api/quarter-close`, `/api/board-report`, `/api/briefing`.
* **`SCORING_*` / `CRAWL_*` / `EMPTY_CRAWL`**, `/api/score`, `/api/score/quick`, `/api/score/product`.
* **`INTEGRATION_DISCONNECT`**, `/api/integrations/sync`, any `/api/integrations/*` action route.
* **`SIGNAL_*`**, `/api/signal-card/[id]`, `/api/signal-card/[id]/link`.
* **`TEAM_INVITE_EXPIRED`**, `/api/teams/[id]/accept`.
* **`READ_ONLY_MODE_ACTIVE`**, every write route on Free accounts past day 14 with incomplete activation: `/api/score`, `/api/score/product`, `/api/chat`, `/api/products` (POST), `/api/teams` (POST). Cleared on rescue (both milestones complete) or on Pro upgrade.

## Handling errors in code

```ts theme={null}
import type { ErrorCode } from '@/lib/errors';

interface ApiError {
  code: ErrorCode;
  message: string;
  action?: string;
  retryable: boolean;
}

async function callDacard<T>(path: string, init?: RequestInit): Promise<T> {
  const res = await fetch(`https://app.dacard.ai${path}`, init);
  if (!res.ok) {
    const body = (await res.json().catch(() => ({}))) as { error?: ApiError };
    const err = body.error;

    if (err?.code === 'CREDIT_EXHAUSTED' || err?.code === 'PLAN_LIMIT_REACHED') {
      // Stop. Surface the plan upgrade flow.
      throw new Error(err.message);
    }

    if (err?.retryable && res.status >= 500) {
      // Retry with exponential backoff (handled by the caller).
      throw Object.assign(new Error(err.message), { retryable: true });
    }

    throw new Error(err?.message ?? `HTTP ${res.status}`);
  }
  return res.json() as Promise<T>;
}
```

## Source of truth

The full code list lives in `apps/web/src/lib/errors.ts`. New codes added to that file ship to this page on the next docs build.
