Enterprise usage limits
Radioso Enterprise tracks customer-facing usage dimensions and lets operators cap them per account.
The customer-facing meters
- Indexed storage bytes. The current size of content Radioso keeps searchable for an account. This is the primary storage meter. It increases when new content is ingested and decreases when content is removed.
- Monthly indexed content bytes. New or changed content embedded during the current calendar month. Resets at the start of each month. Recrawls of pages whose normalized content has not changed do not increment this meter.
- Monthly answers. Generated assistant or retrieval answers in the current calendar month. Resets at the start of each month. A profile that meters conversations instead (see Conversation metering) does not enforce this meter.
- Stored document count. Kept as a secondary guardrail against accounts that create extremely large numbers of tiny documents. It is not the primary storage meter and should not be used for plan sizing.
How indexed storage is measured
- For inline documents, indexed storage is the UTF-8 byte length of the sanitized indexed content.
- For uploaded files, indexed storage is the byte length of the stored source file.
- For website crawls, indexed storage is the byte length of the normalized extracted content that Radioso stores and retrieves from.
The number reflects what Radioso currently keeps searchable. It does not count earlier revisions of recrawled pages, transient HTML fetched during crawling, or content that has been deleted.
Website recrawls
Recrawls update the existing indexed page rather than appending duplicate storage:
- A recrawl of the same workspace, source, and external document ID reserves only the positive byte delta against the storage cap. Pages that shrink reserve zero bytes and are accepted even if the account is currently at its cap.
- A recrawl whose normalized content hash matches the stored content is skipped entirely. No re-embedding happens and no monthly indexed-content usage is recorded for the unchanged page.
- After a fresh, fully successful crawl, pages that were previously indexed for the source but were not found in the new run are removed from the active corpus.
Partial recrawls (resumed from a checkpoint or that ended with failures) do not remove missing pages. Removal only happens when a fresh crawl completes cleanly.
Configuring a profile
Profiles are managed through the admin API. Each profile can set:
monthlyAnswerLimit(integer, nullable)storedDocumentLimit(integer, nullable)storedIndexedByteLimit(integer bytes, nullable)monthlyIndexedByteLimit(integer bytes, nullable)monthlyConversationLimit(integer, nullable) — set this to meter the account in conversations instead of answersrepliesPerConversation(integer, 1–1000, defaults to 10) — how many replies in one customer conversation count as a single unit againstmonthlyConversationLimit
A null value means unlimited for that meter. displayName, monthlyAnswerLimit, and storedDocumentLimit are required on every PUT. The other four fields are each independent: omit one and it keeps its stored value; send it explicitly (including null) and it takes the new value.
curl -X PUT https://radioso.example.com/api/v1/ee/usage-limits/profiles/growth \
-H "Authorization: Bearer $EE_USAGE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Growth",
"monthlyAnswerLimit": 2000,
"storedDocumentLimit": null,
"storedIndexedByteLimit": 5368709120,
"monthlyIndexedByteLimit": 1073741824
}'Byte limits are persisted and accepted in bytes. The dashboard renders them in KB, MB, or GB for readability, but the API contract is always integer bytes.
Seeded profiles
Radioso seeds three profiles from the @radioso/plan-catalog package — comet, satellite, and planet — each carrying that plan’s conversation, document, and storage limits. A new account is assigned the catalog’s default plan, comet, on creation.
The seed only inserts a profile that is missing; it never overwrites one that already exists. Edit comet, satellite, or planet through the admin API above, and the edit stays — a later deploy that re-runs the seed leaves it alone.
comet and satellite are managed plans: a workspace on one of them runs the catalog’s managed model set (gpt-5.4-mini on OpenAI for chat, query rewrite, and rerank) whenever it holds no provider credential of its own for the provider it would otherwise use. The lock is served from the deployment’s OPENAI_API_KEY, so set it on every Enterprise deployment that assigns managed plans; a managed workspace with neither its own OpenAI key nor a deployment key gets a provider_misconfigured error on its first model call. The Settings API reports the lock per capability under managed.
GET /api/v1/plans serves the same catalog, public and unauthenticated, so a pricing page or signup flow can read current plan numbers without a session or admin token:
curl https://radioso.example.com/api/v1/plansReading account usage
The account usage endpoint returns current usage and limits for each meter:
{
"accountId": "…",
"profile": { … },
"storedIndexedBytes": { "used": 134217728, "limit": 5368709120 },
"monthlyIndexedBytes": { "used": 8388608, "limit": 1073741824, "periodStart": "2026-05-01", "resetAt": "2026-06-01T00:00:00.000Z" },
"monthlyAnswers": { "used": 42, "limit": 2000, "periodStart": "2026-05-01", "resetAt": "2026-06-01T00:00:00.000Z" },
"storedDocuments": { "used": 17, "limit": null }
}When a meter has no configured limit, limit is null. The used value is always reported. On an account whose profile meters conversations, the response also carries a monthlyConversations object — see Conversation metering.
Conversation metering
A profile with monthlyConversationLimit set meters and caps customer conversations directly, instead of individual answers. monthlyAnswerLimit on that profile stops being enforced; the account’s usage response reports it with limit: null.
Every kind of work that reserves against the account counts toward the conversation meter at a fixed weight:
| What happened | Counts as |
|---|---|
A conversation reaching a block of repliesPerConversation replies | 1 |
| A standalone answer over the API or MCP | 1 |
| A Ray turn | 1 |
| A test run (dashboard test chat, Workbench replay, eval run, or test execution) | 0.5 |
| An Audience Pulse report | 10 |
| The widget greeting on open | 0 |
The account usage response breaks this down under monthlyConversations:
{
"accountId": "…",
"profile": { "monthlyConversationLimit": 500, "repliesPerConversation": 10, … },
"monthlyAnswers": { "used": 84, "limit": null, "periodStart": "2026-05-01", "resetAt": "2026-06-01T00:00:00.000Z" },
"monthlyConversations": {
"used": 42.5,
"limit": 500,
"credits": 12,
"periodStart": "2026-05-01",
"resetAt": "2026-06-01T00:00:00.000Z",
"byKind": { "conversation": 40, "copilot": 2, "test_run": 0.5, "pulse_report": 0 }
}
}used and the byKind breakdown can land on a half, since a test run counts as 0.5. credits is the account’s prepaid balance on top of monthlyConversationLimit — see Prepaid credits. monthlyConversations is absent (null) on an account whose profile meters answers instead.
Prepaid credits
Prepaid credits add conversations to an account’s balance on top of its plan limit. Credits never expire and only apply to a conversation-metered profile.
curl -X POST https://radioso.example.com/api/v1/ee/usage-limits/accounts/00000000-0000-0000-0000-000000000001/credits \
-H "Authorization: Bearer $EE_USAGE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"conversations": 100, "reference": "evt_1Pxxxxxxxxxxxxxxxx"}'reference identifies the grant — a Stripe event ID or any string unique to the top-up — and makes the call safe to retry. The first call with a given reference credits the account and returns "applied": true. A later call with the same reference grants nothing and returns "applied": false with the same credits balance as the first call. The response also carries the account’s current usage:
{
"credits": 112,
"applied": true,
"usage": { … }
}An accountId that does not exist returns 404.
Plan card
On a conversation-metered account, the dashboard’s Usage page shows a plan card above the meters. It reads GET /api/v1/ee/billing/me and GET /api/v1/plans alongside the account usage response above, and renders the plan name and price, a bar for used / (limit + credits), and the byKind breakdown split into customer conversations, Ray, test runs, and Pulse.
At 80% of capacity the card adds a banner naming whichever kind is driving that month’s usage. At 100% the banner also carries the Upgrade and Buy buttons described below.
- Upgrade starts a subscription checkout for the next self-serve plan above the account’s current one. It is absent once the account already holds the catalog’s
selfServeCeilingPlanId. - Buy 300 more starts a one-time checkout for the catalog’s top-up pack.
- Manage billing opens the Stripe customer portal. It appears once the account has a Stripe customer, which the first checkout creates.
A deployment without Stripe credentials configured skips the card; see Billing setup for the Stripe environment variables and webhook registration.
Enforcement
Limits are enforced before expensive work begins, so customers cannot race past a cap with concurrent uploads or chat traffic.
- Inline document creation and file imports reserve indexed storage bytes before the document is persisted.
- Assistant and retrieval answers reserve a monthly answer slot before the model is called, or a conversation-meter reservation on a conversation-metered account.
- The reservation is released if the work fails before commit.
Requests that exceed a cap receive a 429 response with code: "usage_limit_exceeded" and a details.resource of stored_indexed_bytes, monthly_indexed_bytes, stored_documents, monthly_answers, or monthly_conversations.
Organization creation cap
Enterprise deployments also include a per-user cap on creating additional organizations. This cap is an anti-abuse control, not a usage-limit profile dimension.
The cap applies only to the signed-in additional-organization path, POST /api/v1/account/accounts. Enterprise open signup remains available when other organizations exist, and POST /api/v1/auth/register does not consume this counter. The counter is per user, per UTC calendar month. Deleting organizations does not refund it.
This is an Enterprise capability boundary as well as an anti-abuse limit. Open-source deployments allow the first signup to create one organization for the server, then require later users to join it by invitation. Open-source users cannot call the additional-organization path. Workspace creation remains available in both editions and does not consume the organization counter.
Set the global default with:
EE_MAX_ORGS_PER_USER_PER_MONTH=10When the variable is unset, Radioso uses 10. A request over the cap receives 429 with code: "rate_limit_exceeded" and details.limit, details.used, details.periodStart, and details.resetAt.
Operators can set a per-user override through the existing usage-limit admin API. The same EE_USAGE_ADMIN_TOKEN bearer token is required.
curl -X PUT https://radioso.example.com/api/v1/ee/usage-limits/org-creation/users/00000000-0000-0000-0000-000000000001 \
-H "Authorization: Bearer $EE_USAGE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthlyLimit": 25}'Use null for an unlimited override:
curl -X PUT https://radioso.example.com/api/v1/ee/usage-limits/org-creation/users/00000000-0000-0000-0000-000000000001 \
-H "Authorization: Bearer $EE_USAGE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthlyLimit": null}'Read or remove an override with:
GET /api/v1/ee/usage-limits/org-creation/users/{userId}
DELETE /api/v1/ee/usage-limits/org-creation/users/{userId}Internal usage ledger
In addition to the customer-facing meters, the core platform records an immutable usage event for every embedding call so cost and reliability can be analysed without scanning request logs. This ledger is part of the open-source platform, so it is available without Enterprise Edition; Enterprise builds governance and reporting on top of it rather than maintaining a separate ledger. Each event captures the account, workspace, document revision, provider, model, input bytes, vector count, and either provider-reported or estimated tokens. Events are written with a deterministic idempotency key per (workspace, document, revision, provider, model), so retries do not double-count, and they are summarised into a daily rollup (usage_daily_rollups) for fast internal reporting. Answer-side model usage uses the same ledger structure.
A deployment that already has ee_usage_events, ee_embedding_usage_items, or ee_usage_daily_rollups tables gets them renamed in place to usage_events, embedding_usage_items, and usage_daily_rollups, so historical events carry over under the new names. A fresh install creates the same three OSS tables directly.
Read next
- Deployment — Enterprise Edition rollout, required secrets, and Terraform configuration.
- Self-hosting operations — backup, restore, and upgrade steps for a self-hosted Enterprise deployment.