Rate Limits

NinjaProxy does not impose an artificial requests-per-second quota. Real throughput is governed by your plan's connection allowance, your current balance, and the behavior of the target sites you are connecting to.

Concurrent connections

Each open TCP connection to the proxy gateway counts against your concurrent connection allowance. If you open more than your plan allows, new connections can be rejected until existing ones finish.

PlanMax concurrent connections
Starter50
Growth200
Pro500
EnterpriseCustom
Your current plan and connection allowance are visible in the portal. Leave some headroom for retries instead of running permanently at the exact ceiling.

Request rate

There is no separate RPS switch. Effective request rate depends on the number of open connections, request latency, and whether your client reuses keep-alive connections.

  • The number of concurrent connections your plan allows
  • The latency of the target site
  • Connection reuse in your client

A simple rule of thumb is Max RPS ≈ concurrent connections / average latency.

Bandwidth billing

Usage-billed products charge by data transferred through the proxy. Fixed-price datacenter products follow their own plan terms, but the proxy gateway itself does not add an artificial bandwidth throttle on top.

  • Datacenter: typically fixed monthly pricing
  • Residential: billed by usage
  • Mobile: billed by usage

What happens when balance runs out

When your prepaid balance reaches zero, new proxy requests are rejected until you top up. Existing traffic that is already in flight is not the same thing as opening new requests.

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

{"error": "insufficient_balance", "message": "Top up your balance at portal.ninjasproxy.dev"}
Enable auto top-up in Portal → Billing if you do not want low balance to interrupt scraping or automation jobs.

Best practices

Reuse connections

Reuse sessions and connection pools instead of creating a fresh TCP connection for every request.

import requests

# ✅ Good — one session, connection pool reused
session = requests.Session()
session.proxies = {
    "http":  "http://USERNAME:API_KEY@<HTTP_ENDPOINT>",
    "https": "http://USERNAME:API_KEY@<HTTP_ENDPOINT>",
}

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 with backoff

Transient failures happen. Retry network failures and target-side 5xx responses with exponential backoff, but do not blindly retry balance exhaustion.

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@<HTTP_ENDPOINT>",
    "https": "http://USERNAME:API_KEY@<HTTP_ENDPOINT>",
}

def is_retryable_status(response: requests.Response) -> bool:
    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)

with requests.Session() as s:
    for url in urls:
        r = fetch(url, s)
        if r.status_code == 402:
            raise RuntimeError("Balance exhausted — top up at portal.ninjasproxy.dev")

Cap concurrency deliberately

If you run many workers, use a semaphore or worker pool and stay below your plan limit so retries and spikes still have room.

import asyncio
import httpx

MAX_CONCURRENT = 150

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@<HTTP_ENDPOINT>"
    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")

Next Steps

Use these docs with AI

Start with the AI guide or hand llms.txt to your assistant.
Use with AI
llms.txt