Skip to content

Limits

Every account includes 5 concurrent browsers and 48 free browser hours. To raise either, email [email protected].

Limit Value Counted as
Concurrent browsers 5 Sessions in state ACTIVE right now
Browser hours 48 (172,800 seconds) Lifetime total across every session you have ever run
Creates per minute 60 Create requests to /connect and POST /api/browser/create in a 60 second sliding window. Reconnects do not count
Session lifetime 120 to 86,400 seconds maxLifetime, default 7,200
Minimum billed per session 30 seconds Every session, however short

The 48 hours are not a monthly allowance and they do not reset. Every session adds its full duration to the total when it ends, whatever state it ended in: closed by you, expired at its lifetime, or dead because the browser went away.

A session that dies at minute 40 of a planned 60 bills 40 minutes. Every session counts for at least 30 seconds, so 10 launches that each fail after 5 seconds use 300 seconds, not 50.

Running sessions are never killed at the cap. When the total is reached, the browsers you have keep going and new launches are refused.

maxLifetime is seconds and it is clamped, not rejected. Ask for 30 and you get 120. Ask for a week and you get 86,400. 0 is treated as omitted. Nothing in the response tells you the value moved, so read the expiresAt field on the session rather than assuming what you sent took effect.

Omitting it gives you 7,200 seconds, and all 7,200 of them count against your 48 hours if nothing closes the browser. A session outlives the CDP connection that created it, so a script that exits without closing keeps spending. Close sessions you are finished with.

The sixth simultaneous browser is refused:

{
"error": "Concurrent browser limit reached",
"code": "CONCURRENCY_LIMIT",
"limit": 5,
"active": 5
}

429, with retry-after: 5. A slot opens the moment any running session reaches CLOSED, DEAD or EXPIRED.

Check before you launch with GET /user/api/limits. It does not count against the rate limit:

Terminal window
curl https://browser.rayobyte.com/user/api/limits \
-H "x-api-key: rb_live_YOUR_KEY"
{ "limit": 5, "active": 0, "remaining": 5, "unlimited": false }

remaining also subtracts creates still in progress, so it can be lower than limit minus active.

More than 60 creates in 60 seconds is also 429, and it is a different thing. Every create request counts, including ones refused with a 400 and ones the rate limit itself refused, so a retry loop that hammers a 429 keeps the window full. Reconnects do not count.

{
"error": "Rate limit exceeded",
"code": "RATE_LIMIT",
"limit": 60,
"window": "60s"
}

Both rejections share the status code. Branch on code: CONCURRENCY_LIMIT or RATE_LIMIT. Both bodies carry a limit, so that field alone does not tell them apart. The retry-after header is right for both, 5 seconds for concurrency and 60 for the rate limit.

{
"error": "You've used your 48 free browser hours. Contact [email protected] to keep going.",
"code": "USAGE_CAP_REACHED",
"remainingSeconds": 0
}

402. Retrying does not help. Contact [email protected] and the cap is raised on your account with no key change and no restart.

400. A proxy is mandatory on every launch. The two create paths answer in different shapes.

GET /connect answers in plain text, with the code in the X-Error-Code response header and the rule that failed in parentheses:

HTTP/2 400
x-error-code: PROXY_REQUIRED
content-type: text/plain
A proxy is required for this account. Use your Rayobyte residential proxy or supply your own. (no proxy supplied)

POST /api/browser/create answers in JSON:

{
"code": "PROXY_REQUIRED",
"error": "A proxy is required for this account. Use your Rayobyte residential proxy or supply your own.",
"detail": "no proxy supplied"
}

An empty string and a string of spaces both land here rather than in PROXY_INVALID. A malformed proxy gives 400 PROXY_INVALID with the broken rule as the detail, such as missing port.

Status Code Meaning Worth retrying
400 PROXY_REQUIRED No proxy on the request No
400 PROXY_INVALID Proxy present, malformed No
401 Missing or invalid API key No
402 USAGE_CAP_REACHED 48 browser hours used No
422 PROXY_UNREACHABLE The browser could not connect through the proxy No. Fix the proxy
429 CONCURRENCY_LIMIT 5 browsers already running After retry-after, 5 seconds
429 RATE_LIMIT More than 60 creates this minute After retry-after, 60 seconds
503 CAPACITY Every backend was full or unhealthy Yes, retry-after is 2 seconds
503 Authentication service temporarily unavailable Yes
500, 502 Browser creation failed. The browser did not start Once

The proxy is checked for form, not dialed, before a browser is created. A well-formed proxy with the wrong password, or one that is down, fails when the browser starts, and both create paths answer 422 with the code PROXY_UNREACHABLE and this message:

The browser could not connect through that proxy. Check the proxy host, port, username and password.

Retrying with the same proxy returns the same thing.

503 CAPACITY is the only one of these that is ours rather than yours. It means no machine had a free seat, and the message is All browsers are busy right now, try again in a moment.

On GET /connect, the 400, 422 and 503 codes arrive in the X-Error-Code header with a plain-text body. 401, 402 and 429 are JSON on both paths, because they are answered before the route runs.

The current SDK releases (Python 2.2.0, Node.js 0.2.0) do not pass the code through, and their exception class does not reliably say which of the two limits was hit. Treat any 429 the same way: wait for the retry value, which comes from the retry-after header and is right for both.

import time
from rayobrowse import ConcurrencyLimitError, RateLimitError
try:
ws_url = client.connect_url(os="windows", proxy=PROXY)
except (ConcurrencyLimitError, RateLimitError) as e:
time.sleep(e.retry_after) # 5 for a full account, 60 for the rate limit
import { ConcurrencyLimitError, RateLimitError } from 'rayobrowse';
try {
const wsUrl = await client.connectUrl({ os: 'windows', proxy: PROXY });
} catch (err) {
if (err instanceof ConcurrencyLimitError || err instanceof RateLimitError) {
await new Promise((r) => setTimeout(r, err.retryAfter * 1000));
} else {
throw err;
}
}

To know which limit it was, call the HTTP API directly and read code, or check GET /user/api/limits.

Email [email protected]. Concurrency and the hours cap are raised per account and take effect immediately.

Was this page helpful?