Scraping at scale means one thing: you cannot manually click "rotate IP" in a dashboard every 30 seconds. Whether you're running 50 concurrent browser sessions, a distributed Scrapy cluster, or a Playwright pipeline, IP rotation needs to happen automatically — triggered by code, not a human.
This guide covers exactly that: how to use the ProxyGrow IP rotation API to programmatically change your mobile proxy's exit IP, how to verify the change, and how to integrate it into real scraping workflows.
Get API Access
Dedicated mobile proxies with instant IP rotation API — Ukraine, Romania, Latvia.
Why API Rotation Matters
Residential and datacenter proxies usually rotate IPs passively — you get a new IP on each connection by pool assignment. Mobile proxies work differently. Each modem is a physical device with a SIM card, permanently connected to a carrier network. You get a static IP for your session, which is excellent for account management and session continuity — but it means you need to actively trigger a new IP when you want one.
Manual rotation is fine for occasional use. It breaks down the moment you need to:
- Rotate after every N requests automatically
- React to a 429 or CAPTCHA response by rotating and retrying
- Run parallel tasks each starting with a fresh IP
- Schedule rotations at time-based intervals without human involvement
That's where the rotation API comes in. One HTTP GET request triggers a modem reconnect, and within 3–5 seconds you have a new carrier-assigned IP.
How ProxyGrow IP Rotation Works
ProxyGrow operates dedicated physical modems — USB and M.2 4G/5G devices connected to servers in data centers. Each modem holds one SIM card from a real carrier (Kyivstar, Vodafone UA, Orange Romania, etc.).
When you call the rotation API, the system issues an AT command (AT+CFUN=1,1 or equivalent) to the modem, forcing it to drop the carrier connection and re-register on the network. The carrier assigns a new IP from its dynamic pool. The whole cycle — disconnect, reconnect, new IP assignment — takes 3 to 5 seconds under normal carrier conditions.
This is a true IP change at the carrier level, not a proxy pool shuffle. The new IP is a fresh mobile carrier IP that has never been used by your session before.
The Rotation Link Format
Every dedicated modem in your ProxyGrow account has a unique rotation URL:
http://api.proxygrow.com/rotate?key=YOUR_KEY&modem=MODEM_ID
YOUR_KEY— your account API key, found in the dashboard under API SettingsMODEM_ID— the identifier of the specific modem you want to rotate (e.g.,modem_01,ua_kyiv_01)
A successful rotation returns HTTP 200 with a JSON body:
{
"status": "ok",
"modem": "modem_01",
"new_ip": "93.170.xx.xx",
"rotated_at": "2025-06-16T14:23:01Z"
}
On failure (wrong key, rate limit exceeded, modem offline):
{
"status": "error",
"code": "rate_limit_exceeded",
"retry_after": 60
}
Checking the Current IP
Before or after rotation, you can query the current exit IP of any modem:
http://api.proxygrow.com/ip?key=YOUR_KEY&modem=MODEM_ID
Response:
{
"modem": "modem_01",
"ip": "93.170.xx.xx",
"carrier": "Kyivstar",
"country": "UA",
"updated_at": "2025-06-16T14:23:04Z"
}
Use this endpoint to confirm the rotation completed and the IP actually changed before sending traffic through the proxy.
Python Example
The most common integration. Use requests to trigger rotation, verify the IP changed, then proceed with your scraping task.
import requests
import time
API_KEY = "your_api_key_here"
MODEM_ID = "modem_01"
PROXY_HOST = "proxy.proxygrow.com"
PROXY_PORT = 10001
ROTATE_URL = f"http://api.proxygrow.com/rotate?key={API_KEY}&modem={MODEM_ID}"
IP_CHECK_URL = f"http://api.proxygrow.com/ip?key={API_KEY}&modem={MODEM_ID}"
def get_current_ip():
response = requests.get(IP_CHECK_URL, timeout=10)
return response.json().get("ip")
def rotate_ip(max_retries=3):
previous_ip = get_current_ip()
for attempt in range(max_retries):
resp = requests.get(ROTATE_URL, timeout=15)
data = resp.json()
if data.get("status") == "error":
if data.get("code") == "rate_limit_exceeded":
wait = data.get("retry_after", 60)
print(f"Rate limited. Waiting {wait}s before retry...")
time.sleep(wait)
continue
raise RuntimeError(f"Rotation failed: {data}")
# Wait for modem to reconnect
time.sleep(5)
new_ip = get_current_ip()
if new_ip != previous_ip:
print(f"IP rotated: {previous_ip} → {new_ip}")
return new_ip
print(f"IP unchanged after rotation attempt {attempt + 1}, retrying...")
time.sleep(10)
raise RuntimeError("Failed to confirm IP change after rotation")
def scrape_with_rotation(urls):
proxies = {
"http": f"socks5h://{PROXY_HOST}:{PROXY_PORT}",
"https": f"socks5h://{PROXY_HOST}:{PROXY_PORT}",
}
for i, url in enumerate(urls):
if i % 10 == 0 and i > 0:
rotate_ip()
try:
resp = requests.get(url, proxies=proxies, timeout=30)
if resp.status_code == 429:
print(f"Got 429 on {url}, rotating IP and retrying...")
rotate_ip()
resp = requests.get(url, proxies=proxies, timeout=30)
print(f"[{resp.status_code}] {url}")
except Exception as e:
print(f"Error on {url}: {e}")
urls = ["https://example.com/page/1", "https://example.com/page/2"]
scrape_with_rotation(urls)
Node.js / JavaScript Example
For async workflows using native fetch (Node 18+):
const API_KEY = 'your_api_key_here';
const MODEM_ID = 'modem_01';
const ROTATE_URL = `http://api.proxygrow.com/rotate?key=${API_KEY}&modem=${MODEM_ID}`;
const IP_CHECK_URL = `http://api.proxygrow.com/ip?key=${API_KEY}&modem=${MODEM_ID}`;
async function getCurrentIp() {
const res = await fetch(IP_CHECK_URL);
const data = await res.json();
return data.ip;
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function rotateIp() {
const previousIp = await getCurrentIp();
const res = await fetch(ROTATE_URL);
const data = await res.json();
if (data.status === 'error') {
if (data.code === 'rate_limit_exceeded') {
const waitMs = (data.retry_after || 60) * 1000;
console.log(`Rate limited. Waiting ${data.retry_after}s...`);
await sleep(waitMs);
return rotateIp();
}
throw new Error(`Rotation failed: ${JSON.stringify(data)}`);
}
// Wait for modem reconnect
await sleep(5000);
const newIp = await getCurrentIp();
if (newIp !== previousIp) {
console.log(`IP rotated: ${previousIp} → ${newIp}`);
return newIp;
}
throw new Error('IP did not change after rotation');
}
async function runTask() {
console.log('Rotating IP before task...');
const ip = await rotateIp();
console.log(`Working with IP: ${ip}`);
// Your scraping/automation logic here
}
runTask().catch(console.error);
Bash / curl Example
For quick scripts, cron jobs, or shell-based pipelines:
# Trigger rotation
curl "http://api.proxygrow.com/rotate?key=YOUR_KEY&modem=modem_01"
# Check current IP
curl "http://api.proxygrow.com/ip?key=YOUR_KEY&modem=modem_01"
# Rotate and wait, then check new IP
curl -s "http://api.proxygrow.com/rotate?key=YOUR_KEY&modem=modem_01" && \
sleep 5 && \
curl -s "http://api.proxygrow.com/ip?key=YOUR_KEY&modem=modem_01" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['ip'])"
Integration with Scrapy
Scrapy middleware that rotates IP after every N requests or on a 429 response:
# middlewares.py
import time
import requests
from scrapy import signals
from scrapy.exceptions import IgnoreRequest
class MobileProxyRotationMiddleware:
def __init__(self, api_key, modem_id, rotate_every=20):
self.api_key = api_key
self.modem_id = modem_id
self.rotate_every = rotate_every
self.request_count = 0
self.rotate_url = f"http://api.proxygrow.com/rotate?key={api_key}&modem={modem_id}"
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings
return cls(
api_key=settings.get("PROXYGROW_API_KEY"),
modem_id=settings.get("PROXYGROW_MODEM_ID"),
rotate_every=settings.getint("PROXYGROW_ROTATE_EVERY", 20),
)
def _rotate(self):
resp = requests.get(self.rotate_url, timeout=15)
data = resp.json()
if data.get("status") == "ok":
time.sleep(5) # Wait for modem reconnect
print(f"Rotated to: {data.get('new_ip')}")
elif data.get("code") == "rate_limit_exceeded":
wait = data.get("retry_after", 60)
print(f"Rate limited, sleeping {wait}s")
time.sleep(wait)
def process_request(self, request, spider):
self.request_count += 1
if self.request_count % self.rotate_every == 0:
self._rotate()
return None
def process_response(self, request, response, spider):
if response.status == 429:
self._rotate()
raise IgnoreRequest(f"429 received, rotated IP, dropping request to retry")
return response
Add to your settings.py:
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.MobileProxyRotationMiddleware': 543,
}
PROXYGROW_API_KEY = 'your_api_key_here'
PROXYGROW_MODEM_ID = 'modem_01'
PROXYGROW_ROTATE_EVERY = 20
PROXY_HTTP = 'socks5h://proxy.proxygrow.com:10001'
Integration with Playwright / Puppeteer
Rotate the proxy between browser sessions so each session starts with a fresh IP:
// playwright-rotation.js
const { chromium } = require('playwright');
const API_KEY = 'your_api_key_here';
const MODEM_ID = 'modem_01';
async function rotateAndGetIp() {
await fetch(`http://api.proxygrow.com/rotate?key=${API_KEY}&modem=${MODEM_ID}`);
await new Promise(r => setTimeout(r, 5000));
const res = await fetch(`http://api.proxygrow.com/ip?key=${API_KEY}&modem=${MODEM_ID}`);
const data = await res.json();
return data.ip;
}
async function runSession(targetUrl) {
// Rotate IP before launching browser
const ip = await rotateAndGetIp();
console.log(`Starting browser session with IP: ${ip}`);
const browser = await chromium.launch({
proxy: {
server: 'socks5://proxy.proxygrow.com:10001',
},
});
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(targetUrl);
// ... your automation here
await browser.close();
}
// Run sequential sessions, each with a fresh IP
const urls = ['https://example.com/task/1', 'https://example.com/task/2'];
(async () => {
for (const url of urls) {
await runSession(url);
}
})();
Rate Limits on Rotation
Mobile carrier networks are not designed for rapid SIM re-registration. Rotating too frequently causes issues:
- The carrier may flag the SIM for abnormal reconnect patterns
- The modem needs time to complete cell search and re-register (3–5 seconds minimum)
- Some carriers impose a cooldown before assigning a genuinely different IP
ProxyGrow enforces a minimum interval of 60–120 seconds between rotations depending on the carrier. Attempting to rotate faster will return a rate_limit_exceeded error with a retry_after value in seconds.
Practical rule: plan your rotation intervals around your task structure, not raw time. Rotate between logical task groups (a set of pages for one target, one account session, one search query batch), not after every single request.
Best Practices
Do not rotate mid-session. If you're logged into an account or maintaining a session cookie, rotating will appear as a sudden IP change to the target site — a strong fraud signal. Rotate before the session starts, never during.
Rotate between logical tasks. Define task units (e.g., "scrape all search results pages for keyword X") and rotate between units. This maximizes IP value and minimizes unnecessary rotations.
Verify the IP changed. Always call the /ip endpoint after rotation to confirm you actually have a new IP before sending traffic. Occasionally a modem gets the same IP from the carrier pool — rare, but it happens.
Monitor IP reputation. Keep track of which IPs have been flagged (returned CAPTCHA or 403). Rotate again if you land on a previously problematic IP.
Handle errors gracefully. The rotation API will return errors — modems go offline, rate limits apply. Build retry logic with exponential backoff into your rotation wrapper.
Get API Access
Dedicated mobile proxies with instant IP rotation API — Ukraine, Romania, Latvia.
API Access: Dedicated Plans Only
The rotation API is available exclusively on Dedicated proxy plans. Shared plans connect you to a rotating pool without control over individual modems — there is no rotation API on shared plans by design.
On a Dedicated plan you get:
- Your own physical modem with a fixed carrier SIM
- Full API access to rotate, check IP, and query modem status
- Guaranteed bandwidth — no sharing with other users
- Ability to choose which modem/carrier you want for each task
If you're building a scraping pipeline, automation tool, or multi-account management system that needs programmatic IP control, a Dedicated plan is the right choice.
The rotation API is simple, stable, and designed to integrate cleanly into any language or framework. One HTTP GET, five seconds of wait, and you have a fresh carrier IP ready for your next task.