# Limits

Every app runs with resource limits. This page lists each one, its default, the error you get when you cross it, and what to change in your capsule. Limits apply the same way in `fuast dev` and on hosted apps, except where a row says otherwise. Self-hosted platforms can change some defaults with environment variables, listed at the end.

## Handler limits

These apply to every query, mutation, action, endpoint, and schedule call. Crossing one fails the call, and a mutation, writable endpoint, or schedule rolls back its whole transaction.

| Limit | Default | Error | What to do |
| --- | --- | --- | --- |
| Rows returned | 1000 | `collect() exceeded the 1000 row return limit. Use paginate().` for one read, `Database rowsReturned limit exceeded (1000).` across reads | `paginate()`, or `take(n)` with a small `n`. |
| Rows read | 5000 | `Database rowsRead limit exceeded (5000).` | Add an index that matches the filter so the scan stops early. `count()` spends this budget too. |
| Bytes read | 4 MiB | `Database bytesRead limit exceeded (4194304).` | Return fewer rows. Move large text to storage. |
| Direct `get` calls | 1000 | `Database directGets limit exceeded (1000).` | Replace per-row `get` loops with one indexed query. |
| Index scans | 100 | `Database scanCalls limit exceeded (100).` | Fold repeated queries into one range. |
| Writes | 1000 | `Database writes limit exceeded (1000).` | Split the work across mutations or schedule runs. |
| Field value size | 64 KiB | `Value for <table>.<field> exceeds 65536 bytes.` | Store the payload with storage and keep the key. Any value within this limit can be indexed. |
| `take(n)` | 0 to 1000 | `take count must be an integer between 0 and 1000.` | Paginate instead. |
| Page size | 1 to 1000 | `pagination numItems must be an integer between 1 and 1000.` | Use 10 to 100 for UI lists. |
| Database time | 5000 ms per call | `Database transaction timed out.` | Do less per call. Keep `fetch` calls short. |
| CPU time | 5000 ms | `Handler exceeded the 5000ms CPU time limit.` | Avoid heavy loops. Precompute during writes. |
| Wall-clock time | 10 s | `Handler exceeded the 10000ms wall-clock limit.` | Same as above. Slow `fetch` calls count. |
| Memory | 128 MB per isolate | The call fails. | Do not build huge arrays or strings. |
| Waiting for the write lock | 30 s | `The app is busy with other writes. Retry shortly.` (code `busy`) | Keep mutations short so they do not queue behind each other. |

Hosted CPU, wall-clock, memory, and write-lock limits come from the isolate runtime. `fuast dev` runs handlers in Bun workers. It enforces the database budgets, database time, the 10 s wall-clock limit, and the action limits, but not the separate CPU and memory limits, so a CPU-heavy handler that works locally can still fail once deployed.

## Outbound fetch

| Limit | Default | Error |
| --- | --- | --- |
| Timeout per request | 10 s | `fetch timed out after 10000ms.` (code `timeout`) |
| Request body | 5 MiB | `fetch request body exceeds 5242880 bytes.` (code `too_large`) |
| Response body | 5 MiB | `fetch response body exceeds 5242880 bytes.` (code `too_large`) |
| Redirects | 5 | `fetch: too many redirects (max 5).` |
| Private addresses | Blocked | Requests to loopback, private, and link-local addresses fail with code `ssrf`. |

Fetch time also counts toward the handler's wall-clock limit and holds the app's write lock in a mutation.

## Action limits

| Limit | Default | Error |
| --- | --- | --- |
| Arguments | 16 KiB | `Action arguments exceed 16384 bytes.` |
| Result | 48 KiB | `Action result exceeds 49152 bytes.` |
| Runtime | 5000 ms | `Action exceeded 5000ms.` |

Pass an id and read the data inside the action instead of passing the data. Return only the fields the client needs. A failed action does not undo its outbound effects, so do not assume it is safe to retry.

## Endpoint and connection limits

| Limit | Default | Behavior |
| --- | --- | --- |
| Endpoint request body | 5 MiB | `413` with `Request body is too large (5 MiB max).` (code `too_large`) |
| WebSocket message | 1 MiB | The connection closes. Send large data through storage. |
| Mutations and actions in flight per connection | 4 | Later calls queue in order. |
| Queued calls per connection | 1000 | Further calls fail. Batch into one mutation. |

## App limits

| Limit | Default | Error | What to do |
| --- | --- | --- | --- |
| Stored rows | 100000 | `State row limit exceeded (100000).` | Delete rows you no longer need. Keep summaries instead of raw events. |
| Stored row bytes | 64 MiB | `State byte limit exceeded (67108864).` | Shorten values. Move blobs to storage. |
| Requests per UTC day | 100000 | HTTP 429, `bucket: "requests"` | Cut round trips. One live subscription beats polling. |
| Mutations per UTC day | 20000 | HTTP 429, `bucket: "mutations"` | Batch writes into one mutation. Actions and writable endpoints count here too. |
| Emails per UTC day | 100 | `fuast_quota_exceeded`, `bucket: "email"` | Send a digest from a schedule instead of one email per event. |
| Pending jobs | 10000 | `Pending job limit exceeded (10000). Cancel jobs or let them finish first.` | Batch work into fewer jobs, or cancel stale ones (`fuast jobs cancel`). See [jobs](https://fuast.com/docs/jobs.md#limits). |
| Jobs scheduled per handler call | 1000 | `Job limit exceeded: at most 1000 jobs per handler call.` | Schedule one job that fans out in batches. |
| Job arguments | 64 KiB | `Job arguments exceed 65536 bytes.` | Pass ids, not documents. |
| Log buffer | 1000 entries or 256 KiB | Oldest entries are dropped. No error. | Log less, or read logs sooner. |
| Log entry size | 16 KiB | Long entries are cut and `data` becomes `{ truncated: true }`. | Log a summary, not the whole object. |
| Favicon | 64 KiB, `.svg` or `.ico` | Build error: `favicon <path> is <n> bytes; the limit is 65536 bytes (64 KiB).` | Use a smaller icon. |

Stored row limits fail the write that crosses them. They have no reset: delete data to get under them again.

## Email limits

| Limit | Default | Error |
| --- | --- | --- |
| Emails per app per UTC day | 100 | `fuast_quota_exceeded`, `bucket: "email"` |
| Recipients per call | 1 | `ctx.email.send needs \`to\` as one email address.` |
| Subject | 998 characters | `The email subject is too long.` |
| Body (`text` + `html`) | 256 KiB | `The email body is too large (256 KiB max).` |

`fuast dev` logs emails instead of sending them. See [email](https://fuast.com/docs/email.md).

## Storage limits

| Limit | Default | Error |
| --- | --- | --- |
| File size | 25 MiB | `413`, `code: "too_large"` |
| Stored per workspace | 1 GiB | `413`, `code: "quota_exceeded"` |
| Blocked types | Executables, HTML, JavaScript | `415`, `code: "blocked_type"` |
| Uploads per IP | 60 per minute | `429`, `code: "rate_limited"` |

See [storage](https://fuast.com/docs/storage.md#errors).

## Team table limits

| Limit | Default | Error |
| --- | --- | --- |
| Row size | 64 KiB of JSON | `A team table row can be at most 65536 bytes.` |
| Rows per team table | 10000 | `Team table <name> is full (10000 rows).` |
| Rows added at once | 1000 | `Add at most 1000 rows at once.` |
| Table name | 1 to 64 letters, digits, `_`, `-`, `.` | `Team table names use 1-64 letters, digits, ...` |

See [teams](https://fuast.com/docs/teams.md#team-tables).

## Server env limits

| Limit | Default | Notes |
| --- | --- | --- |
| Key format | `[A-Za-z_][A-Za-z0-9_]*`, at most 128 characters | Enforced for keys set in the dashboard Env tab. |
| Value size | 32 KiB | Enforced for values set in the dashboard. Keep `.env.fuast.server` values under the same size. |
| Where it is readable | `ctx.env` in handlers | Never in client code, shared code, or at build time. |

`.env.fuast.server` travels with the deploy, so it is limited to 1 MiB like any source file.

## Deploy limits

| Limit | Default | Error |
| --- | --- | --- |
| Source files per deploy | 1000 | `413`, `A deploy can have at most 1000 files.` |
| Source bytes per deploy | 20 MiB | `413`, `Deploy source is <n> bytes; the limit is 20971520.` |
| One source file (and `.env.fuast.server`) | 1 MiB | `413`, `<path> is <n> bytes; files can be at most 1048576 bytes.` |
| Build time | 120 s | The deploy fails with a build error. |
| Macro imports (`with { type: "macro" }`) | Not allowed | `422`, code `macro_import` |
| Deploys per IP | 30 per minute | `429`, `Too many deploys. Wait a minute.` |

Deploys upload source, not build output, so these limits are generous for a capsule. Large data belongs in storage or the database, not in source files.

## Unclaimed previews

An app deployed without an account is an [unclaimed preview](https://fuast.com/docs/reference.md#deploy-without-an-account) until a person claims it. It runs with the limits below instead of the app limits above. Claiming lifts the quotas at once. Email, team tables, the member list, schedules, and jobs stay off after the claim until the owner trusts the app. See [restricted apps](https://fuast.com/docs/security.md#restricted-apps).

| Limit | Default | Error | What to do |
| --- | --- | --- | --- |
| Lifetime | 72 hours from the first deploy. Redeploys do not extend it. | The preview stops serving and is deleted. | Give the person the claim link. |
| New previews per client address | 5 a day. Deleting a preview does not give one back. | `429`, code `unclaimed_ip_limit` | Claim one, or run `fuast login`. |
| Redeploys of previews per client address | 30 a day | `429`, code `unclaimed_ip_limit` | Batch your changes. Check locally with `fuast check` and `fuast dev` first. |
| Deploy requests per client address, failed ones included | 100 per 24 hours | `429`, code `rate_limited` | Fix build errors locally with `fuast check`. |
| Previews on the platform at once | 500 | `503`, code `unclaimed_capacity`, with `Retry-After` | Try later, or run `fuast login` and deploy to an account. |
| Source per deploy | 200 files, 1 MiB | `413`, code `too_large` | Keep data out of source files. |
| Stored rows | 2000 | `State row limit exceeded (2000).` | Claim the app. |
| Stored row bytes | 2 MiB | `State byte limit exceeded (2097152).` | Claim the app. |
| Stored files | 2 MiB per preview | `An unclaimed preview can store 2097152 bytes. Claim this app to use your workspace's storage.` | Claim the app. |
| Requests per UTC day | 2000 | HTTP 429, `bucket: "requests"` | Claim the app. |
| Mutations per UTC day | 500 | HTTP 429, `bucket: "mutations"` | Claim the app. |
| Outbound `fetch` calls | 10 per handler call, 100 per UTC day | `This unclaimed preview is out of outbound fetch calls: it gets 10 per handler call and 100 a day. Claim the app to lift the limit.` (code `fuast_quota_exceeded`) | Claim the app. A call sets its share of the day's budget aside when it starts and gives back what it did not use, so calls that overlap near the end of the budget may get fewer than 10. |
| Pending jobs | 100 | `Pending job limit exceeded (100). …` | Jobs do not run until the claim anyway. |
| Emails | 0 | `Claim this app to use email: ctx.email.send is off in an unclaimed preview.` (code `unclaimed`) | Claim the app, then trust it. |
| Team tables, workspace members | Not available | `Claim this app to use team tables: …`, `Claim this app to use ctx.workspace: …` (code `unclaimed`) | Claim the app, then trust it. |
| Schedules and jobs | Registered, not run | No error. `fuast inspect` shows them. | They start when the claimed app is trusted. Jobs queued before the claim are cancelled. |
| Reports before suspension | 3 different client addresses | The preview stops serving (`403`, code `suspended`, on deploy and claim). | The platform's operator reviews it. |

`fuast inspect <preview-slug> --usage` shows these limits beside the current counts, and the response of the deploy lists them under `limits`.

## Rate limits

The platform limits some routes per client IP in a sliding window. Crossing one returns HTTP 429.

| Route | Limit |
| --- | --- |
| `POST <app>/api/call` | 600 per minute |
| Storage uploads | 60 per minute |
| Deploys | 30 per minute |
| Sign-in | 10 per minute |
| Sign-up | 5 per minute |
| Password token login (`fuast login --email`) | 10 per minute |
| `fuast login` browser approval start | 10 per minute |
| `fuast login` approval polling | 120 per minute |
| Resend verification email | 3 per 10 minutes |
| Request access to a private app | 5 per hour per app |
| Report an unclaimed preview | 10 per hour |
| Claim an app (`POST /api/claims/<token>/claim`) | 30 per minute |

WebSocket calls from the app's own client are covered by the daily quotas, not by these per-minute limits.

## Reading a quota error

Daily quota errors on HTTP routes return status 429 with this body:

```json
{
  "code": "fuast_quota_exceeded",
  "error": "requests quota exceeded. Limit: 100000.",
  "bucket": "requests",
  "current": 100001,
  "limit": 100000,
  "resetAt": "2026-11-11T00:00:00.000Z",
  "retryAfterSeconds": 41230,
  "suggestion": "Retry after the reset, or reduce request frequency.",
  "command": "fuast inspect my-app --usage"
}
```

The response also carries `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`. Do not retry in a tight loop. Wait `retryAfterSeconds`.

Over the WebSocket, the error arrives as `{ op: "error", error, code: "fuast_quota_exceeded", quota: { ...same body } }`. Mutation and action promises reject with an error whose `code` is `fuast_quota_exceeded`.

| `bucket` | Counts | Resets |
| --- | --- | --- |
| `requests` | Queries, mutations, actions, endpoint calls, HTTP calls | 00:00 UTC |
| `mutations` | Mutations, actions, writable endpoint calls | 00:00 UTC |
| `email` | `ctx.email.send` calls | 00:00 UTC |

Owners and workspace admins can still read usage, logs, and data exports after an app runs out of quota. Those reads do not spend the app's budget.

## Checking usage

```sh
fuast inspect my-app --usage          # hosted app, by slug
fuast inspect my-app --usage --json
fuast inspect --port 3000 --usage     # local
```

Usage shows stored rows and bytes and today's request, mutation, and email counts beside their limits. `fuast dev` counts the same way but never rejects on daily quotas. Handler, action, and stored-row limits do apply locally, so local testing catches them.

`fuast inspect` also prints per-read database metrics:

```txt
  read by_owner: returned=20 read=20 bytes=4096
  read by_creation: returned=50 read=4800 bytes=1900334 full-scan
```

`returned`, `read`, and `bytes` are the counters behind the handler limits. `full-scan` marks a read that walked `by_creation` on a large table without a range. It is the usual cause of a `rowsRead` failure: add an index that matches the filter. To see the metrics of one query with your own arguments, run it with `fuast inspect --query`. See [test and debug](https://fuast.com/docs/testing.md#measure-one-query).

## Self-hosted overrides

Operators running their own platform can change these defaults. Every limit uses one variable family, `FUAST_LIMIT_*`, and the platform, the runtime, and `fuast dev` all read it the same way, so set the same values for every process (the platform sends the database budgets with each call, the runtime enforces the isolate limits). Values must be positive integers; anything else is ignored.

| Variable | Changes | Default |
| --- | --- | --- |
| `FUAST_LIMIT_ROWS_RETURNED` | Rows returned per handler call | 1000 |
| `FUAST_LIMIT_ROWS_READ` | Rows read per handler call | 5000 |
| `FUAST_LIMIT_BYTES_READ` | Bytes read per handler call | 4194304 |
| `FUAST_LIMIT_DIRECT_GETS` | Direct `get` calls per handler call | 1000 |
| `FUAST_LIMIT_SCAN_CALLS` | Index scans per handler call | 100 |
| `FUAST_LIMIT_WRITES` | Writes per handler call | 1000 |
| `FUAST_LIMIT_FIELD_BYTES` | Size of one field value | 65536 |
| `FUAST_LIMIT_INDEX_KEY_BYTES` | Encoded size of one indexed field. The default fits any value within the field limit. | 131136 |
| `FUAST_LIMIT_TRANSACTION_MS` | Database time per handler call | 5000 |
| `FUAST_LIMIT_STATE_ROWS`, `FUAST_LIMIT_STATE_BYTES` | Stored rows and row bytes per app | 100000, 67108864 |
| `FUAST_LIMIT_JOBS_PENDING`, `FUAST_LIMIT_JOBS_PER_CALL`, `FUAST_LIMIT_JOB_ARGS_BYTES` | Pending jobs per app, jobs scheduled per handler call, job argument bytes ([jobs](https://fuast.com/docs/jobs.md)) | 10000, 1000, 65536 |
| `FUAST_LIMIT_DAILY_REQUESTS`, `FUAST_LIMIT_DAILY_MUTATIONS`, `FUAST_LIMIT_DAILY_EMAIL` | Daily quotas per app | 100000, 20000, 100 |
| `FUAST_LIMIT_CPU_MS`, `FUAST_LIMIT_WALL_MS`, `FUAST_LIMIT_MEMORY_MB` | Isolate CPU time, wall-clock time, memory. `fuast dev` applies the wall-clock limit only. | 5000, 10000, 128 |
| `FUAST_LIMIT_ACTION_MS` | Action runtime | 5000 |
| `FUAST_LIMIT_ACTION_ARGS_BYTES`, `FUAST_LIMIT_ACTION_RESULT_BYTES` | Action argument and result size | 16384, 49152 |
| `FUAST_LIMIT_WRITE_LOCK_WAIT_MS` | How long a write waits for the app's write lock | 30000 |

`fuast inspect` and `/__fuast/manifest` show the limits in effect under `limits`, so you can check what a process picked up.

Older names still work but print a deprecation warning once: `FUAST_DB_ROWS_RETURNED`, `FUAST_DB_ROWS_READ`, `FUAST_DB_BYTES_READ`, `FUAST_DB_DIRECT_GETS`, `FUAST_DB_SCAN_CALLS`, `FUAST_DB_WRITES`, `FUAST_DB_VALUE_BYTES` (and `FUAST_LIMIT_VALUE_BYTES`), `FUAST_DB_INDEX_KEY_BYTES`, `FUAST_DB_TRANSACTION_MS`, `FUAST_STATE_ROWS`, `FUAST_STATE_BYTES`, `FUAST_QUOTA_REQUESTS`, `FUAST_QUOTA_MUTATIONS`, `FUAST_QUOTA_EMAIL`, `FUAST_CPU_MS`, `FUAST_WALL_MS` and `FUAST_HANDLER_TIMEOUT_MS`, `FUAST_MEM_MB`, `FUAST_ACTION_TIMEOUT_MS`, `FUAST_WRITE_LOCK_WAIT_MS`. When both an old and a new name are set, the `FUAST_LIMIT_*` name wins.

Unclaimed previews have their own family, read by the platform. Values must be positive whole numbers:

| Variable | Changes | Default |
| --- | --- | --- |
| `FUAST_UNCLAIMED_DEPLOYS` | `off` refuses deploys without an account. | on |
| `FUAST_UNCLAIMED_TTL_HOURS` | Lifetime of a preview, from its first deploy | 72 |
| `FUAST_UNCLAIMED_NEW_PER_IP`, `FUAST_UNCLAIMED_REDEPLOYS_PER_IP`, `FUAST_UNCLAIMED_ATTEMPTS_PER_IP` | New previews and redeploys per client address per UTC day (kept in the database, so a restart does not reset them), and deploy requests per 24 hours | 5, 30, 100 |
| `FUAST_UNCLAIMED_MAX_LIVE` | Previews that may exist at once | 500 |
| `FUAST_UNCLAIMED_MAX_IN_FLIGHT`, `FUAST_UNCLAIMED_BUILD_CONCURRENCY` | Anonymous deploys queued or building at once, and building at once. Keep the second below `FUAST_BUILD_CONCURRENCY` so signed-in deploys always have a build slot. | 8, 1 |
| `FUAST_UNCLAIMED_DEPLOY_MAX_FILES`, `FUAST_UNCLAIMED_DEPLOY_MAX_BYTES` | Source files and bytes per anonymous deploy | 200, 1048576 |
| `FUAST_UNCLAIMED_STATE_ROWS`, `FUAST_UNCLAIMED_STATE_BYTES`, `FUAST_UNCLAIMED_JOBS_PENDING` | Stored rows, row bytes, and pending jobs per preview | 2000, 2097152, 100 |
| `FUAST_UNCLAIMED_STORAGE_BYTES` | Stored file bytes per preview | 2097152 |
| `FUAST_UNCLAIMED_DAILY_REQUESTS`, `FUAST_UNCLAIMED_DAILY_MUTATIONS`, `FUAST_UNCLAIMED_DAILY_RERUNS` | Daily quotas per preview | 2000, 500, 20000 |
| `FUAST_UNCLAIMED_DAILY_FETCH`, `FUAST_UNCLAIMED_FETCH_PER_RUN` | Outbound `fetch` calls per preview per UTC day (`0` turns outbound fetch off for previews), and per handler call | 100, 10 |
| `FUAST_UNCLAIMED_REPORTS_TO_SUSPEND` | Reports from different client addresses that suspend a preview | 3 |
| `FUAST_UNCLAIMED_SWEEP_INTERVAL_MS`, `FUAST_UNCLAIMED_SWEEP_BATCH` | How often expired previews are deleted, and how many per run | 60000, 20 |

Other operator settings are not limits on what a handler may do, so they keep their own names:

| Variable | Process | Changes |
| --- | --- | --- |
| `FUAST_MAX_ISOLATES`, `FUAST_RUNTIME_MAX_APPS` | runtime | Cached isolates and open app databases. |
| `FUAST_ALLOW_PRIVATE_FETCH` | runtime | `1` lets handlers fetch private addresses. Never set it on a shared platform. |
| `FUAST_QUOTA_WORKSPACE_EMAIL`, `FUAST_QUOTA_INVITES` | platform | Emails per workspace per day across its apps, and share invites per workspace per day. |
| `FUAST_STORAGE_MAX_FILE_BYTES`, `FUAST_STORAGE_WORKSPACE_BYTES` | platform | File size and storage per workspace. |
| `FUAST_DEPLOY_MAX_FILES`, `FUAST_DEPLOY_MAX_BYTES`, `FUAST_DEPLOY_MAX_FILE_BYTES` | platform | Deploy upload size. |
| `FUAST_BUILD_TIMEOUT_MS`, `FUAST_BUILD_CONCURRENCY` | platform | Sandboxed build time limit and parallel builds. |
| `FUAST_DISABLE_RATE_LIMITS` | platform | `1` turns off per-IP rate limits. For tests only. |
| `FUAST_DEV_WRITE_LOCK_TIMEOUT_MS`, `FUAST_DEV_LOAD_TIMEOUT_MS` | `fuast dev` | Local write-lock wait (defaults to `FUAST_LIMIT_WRITE_LOCK_WAIT_MS`) and the time a server bundle may take to load (10000). |
| `FUAST_DEV_CLOCK_OFFSET_MS` | `fuast dev` | Milliseconds added to the clock cron schedules are evaluated against (tests; default 0). Jobs use the real clock. |

The endpoint body size, log sizes, email sizes, team table sizes, and the per-IP rate limits are fixed. A per-IP limit counts an IPv4 address, or a whole IPv6 `/64`, as one client. See [reference](https://fuast.com/docs/reference.md#platform-environment) for the rest of the platform environment.
