Skip to content

Docs

One URL. The username does the targeting.

Connection format first, then the username tokens, snippets for curl, Python, Node and Playwright, and the API. Pool, country, rotation and session are all chosen in the proxy username, so one credential serves every request.

Connection format

The gateway takes HTTP and SOCKS5 on two ports with the same credentials. USERNAME stands for the account part of your username; it and the password appear in the dashboard right after your first purchase. The dashboard's connection builder writes these usernames for you and tests them.

texttext
# HTTP (CONNECT) on port 7000, SOCKS5 on port 7001; same username and password
http://<username>:<password>@gw.portproof.org:7000
socks5h://<username>:<password>@gw.portproof.org:7001

# <username> = USERNAME-<pool>-<country>[-sid-<name>][-carrier-<name>][-rot-<mode>][-ttl-<seconds>]
#   lower case, hyphen separated, no hyphen inside a value
# USERNAME and the password (pak_••••) are in the dashboard under Proxies, and in GET /v1/traffic

USERNAME-mbl-us-rot-auto10                  # mobile 4G/5G, United States, new exit every 10 minutes
USERNAME-peer-de-rot-ondemand               # residential, Germany, new exit on every connection
USERNAME-any-fr-rot-auto60                  # whichever pool answers in France, hourly
USERNAME-mbl-gb-sid-cart7-rot-sticky        # sticky session "cart7": the same device while it is online
USERNAME-mbl-pl-carrier-play-rot-auto5      # prefer one carrier in Poland

Username tokens

Pool and country are positional; every other token is a name followed by its value. Values are lower case and never contain a hyphen.

TokenValuesMeaning
poolmbl · peer · anyMobile 4G/5G, Residential, or whichever is available. Second position in the username.
countryus · gb · fr · …Two-letter country code, lower case. Third position. Mobile 4G/5G serves us, gb, fr, nl, pl and ge (Georgia, the country).
rotondemand · auto5 · auto10 · auto20 · auto60 · stickyWhen the exit changes: every connection, on a timer in minutes, or held for a session. Default auto10.
sid[a-z0-9_] up to 64 charactersSession name. Required with rot-sticky: the same sid returns to the same device while it is online.
ttl60 to 2592000 (seconds)How long a sticky session is kept. Set once per sid; it cannot be changed afterwards.
carrierlower-case name without spaces or hyphensPrefer one mobile carrier. A soft filter: when nothing matches, the gateway widens to the country.

Rotation and sessions

New IP each connection
rot-ondemand
Every 5 minutes
rot-auto5
Every 10 minutes
rot-auto10
Every 20 minutes
rot-auto20
Every 60 minutes
rot-auto60
Sticky session
rot-sticky
  • A sticky session needs both sid-<name> and rot-sticky. Use one session name per logical visitor; letters, digits and underscore.
  • A sticky session keeps the same device. The carrier may still change the IP of that device. We do not sell fixed IP addresses on this product.
  • A country with no device online answers with a gateway error (502). A carrier is a preference: when nothing matches, the gateway widens to the country.
  • Traffic is counted in both directions at the gateway; the balance in the dashboard and the API refreshes every five minutes.

curl

Keep the password in an environment variable; never paste it into shared scripts.

shsh
# your exit address, as the destination sees it
curl -x "http://USERNAME-mbl-us-rot-auto10:$PROXY_PASSWORD@gw.portproof.org:7000" https://api.portproof.org/v1/echo-ip

# SOCKS5 with remote DNS
curl -x "socks5h://USERNAME-peer-de-rot-ondemand:$PROXY_PASSWORD@gw.portproof.org:7001" https://api.portproof.org/v1/echo-ip

# a sticky session: both requests leave through the same device
curl -x "http://USERNAME-mbl-gb-sid-cart7-rot-sticky:$PROXY_PASSWORD@gw.portproof.org:7000" https://api.portproof.org/v1/echo-ip
curl -x "http://USERNAME-mbl-gb-sid-cart7-rot-sticky:$PROXY_PASSWORD@gw.portproof.org:7000" https://api.portproof.org/v1/echo-ip

Python

python · requestspython
import os, requests

API = "https://api.portproof.org/v1"
H = {"Authorization": f"Bearer {os.environ['PORTPROOF_API_KEY']}"}

# balance, expiry and credentials
t = requests.get(f"{API}/traffic", headers=H, timeout=10).json()
c = t["credentials"]
print(t["gb_left"], "GB left, expires", t["expires_at"])

# build the username yourself, or let the API validate it for you
username = f"{c['username_base']}-mbl-us-rot-auto10"
proxy = f"http://{username}:{c['password']}@{c['host']}:{c['http_port']}"

r = requests.get("https://api.portproof.org/v1/echo-ip", proxies={"http": proxy, "https": proxy}, timeout=30)
print("exit:", r.json())

Node

node 22 · undicinode
import { ProxyAgent, fetch as ufetch } from 'undici';

const API = 'https://api.portproof.org/v1';
const H = { authorization: `Bearer ${process.env.PORTPROOF_API_KEY}`, 'content-type': 'application/json' };

// the API builds and validates the URL: pool, country, rotation, optional session, protocol
const built = await (await fetch(`${API}/traffic/build-url`, {
  method: 'POST',
  headers: H,
  body: JSON.stringify({ pool: 'peer', country: 'DE', rotation: 'ondemand', protocol: 'http' }),
})).json();

// undici's ProxyAgent speaks HTTP CONNECT
const agent = new ProxyAgent(built.url);
console.log(await (await ufetch('https://api.portproof.org/v1/echo-ip', { dispatcher: agent })).json());

Playwright

One browser context per sticky session keeps cookies and exit device together.

typescripttypescript
import { chromium } from 'playwright';

// one sticky session per browser context: the same device for the whole visit
const session = `run${Date.now().toString(36)}`;
const browser = await chromium.launch();
const context = await browser.newContext({
  proxy: {
    server: 'http://gw.portproof.org:7000',
    username: `USERNAME-mbl-us-sid-${session}-rot-sticky`,
    password: process.env.PROXY_PASSWORD,
  },
  locale: 'en-US',
  timezoneId: 'America/New_York',
});
const page = await context.newPage();
await page.goto('https://api.portproof.org/v1/echo-ip');
console.log(await page.textContent('body'));
await browser.close();

Other tools

Anything that accepts an HTTP or SOCKS5 proxy with a username and password works. Neutral technical configs only; see the acceptable-use policy.

json · pythonjson
# Cloud browsers and scrapers take the same three values: server, username, password
{ "proxy": { "server": "http://gw.portproof.org:7000",
             "username": "USERNAME-peer-us-rot-ondemand",
             "password": "pak_••••" } }

# Scrapy settings.py
HTTPPROXY_ENABLED = True
# then per request: meta={"proxy": "http://USERNAME-peer-us-rot-ondemand:PASSWORD@gw.portproof.org:7000"}

API and authentication

Production API
https://api.portproof.org/v1
This deployment
https://api.portproof.org/v1 · OpenAPI at https://api.portproof.org/docs
Keys
pk_live_… and pk_test_…, scoped, created per project in the dashboard. Shown once. Send as Authorization: Bearer.
Conventions
Idempotency-Key on every write, cursor pagination, RFC 9457 problem details, per-key rate limits in response headers.
Gateway
gw.portproof.org · 7000 http · 7001 socks5

Traffic API

Everything the dashboard shows is an API call. Buying from the API uses your account balance; every other payment method runs through checkout.

shsh
# balance, expiry, credentials and limits
curl https://api.portproof.org/v1/traffic -H "Authorization: Bearer $PORTPROOF_API_KEY"
# -> { "gb_total": 25, "gb_used": 3.2, "gb_left": 21.8, "expires_at": "...", "enabled": true,
#      "credentials": { "host": "gw.portproof.org", "http_port": 7000, "socks_port": 7001, "username_base": "...", "password": "pak_••••" },
#      "limits": { ... } }

# countries with devices online now, per pool (cached 60 s)
curl https://api.portproof.org/v1/traffic/countries -H "Authorization: Bearer $PORTPROOF_API_KEY"

# build and validate a proxy URL with snippets
curl -X POST https://api.portproof.org/v1/traffic/build-url -H "Authorization: Bearer $PORTPROOF_API_KEY" -H "Content-Type: application/json" \
  -d '{"pool":"mbl","country":"US","rotation":"sticky","session":"cart7","protocol":"http"}'

# test a connection from our side: exit country and latency (6 per minute)
curl -X POST https://api.portproof.org/v1/traffic/test -H "Authorization: Bearer $PORTPROOF_API_KEY" -H "Content-Type: application/json" \
  -d '{"pool":"mbl","country":"US","rotation":"auto10"}'

# new password (the old one keeps working for about 30 seconds)
curl -X POST https://api.portproof.org/v1/traffic/regenerate -H "Authorization: Bearer $PORTPROOF_API_KEY" -H "Idempotency-Key: $(uuidgen)"

# prices: the per-GB ladder
curl https://api.portproof.org/v1/pricing

Webhooks

Create webhooks in the dashboard or with POST /v1/webhooks; the secret is shown once. Deliveries retry with exponential backoff for 24 hours.

nodenode
// verify Portproof-Signature: t=<unix>,v1=<hex hmac-sha256(secret, t + "." + body)>
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return Math.abs(Date.now() / 1000 - Number(parts.t)) < 300 &&
    timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
// traffic events: usage.80pct, usage.cap_reached (once per purchase cycle, also sent by email)

MCP

jsonjson
// stdio MCP server: the same routes as tools; buy_traffic needs confirm:true + idempotency_key
{
  "mcpServers": {
    "portproof": {
      "command": "npx",
      "args": ["-y", "@portproof/mcp"],
      "env": { "PORTPROOF_API_KEY": "pk_live_...", "PORTPROOF_API_URL": "https://api.portproof.org/v1" }
    }
  }
}
// tools: get_pricing, get_traffic, list_countries, build_proxy_url, test_connection, buy_traffic (account balance), get_status

Errors and limits

The first block comes from the proxy gateway on your connection; the second from the API.

StatuscodeMeaning
407E_AUTH_INVALIDgateway: wrong username base or password (regenerated passwords replace the old one after about 30 seconds)
407E_CAP_EXCEEDEDgateway: the GB balance is used up or expired; buy more to continue
400E_USERNAME_PARSEgateway: the username is malformed (upper case, a hyphen inside a value, unknown token)
429E_RATE_LIMITED_CONNgateway: too many parallel connections; back off and retry
429E_SESSION_LIMITgateway: too many sticky sessions open at once
502E_NO_STOCK_COUNTRYgateway: no device online in that pool and country right now
400validation_failedAPI: body or query failed validation; errors[] lists paths
400idempotency_key_requiredAPI: write without Idempotency-Key
402kyb_requiredAPI: the order would pass the monthly volume that needs a business check
403insufficient_scopeAPI: the key lacks the scope
409idempotency_conflictAPI: same key, different body inside 24 h
429rate_limitedAPI: read 600 / 5 min, write 120 / 5 min per key; POST /traffic/test 6 per minute