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.