IPRoyal API Guide: Programmatic Proxy Management
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:
- An active IPRoyal account with at least one proxy product (residential, datacenter, or mobile).
- An API key generated from Dashboard → Account → API.
- 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
| Parameter | Type | Description |
|---|---|---|
type | string | residential, datacenter, or mobile |
country | string | ISO 3166-1 alpha-2 (e.g., us, de, jp) |
city | string | City name (e.g., new york) — residential only |
format | string | host_port_user_pass or user_pass_host_port |
count | integer | Number of proxy strings to return (max 1000) |
sessionType | string | rotating or sticky |
sessionDuration | integer | Sticky 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
| Field | Type | Description |
|---|---|---|
totalPurchased | float | GB purchased in the current billing period |
totalUsed | float | GB consumed |
totalRemaining | float | GB remaining |
usagePercent | float | Percentage 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
| Code | Meaning | Action |
|---|---|---|
200 | Success | Continue |
400 | Bad request (invalid params) | Check query/body parameters |
401 | Unauthorized | Verify API key |
403 | Forbidden | Check account permissions or quota |
404 | Not found | Verify endpoint path |
429 | Rate limited | Back off exponentially |
500 | Server error | Retry 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
| Task | Dashboard | API |
|---|---|---|
| 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.
