Quotas and billing
What one unit is, how blocks are charged to the wallet, and how to read usage before a limit becomes a 429.
AACSearch bills in two layers: a monthly plan with included allowances, and a prepaid wallet that absorbs overage. Nothing is charged per request until the allowance is gone.
The four metered metrics
| Metric | Counts | Charged |
|---|---|---|
searches | Every billable query: /search, /answer retrieval, voice, image | Per block, over the allowance |
documents | Documents in the organization’s indexes, counted from the mirror (SearchDocument) | Per block, over the allowance |
ai_tokens | Tokens generated by answers, assistants, image descriptions | Per block, over the allowance |
reindex | Each rebuild run | Per run, over the allowance |
Not metered: /suggest (autocomplete must be free per keystroke), /status, /capabilities, analytics reads, and console traffic.
documents is a gauge, not a counter — it is read from SearchDocument, so deleting documents releases the allowance at the next billing check rather than at the end of the period.
Allowances
From the billing catalog (packages/billing/config.ts):
| Free | Pro ($29/mo) | Enterprise | |
|---|---|---|---|
| Searches | 10,000 | 500,000 | 5,000,000 |
| Documents | 1,000 | 50,000 | 1,000,000 |
| AI tokens | 10,000 | 100,000 | 2,000,000 |
| Reindex runs | 2 | 50 | 500 |
| Indexes | 5 | 10 | 100 |
| API keys | 3 | 10 | 100 |
| Connectors | 5 | 10 | 100 |
| Max results per page | 50 | 250 | 500 |
| Query-log retention | 7 days | 90 days | 365 days |
Overage pricing, per block:
| Metric | Block size | Pro | Enterprise |
|---|---|---|---|
| Searches | 1,000 | 50¢ | 25¢ |
| Documents | 1,000 | 20¢ | 10¢ |
| AI tokens | 1,000 | 2¢ | 1¢ |
| Reindex | 1 run | 5¢ | 2¢ |
Free has no overage prices: a limit is a wall, not a bill. Free includes aiAnswer (RAG) with a small hard-capped allowance of 10,000 AI tokens; it has no vector, hybrid, multi-search, curations, grouping, geo, reindex, export, custom-sort or query-analytics features.
How a charge is computed
- The period is the calendar month in UTC (
2026-10), resetting at00:00 UTCon the first. - Usage counters are incremented per call.
- While
used <= limit, the call costs nothing andx-aac-charged-centsis0. - Past the limit, each started block is debited: 1,001 searches after a 1,000-search block boundary means two blocks, not 1.001 of one.
- If the wallet cannot cover the block, the call fails with
402 PAYMENT_REQUIRED.
Because billing is per block, batching is cheaper than streaming: one import of 500 documents costs the same as one document.
Reading the state
curl -sS "$AAC_API/billing/usage" -H "Authorization: Bearer $AAC_KEY"{
"usage": {
"plan": "pro",
"planName": "Pro",
"period": "2026-10",
"resetsAt": "2026-11-01T00:00:00.000Z",
"searches": { "used": 512400, "limit": 500000 },
"documents": { "used": 51204, "limit": 50000 },
"aiTokens": { "used": 24800, "limit": 100000 },
"indexes": { "used": 3, "limit": 10 },
"apiKeys": { "used": 2, "limit": 10 },
"connectors": { "used": 1, "limit": 10 },
"wallet": { "balanceCents": 4200, "currency": "USD" }
},
"request_id": "req_2b4d6f8a0c2e4b6d8f0a2c4e"
}used may exceed limit — that is the overage, already charged or about to be. wallet is null when the tenant has never held credit.
curl -sS "$AAC_API/billing/wallet" -H "Authorization: Bearer $AAC_KEY"{
"wallet": { "balanceCents": 4200, "currency": "USD" },
"plan": "pro",
"period": "2026-10",
"resets_at": "2026-11-01T00:00:00.000Z",
"transactions": [
{
"id": "txn_01HZYA",
"type": "credit",
"amount_cents": 5000,
"balance_after_cents": 5000,
"description": "Top-up credits_50",
"metric": null,
"created_at": "2026-10-02T09:12:00.000Z"
},
{
"id": "txn_01HZYB",
"type": "debit",
"amount_cents": -800,
"balance_after_cents": 4200,
"description": "search overage: 16 blocks",
"metric": "searches",
"created_at": "2026-10-21T18:03:11.000Z"
}
]
}amount_cents is signed: credits positive, debits negative. The wallet endpoint also reconciles purchases before answering, so a top-up whose payment webhook was delayed still shows up the next time you poll.
Top-ups
Three prepaid packs: credits_10 ($10), credits_50 ($50), credits_200 ($200). They are bought through the payment provider (Stripe checkout in this deployment) and reconciled into the ledger — a missing webhook delays the credit, it does not lose it.
Credit packs do not expire and are not refundable into the plan: they only cover overage.
When a limit is hit
| Situation | Response |
|---|---|
| Search quota exhausted, wallet has balance | Call succeeds, block debited, x-aac-charged-cents non-zero |
| Search quota exhausted, wallet empty | 402 PAYMENT_REQUIRED, details.metric, top-up link |
| Documents above allowance on Free | 429 QUOTA_EXCEEDED on the next import |
Plan feature not included (e.g. vectorSearch on Free) | 402 PAYMENT_REQUIRED (details.feature, details.upgradeTo) |
| New key or index over the plan limit | Rejected at creation time in the console: Plan limit reached: N API keys |
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly searches quota exhausted (512400/500000 for 2026-10)",
"request_id": "req_3c5e7a9b1d3f5a7c9e1b3d5f",
"details": {
"metric": "searches",
"limit": 500000,
"used": 512400,
"period": "2026-10",
"resetsAt": "2026-11-01T00:00:00.000Z"
}
}
}Handling it well: on QUOTA_EXCEEDED, read details.metric and details.resetsAt, stop retrying until then, and fall back to a cached or keyword-only result set rather than showing an error page. The distinction between 429 RATE_LIMITED (seconds) and 429 QUOTA_EXCEEDED (until the period rolls over) is in error.code, not the status.
Approaching a limit
The scheduler checks usage every few minutes. When searches, documents or
ai_tokens crosses 80 % and again 100 % of the plan's included allowance,
every organization owner gets an e-mail and an in-app notification (unless they turned in-app updates off) — once per
metric, threshold and period, not on every request. If the first check already
finds a metric at 100 %, only the "used up" notice is sent. documents is a stock,
not a monthly flow: its notice is sent once per threshold and only re-armed when the
document count drops below that threshold again, so a full index does not notify
every month.
The thresholds measure the same numbers as GET /billing/usage and the console.
Invoices
After a month closes, the daily scheduler job writes one invoice per organization
for that month: the allowance used, overage blocks charged to the
wallet (priced with the plan and block price in force when each block was
charged, even if the plan changed since), blocks the wallet could not pay for as a
separate unpaid line, and other wallet movements. The subscription itself is invoiced by your
payment provider and only referenced. Invoices are listed in Settings → Billing
with a PDF download, and over the API at GET /billing/invoices (scope
billing:read). Details: Billing API.
When a payment fails
| State | What you notice |
|---|---|
past_due | A banner in the console and one e-mail to owners. Nothing is switched off |
suspended | After the grace period (7 days by default) the organization runs on Free: search keeps working within Free limits, paid features answer 402 FEATURE_NOT_AVAILABLE with reason: "past_due" |
back to active | As soon as the provider reports a successful payment — the plan returns immediately |
Keeping costs sane
- Watch
x-aac-quota-remainingon every response rather than polling/billing/usage. Alert at 10% remaining. - Turn off what you do not use. Semantic search on a plan without
hybridSearchfails loudly, but voice and image calls on a Free plan (10,000 AI tokens) will hit the cap quickly. - Prefer
suggestfor typeahead. It is free; using/searchper keystroke is not. - Batch imports. Blocks are charged per started block, not per document.
- Use
filter_byoverper_page. Fetching 250 rows to filter client-side still costs one search but wastes bandwidth and latency budget. - Retention is a quota too. Query-log retention of 7 days on Free means
/analytics/overview?days=30will be based on less history, not on nothing — the pre-aggregated rollup keeps the daily series.
Organization budgets
Open Budgets in the wallet. An organization owner can set monthly limits for searches, new documents, AI token units, full rebuilds and wallet spending in cents. Blank means unlimited; zero blocks new usage. All UTC calendar-month budgets apply together with plan limits.
The panel separates actual usage, pending reservations and availability, and shows when accounting began. Accounting starts when the budget is activated. Money means actual wallet debits: a billing block collected by an operation is attributed to the organization, including previously unpaid blocks. It is not a per-request quote. Unspent reservations are returned after settlement. Uncertain reservations after a process crash remain reserved rather than being silently treated as unused.