Rate Limits

NinjasProxy does not impose artificial request-per-second throttles. Your throughput is governed by three practical constraints: concurrent connections allowed by your plan, the per-GB bandwidth billing model, and your account balance.

Concurrent Connections

Each open TCP connection to the proxy gateway counts against your concurrent-connection allowance. New connections beyond this limit receive a 503 Service Unavailable or a TCP reset until a slot frees up.

PlanMax concurrent connections
Starter50
Growth200
Pro500
EnterpriseCustom (contact sales)
Your current plan and concurrency limit are visible in Portal → Settings → Plan. Short-term bursts slightly above the limit may be allowed by a small grace buffer, but sustained overuse will trigger connection rejection.

Request Rate

There is no separate requests-per-second (RPS) quota. Your effective RPS is determined naturally by:

A practical formula: Max RPS ≈ Concurrent Connections / Average Request Latency (s). With 200 concurrent connections and an average 1 s latency, you can sustain ~200 RPS.

Bandwidth Billing

Bandwidth is billed per-GB of data sent through the proxy (request + response bytes combined). There is no artificial bandwidth throttle — your connection is only limited by peer network capacity (typically 100 Mbps+ per residential/mobile peer).

What Happens When Balance Runs Out

When your prepaid balance reaches zero, new proxy requests are rejected immediately with a 402 Payment Required-equivalent response from the gateway. Existing open connections are not mid-stream terminated, but no new connections are accepted until the balance is topped up.

# Balance-exhausted response from the proxy gateway
HTTP/1.1 402 Payment Required
X-NinjasProxy-Error: insufficient_balance
Content-Type: application/json

{"error": "insufficient_balance", "message": "Top up your balance at portal.ninjasproxy.com"}
Set up auto top-up in Portal → Billing to avoid interruption. You can configure a minimum-balance threshold and an automatic top-up amount.

Best Practices

Connection pooling

Reuse connections with HTTP keep-alive instead of opening a fresh TCP connection per request. Most HTTP clients do this automatically when you reuse the session object:

import requests

# ✅ Good — one session, connection pool reused
session = requests.Session()
session.proxies = {
    "http":  "http://USERNAME:API_KEY@r.ninjasproxy.com:8080",
    "https": "http://USERNAME:API_KEY@r.ninjasproxy.com:8080",
}

for url in urls:
    r = session.get(url, timeout=30)

# ❌ Bad — new TCP connection for every request
for url in urls:
    r = requests.get(url, proxies=proxies, timeout=30)

Retry logic with exponential backoff

Transient failures (peer disconnect, target 5xx, connection timeout) are normal in distributed proxy networks. Wrap requests in a retry loop with exponential backoff:

import requests
from tenacity import (
    retry,
    stop_after_attempt,
    wait_exponential,
    retry_if_exception_type,
    retry_if_result,
    before_sleep_log,
)
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

PROXIES = {
    "http":  "http://USERNAME:API_KEY@r.ninjasproxy.com:8080",
    "https": "http://USERNAME:API_KEY@r.ninjasproxy.com:8080",
}

def is_retryable_status(response: requests.Response) -> bool:
    """Retry on 5xx errors and 407 (proxy auth glitch), but not on 402 (balance)."""
    return response.status_code in {500, 502, 503, 504, 407}

@retry(
    retry=(
        retry_if_exception_type((requests.ConnectionError, requests.Timeout))
        | retry_if_result(is_retryable_status)
    ),
    wait=wait_exponential(multiplier=1, min=2, max=30),
    stop=stop_after_attempt(5),
    before_sleep=before_sleep_log(logger, logging.WARNING),
    reraise=True,
)
def fetch(url: str, session: requests.Session) -> requests.Response:
    return session.get(url, proxies=PROXIES, timeout=30)

# Usage
with requests.Session() as s:
    for url in urls:
        try:
            r = fetch(url, s)
            if r.status_code == 402:
                raise RuntimeError("Balance exhausted — top up at portal.ninjasproxy.com")
            print(r.status_code, url)
        except Exception as e:
            print(f"Failed after retries: {url} — {e}")

Adaptive concurrency

If you're running many workers, use a semaphore to cap concurrency below your plan limit, leaving headroom for retries:

import asyncio
import httpx

MAX_CONCURRENT = 150  # Stay below your plan limit

async def scrape(url: str, client: httpx.AsyncClient, sem: asyncio.Semaphore) -> str:
    async with sem:
        r = await client.get(url, timeout=30)
        r.raise_for_status()
        return r.text

async def main(urls: list[str]):
    proxy = "http://USERNAME:API_KEY@r.ninjasproxy.com:8080"
    sem   = asyncio.Semaphore(MAX_CONCURRENT)

    async with httpx.AsyncClient(proxy=proxy) as client:
        tasks = [scrape(url, client, sem) for url in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)

    for url, result in zip(urls, results):
        if isinstance(result, Exception):
            print(f"Error {url}: {result}")
        else:
            print(f"OK {url}: {len(result)} bytes")

asyncio.run(main(["https://example.com"] * 500))

Next Steps