Skip to main content

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.

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

{
  "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

RetryableMeaning
trueThe condition is transient. Back off, then retry. Use exponential backoff for 5xx and rate limit errors.
falseThe 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.

Code catalog

CodeHTTPRetryableWhat it meansWhat to do
AUTH_REQUIRED401NoThe request has no valid Clerk session or API key.Sign in, mint an API key, or attach Authorization: Bearer <token> and retry.
FORBIDDEN403NoThe 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_REQUIRED403NoThe user has not completed the activation contract (Day 1 interview + Week 1 org map).Send the user through /onboarding/welcome.
PLAN_LIMIT_REACHED402NoThe account has hit a plan ceiling (products, members, or feature gate).Move up a plan. See /api-reference/billing/create-checkout-session.
CREDIT_EXHAUSTED402NoThe 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_GATED403NoThe feature exists on a higher tier than the caller’s current plan.Move up a plan to unlock.
SCORING_FAILED500YesThe 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_TIMEOUT504YesThe scoring run exceeded the time budget.Retry. If it persists, point the read at a leaner page.
SCORING_RATE_LIMITED429YesMore than 5 score calls in the trailing 60-second window.Back off 60 seconds, then retry. See Rate Limits.
CRAWL_FAILED502YesThe public-web crawl could not reach the URL.Confirm the URL is public and reachable. Retry.
CRAWL_TIMEOUT504YesThe crawler hit the per-page time budget.Retry, or point at a faster page.
EMPTY_CRAWL422YesThe crawler reached the URL but found no scoreable content.Point at the main product page, not a marketing redirect.
INTEGRATION_DISCONNECT401NoThe integration’s OAuth token expired or was revoked.Reconnect the integration in Settings > Integrations.
TEAM_INVITE_EXPIRED410NoThe invite token has aged out.Ask an admin to resend the invite.
SIGNAL_VALIDATION_FAILED400YesA signal-card submission missed required fields.Complete the assessment, then retry.
SIGNAL_RATE_LIMITED429YesA signal card was generated for the same target within the last hour.Wait an hour, then retry.
READ_ONLY_MODE_ACTIVE403NoThe 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.
NETWORK_ERROR502YesAn upstream dependency was unreachable from the API.Retry with backoff.
INTERNAL_ERROR500YesUnhandled 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

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.