Limits
Every hard limit an app can run into, in one place. Check here before committing to a design; each row links to where it's explained.
Framework
| Limit | Value | Past it |
|---|---|---|
| Web framework | Next.js only (App Router, under web/) | The dev server runs next dev and deploys run next build; nothing else is served |
| Versions a build starts from | next ^16, react ^19, react-dom ^19, typescript ^5.8 | The app's own package.json can pin others; @inferencebureau/apps-sdk is always the bundled one |
| What a deploy ships | web/, api/ and the root package.json | Other folders stay in the app's files, not the build |
| Deploy build | 4 minutes | The deploy fails: "The build took too long." See Deploys |
Files and pushes
| Limit | Value | Past it |
|---|---|---|
| One file, pushed inline | 1 MiB | The CLI uploads bigger files to storage first (Getting started) |
| One file, uploaded | 50 MiB | Refused: "Files can be at most 50 MiB." Not synced |
| Files in an environment | 2,000 | The push is refused |
| Changes in one push | 200 (and 8 MiB) | The CLI splits bigger pushes for you |
| Path length | 512 characters | The file is refused |
| Never synced | .git, .ib, node_modules, .next, .vercel, .turbo, .DS_Store, .ibignore entries | Skipped |
Environments
| Limit | Value | Past it |
|---|---|---|
| Development environments per app | 10, development included | Adding another is refused |
| Production | 1 per app | Changes only through a deploy |
| Environment name | 20 characters: lowercase letters, digits and single hyphens, starting with a letter. Not production, development, prod, dev, both, deploy, new or preview | Refused |
| Dev server | Sleeps after about 30 minutes without activity (kept awake while ib-apps dev is connected); 2 vCPU. A wake takes about 7 seconds, a first start (or a changed package.json, which reinstalls) about 20 | Wakes on the next request while you've synced, previewed or run ib-apps dev in the last 6 hours; after that only those wake it (Preview). A start that keeps failing is retried after 1, 5 and 30 minutes, then waits for your next sync. Or run it on your computer with ib-apps dev --local |
| Local dev server | ib-apps dev --local: a heartbeat every 5 seconds keeps the environment pointed at your computer; 45 seconds without one hands it back | The hosted dev server wakes on the next request |
Records and queries
| Limit | Value | Past it |
|---|---|---|
| A record's stored JSON | 256 KiB | The write is refused (Models) |
| Page size (first) | 50 by default, at most 250 | Clamped to 250 (Records) |
| Filter operators | equals, notEquals, in, notIn, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isSet, startsWith, contains; combined with AND, OR, NOT | An unknown operator is an error naming the ones there are |
contains | Text fields only: string, richText, email, url, enum | An error |
| Not filterable or sortable | password, encryptedString, file, and fields that aren't stored (relationships, computed, vector) | An error |
| Text search | Every word, across the model's text fields; a model needs at least one | An error for a model without text fields |
| Vector fields | At most 4,000 dimensions, matching the field's dimensions | The schema or write is refused |
| Inline (base64) file in a write | About 3 MB, within the 4 MiB body | Use a direct upload or copyURL |
| Direct file upload | 1 GiB; the upload URL lasts an hour, and the upload must be written to a record within 24 hours | Refused, or swept away |
copyURL | 50 MiB, fetched within 30 seconds | The write fails |
| One query (records API, MCP) | 15 seconds | Refused with query_timeout: declare an index for the filter or sort, and page with after rather than a deep skip (Models) |
| Calls to the records API and MCP | 500 a second per environment, in bursts of up to 1,000 | 429 with Retry-After |
| Records API and MCP calls running at once | 20 per environment; a watch for changes doesn't count | 429 concurrency_limit_exceeded with Retry-After: 1 |
| Storage reads (list, get) | 50 a second per environment, in bursts of up to 300 | 429 with Retry-After |
| Storage writes (put, delete, upload URLs) | 10 a second per environment, in bursts of up to 60 | 429 with Retry-After |
| Development emails (logged, not sent) | 60 a minute per environment, in bursts of up to 20 | 429 with Retry-After |
| Copying production's data to development | 50,000 records and 2,000 files | Refused: copy a model at a time |
Actions, jobs and schedules
| Limit | Value | Past it |
|---|---|---|
An action, run and onSuccess together (timeoutMS) | 30 seconds by default (5 minutes as a background job), between 1 second and 15 minutes | The run fails with a timeout (How actions run) |
| A background job's attempt | Its action's timeoutMS: 5 minutes unless it says, at most 15 | The attempt fails and is retried (Retries) |
| Other records written with a run | 500 | The run fails and writes nothing (Writing other records) |
| A job's params | 1 MiB of JSON | Refused |
| Retries | 6 by default, at most 20; backoff from 15 seconds doubling to an hour | The job fails |
| Queued ahead | 30 days | Refused |
concurrency | 1 to 100, with a concurrencyKey (200 characters); dedupeKey 300 characters | Refused, or the key is cut |
| Schedules | Five-field cron, so at most once a minute | Missed occurrences older than 10 minutes are skipped |
| Listing jobs | 100 by default, at most 500 | Clamped |
Request bodies
| Limit | Value | Past it |
|---|---|---|
| The API | 4 MiB of JSON | 413 payload_too_large |
| MCP | 512 KiB per message | 413 |
| Shopify webhooks | 1 MiB (Shopify's own limit) | Refused |
| App proxy and extension URL responses (Storefront) | 5 MiB, answered within 25 seconds | 502 "The app's answer was too large." or 504 "The app didn't answer." |
| A sleeping development app behind the proxy | Waits up to 8 seconds for it to wake (a wake usually takes about 7) | The store is told the app is starting; the next request finds it awake |
| Bulk metafield edits | 250,000 changes a run; past 2,500 they go in bulk | Refused (Bulk metafield edits) |
| A metafield run's CSV | 4.5 MB in the request, or 100 MB by csvUrl | 413, or the plan fails |
Environment variables
| Limit | Value | Past it |
|---|---|---|
| Variables per environment | 200 | Adding another is refused |
| A value | 32 KiB | Refused |
| A name | Letters, digits and _, not starting with a digit, at most 128 | Refused |
| Reserved names | IB_*, VERCEL*, __NEXT*, NEXT_RUNTIME*, NODE_ENV, PORT, HOSTNAME, PATH, HOME, NEXT_PUBLIC_SHOPIFY_API_KEY | Refused: Inference Bureau or the runtime sets them |
Shopify
| Limit | Value | Past it |
|---|---|---|
| Admin API pacing | Paced against the store's cost bucket; waits up to 8 seconds in a request, 60 in the background (The Admin API) | Shopify's throttled answer comes back to the call |
| A sync run's paged part | About 29 minutes | Stops: "The run ran out of time." Start it again with since. Models listed in bulk carry on until they're done |
| Listed in bulk | Past 100 pages: about 1,000 products or orders, 2,500 customers or inventory items, 10,000 collections | One bulk pass per store at a time (Big stores) |
| App name and proxy subpath | 30 characters | Cut to 30 |
| Production installs on Free | 5 stores | Refused before Shopify's approval page (Free's limits) |
Extensions
| Limit | Value | Past it |
|---|---|---|
| Handle | 50 lowercase letters, digits and dashes | The extension isn't released (Folders and handles) |
| Sidekick tools | 20 per app; names 64 characters, descriptions 512; the app's summary 1,000 | Reported on the release (Sidekick tools) |
| Web pixel script | 128 KiB built | The pixel stays out of the release |
| Flow template description | 1,024 characters | The template isn't released |
| Types not released | Payments extensions (payments_extension) | Reported, never sent |
| One release's builds | 10 minutes | Extensions not built stay out |
Custom domains
| Limit | Value | Past it |
|---|---|---|
| Count, across the team's apps | Free none, Pro 5, Scale 25 | Refused until one is removed (Custom domains) |
| Uniqueness | One team and app per hostname | Refused |
Plan quotas
Pooled across the team's production environments; development is never counted. Pro and Scale charge overage past an allowance; Free pauses its apps until the month ends. Details on Plans and usage.
| Free | Pro | Scale | |
|---|---|---|---|
| Runs a month | 50,000 | 500,000 | 5,000,000 |
| Requests a month | 100,000 | 1,000,000 | 10,000,000 |
| Storage | 0.5 GB-month | 5 GB-months | 50 GB-months |
| Bandwidth | 5 GB | 50 GB | 500 GB |
| Production apps | 1 | Unlimited | Unlimited |
| Production store installs | 5 | Unlimited | Unlimited |
| Custom domains | None | 5 | 25 |
| Logs kept | 1 day | 14 days | 30 days |
| Restore records back | 1 day | 7 days | 30 days |
| Dev servers awake at once | 2 | 10 | 25 |
| Dev server hours | 100 awake hours per app a month | About 100 per app a month, fair use | Same as Pro |
A dev server that can't start for one of these limits shows as asleep to visitors, and Logs say which limit held it. One goes to sleep 30 minutes after it was last used, which frees its place. Dev servers also start only once one of the team's owners or admins has verified their email address. When development servers are at capacity across Inference Bureau, a start waits a few minutes; production is never affected. None of this applies to ib-apps dev --local, which runs development on your own computer.