a9script

Docs / Scripts

Limits and safety

Everything here is a number the platform holds you to, or a thing it will not let you do. None of it is arbitrary: each one exists because the alternative is a run, an endpoint or a machine that nobody can rescue at three in the morning.

What bounds a run

Four budgets: time per segment, steps, objects and stack depth. What each one counts, what happens at its edge and what to change when a script reaches one is on Budgets.

What a run starts with is bounded too. A run takes whatever its endpoint accepted: the event it starts with may be six times the request body limit below, plus room for the event’s own fields. Six times is the most a body can grow once it is part of the event: a body that is not JSON arrives as text, and each control character in it takes six bytes in the event, each byte that is not valid text three. The value a settings script answers may be 1 MiB as JSON. Past either, the run ends before its first line — INPUT_TOO_LARGE for the event (a mail larger than an endpoint would accept, say), SETTINGS_FAILED for settings — and its error says how large the input was and what the limit is. Send large data as a file or a dataset, and the run its name. The objects an input holds count against the run’s object budget like the ones it creates: a body of a few hundred thousand small records can end the run with HEAP_LIMIT before its first line, and the error says how many the input brought.

What bounds a call

  • Repeats. A call that failed in a way worth asking again about is repeated 3 times by default — four attempts in all — waiting longer each time. A configured call can ask for more, up to ten. What counts as worth asking again depends on the call: one that changes something is never repeated on a 5xx unless it is marked idempotent.
  • Silence. A call is cut off after 5 minutes without receiving any data. It is not a limit on how long the call may take: a slow, large download finishes as long as bytes keep arriving.
  • Absolutely. One call and all its repeats together may not exceed 60 minutes. Nothing you configure can lengthen this.
  • One wait. A run suspended because the far end said not now wakes after at most 24 hours, whatever the far end asked for. A longer answer is taken as 24 hours and the run’s log records both numbers, so nothing pretends the far end asked for the number it got. checkpoint refuses a longer wait outright: waking a day late does the work at the wrong time. A run that wants to wake up next week is a schedule, not a wait.
  • Coming back. A machine brings back 32 waiting runs at a time. One run that has finished waiting is picked up as soon as it is due, so a short wait really is short; a thousand of them finishing together come back in batches, and the last of that thousand waits for the batches in front of it. An installation can raise the number.

What bounds data

Datasets, mappings and state live on the platform’s side, and a script works through them a slice at a time. Inside these edges nothing needs ceremony; past one, the call is refused before the store is touched, as DATA_CALL_TOO_LARGE — its message names the call, the bound and the way on.

  • What one call answers into a script is at most 1 MiB. Large data stays on the platform’s side: pass names and handles, and read it back a page at a time.
  • A dataset holds at most 500000 rows and 512 MiB of values, one value at most 1 MiB. A write past either is refused as DATASET_FULL, and a batch lands entirely or not at all. A state value is at most 64 KiB.
  • A page — dsPage, listMappings, dsDiffKeys — answers at most 1000 rows, and stops early once it carries 768 KiB of values, never below one row. Continue past the last key you read (after) and stop on an empty page, and a short page costs you nothing; an offset works too, but steps over every row before it, which a large walk feels.
  • A batch call — setMappings, getMappings, dsPutMany — takes at most 10000 items. dsGetMany reads at most 1000 keys whose values come to 768 KiB at most; ask in several calls.
  • dsStats answers at most 1000 groups. A path with more is nearly unique, so the call is refused; dsDuplicates finds the values that repeat.
  • A resource holds at most 64 KiB of text (readResource); larger reference data belongs in a dataset or a file store.
  • One write to the platform — an Activate, one change of an apply, a call through the MCP door — carries at most 256 KiB of JSON, and is refused past it as PAYLOAD_TOO_LARGE. apply writes a file at a time; a folder applied whole (--atomic, --dry-run) is one write, and so is a folder carried through the MCP door, which holds every file in the call.
  • Files have their own edges — one file, a store, the environment, a listing’s page — on Files and file stores; a schedule looks for its next time up to 28 years ahead (Triggers and concurrency).

What bounds a door

Your endpoints are on the public internet, so they are metered.

  • A request body over 1 MB is refused before it is read.
  • A tenant may send 1200 requests a minute across all its endpoints; one endpoint may take 600. The minute slides: a batch of that size arriving all at once passes whole, and the allowance comes back continuously rather than at the top of the next minute. Over either number, the caller gets 429 with a Retry-After saying when the next request will pass — which every serious webhook sender already knows how to honour. A hosted app’s page reads count only against the app’s own endpoint, never the tenant’s number: a page is many files, and the tenant’s number is shared by all its environments, so a busy app in one must not turn away webhooks in another.
  • Verification comes first, and refusals cost nothing: a caller with no credential is turned away before it can spend any of your allowance.
  • Each door has its own queue lane, and a lane holds at most 1000 events waiting to run, and at most 256 MiB of them. Over either, the caller gets 429 with LANE_FULL, saying which of the two it met. Its Retry-After is measured or absent: where the platform has watched that lane drain, the header says how long the room this event needs takes to free at that pace; where nothing is draining, there is no header, because a number there would be invented. Only that door refuses: the same tenant’s other endpoints and schedules keep their room, and work already accepted still finishes.
  • A mailbox, a schedule and a system trigger have no caller to answer when their lane is full. A mailbox leaves its mail in the mailbox and reads it again, in order, once the lane has room. A schedule that comes due while its lane is full does not fire that time, and a system trigger’s scripts do not hear that occurrence; the node’s log says so.
  • A message too large for its mailbox’s lane even when the lane is empty — more scripts listening than the lane holds, or a message heavier than the lane — never runs. The node’s log names the message and the number to raise, and the mailbox moves on to the next message.

An installation can change these numbers; the defaults are what you get. The installation’s numbers are also the most any one tenant gets: whoever administers your tenant can narrow the three counts for it — fewer requests a minute, fewer events waiting — and never widen them. That is on Administering a tenant.

What the platform will not let you do

  • Reach the network except through a connection. There is no HTTP client to import. This is what makes credentials, retries, origin limits, redaction and the recorded request true of every call, with nothing to remember.
  • Hold a credential in a script. Secrets go on connections and mail accounts, are encrypted at rest, and never come back to any surface — not to a form, not to a command, not into a run’s recorded request.
  • Touch a filesystem. Files are stored by the platform, and a script reaches them two ways: by the handle it was given, or by name out of a file store. Either way the bytes stay outside the run — a script holds a handle or a path, never the file.
  • Reach another environment. Sandbox and production are sealed off from each other, and so is every other tenant. Promoting work between your own two environments is a deliberate, reviewed act — never something a script does.
  • Send a platform call something that is not data. A call receives JSON, so a Map, a Set or a regular expression is refused at the call with an error the script can catch, naming where the value sits. Send Object.fromEntries([...map]), [...set] or pattern.source instead. The log functions show them: a Map as its pairs, a Set as its values, a pattern as its text.
  • Return something that is not data. A result is stored as JSON, so a function, a bigint or a value that refers to itself fails the run rather than being silently trimmed.

Which guarantee is which

Three different promises, easy to conflate and expensive to guess at.

One event a sender sent becomes at most one event the platform accepted. For senders that ship a delivery id — most of the well-known ones — a repeated delivery of the same event is recognised and dropped at the door. That holds also when one copy arrived while the platform was still loading its configuration and was held until it could be routed.

One accepted event runs at most once. Work is not handed out twice, and a run that was interrupted continues rather than starting again. Delivery can still fail — when every attempt is exhausted, what is left is a dead letter, and finding it is on When the work jams.

What an interruption costs. A run’s state is saved before every call it makes and again when the answer is in, every time it pauses, and at least every 5 seconds while it computes in between. A run cut off while parked, or after a call’s answer was recorded, continues from that point when a machine picks it up — a call whose answer was already recorded is not made again. A run cut off while a call to another system was out — sent, not yet answered — is continued only if that function is marked idempotent, since continuing means making the call again; otherwise it is ended. A function that only fills in a template or maps data reaches no other system, so a run cut off inside one is continued too. A run is continued only up to the platform’s limit on how often one run may be picked up again; past it, it is ended. A run cut off in the middle of its own computation, with no call in flight, is not continued: it is ended and reported as failed, its log keeps every line it wrote, and nothing was done twice, because the work since its last saved point had reached nothing outside the run.

Neither one makes your script idempotent. If the far end charges a card or raises a ticket, and your integration must survive the sender genuinely sending the same order twice — a different event, hours later, because their system retried a business process rather than a delivery — that is yours to handle: remember what you have already done (a mapping, a dataset key, an idempotency key the far end honours) and check it before you act.

And for a sender that signs no delivery id, neither guarantee answers “is this request fresh”. That is what the replay window on the endpoint’s incoming authentication is for.

What the dashboard’s numbers are

Run counts are exact over every window. How long runs took is exact over the last day. Over a week or a month the platform does not keep every run’s duration in reach of the page: each one lands in a bucket 5 % wide, and the median and p95 are read from those. Such a number is never below the exact one and at most 5 % above it, and the page shows it with ≈. An alert rule’s percentile is the last day’s, so it is exact.

Production is meant to feel different

  • The environment you are in is stated at the top of the screen, and production says so in colour. Anything that changes it asks first, naming where.
  • Everything that runs carries a switch — a main script, a connection, a mail account, a watched mailbox, a function, a notification rule. Turning one off stops it at every door immediately: a webhook is still accepted and acknowledged, so the sender does not retry for days, and nothing runs; a caller waiting for an answer is told at once. Nothing that arrives for a switched-off script or endpoint is kept for later — a script that is switched off says what each door does. A watched mailbox is the exception: switched off, it reads nothing and keeps its place, so its mail is still there when it is switched back on. (A library, a settings script, an automation and a resource have no switch, because there is nothing there to stop.)
  • Health latches. A failure stays visible until somebody acknowledges it, rather than disappearing the moment the next run succeeds. A green screen means nothing has failed since somebody last looked — not that last night was quiet.
Rendered from docs/guide/limits-and-safety.md in the product's own repository, at build time. Found a problem on this page? Write to the address in the footer.