Limits
Two kinds of number appear below. Boundaries are enforced and produce a specific error, so you can code against them. Current settings are what today’s single instance is configured for; they will move as capacity does, and you should treat them as capacity signals rather than contract.
Boundaries
Section titled “Boundaries”| Limit | Value | Response when exceeded |
|---|---|---|
| Request body | 8 MiB | 413 too-large |
| SQL text per statement | 1 MiB | 400 sql-denied |
Statements per /query |
1 | 400 bad-request |
Statements per /batch |
100 | 400 bad-request |
Request body — 8 MiB
Section titled “Request body — 8 MiB”{ "type": "https://errors.sqlited.dev/too-large", "status": 413, "title": "Too large", "detail": "the request body is too large"}The limit is on the encoded JSON, not on your data. Base64 costs blobs 33%, so a single blob has a practical ceiling near 6 MiB, and a batch of 100 rows each carrying a 100 KiB blob is over the limit even though the rows total 10 MB of actual bytes. Split the batch.
If you are pushing against this, the answer is usually more, smaller batches — the round-trip cost is already amortized at 100 statements, so five batches of 20 cost barely more than one of 100.
SQL text — 1 MiB per statement
Section titled “SQL text — 1 MiB per statement”{ "type": "https://errors.sqlited.dev/sql-denied", "status": 400, "detail": "statement is 1049000 bytes, over the 1048576-byte limit"}A megabyte of SQL is not a query, it is data pasted into a query. Bind it instead: params does not count
against this limit, and a parameterized statement is faster (SQLite caches the prepared form) and cannot be
injected into. The one legitimate case is a giant INSERT … VALUES (…), (…), … — use a batch.
Statements per request
Section titled “Statements per request”/query takes exactly one. A ;-separated second statement is refused, and this is a
deliberate refusal rather than a size limit:
{ "status": 400, "detail": "send one statement per request; use a batch for several" }/batch takes 1 to 100:
{ "status": 400, "detail": "a batch may hold at most 100 statements, not 250" }An empty statements array is also a 400 — it is almost always a bug in a loop that built no work, and
succeeding silently would hide it.
Current settings
Section titled “Current settings”| Setting | Today | What it means for you |
|---|---|---|
| Databases open per worker | 16 | The 17th concurrently-active database gets 503 capacity. |
| Idle close | 5 min | An untouched database is closed and its lease released. |
| Databases per organization | no limit | Create as many as you like; only concurrency is bounded. |
| Request rate | no limit | There is no rate limiter yet. Please do not make us add one in a hurry. |
| Database size | no limit | Bounded by the instance disk, which is not large. See below. |
Worker capacity — 16 open databases
Section titled “Worker capacity — 16 open databases”{ "type": "https://errors.sqlited.dev/capacity", "status": 503, "title": "Worker at capacity", "detail": "this worker already holds 16 databases open", "retryable": true}This counts databases currently open on that worker, not databases you own. A database opens on first use and closes after five minutes idle, so sixteen is the number that can be simultaneously active, and in practice a hundred low-traffic databases cycle through it without ever hitting the ceiling.
It is a 503 with retryable: true rather than a 4xx because there is nothing wrong with your request —
a worker at its limit refuses rather than evicting somebody else’s database mid-request, and the standard
retry with backoff is the correct response. The honest reason the
number is 16 and not 64: an open database is a materialized file plus a page cache plus a heartbeated
lease, and the current instance has 2 GiB of RAM. It goes up when the instance does.
Idle close — 5 minutes
Section titled “Idle close — 5 minutes”Nothing is lost when a database closes; every acknowledged write was already durable in object storage
before you got your 200. The only cost is the next request paying to reopen it — a clean restart is
about 300 ms, versus roughly 40 s for the unclean case where a segment replay is needed.
The practical consequence: if a request that normally takes 5 ms occasionally takes 300 ms, that is a cold open, not a problem. If it takes 40 s, tell us — that means the previous shutdown was unclean.
Database files live on the instance’s EBS volume while open. There is no per-database size limit and no quota, which is a gap rather than a feature: a database large enough to fill the volume would take the worker down with it. Durable state in object storage is unaffected, but availability would not be. Keep databases in the megabytes-to-low-gigabytes range for now, and talk to us before loading something big.
Not limited, but worth knowing
Section titled “Not limited, but worth knowing”- A long statement blocks the worker. There is no statement timeout and no way to cancel a query in flight. See the caveat.
- One writer per database. Writes to the same database serialize; this is a consistency property, not a throttle, and it is what makes the durability guarantee possible.
- Parameters per statement are bounded only by SQLite’s own limit, which is far above anything the 8 MiB body allows.
If a limit is in your way
Section titled “If a limit is in your way”None of these numbers is defended on principle — they are the settings of one small instance plus a few refusals that keep the durability story true. If one is blocking something reasonable, say so; the ones in the second table can move today.