AACsearch
Billing

Quotas

How AACsearch enforces plan limits — soft caps, hard caps, overage, the quota reset schedule, and what happens when you go over.

A quota is a plan limit measured over a period. AACsearch ships four quotas (search units, indexed documents, connector syncs, AI wallet) and three thresholds per quota (soft cap at 80%, hard cap at 100%, overage above 100%). This page describes how the gates fire and what you see when they do.

The quota gate

Every search and ingest request goes through a stack of gates in this order:

Auth gate         (valid key + org)
  → Feature gate  (plan allows this operation)
  → Quota gate    (org has remaining units in the period)
  → Rate gate     (per-key rate limit not exceeded)
  → Search engine

The quota gate calls checkQuota(orgId, "search") in packages/api/modules/entitlements/middleware/quota-check.ts. It returns one of:

  • allowed: true — proceed.
  • allowed: false, reason: "search_quota_exceeded" — 429 response.
  • allowed: true, isSoftCap: true — proceed but the response carries an advisory header.

Soft caps (80%)

At 80% of any quota the dashboard surfaces a warning and the API attaches an advisory header to every response:

X-Aacsearch-Quota-Warning: search 82% used; resets 2025-11-01T00:00:00Z

The header is purely informational — requests still succeed. Use it to drive your own alerting (Datadog monitor, Slack webhook, etc.) instead of relying on dashboard email alerts.

Dashboard surfaces:

  • Orange banner on /[orgSlug]/overview and /[orgSlug]/settings/billing.
  • Quota tile shows 82% used in warning color.
  • A one-time email to billing contacts when each quota first crosses 80% in a period.

Hard caps (100%)

At 100% the gate flips and requests are rejected:

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

{
  "error": "search_quota_exceeded",
  "detail": "Monthly search quota reached. Upgrade or wait for the period reset.",
  "quota": "search",
  "limit": 1000000,
  "used": 1000000,
  "resetsAt": "2025-11-01T00:00:00Z"
}

What stops at the hard cap depends on the quota:

QuotaAt hard cap, what stops?What still works
Search unitsNew POST /search, multi-search, suggestDashboard reads, admin operations
Indexed documentsNew ingest (PUT documents, batch, connector full/delta sync)All reads
Connector syncsNew sync runs (heartbeats still work; pending syncs queued)Reads, ingest via REST
AI wallet (balance 0)Knowledge, embeddings, rerank, summarize, chatSearch, ingest, everything non-AI

Failures emit SearchUsageEvent with type: "quota_block" so they show up in Analytics → Failed.

Overage (above 100%, paid plans only)

Paid plans can opt in to overage — a metered, post-paid amount billed at end of period for usage beyond the hard cap. When overage is enabled (/settings/billingAllow overage):

  • Requests above the hard cap continue to succeed instead of returning 429.
  • Each overage unit is recorded in OverageTransaction with a per-unit price (currently $0.0001 per search unit for Pro, lower for Business — exact rates in packages/payments/lib/entitlements.ts).
  • The dashboard shows Overage so far this period: $4.32 on /settings/billing.
  • The next invoice includes one line per quota with overage.

Overage is off by default on Pro and on by default on Business. Free and Starter cannot enable overage — they always hard-cap.

To prevent runaway bills, set a spending limit on the org (/settings/billingSpending cap). When the limit is hit, overage stops and 429s resume.

Quota reset schedule

Quotas reset on the first day of your billing period. Most customers are on monthly billing; the reset is at 00:00:00 UTC on the 1st of the month. Annual customers reset on the anniversary of their first payment.

You can see the next reset on /[orgSlug]/settings/billing and in the X-Aacsearch-Quota-Reset header on any response near the cap.

Reset behavior:

  • The counter goes back to 0.
  • Overage charges from the previous period are billed on the renewal invoice — they do not affect the new period's limits.
  • Spending caps reset to the configured value.

There is no rollover. Unused quota does not carry forward to the next period.

Soft vs hard caps — when each makes sense

Default behavior:

  • Searches — hard cap at 100%. Going past quota means runaway widget traffic; the protection is worth the user-visible 429.
  • Indexed documents — hard cap at 100%. Writing more than your steady-state cap usually means a bug in the connector; blocking ingest is the right default.
  • Connector syncs — soft cap at 100% for the first month after the cap is breached, then hard cap. Gives operators a window to either raise the plan or stretch the sync interval.
  • Wallet — always hard cap (zero balance = no AI). There is no soft mode for the wallet because micro-USD draws can be arbitrarily fast.

The defaults live in packages/payments/lib/entitlements.ts. Enterprise customers can override per-quota via their contract.

Grace periods after cancellation

When you cancel a paid plan, you keep full access until the end of the paid period. After that:

  • Reads — granted for 7 more days at the previous plan's limits. This window is the grace period for reads.
  • Writes — stop immediately at the end of the paid period. No grace.

The grace period exists to let you re-subscribe without data loss. After 30 days of no payment, dormant indexes are flagged for archival and the wallet balance is preserved indefinitely.

For details: Upgrade & downgrade.

Diagnostics

When a quota fires, check:

  1. /[orgSlug]/overview — quota tile and percent used.
  2. /[orgSlug]/analytics — per-day chart shows when the spike happened.
  3. /[orgSlug]/analytics → Failed tabsearch_quota_exceeded failures.
  4. X-Aacsearch-Quota-* response headers — programmatic check.
  5. /admin/wallet (admin only) — wallet-specific draw history.

If a 429 surprises you, the audit log will not help — quota blocks are not audit events (they happen on every blocked request and would flood the log). Use the analytics view instead.

API response headers

Every successful request includes:

HeaderMeaning
X-Aacsearch-Quota-UsedUnits consumed in the current period
X-Aacsearch-Quota-LimitHard-cap value for this org
X-Aacsearch-Quota-ResetISO-8601 timestamp of the next reset
X-Aacsearch-Quota-WarningSet only above 80%; format: <quota> N% used; resets …

Use these in production code instead of polling the dashboard.

On this page