Limits and usage

What gets metered, what happens when you reach a limit, and where to read your current usage.

Limits exist in two layers, and telling them apart matters when something starts returning 429. Backpressure is the deployment saying "not right now" — wait and it works. Quota is the account saying "not this month" — waiting does not help. They carry different response bodies for exactly that reason.

What is metered

Three units, counted per workspace, per UTC calendar month. The month resets when the invoice period does, not on a rolling window.

UnitWhat it counts
eventsIngestedEvents accepted at ingest. A replayed batch is idempotent and is not counted twice for the same knowledge.
answersAnswers served. Both answer endpoints count, so using the cheaper route is never a way to go untracked.
modelTokensTokens across every model-using component — extraction and the reader — in one rollup, so two counters can never disagree about one bill.

Messages the admission gate drops still arrived, and are still counted as ingested. Extraction cost and usage are deliberately separate numbers.

Where to see current usage

Admins can read month-to-date usage against the effective limits:

LOUVAIN_URL=https://your-deployment.example.com/api
LOUVAIN_TOKEN=louvain_your_token_here

curl -H "Authorization: Bearer $LOUVAIN_TOKEN" "$LOUVAIN_URL/v1/org/usage"
{
  "monthToDate": { "eventsIngested": 18422, "answers": 311, "modelTokens": 4180233 },
  "limits": { "monthlyIngest": null, "monthlyAnswers": null, "monthlyTokens": null }
}

A null limit means no limit is set — the correct and explicit answer for a self-hosted install.

In the app:

  • Knowledge (admins) shows extraction health: asserted and hedged claims, quarantined messages, messages that yielded nothing, the extractor's recent call and latency history, and the concurrency governor's state.
  • Activity and Knowledge both show the queue: what is pending, what is in flight, what dead-lettered, and how far behind it is in plain terms ("caught up", "4m", "1.2h").

The full per-outcome breakdown — including messages stopped for budget — is on GET /v1/org/extraction (admins), which the Knowledge page reads a subset of.

Counters are buffered and flushed on a short timer, and limit checks are cached for about a minute. A small overshoot past a limit is expected behaviour rather than a bug — a hard cap enforced by an aggregate query on every single request would cost more than the overshoot ever does.

What happens at a limit

Ingest, past the deployment's backlog limit

Past the configured backlog the funnel refuses rather than degrading, because accepting faster than the pipeline drains turns a busy afternoon into a backlog that is still draining tomorrow.

HTTP/1.1 429 Too Many Requests
Retry-After: 6

{ "error": "backlog_full", "pending": 61240, "retryAfterSeconds": 6 }

The Retry-After value scales with how far behind the queue is, and is capped at 60 seconds so one refusal never parks a connector for an unreasonable stretch. Honour it. A source that backs off and returns gets its data in; one that treats a slow 200 as success keeps piling on.

The backlog limit is a deployment setting (LOUVAIN_MAX_QUEUE_DEPTH, 50,000 pending events by default; set it to 0 to disable the check). It is deployment-wide, not per workspace.

Ingest, past the workspace's monthly limit

HTTP/1.1 429 Too Many Requests

{ "error": "quota_exceeded", "reason": "monthly ingested events limit reached: <used> of <limit>" }

No Retry-After, and a distinct error code, because retrying changes nothing until the month rolls over or the limit is raised. A connector that treats this like backpressure will hammer a limit it can never pass.

Answers, past the workspace's monthly limit

The same quota_exceeded shape, on both answer endpoints.

Extraction, past the token budget

The token budget behaves differently from the other two, and the difference is deliberate. When a workspace has spent its monthly token budget:

  • Ingest keeps working. Events are still accepted and stored.
  • The model is not called. The message's outcome is recorded as budgeted, with the reason attached — never as "this message contained nothing".
  • The verbatim passage is kept. Hitting a spend limit degrades you to verbatim search over your own text; it does not delete knowledge.
  • No new typed claims or observations are extracted from those messages until the budget resets or is raised.

The budget is checked after the injection quarantine and before the model: refusing a hostile message costs nothing and must not consume your budget, and a budget enforced after the call is not a budget.

That budgeted count is reported alongside every other message outcome on GET /v1/org/extraction, because a workspace silently not being extracted is exactly the thing that must not be invisible.

Extraction, when the model provider is throttling or slow

A 429, a 5xx, a timeout, or a provider failure relayed inside a 200 body (how OpenRouter reports an upstream error) is the provider's condition, not the event's. It does not consume one of the event's retry attempts: the event returns to the queue with the attempt refunded and is re-dispatched on the reconciler's next sweep, while the extraction governor's backoff paces how fast retries reach the provider. Only failures that are about the event itself — output the schema rejects, a 400 — count toward LOUVAIN_QUEUE_RETRY_LIMIT and can dead-letter it.

An account that has run out of money or headroom is the provider's condition too, whichever status code it arrives as: a 402, or a 403 reading "key limit exceeded" or "spending limit". No property of a document causes an account to hit its cap, and the same message extracts perfectly once the cap is raised — so extraction pauses and the events keep their attempts. A 429 reading "rate limit exceeded" is not this: that is throughput, and it backs off and recovers as it always did.

Deferrals are free up to a grace of ten per event, because a document that times out on every attempt is saying something about the document. A failure below the request is different: a socket that could not connect, a reset, a name that would not resolve. No document can cause those, so they get a grace twenty times wider — at the governor's backoff, hours of a genuine outage cost nothing. Wider, not unlimited: an endpoint that will never resolve is not an outage that ends, and every event has to terminate in a recorded outcome rather than circulate between pending and failed for as long as the deployment stands. If events did dead-letter on a provider failure — a deployment older than this rule — scripts/requeue-dead.ts --org <slug> --apply puts them back on the queue with their counters reset, and records it in the org's audit log.

The same reasoning covers a worker that dies mid-job (a deploy, a restart): its lease expires, the event returns to the queue, and the first two expiries hand the attempt back rather than spending it. An event that outlives its lease a third time is a pattern about the event, and from then on expiries count.

Extraction, once per content

The same text arriving as several events — one email in every connected mailbox, an export replayed beside the live feed — is read by the model once per workspace. Later copies replay the stored reading through the ordinary write path, with their own container and attestation, so who can see the result is exactly what it would have been; only the model call is saved, and with it the disagreement between runs (the model does not read a sentence the same way twice). A replay is judged against the SHARED TEXT alone, by the whole of the rule the write path applies: every name, quote, figure, date and prose sentence has to be supported by the text both copies contain. The first reading was made with its container's own context — recent messages, current knowledge — and nothing that context supplied follows the text elsewhere, whichever field it rode in. So a public "Acme renewed at the same price" cannot replay a price only a private channel ever stated, and the second copy may learn less than a fresh reading would, never more. The event's own date is part of the key, because "a month ago" resolves against it. A vocabulary change, a model change or a re-read produces a fresh reading.

Extraction, when the model provider refuses payment

A 402 from the extraction endpoint — the provider account is out of credits — is not a rate limit, and retrying into it cannot succeed. The governor pauses extraction for five minutes and then probes with a single call; the pause repeats until the account is topped up. The queue is durable, so nothing is lost — events wait rather than dead-lettering one failed retry at a time. The pause is visible on GET /v1/org/extraction as governor.creditPausedMs.

Who sets the limits

Limits are configured by whoever runs the deployment: a default for the whole install, overridable per workspace. There are no plans or tiers built into the product — a limit is a number your operator sets, and all three default to no limit.

Raising or lowering a workspace's limits takes effect immediately rather than after the cache window, so a change made while someone is watching visibly works.

Quotas are account limits, never visibility limits

Nothing about a quota reaches the permission algebras. A workspace that is over budget stops having new knowledge extracted, and every member continues to see exactly what their principal's access already allowed. This is the same doctrine as roles: administration gates administration, never sight.

Sign-in and account rate limits

Separate from quota, and aimed at credential attacks rather than consumption. They return 429 with {"error": "too_many_attempts"} and a Retry-After.

ActionLimit
Sign in10 attempts per 15 minutes per email address, and 50 per 15 minutes per IP
Sign up10 per hour per IP
Password reset request5 per hour per email address
Invitation acceptance20 per hour per IP

Both sign-in dimensions exist on purpose: one account cannot be ground down from many addresses, and one address cannot spray many accounts. A multi-node deployment normally fronts these with its load balancer's own limiter as well.

When events stop arriving

If ingestion looks stalled and you are not at a limit, check the queue. Events that failed processing are dead-lettered rather than lost, listed with their attempt count and error, and an admin can requeue them in one action from the API. Every event terminates in a recorded outcome — an event with no explanation is a bug worth reporting, not normal.