Limits & quotas

Limits & quotas

Every cap, quota, and rate limit the Telenow App Platform enforces — with the exact number, the HTTP status you get at the cap, and whether the value is rejected (the request fails), clamped (silently pulled into range), or truncated (the extra data is dropped but the request succeeds).

These guardrails exist so one app's runaway loop can't starve other tenants. They are generous for legitimate apps. Caps are per (org, app) unless noted — your app sees its own counts in one org, isolated from every other install.

A note on "soft" caps: some counts (the row cap, the proxy/circuit-breaker state, the Data-API rate buckets) are process-local and cached, so on a multi-node cluster the effective limit may overshoot slightly before it settles. They stop abuse; they are not billing.

Data store & reads

LimitValueAt the capBehaviour
Records per (org, app), across all object types100000 (MAX_ROWS_PER_APP)413 Payload Too Large, message app data store is full (max 100000 records per app)Rejectedcreate fails
List default page size (when limit unset)100default
List page size range1..=500over-large → 500; 0 → the default; negative → 1Clamped (not errored)
Semantic search topK / limit default20default
Semantic search topK / limit range1..=500same clamp as a plain listClamped — on builds before mid-2026 this path was unclamped, so a negative limit errored the query and a huge topK scanned the whole vector index
Agent object.query result rows50Fixed — no manifest override, no pagination; filter instead
Expand fields per read (expand=)8 (MAX_EXPAND_FIELDS)extra expand keys ignoredunknown / non-relation keys silently ignored
Referenced rows fetched per expand field500 (MAX_EXPAND_IDS)to-many beyond 500Truncated
Computed-field rendered length8192 chars (MAX_COMPUTED_LEN)over-length outputTruncated
Offset pagination over RESTnot supporteduse limit + sort

The row count is soft and process-local, cached with a 60-second TTL. A cluster may overshoot the cap slightly, and freed rows (deletes) free up capacity within about one minute as the cache refreshes.

The cap covers every writer, not just the REST API: dashboard-bridge creates, agent object.create tools, inbound webhooks, workflow create-object steps, event-rule upserts, and public-link form submissions. That last one is the one to think about when you issue a form link to someone outside the org — it is the only write into your store an unauthenticated caller can reach, so the row cap is what bounds a link that gets shared further than you intended. Prefer single-use links, and keep expiries short. (On builds before mid-2026 the form path skipped the cap entirely.)

POST /api/app-data/patient
Authorization: Bearer <app key>

HTTP/1.1 413 Payload Too Large
{ "success": false, "error": "app data store is full (max 100000 records per app)" }

Blobs / files

LimitValue
Per file25 MB (MAX_BLOB_SIZE_BYTES)
Total per app (in one org)1 GB (MAX_TOTAL_BLOB_BYTES)
Number of files per app10000 (MAX_BLOBS_PER_APP)

A put over any of these is rejected. The byte cap and the file-count cap are both enforced — a flood of tiny files is blocked by the 10,000-file cap even though it fits under 1 GB.

Data API rate limit

The programmatic app-key Data API (/api/app-data/...) is rate-limited with a token bucket, per (org, app):

SettingValue
Sustained rateREFILL_PER_SEC = 20.0 (~20 req/s)
BurstBURST = 60.0
Over limit429 Too Many Requests, body retry_after_secs: 1, message Data API rate limit reached — slow down

The in-dashboard data proxy (member-authed — a human is signed in) is not subject to this limit. Only the programmatic app-key Data API is rate-limited here.

GET /api/app-data/patient
Authorization: Bearer <app key>

HTTP/1.1 429 Too Many Requests
{ "success": false, "error": "Data API rate limit reached — slow down", "retry_after_secs": 1 }

Tools

LimitValueBehaviour
Tool timeoutSecs130 seconds, default 15 when omittedClamped to 1..=30
object.query agent toolhard-capped at 50 rows, equality-only filtersrows beyond 50 dropped
http tool base_urlmust be public HTTPSRejected — no localhost / private / loopback / link-local / cloud-metadata (SSRF-guarded)
http tool response<= 1 MBRejectedapp tool response exceeded 1 MB cap
http tool non-2xx responseany non-2xxaborts the tool

Note: timeoutSecs clamps to 1–30 seconds (not 1–10). A 15-second default applies when you omit it.

Sandbox runtime (js / sandbox handlers)

The hardened code runtime applies its own hard limits, independent of timeoutSecs:

LimitValue
Source size<= 16 KB (MAX_SOURCE_BYTES)
Serialized output<= 256 KB (MAX_OUTPUT_BYTES)
Heap16 MB (hard QuickJS cap → JS exception, never host OOM)
Native stack256 KB
Execution timeoutclamped 100 ms5000 ms (independent of timeoutSecs)
Global concurrencydefault 32 in-flight → returns status busy over the cap

Automation

Manifest caps (validated at upload — rejected otherwise)

LimitValue
Schedules per app20 (MAX_SCHEDULES)
Schedule minimum interval300 seconds (5 min) (MIN_SCHEDULE_SECS)
Workflows per app20 (MAX_WORKFLOWS)
Steps per workflow20 (MAX_WORKFLOW_STEPS)
Inbound hooks per app20 (MAX_INBOUND_HOOKS)

every is a simple <positive int><unit> duration — unit in {m | min, h | hr, d | day}. No seconds, no weeks, no cron. Any multiple is allowed (e.g. 90m, 6h, 2d), but anything under 5 minutes is rejected at validate.

"schedules": [
  { "key": "nightly", "every": "24h", "handler": { "kind": "rule", "object": "...", "key": "...", "map": {} } }
]
// "every": "2m"  → rejected (under the 5-minute floor)

Inbound hooks

LimitValueBehaviour
POST body256 KB (DefaultBodyLimit)Rejected over the cap

Workflow runtime

LimitValueBehaviour
In-flight runs per (org, app)1000 (MAX_RUNNING_RUNS_PER_APP)over the cap, a trigger is silently dropped
Step attempts6 (MAX_STEP_ATTEMPTS)after 6 the run is marked failed
Retry backoff5s * 2^attempt, capped at MAX_DELAY_SECS = 7 days
delay stepclamped to max 7 daysClamped
http step request body<= 256 KB (HTTP_REQ_BODY_CAP)step retries, then fails after MAX_STEP_ATTEMPTS
http step stored responsetruncated to ~8 KB (HTTP_RESP_CAP)Truncated
http step timeoutclamped 130s, default 15sClamped

Step outcomes: a 5xx response or a transport error retries (backoff absorbs transient failures); a 4xx response and an update-object with no matching row are non-fatal (the step is Done).

Agents / knowledge bases / eval

Manifest caps (reject the upload otherwise)

LimitValue
agents[]<= 50
agentTeams[]<= 20
Members per team1..=10 (unique, key-safe refs; entry must equal a member ref)
knowledgeBases[]<= 20
Documents per KB<= 100
Document body<= 512 KB (title must be non-empty)

Runtime cap

LimitValueAt the cap
Provisioned agents per (org, app)100 (MAX_AGENTS_PER_APP)413this app has reached its agent limit (100) for this org

This runtime cap is enforced across every create path — dashboard create, the app-key POST /api/app-agents, and whole-team create. It is distinct from the 50 manifest-template cap (agents[]): the 50 caps how many templates a manifest may ship; the 100 caps how many real agents you may provision.

Eval

LimitValueBehaviour
Scenarios per requestmax 3 (MAX_EVAL_SCENARIOS), at least 1 requiredotherwise 400
Per-scenario max_turnsdefault 4, clamped to 6 (EVAL_MAX_TURNS)Clamped
Whole-request budget200s (EVAL_REQUEST_BUDGET_SECS)400eval timed out — try fewer scenarios or turns

Campaigns

LimitValueAt the cap
Active campaigns per app per org (draft/running/paused)50 (MAX_CAMPAIGNS_PER_APP)400app campaign limit reached (50 active)
Targets per campaign5000 (MAX_TARGETS_PER_CAMPAIGN)explicit lists truncated to 5000; targetQuery keyset-pages up to 5000 then stops
Create body2 MBRejected over the cap

HTTP proxy (telenow.http / bridge)

The server-side proxy your dashboard UI calls through (telenow.http(...)). State is process-local (single node).

GuardValueOver the limit
Per-org rate (token bucket)REFILL_PER_SEC = 20 sustained, BURST = 40429, retry_after 1s, app API proxy rate limit reached — slow down
Global concurrency (semaphore)64 in-flight429, retry_after 1s, proxy is at capacity — retry shortly
Per-host circuit breakertrips after TRIP_AFTER = 5 consecutive transport/5xx failures, stays open OPEN_FOR = 30s429, retry_after 30s, upstream <host> is temporarily unavailable
Response size<= 1 MBRejected
SchemeHTTPS-onlyRejected

A 4xx response counts as "up" — it does not trip the breaker.

Package / upload

All of these are rejections, not truncations — the whole package upload fails if any is exceeded.

LimitValueAt the cap
Whole multipart upload body32 MB (PACKAGE_UPLOAD_LIMIT)413package exceeds the 32 MB limit
Zip total uncompressed40 MB (MAX_TOTAL_UNCOMPRESSED)rejected (zip-bomb guard)
Files in the zip400 (MAX_ENTRIES)rejected
UI bundle (ui.entry, built JS)8 MB (MAX_BUNDLE_BYTES)rejected
UI styles (built CSS)2 MB (MAX_STYLES_BYTES)rejected
Per screenshot4 MB (MAX_SCREENSHOT_BYTES)rejected
Screenshots5 (MAX_SCREENSHOTS)rejected
README.md / CHANGELOG.md (each)64 KB (MAX_README_BYTES)rejected
Sandbox codeFile64 KB (MAX_CODE_BYTES)rejected
telenow.app.json512 KB (MAX_MANIFEST_BYTES)rejected

Session token & stream tickets

TokenTTLNotes
App session token1800s (30 min)minted via telenow.session.token() / POST /:appId/session-token
Live-call / object-change stream ticket30ssingle-use

Next