Limits and metering

Service limits, resource budgets, and usage metering. See pricing for plan allowances.

Room settings

See Room configuration for the complete option table, defaults, and units. This page lists service limits and what happens when they are reached. Plan allowances are on pricing.

Schema limits

defineSchema throws on a schema past one of these, so it fails at build time and never reaches a server. The complete list, including string, list and id bounds, is in schema limits.

LimitValue
Fields per entity or singleton255
Message shapes declared under messages256 per schema
RPCs declared under rpc, both directions together60,000 per schema

Where a room runs

Your project gets its own server, sized from the size class your room types declare. A bigger class gets you a bigger server. Declaring nothing gets you Standard.

Every project starts on one server and gets another when its rooms need one. The room budget below applies to each server on its own, so a project on three servers has three servers’ worth of budget rather than one budget divided three ways.

Room budget

One server holds a fixed number of awake rooms. irtio sets the number, per size class:

Size classRoom memoryRooms per server
Standard64 MB20
Large256 MB8
Extra large1 GB3

A room counts against the budget while it is awake. Asleep rooms cost nothing, so a project with thousands of rooms and twenty awake ones fits on one server.

A project with room types in more than one class shares one budget. Each awake room takes a slot of its own class: five awake Standard rooms use a quarter of the budget, one awake Extra large room uses a third. The server is sized for the largest class the project declares.

When a project fills a server’s budget, irtio adds a server and the next new room starts there. See when a project gets another server.

Players per room

maxClients defaults to 64. Measure your game’s capacity with load tests, then set a count that stays within your tick-time and bandwidth budgets.

Relay rooms

Relay rooms provide messaging and, with a deployed schema, shared state. See relay rooms for setup and ownership policies.

LimitValueAt the limit
State per room512 KBWrite refused with E_STATE_CAP
Records per collection1024Add refused with E_RECORD_CAP
Write operations per client per second35Whole frame refused with E_WRITE_RATE
Ownership request response5 secondsUnanswered request refused
Persisted player fields (persist)4 KB per playerSchema refused at compile
Players with persisted fields10,000 per projectBumpable on request to support@irt.io
Persisted fields kept90 days from the last writeThe player starts at schema defaults

Changing a deployed relay schema resets the room’s stored state and reconnects clients against the new schema. Deploying the same schema preserves state.

Ports a client connects to

TrafficPort range
Room traffic443, TCP (a secure WebSocket to <region>.irt.io)
Voice media40000-40999, UDP and TCP, and only where voice is switched on. TCP on the same ports is the fallback for networks that block UDP

Room traffic goes through the regional router, so a player’s network only needs outbound 443. The game servers accept room connections from the router alone. What protects a room is the join credential your project issues.

Memory

Declare a room class to set its memory ceiling: Standard 64 MB, Large 256 MB, or Extra large 1 GB. Server sizing changes take effect on a restart. Keep collections bounded and measure memory during load tests.

The ceiling covers everything the room holds, not only its JavaScript objects. Typed arrays, Buffers, and WebAssembly memory count against it the same way, so a room that keeps decoded assets, audio buffers, or a WebAssembly heap can reach its ceiling while its object graph stays small.

If a room exceeds its ceiling, it restarts from its saved state and the log line names what it was holding and how much of that was off the JavaScript heap. Three restarts in a minute close the room.

When the server itself nears its memory limit, it refuses new rooms with E_TENANT_WAKING, which clients retry, and puts rooms with no players in them to sleep early. Rooms with players keep running. If there is no such room left to sleep, the server restarts its largest room from that room’s saved state, and changes since that room’s last snapshot are lost.

Connection limits

Enforced by the server, per connection or per project. Exceeding one closes the socket or refuses the frame rather than degrading quietly.

LimitDefault
Frames per second per connection240, with a burst allowance of twice that
Joins per client address per minute600, per server. The whole project is also capped at 6,000 a minute, per server, which one address cannot reach
New connections per client address per minute120, per server. An IPv6 address is counted by its /64
Chat line512 bytes of UTF-8. Refused by name and never trimmed. See chat
Chat lines per second per connection1, with a burst allowance of 5. The line that does not fit is refused by name, told once, and the connection stays open
Outbound buffer per socket before it is closed4 MiB for a player, 256 KB for a viewer, plus the size of a WELCOME still being sent. See slow clients
Time to send the join handshake after connecting5 seconds
Declared room classStandard: 64 MB; Large: 256 MB; Extra large: 1 GB
Room worker heapDerived from your server’s memory in production, so a room reaches its own limit before the server reaches its. 256 MB in local dev
Awake rooms per server20 Standard, 8 Large or 3 Extra large, per server. A project gets another server when one is full, except on the free plan, which stays on one
Rooms awake at once, free plan10, per server
New rooms per client address per minute60, per server. A join to a room the server already holds in memory is not counted; waking a hibernated room counts as a start. Refused with E_RATE_LIMITED
Players in one relay room64, or 16 on the free plan
Relay message size64 KB, counted with the message envelope. Refused with E_WRITE_REJECTED and the connection stays open. See relay rooms
Relay message bytes per second per connection256 KB, with a burst allowance of 1 MB. The message that does not fit is dropped, the sender is told once with E_RATE_LIMITED, and the connection stays open. Rooms with deployed room code are not counted
Viewers in one relay room’s audience256, or 64 on the free plan. Counted separately from the players, so a full audience never costs a player their seat. Refused by name with E_AUDIENCE_FULL. See audience rooms
Messages per second from one audience peer2, with a burst allowance of 10. The message that does not fit is dropped, the sender is told once, and the connection stays open
Audience credentials minted per room512, with a refill of 600 an hour
Audience credentials minted per IP address30, with a refill of 60 an hour
Audience credential lifetime3 hours. Scoped to the project and room it was minted for
Encoded state in one relay room512 KB. The write that would cross it is refused by name and the room keeps serving. See relay rooms
Records in one collection of a relay room1024. The add is refused by name
Write operations per client per second in a relay room35, counted per operation and not per frame. The frame that does not fit is refused whole by name and the connection stays open
Ticks of body poses kept for room.rewindWhatever physics.history says, 0 to 240 (one second at the maximum tick rate). Off unless declared. Costs about 120 bytes per body per tick of room heap, so 100 bodies at depth 30 is about 350 KB. See lag compensation
Declared history channels per physics collection8. Off unless declared. Costs channels x depth x bodies x 4 bytes of room heap, so 4 channels at depth 30 over 100 bodies is about 48 KB on top of the pose cost. See lag compensation
Room worker restarts per minute before the room closes3

Slow clients

When a socket’s outbound buffer exceeds its budget, irtio closes it with code 1008 and reason E_SLOW_CONSUMER.

SeatBuffer budget
Player4 MiB
Viewer256 KB

The budget is per socket. While a join or resync WELCOME is still leaving the server, the budget is raised by that welcome’s own size and returns to normal once it is sent, so a large state snapshot does not by itself cost a player the connection. There is no throttling. Reconnect to receive a fresh snapshot in a room with state. These limits also apply to platform connections forwarding player traffic through relay rooms and are not configurable per project. Refused bytes are not billed as egress.

Platform error text

A message irtio writes for one of its own errors is at most 96 bytes of UTF-8, so a str(96) field can always carry one. That covers every code in the error catalogue and every refusal a room API rejects with, including the replay walls. A message that would run longer is cut on a character boundary and ends with a single …, so it is always valid UTF-8 and you can tell a cut message from a short one.

The bound is on irtio’s text, not yours. Your own data still has to fit the field you declared for it, and @irtio/schema throws on a value that does not rather than trimming it. See schema limits.

The rows marked “per server” are abuse limits, not per-project budgets. They stop one server being flooded, and a project on more than one server has each of them enforcing its own copy. See limits per server.

Storage limits

LimitValue
Save generations kept per room, per deployment version10
Player KV value16 KiB
Player KV key256 bytes
Player id256 bytes
Distinct keys one player may hold in a project128
KV rows one project may hold1,000,000
Player KV reads per project12,000/min, burst 3,000
Player KV writes and deletes per project6,000/min, burst 2,000
Uploaded room bundle32 MiB

Going over a KV limit rejects the write with a named error code rather than truncating the value. See saves and restores and the error catalogue.

Public API limits

Quick match, player identity and leaderboard reads are open endpoints. A stranger holding your project key can call them, because that is what makes them work from a browser with no login. So they are rate limited on two dimensions at once, per calling address and per project.

The address limit stops one machine flooding. The project limit stops a botnet making one customer everyone else’s problem. The address allowance is drawn from first, so a single flooding client runs out before it can spend the allowance everyone else is sharing. An IPv6 address is counted by its /64, the block one home connection is given.

The project allowance is only spent by requests about a project that exists. On the identity exchange it is only spent by a valid identity, so a flood of junk credentials cannot use up the allowance your real players need. Link code redemption is the exception by design: every guess against your project counts, because that count is what keeps a short code safe to type.

EndpointPer addressPer project
POST /match (join a queue)30/min, burst 10600/min, burst 120
POST /match/party (mint a party code)20/min, burst 5300/min, burst 60
GET /match/status (queue depth)300/min, burst 303,000/min, burst 300
POST /v1/identity (mint an identity)60/hour, burst 30600/min, burst 600
POST /v1/identity/assertion (exchange)60/min, burst 203,000/min, burst 300
GET /v1/leaderboard/... (read a board)120/min, burst 303,000/min, burst 300
POST /v1/account/link-code (mint a link code)6/min, burst 3300/min, burst 60
POST /v1/account/link (redeem one)10/min, burst 5300/min, burst 60
/v1/account, /v1/account/devices (list, revoke, delete)30/min, burst 10600/min, burst 120
GET /v1/presence (where are these players)120/min, burst 303,000/min, burst 300
GET /v1/presence?wait= (watch for a change)20/min, burst 5600/min, burst 120
POST /v1/audience (mint an audience credential)60/hour, burst 30600/hour, burst 512 per room

Audience credential minting is limited per IP and per room. See audience rooms.

Leaderboard limits

LimitValue
Board rows one project may hold, across all boards1,000,000
Board name1-64 characters of a-z 0-9 . _ -
Scorea whole number, within the safe-integer range
Rows in one top read10 by default, 100 maximum
Neighbours on each side of an around read5 by default, 25 maximum
Device credentials one account may hold10
Link code lifetime10 minutes, single use

Scores are integers by decision, not by omission. See leaderboards.

Quick-match limits

LimitValue
Party size a queue may declare2 to 64
Queues one project may declare32
Queue name1-32 characters of a-z 0-9 . _ -
Default wait before “no match”30 seconds
Longest wait a client may ask for2 minutes
Live tickets one identity may hold in one queue1 (queueing again retires the earlier request)
Party size2 to 64, and never more than the queue seats
Party code lifetime10 minutes (expiry stops new members joining, not members already queued)
Room occupancy freshness for a backfill offer10 seconds

Skill rating limits

LimitValue
Players named in one result report2 to 64
Finishing placea whole number, 1 to 64 (ties share a place)
Rating rows one project may hold, across all queues1,000,000
Starting rating for a player who has never played1500, deviation 350
Rating band at 0 seconds waited100, plus 100 every 5 seconds, unbounded after 60

A rating is written only by room.ratings.report from your own room code. There is no client-originated path and no CLI submit, for the same reason there is none for leaderboards.

Client-side prediction

BudgetDefaultBeyond the budget
Non-owned predicted bodies64Bodies use kinematic proxies
Proxies256Bodies have no local collider

Configure maxPredictedBodies and maxProxyBodies in the client’s physics options. Monitor stats.absent and stats.lastResimMicros. See prediction.

Room log limits

LimitValue
One log line8,192 characters (UTF-16 units). The rest is cut. In irtio logs and the dashboard a long line may be cut without a marker; in local irtio dev it ends with … [truncated N chars]
Log ingest per project256 KiB per minute, counted after the per-line cut

Lines over the per-minute budget are dropped, not queued, and nothing in irtio logs marks the gap. If verbose lines go missing while you debug, log less or log a summary instead of a whole object. See logs.

Retention

DataKept for
Log lines7 days
Log lines, buffered inside your project’s serverthe last 500 per room
Rows in the room listpruned 30 days after the room was last reported. This removes the listing entry only; the room’s stored state is not affected
A room’s stored state (snapshot, saves, alarms)forever, unless its type declares a retention window
Save generationsthe newest 10 per room per deployment version
Deploymentsforever, because old snapshots need their schema to decode

Room state is kept forever by default. A room type can opt into having its rooms removed a set time after they last went to sleep by declaring retention: '10m' (see room types). The window is a floor: the sweep runs every 15 minutes, so a room is deleted on the first sweep after its window lapses.

Usage and metrics

The dashboard and irtio usage show room hours, voice participant-minutes, storage, and data out. See pricing for rates and allowances.

Operational metrics include sockets, roomsByState.*, ingressBytes, egressBytes, and rss. Use them to monitor connections, room lifecycle, traffic, and memory. Hibernated rooms do not accrue room hours.

Usage caps

Each billed meter has a cap, and what happens at the cap depends on whether the account has a card on file.

TierAt the cap
Free, no cardA hard wall. New joins, new rooms, new voice calls and new writes are refused. Nothing is deleted and nothing already running is killed
Card on fileAn alert. The billing page shows it, one email goes out, and the project keeps being served
Owner-set ceilingA hard wall, whether or not there is a card. Setting a ceiling is asking to be stopped, so you are stopped

Set a ceiling per project and per meter with PATCH /v1/projects/<id>/caps, and clear one by setting it to null. GET /v1/projects/<id>/caps shows where a project stands.

The voice wall refuses new calls, not calls already running. A participant who joined before the wall keeps talking until they leave.

Nothing already playing is cut off

A cap refuses new work, never work in flight:

  • A player connected before the cap tripped keeps playing until they leave. Their session is not closed, their room is not stopped, and their writes keep being accepted.
  • A room that already exists keeps running, and other players can still join it. Only a new room is refused by the room-hours cap.
  • A storage cap refuses writes. Reads keep serving and deletes keep working, which is how a project gets back under the line.
  • Nothing is ever deleted by a cap.

The cost of that grace is that data out keeps accruing under sessions that were already open. In practice session churn bounds it, and the overage is preferred to disconnecting somebody mid-game.

Enforcement timing

Usage caps update asynchronously. Room-hour enforcement can lag by about 95 seconds; storage enforcement can lag by about 17 minutes. A server temporarily unable to refresh caps keeps its last received state. Use headroom when choosing a ceiling.

Data-out enforcement

At the data-out allowance, sockets close with code 4290 and rooms hibernate with saved state. The dashboard warns at 80% and at the limit. Allowances reset with the billing period; see pricing.

Enforcement works in budget chunks, so usage can exceed the allowance by up to roughly 5 GB before forwarding stops. A separate sustained-rate limit drops messages if a project exceeds it for more than ten seconds, with a room log warning.

Replays

A room type that sets replay keeps recent state in memory so room.replay.clip(seconds) can save it. Nothing is stored until a clip is taken. With replay: { record: true } the room stores the whole match instead, in the background, as the oldest seconds fall out of memory. See replays.

LimitValueWhen you reach it
Recorded footageseconds, 60 by default, 300 at mostThe oldest seconds are dropped
Memory per recording room8 MBThe oldest seconds are dropped early, so a busy room holds fewer than seconds
One recorded momentmust fit in that 8 MBA room whose whole state is larger than the ring cannot record at all: recording stops, a line says so in the room’s logs, and room.replay.clip() is refused with E_CLIP_DISARMED
Clips per roomone per secondThe call is refused with E_CLIP_COOLDOWN
Clip size16 MBThe clip is refused with E_CLIP_TOO_LARGE
How long a clip is keptttl, 30 days by default, '1m' to '3650d'The clip is deleted and its link stops working
One recording256 MB of stored bytesRecording stops, a line says so in the room’s logs, and clips keep working
Recording chunks waiting on the store2Recording stops rather than leaving a hole in the footage, a line says so in the room’s logs, and clips keep working
Footage lost if the room stops without warningup to secondsEverything already stored still plays
How long a recording is keptttl, 30 days by default, counted from its first stored chunkThe recording is deleted and its link stops working. A ttl shorter than the match deletes the recording while it is still being made
Excerpt window30 secondsThe call is refused with E_EXCERPT_TOO_LONG, never shortened silently
Excerpts per room4 at once, one more per secondThe call is refused with E_EXCERPT_COOLDOWN
Excerpt output4 MB of framesThe call is refused with E_EXCERPT_TOO_LARGE. Ask for fewer seconds, or name the collections and ids you need

room.replay.excerpt() reads the same in-memory footage back into the room as plain frames. It is experimental, it stores nothing, and its limits are counted separately from clips, so a killcam that reads footage does not use up the room’s clips. Excerpt output is not filtered by interest management: it carries what the recorder recorded (public collections unless replay.view or replay.omniscient says otherwise), and the room decides who may see it.

Set ttl in the room config to change how long clips and recordings from that room type are kept. The lifetime is fixed when the clip is saved, so changing ttl does not re-time clips you already saved.

A link can keep working for up to a minute after the clip expires, so the '1m' floor is honest to about a minute.

A clip or a recording counts as stored bytes while it is kept, and as bandwidth each time it is watched.

Clips and recordings are stored and served compressed. The recording cap counts compressed stored bytes; replay() handles decoding.