Skip to main content

IPRoyal API Guide: Programmatic Proxy Management

· 10 min read
Yassine El Haddad
Software Developer & Automation Specialist

I build production AI agents, web scrapers, and automation pipelines. Most of what I publish here comes from the actual problems they run into: proxies that get banned, anti-bot stacks that fingerprint your client, RAG that drifts when the underlying data moves. Stack: Python, TypeScript, Go, FastAPI, LangChain, Crawlee, Playwright, deployed on AWS, GCP, and Cloudflare.

Yes — IPRoyal has a REST API. It lets you generate proxy credentials with geo-filters, check remaining bandwidth, manage sub-users, and build fully automated rotation pipelines without touching the dashboard.

This guide covers every API surface: authentication, proxy list generation, bandwidth polling, sub-user management, error handling, and ready-to-run Python and Node.js examples.

Prerequisites​

Before you make your first API call you need:

  1. An active IPRoyal account with at least one proxy product (residential, datacenter, or mobile).
  2. An API key generated from Dashboard → Account → API.
  3. The base URL: https://dashboard.iproyal.com/api/v1.

All endpoints require the Authorization: Bearer <YOUR_API_KEY> header. There is no OAuth flow — the key is a long-lived static token, so keep it in an environment variable, not in source code.


Authentication​

Generating an API key​

Log in to dashboard.iproyal.com, navigate to Account → API, and click Generate API Key. Copy it immediately — IPRoyal only shows the full token once.

Verifying the key​

curl -s https://dashboard.iproyal.com/api/v1/user/profile \
-H "Authorization: Bearer $IPROYAL_API_KEY"

A 200 OK with your account object confirms the key works. A 401 means the key is wrong or has been revoked.

Python helper​

import os
import requests

API_KEY = os.environ["IPROYAL_API_KEY"]
BASE_URL = "https://dashboard.iproyal.com/api/v1"

session = requests.Session()
session.headers.update({"Authorization": f"Bearer {API_KEY}"})

def get(path: str, **params) -> dict:
response = session.get(f"{BASE_URL}{path}", params=params)
response.raise_for_status()
return response.json()

Generating proxy lists​

The proxy list endpoint returns a formatted list of host:port:user:pass strings, optionally filtered by country and proxy type. You can pipe the output directly into a scraper without manual copy-paste from the dashboard.

Endpoint​

GET /proxy-manager/proxies

Query parameters​

ParameterTypeDescription
typestringresidential, datacenter, or mobile
countrystringISO 3166-1 alpha-2 (e.g., us, de, jp)
citystringCity name (e.g., new york) — residential only
formatstringhost_port_user_pass or user_pass_host_port
countintegerNumber of proxy strings to return (max 1000)
sessionTypestringrotating or sticky
sessionDurationintegerSticky session length in minutes (1–10080)

Python example — rotating US residential proxies​

proxies_raw = get(
"/proxy-manager/proxies",
type="residential",
country="us",
format="host_port_user_pass",
count=50,
sessionType="rotating",
)

proxy_list = proxies_raw.get("proxies", [])
# Each entry: "geo.iproyal.com:12321:user_country-us:password"
print(f"Received {len(proxy_list)} proxy strings")

Node.js example — sticky German proxies (1-hour sessions)​

import fetch from "node-fetch";

const BASE_URL = "https://dashboard.iproyal.com/api/v1";
const API_KEY = process.env.IPROYAL_API_KEY;

async function getProxies(options = {}) {
const params = new URLSearchParams({
type: "residential",
country: "de",
format: "host_port_user_pass",
count: "10",
sessionType: "sticky",
sessionDuration: "60",
...options,
});

const res = await fetch(`${BASE_URL}/proxy-manager/proxies?${params}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});

if (!res.ok) throw new Error(`IPRoyal API error: ${res.status}`);
return res.json();
}

const data = await getProxies();
console.log(data.proxies);

Formatting the output for use in Playwright​

import re

def parse_proxy_string(raw: str) -> dict:
"""Convert 'host:port:user:pass' to a Playwright proxy object."""
host, port, user, password = raw.split(":")
return {
"server": f"http://{host}:{port}",
"username": user,
"password": password,
}

playwright_proxies = [parse_proxy_string(p) for p in proxy_list]

Checking bandwidth usage​

Running a long scraping job without monitoring bandwidth leads to surprise cost spikes. IPRoyal exposes usage data via the /usage endpoint so you can poll it periodically and halt jobs before exhausting your balance.

Endpoint​

GET /usage/bandwidth

Response fields​

FieldTypeDescription
totalPurchasedfloatGB purchased in the current billing period
totalUsedfloatGB consumed
totalRemainingfloatGB remaining
usagePercentfloatPercentage of purchased bandwidth consumed

Python — bandwidth guard​

def get_bandwidth() -> dict:
return get("/usage/bandwidth")

def assert_sufficient_bandwidth(min_gb: float = 1.0):
usage = get_bandwidth()
remaining = usage.get("totalRemaining", 0)
if remaining < min_gb:
raise RuntimeError(
f"Low bandwidth: {remaining:.2f} GB remaining. "
"Top up at https://iproyal.com/?r=use-apify"
)
print(f"Bandwidth OK — {remaining:.2f} GB remaining ({usage['usagePercent']:.1f}% used)")

Node.js — periodic usage check​

async function checkBandwidth(minGB = 1.0) {
const res = await fetch(`${BASE_URL}/usage/bandwidth`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});

const data = await res.json();
if (data.totalRemaining < minGB) {
throw new Error(
`Only ${data.totalRemaining.toFixed(2)} GB left. Top up your IPRoyal account.`
);
}
return data;
}

// Poll every 5 minutes during long jobs
setInterval(() => checkBandwidth(0.5).catch(console.error), 5 * 60 * 1000);

Sub-user management​

Sub-users let you allocate bandwidth to separate teams, projects, or clients without sharing the main API key. Each sub-user gets its own credentials and bandwidth quota.

Create a sub-user​

POST /sub-users
import json

def create_sub_user(username: str, password: str, bandwidth_gb: float) -> dict:
response = session.post(
f"{BASE_URL}/sub-users",
json={
"username": username,
"password": password,
"bandwidthLimitGB": bandwidth_gb,
},
)
response.raise_for_status()
return response.json()

new_user = create_sub_user("scraper-team-a", "strongpassword123", 10.0)
print(f"Sub-user created: {new_user['id']}")

List sub-users and their usage​

sub_users = get("/sub-users")
for user in sub_users.get("users", []):
print(
f"{user['username']}: "
f"{user['usedGB']:.2f} / {user['limitGB']:.2f} GB used"
)

Node.js — reset sub-user bandwidth​

async function resetSubUserBandwidth(subUserId, newLimitGB) {
const res = await fetch(`${BASE_URL}/sub-users/${subUserId}/bandwidth`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ bandwidthLimitGB: newLimitGB }),
});

if (!res.ok) throw new Error(`Failed to reset bandwidth: ${res.status}`);
return res.json();
}

Building automated proxy workflows​

Pattern 1 — Pre-flight check before a scraping run​

This pattern checks bandwidth before starting a job and fetches a fresh proxy pool, avoiding stale credentials.

import random
import time

def run_scraping_job(urls: list[str], country: str = "us"):
# 1. Pre-flight bandwidth check
assert_sufficient_bandwidth(min_gb=0.5)

# 2. Fetch a fresh proxy pool
raw = get(
"/proxy-manager/proxies",
type="residential",
country=country,
format="host_port_user_pass",
count=100,
sessionType="rotating",
)
pool = raw.get("proxies", [])
if not pool:
raise RuntimeError("Empty proxy pool returned by IPRoyal API")

# 3. Scrape with random proxy selection
for url in urls:
proxy_str = random.choice(pool)
host, port, user, password = proxy_str.split(":")
proxies = {"http": f"http://{user}:{password}@{host}:{port}"}

try:
resp = requests.get(url, proxies=proxies, timeout=15)
resp.raise_for_status()
yield url, resp.text
except requests.RequestException as exc:
print(f"Failed {url}: {exc}")
time.sleep(1)

Pattern 2 — Per-domain sticky sessions​

Some sites require session continuity (login flows, multi-page scrapes). This pattern assigns a sticky session per target domain.

from functools import lru_cache

@lru_cache(maxsize=128)
def get_sticky_proxy(domain: str, country: str = "us") -> dict:
"""Cache a sticky proxy per domain for the lifetime of the process."""
raw = get(
"/proxy-manager/proxies",
type="residential",
country=country,
format="host_port_user_pass",
count=1,
sessionType="sticky",
sessionDuration=60, # 1-hour sticky session
)
proxy_str = raw["proxies"][0]
host, port, user, password = proxy_str.split(":")
return {"http": f"http://{user}:{password}@{host}:{port}"}

Pattern 3 — Async rotation with aiohttp​

For high-concurrency pipelines, async rotation maximizes throughput by distributing concurrent requests across a pre-fetched pool.

import asyncio
import aiohttp

async def fetch(session: aiohttp.ClientSession, url: str, proxy: str) -> tuple:
host, port, user, password = proxy.split(":")
proxy_url = f"http://{host}:{port}"
try:
async with session.get(
url,
proxy=proxy_url,
proxy_auth=aiohttp.BasicAuth(user, password),
timeout=aiohttp.ClientTimeout(total=15),
) as resp:
return url, resp.status, await resp.text()
except Exception as exc:
return url, None, str(exc)

async def scrape_batch(urls: list[str], country: str = "us"):
raw = get(
"/proxy-manager/proxies",
type="residential",
country=country,
format="host_port_user_pass",
count=min(len(urls), 100),
sessionType="rotating",
)
pool = raw["proxies"]

async with aiohttp.ClientSession() as http:
tasks = [
fetch(http, url, pool[i % len(pool)])
for i, url in enumerate(urls)
]
return await asyncio.gather(*tasks)

Error handling​

The IPRoyal API uses standard HTTP status codes. Most errors are client-side (bad parameters, exhausted bandwidth, or invalid key). Here is a concise error-handling wrapper that you can reuse across all endpoints.

Status codes​

CodeMeaningAction
200SuccessContinue
400Bad request (invalid params)Check query/body parameters
401UnauthorizedVerify API key
403ForbiddenCheck account permissions or quota
404Not foundVerify endpoint path
429Rate limitedBack off exponentially
500Server errorRetry with back-off; contact support if persistent

Python — robust retry wrapper​

import time
from requests.exceptions import HTTPError

def get_with_retry(path: str, max_retries: int = 3, **params) -> dict:
for attempt in range(max_retries):
try:
return get(path, **params)
except HTTPError as exc:
status = exc.response.status_code
if status == 429:
wait = 2 ** attempt
print(f"Rate limited. Retrying in {wait}s...")
time.sleep(wait)
elif status in (500, 502, 503):
wait = 2 ** attempt
print(f"Server error {status}. Retrying in {wait}s...")
time.sleep(wait)
else:
raise # 4xx errors are not retriable
raise RuntimeError(f"Max retries exceeded for {path}")

Node.js — retry with exponential back-off​

async function fetchWithRetry(path, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch(`${BASE_URL}${path}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
...options,
});

if (res.ok) return res.json();

if ([429, 500, 502, 503].includes(res.status)) {
const wait = Math.pow(2, attempt) * 1000;
console.warn(`HTTP ${res.status}. Retrying in ${wait}ms...`);
await new Promise((r) => setTimeout(r, wait));
} else {
throw new Error(`Non-retriable error: ${res.status}`);
}
}
throw new Error(`Max retries exceeded for ${path}`);
}

IPRoyal API vs manual dashboard management​

TaskDashboardAPI
Generate single proxy string✅✅
Bulk-generate 100+ proxies❌ (tedious)✅
Check bandwidth in a script❌✅
Create sub-users programmatically❌✅
Trigger rotation on failure❌✅
CI/CD pipeline integration❌✅

Once your scraping pipeline exceeds a handful of target sites, the API becomes essential. Manual credential management at scale — rotating IPs per domain, reallocating quotas between projects, enforcing bandwidth guards — is impractical in the dashboard.


When IPRoyal's API is (and isn't) the right choice​

Best for:

  • Mid-scale scraping pipelines (10GB to 500GB/month) on pay-as-you-go basis.
  • Projects where bandwidth never expires between bursts.
  • Teams that need lightweight sub-user isolation without a full enterprise proxy management layer.

Consider alternatives if:

  • You need a managed proxy API that also handles JavaScript rendering, CAPTCHA solving, and IP scoring (look at Bright Data's Web Unlocker API).
  • You need a complete scraping platform with scheduling, storage, and monitoring — Apify wraps proxies directly into Actor runs, so you never write rotation code manually.
  • You need carrier-grade 4G/5G mobile IPs. IPRoyal's residential pool is ISP-based, not mobile-carrier-based.

For more on how IPRoyal compares to the wider market, see Best Residential Proxies 2026 and the full IPRoyal residential proxy review.


FAQ​

Does IPRoyal have an API?

Yes. IPRoyal provides a REST API accessible at https://dashboard.iproyal.com/api/v1. You can use it to generate proxy credentials, check bandwidth usage, manage sub-users, and automate proxy rotation workflows. Authentication is via a static Bearer API key generated from the dashboard.

How do I generate proxy lists programmatically with IPRoyal?

Call GET /proxy-manager/proxies with type, country, count, and sessionType query parameters. The response contains an array of pre-formatted proxy strings ready for use with requests, aiohttp, Playwright, or any HTTP library. See the Generating proxy lists section above for full Python and Node.js examples.

Can I check my bandwidth usage via the IPRoyal API?

Yes — call GET /usage/bandwidth. The response includes totalPurchased, totalUsed, totalRemaining, and usagePercent. You can poll this endpoint inside a scraping loop to halt execution before your balance runs out.

Does IPRoyal support sub-user management via API?

Yes. You can create sub-users with individual bandwidth limits via POST /sub-users, list their usage with GET /sub-users, and update their quota with PATCH /sub-users/{id}/bandwidth.

What HTTP errors does the IPRoyal API return?

Standard REST codes: 401 for authentication failure, 400 for bad parameters, 429 for rate limiting (apply exponential back-off), and 5xx for transient server errors (retryable). See the Error handling section for copy-paste retry wrappers in Python and Node.js.

How do I integrate IPRoyal proxies into a Playwright scraper?

Generate a proxy string from the API, parse it into { server, username, password }, and pass it to the Playwright BrowserType.launch() context proxy option. For multi-page sessions, use the sticky session type to maintain the same exit IP across page navigations.


Get started with IPRoyal — pay-as-you-go residential proxies with a full API and non-expiring bandwidth.

Advertise on this page