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 Dacard.ai API runs three kinds of throttling. They stack. A request must clear every layer to succeed.
  1. Per-action rate limits, short-window throttles on burst behavior. Source: packages/core/src/rate-limit.ts.
  2. Plan quotas, monthly credit pools and feature gates. Source: packages/shared/src/plans.ts.
  3. Per-tier API call ceilings, Business and Enterprise only.

Per-action rate limits

ActionLimitWindowEndpoint
score5 requests60 secondsPOST /api/score, POST /api/score/quick, POST /api/score/product
chat30 requests60 secondsPOST /api/chat
The limit returns 429 Too Many Requests with a Retry-After header. Hits are tracked per userId against a 60-second sliding window. The gate fails closed if the database is unreachable. There is no per-action rate limit on api (API key calls). They are tracked but enforced through the per-tier monthly ceiling.

Plan quotas

PlanMonthly creditsScoresChat messagesAPI calls/moProductsSeats
Free80330011
Pro1,000up to 100up to 1,000053
Business2,000up to 200up to 2,00025,0002510
EnterpriseUnlimitedUnlimitedUnlimited100,000UnlimitedCustom
Free and Pro do not include programmatic API access. Mint API keys on Business or Enterprise from Settings > API Keys. Numerics live in packages/shared/src/plans.ts. The source file is authoritative. Check live consumption with Get Usage and Quota.

429 response shape

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60

{
  "error": {
    "code": "SCORING_RATE_LIMITED",
    "message": "Too many reads in a row.",
    "action": "Give it 60 seconds and try again.",
    "retryable": true
  }
}
Plan-quota exhaustion returns 402 with code: "CREDIT_EXHAUSTED" or code: "PLAN_LIMIT_REACHED". Those are not retryable. See Errors.

Exponential backoff

async function callWithBackoff<T>(fn: () => Promise<Response>): Promise<T> {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fn();
    if (res.ok) return res.json() as Promise<T>;

    if (res.status !== 429) {
      const body = await res.json().catch(() => ({}));
      throw new Error(body.error?.message ?? `HTTP ${res.status}`);
    }

    const retryAfter = Number(res.headers.get('Retry-After') ?? 60);
    const jitter = Math.random() * 0.25 * retryAfter;
    const delay = (retryAfter + jitter) * 1000 * Math.pow(1.5, attempt);
    await new Promise((r) => setTimeout(r, delay));
  }
  throw new Error('Exceeded max retries on 429');
}
Cap retries. A user staring at a spinner is a worse experience than a clear error.

Anonymous scoring

POST /api/score (no session) and POST /api/score/quick accept anonymous reads at 1 per IP per hour. The result is held until a sign-up links it via POST /api/score/link.