dabadataDocs
Concepts / Plans and limits

Concepts

Plans and limits

Every hosted dabadata project belongs to a workspace, and the workspace's plan sets its limits. This page lists them, says exactly what a call gets back when it reaches one, and shows how to read your usage. Prices are on the pricing page.

The plans

LimitFreeStarterProScaleEnterprise
Concurrent connections, workspace-wide502005003,0003,000
Database storage500 MB5 GB20 GB100 GB100 GB
Egress per month2 GB25 GB100 GB500 GB500 GB
File storage, workspace-wide2 GB20 GB100 GB500 GB500 GB
Largest file50 MB500 MB5 GB5 GB5 GB
Trace retention3 days14 days90 days365 days365 days
API log retention1 day7 days30 days90 days90 days
Requests per second, per key20502001,0001,000
Projects25unlimitedunlimitedunlimited
Team members125unlimitedunlimited
Sites, workspace-wide1310unlimitedunlimited
Custom domains for sites, workspace-wide013510
Size of one site deploy100 MB1 GB1 GB2 GB2 GB
Files in one site deploy1,00010,00020,00020,00020,000
Webhook deliveries per month, workspace-wide10,000100,0001,000,00010,000,00010,000,000
Webhooks per environment550505050
Scheduled actions per environment21050100100
Shortest interval of a schedule1 hour1 minute1 minute1 minute1 minute
Idle pauseafter 7 daysnevernevernevernever
  • Bytes are binary: 1 MB is 220 bytes and 1 GB is 230 bytes.
  • Enterprise values are starting points; any of them can be set per workspace.
  • Storage and connections are shared by every project of the workspace and by both environments of each, live and test together.
  • Team members are the workspace's seats: members and pending invitations together. API log retention is how long the request and realtime logs are kept.

Your plan's limits are in the answer of GET /v1/project and GET /v1/project/usage, under limits; null there means unlimited.

Changing the plan

The workspace's owner changes the plan in the console, on Usage & plan. During the beta, Free and Starter are self-service and take effect at once; Pro and Scale are switched on by us on request (the API answers 403 plan_requires_approval), and nothing is billed. Moving to a smaller plan is refused with 409 over_limit while the workspace has more projects, more members and pending invitations, or more sites or custom domains than the smaller plan allows. Enterprise is arranged with us. See Plans in the beta.

Once billing is switched on, paid plans start with a Stripe checkout and change in the billing portal, and the owner may raise the connection, egress, storage and file storage caps above the allowance so that usage past it is billed instead of refused. Until the owner raises a cap, every limit on this page stays a hard cap. See Billing.

Where limits apply

Plan limits apply to hosted projects only. A self-hosted server's own project (the one set up by DABADATA_PROJECT_KEY) has no plan and no limits; the only throttle it has is on the demo's token route, and its request and realtime logs are kept for DABADATA_LOG_DAYS days (default 1). Every count is kept per server process.

What happens at a limit

A call that is over a limit gets a clear error with a machine-readable code; calls that are not over a limit keep working.

LimitWhen it bitesAnswer
ConnectionsA new socket while the workspace already has its allowance open403 plan_limit, limit: "connections"
Request rateMore calls per key than the plan's rate allows429 rate_limited with Retry-After
Token routesMore than 60 token calls a minute per environment, or 30 refreshes and exchanges a minute per IP429 rate_limited with Retry-After
Storage, egressWrites, after 7 days over the allowance403 plan_limit, limit: "storage" or "egress"
ProjectsCreating or restoring a project beyond the allowance403 plan_limit, limit: "projects"
Team membersInviting someone when members and pending invitations fill every seat403 plan_limit, limit: "members"
Sites, custom domainsCreating a site or connecting a domain beyond the allowance403 plan_limit, limit: "sites" or "customDomains"
Site deploy size, filesStarting a deploy that is too big or has too many files403 plan_limit, limit: "siteBytes", "siteFiles" or "maxFileBytes"
Trace retentionEvery nightOlder trace rows are deleted
API log retentionEvery nightOlder request and realtime log rows are deleted
Idle pauseFree, after 7 days without callsSockets closed; the next call wakes the project

Connections

A connection is one open WebSocket. The allowance counts the workspace's open sockets in every project and environment together. A socket that would go over it is refused at the upgrade:

json
{
  "error": "plan_limit",
  "limit": "connections",
  "message": "plan limit: 50 concurrent connections (free) - close other connections or change the plan"
}

A browser does not show the body: the socket simply closes before it opens, and the JavaScript client keeps retrying with its usual back-off, so it connects as soon as another socket closes. The refusal is written to the trace of the environment as op: "connect", decision: "denied", with the explanation plan limit: 50 concurrent connections (free), so you can see it in the viewer. One client holds one socket however many tables it subscribes to. The console's own sockets (its live views of your project) never count toward the allowance.

Request rate

Each key gets a token bucket that refills at the plan's requests per second and holds twice that, so short bursts of up to 2x pass. The bucket belongs to the secret key of the call, else its publishable key, else (a call with a token and no key) the environment. Socket upgrades count as calls. Over it:

text
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Content-Type: application/json; charset=utf-8

{ "error": "rate_limited", "retryAfter": 1 }

retryAfter and Retry-After are whole seconds, at least 1, until the bucket has room again.

Token routes

The routes that hand out tokens have buckets of their own, on top of the request rate:

  • POST /v1/users/token, /v1/users/exchange and /v1/users/refresh share one bucket per environment: 60 a minute, one back every second.
  • /v1/users/refresh and /v1/users/exchange share one bucket per client IP: 30 a minute, one back every 2 seconds.

Both answer the same 429 rate_limited. A backend that mints a token for every page view will hit the first; mint once per sign-in and let the client refresh.

Storage and egress

Storage (the size of your tables, indexes included, summed over the workspace) and egress (bytes sent this calendar month) are measured once a night. When the workspace is over its allowance, the day it went over is recorded and you have 7 days to get back under it or change the plan. After that, writes are refused:

json
{
  "error": "plan_limit",
  "limit": "storage",
  "message": "plan limit: storage over the free allowance (500 MB) since 2026-10-01 - writes are refused until usage is back under it or the plan changes; reads keep working"
}
  • Writes are POST /v1/mutate and POST /v1/events. Everything else keeps working: reads, live subscriptions, tokens, definitions and the export, so you can always get your data out or delete rows to make room.
  • The state is checked again every night: once the workspace is back under, the next nightly run clears it and writes work again. Egress counts the calendar month, so an egress overage clears with the first run of the next month.

Projects

Creating a project beyond the plan's allowance is refused with 403 { "error": "plan_limit", "limit": "projects", "message": "plan limit: 2 projects (free) - delete a project or change the plan" }. A project being deleted no longer counts; restoring it in the console counts it again, and is refused the same way when the allowance is full.

Team members

Every member of the workspace and every pending invitation takes a seat. An invitation that would need one more than the plan has is refused:

json
{
  "error": "plan_limit",
  "limit": "members",
  "message": "plan limit: 1 members (free), pending invitations included - remove a member, revoke an invitation or change the plan"
}

Free has one seat, the owner's, so inviting starts at Starter. Moving down to a plan with fewer seats than are taken is refused (409 over_limit). See Invitations and seats.

Workspaces per account

Not a plan limit but a cap per person: an account may own at most 3 workspaces that are not deleted, the one made at sign-up included. A fourth is refused with 403 { "error": "plan_limit", "limit": "workspaces", "message" }. Workspaces you are a member of but do not own do not count. See The workspace cap.

Order of the checks

For a call placed in a hosted environment the checks run in this order, and a refused call spends nothing in the checks after the one that refused it: the project's status (410, or waking a paused project), the per-IP bucket, the token-route bucket, the request rate, the storage and egress cap, and then the route itself with its own credentials and rules.

Handling a 429

Wait for Retry-After and try once more; do not retry in a tight loop, which only keeps the bucket empty:

js
async function call(url, init, tries = 3) {
  const res = await fetch(url, init);
  if (res.status !== 429 || tries <= 1) return res;
  const wait = Number(res.headers.get("Retry-After")) || 1;
  await new Promise((r) => setTimeout(r, wait * 1000));
  return call(url, init, tries - 1);
}

Usage metering

The server counts, per environment and per day (UTC):

CounterWhat it counts
requestsEvery answered call placed in the environment - refusals such as 401, 403, 410 and 429 included, and the console's calls - and every socket opened. Calls that never reach a project (health, meta, unknown routes, a 401 before the project is known) are not counted.
egressBytesResponse bodies, plus every message sent on the environment's sockets. Headers are not counted.
peakConnectionsThe most sockets of the environment open at the same moment that day.
dbBytesThe size of the environment's tables with their indexes, measured by the nightly job.

Counts are kept in memory and written to the database every 60 seconds. Read them with the environment's secret key; days is 1 to 400 (default 30):

bash
curl "https://dabadata.com/v1/project/usage?days=2" -H "Authorization: Bearer $DABADATA_SECRET_KEY"
json
{
  "days": [
    { "day": "2026-10-14", "requests": 18234, "egressBytes": 51234567, "peakConnections": 23, "dbBytes": 7340032 },
    { "day": "2026-10-15", "requests": 9120, "egressBytes": 20480133, "peakConnections": 17, "dbBytes": 7864320 }
  ],
  "month": { "requests": 241877, "egressBytes": 690123456, "peakConnections": 31, "dbBytes": 9437184 },
  "limits": { "plan": "free", "connections": 50, "dbBytes": 524288000, "egressBytes": 2147483648, ... }
}
  • days is this environment only: exactly that many days ending today, oldest first, with zeros where nothing happened. Counts still in memory are written first, so today is up to date.
  • month is the calendar month so far for the whole workspace, every project and environment: the numbers your limits are checked against. requests and egressBytes are sums, dbBytes is the sum of each environment's latest size, and peakConnections is the highest daily sum of the environments' peaks - an upper bound, since their peaks need not have been at the same moment.

Idle pause on Free

An environment on the Free plan with no calls for 7 days - no call with one of its keys or tokens - is paused by the nightly job. Each environment is judged on its own, so a test environment nobody uses is paused while live keeps running. Pausing closes its sockets with close code 4001 and reason project paused; nothing is refused afterwards: the next call wakes it up and is answered normally. Looking at a project in the console neither wakes it nor keeps it awake; only calls with its keys or tokens do. The environment's sites pause with it; visits to a site do not wake it, a deploy does. Paid plans never pause.

The nightly job

Once a day, at 03:30 UTC, the server measures the database size of every environment, compares each workspace's storage and egress with its allowance (starting or clearing the 7 days above), deletes trace rows older than the plan's trace retention and request and realtime log rows older than its API log retention, along with every expired token, expired SQL commit tokens, and mailed sign-in links that were used or expired more than 7 days ago, pauses idle Free environments, and removes for good the projects whose 7 days of deletion are over. Usage history is kept for 400 days. A server that has not run the job for 25 hours runs it when it starts.

dabadata documentation, written against API contract v2.