> ## 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.

# Rate limits and quotas

> Per-action rate limits, plan quotas, credit accounting, and the 429 response shape.

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. Backed by `credit_usage`. 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. Free and Pro do not include programmatic API access.

## Per-action rate limits

| Action | Limit | Window | Endpoint |
| - | - | - | - |
| `score` | 5 requests | 60 seconds | `POST /api/score`, `POST /api/score/quick`, `POST /api/score/product` |
| `chat` | 30 requests | 60 seconds | `POST /api/chat` |

Hits are tracked per `userId` against a 60-second sliding window. The limit returns `429 Too Many Requests` with a `Retry-After` header that matches the window.

If the rate-limit infrastructure cannot reach the database, the gate fails closed. The request is denied with `Retry-After: 60` rather than allowed through. This is intentional: silent rate-limit bypass is a worse failure mode than a brief outage.

There is no global per-action rate limit on `api` (API key calls). API key calls are tracked but enforced through the per-tier monthly ceiling below.

## Plan quotas

| Plan | Monthly credits | Scores (10 credits each) | Chat messages (1 credit each) | API calls/mo | Products | Seats |
| - | - | - | - | - | - | - |
| Free | 80 | 3 | 30 | 0 | 1 | 1 |
| Pro | 1,000 | up to 100 | up to 1,000 | 0 | 5 | 3 |
| Business | 2,000 | up to 200 | up to 2,000 | 25,000 | 25 | 10 |
| Enterprise | Unlimited | Unlimited | Unlimited | 100,000 | Unlimited | Custom |

Free and Pro do not include programmatic API access. Mint API keys on Business or Enterprise from `Settings > API Keys`. The credit pool resets at the start of each billing cycle. Score packs (one-time top-ups) carry over.

Quota numerics live in `packages/shared/src/plans.ts`. If the table above drifts from the source file, the source file wins. Open an issue.

Check live consumption with [Get Usage and Quota](https://docs.dacard.ai/api-reference/user/get-usage-and-quota.md):

```bash theme={null}
curl -X GET https://app.dacard.ai/api/usage \
  -H "Authorization: Bearer $DACARD_API_KEY"
```

## Anonymous scoring

`POST /api/score` (without a session) and `POST /api/score/quick` accept anonymous reads but cap them tightly.

* 1 anonymous read per IP per hour.
* Result is held until a sign-up links it via `POST /api/score/link`.
* No history, no portfolio, no DAC chat.

Authenticated callers should always carry a session or API key. Anonymous fallthrough is a sign-up funnel, not a production flow.

## 429 response shape

Per-action and per-IP rate limits both return the same shape:

```http theme={null}
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 Payment Required` with `code: "CREDIT_EXHAUSTED"` or `code: "PLAN_LIMIT_REACHED"`. Those are not retryable. See [Errors](https://docs.dacard.ai/api-reference/errors.md) for the full catalog.

## Exponential backoff

The reference pattern below handles per-action 429s. Plan-quota errors should not be retried.

```ts theme={null}
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');
}

const score = await callWithBackoff<ScoreResult>(() =>
  fetch('https://app.dacard.ai/api/score', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.DACARD_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url: 'https://example.com' }),
  }),
);
```

Cap retries. A user staring at a spinner is a worse experience than a clear error.

## Score packs

Add scoring capacity to any paid plan without upgrading the tier.

| Pack | Scores | Price |
| - | - | - |
| Small | 20 | \$29 |
| Medium | 50 | \$49 |
| Large | 150 | \$99 |

Score packs do not expire. Plan credits reset each cycle regardless of pack balance.

## Upgrading

Upgrade from `Settings > Billing` in the dashboard, or programmatically via [Create Checkout Session](https://docs.dacard.ai/api-reference/billing/create-checkout-session.md). Enterprise customers can negotiate custom limits and SLAs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.