Skip to content

Docs

Proxy setup and API documentation.

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.

Jump to an example

Dedicated ports (one mobile device per customer) have their own page: setup, rotation and renewal.

Setting up an eSIM or SMS service? Follow the eSIM installation checklist or compare SMS service types. A data eSIM and a web-managed SMS number have different capabilities.

SMS & eSIM Reseller API — catalogue, wallet purchases and delivery →

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-es-rot-ondemand               # residential, Spain, 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-<name>-rot-auto5    # prefer one carrier in Poland (a preference; we publish no carrier list yet)

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. Two names to keep straight: the API calls the pools mobile and residential, while the proxy username calls them mbl and peer. Compare mobile 4G/5G proxies and residential proxies, then check live country availability for your run. For your client, check the HTTP CONNECT requirements or SOCKS5 authentication and setup requirements before ordering.

Proxy username tokens, values and meanings
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. We publish no list of carrier names yet, so leave it out unless you know the name.

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.

Choose between rotating IPs and sticky proxy sessions →

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-es-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

Test a proxy with curl: connection, exit IP and errors →

Python Requests

python · requestspython
import os, requests

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

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

# 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())

Python Requests proxy setup and authentication →

Node.js fetch

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: 'residential', country: 'ES', 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());

Node.js fetch proxy setup with Undici →

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();

Playwright proxy setup, authentication and sessions →

Find your client

Use the guide for the library that sends your request. Proxy arguments, authentication and timeout behaviour differ between clients. Filling in HTTPS_PROXY or a field labelled HTTPS proxy? Read why an HTTPS proxy setting takes an http:// address.

  • Docker proxy settings

    Distinguish daemon image pulls, build steps and requests in running containers.

  • Proxy URL format

    Original credential fields, component encoding and client-specific inputs.

  • Plan parallel proxy work

    Measured task duration, request pacing and account limits.

  • Python pip

    An isolated package-index check without installing packages or changing saved settings.

  • npm CLI

    An isolated registry check without installing packages or changing saved settings.

  • PHP cURL

    Authenticated CONNECT and separate gateway and destination errors.

  • Python urllib

    Standard-library requests, routing exceptions and hidden input.

  • Python HTTPX

    Synchronous and asynchronous clients.

  • Python aiohttp

    Async sessions, deadlines and cleanup.

  • Scrapy

    Request routing and crawl limits.

  • Axios in Node.js

    An authenticated HTTPS CONNECT agent.

  • Go net/http

    Standard-library transport settings.

  • Postman desktop

    Proxy settings and a small API check.

  • PowerShell

    A request-scoped gateway and prompted credentials.

  • Firefox desktop

    Native HTTP proxy settings and restoration steps.

  • GNU Wget

    Authenticated downloads with explicit limits.

  • Chrome with FoxyProxy

    An HTTP proxy for chosen sites, signed in by the extension.

Инструкции по настройке на русском →

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
Keys start with pk_ and are created per project in the dashboard. Shown once. Send as Authorization: Bearer. There is no sandbox: every key acts on your real balance, whatever its prefix.
Scopes
A key holds the scopes chosen when it was created. Reading your balance and limits (GET /traffic) needs ports:read or ports:write; the proxy password comes back only with ports:write. ports:write also protects generated credentials (build-url and build-urls) and actions that spend or change something: test, regenerate, buy, checkout, wallet top-up, cancelling an order and cart writes. billing:read reads the account (/me), wallet, orders, invoices, affiliate figures and the cart. Account details, password, sessions and payouts are only changed from a signed-in dashboard session, never with a key. A key without the scope gets 403 insufficient_scope.
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": null, "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":"mobile","country":"US","rotation":"sticky","session":"cart7","protocol":"http"}'

# test a connection from our side: whether it works, the exit IP and the latency (6 per minute)
# it does not confirm the exit country: look the IP up in the geolocation database your target uses
curl -X POST https://api.portproof.org/v1/traffic/test -H "Authorization: Bearer $PORTPROOF_API_KEY" -H "Content-Type: application/json" \
  -d '{"pool":"mobile","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)"

# buy whole GB from your account balance (key scope ports:write). accept_terms: true is required, as at checkout:
# it records that you accept the terms and the acceptable-use policy and ask for the service to start at once.
# The same Idempotency-Key with the same body replays the same order; a refused purchase stays under its key for 24 h,
# so after fixing the cause (for example a top-up) send a new key.
curl -X POST https://api.portproof.org/v1/traffic/buy -H "Authorization: Bearer $PORTPROOF_API_KEY" -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" -d '{"gb":25,"accept_terms":true}'

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

Generate connections in bulk

Use POST /v1/traffic/build-urls for 1–100 sticky connections. It validates the same pool, country and protocol as the single builder. A ports:write key is required because the response includes your proxy password.

curl -sS -X POST https://api.portproof.org/v1/traffic/build-urls \
  -H "Authorization: Bearer $PORTPROOF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pool":"residential","country":"any","protocol":"http","count":10,"session_prefix":"research"}'

The response has a connections array with a username, URL, masked URL and code examples for each session. Names run from research_1 to research_10; repeat that prefix to reuse those sessions. Prefixes accept 1–48 lower-case letters, digits or underscores.

Generating URLs makes no purchase or gateway connection and consumes no GB. The connections share your existing balance and gateway ports. Distinct names do not guarantee distinct devices or IPs. Keep responses out of logs and shared files.

For an integration: read GET /traffic for balance, check GET /traffic/countries, build the connections, then configure your workers. Both builders use the read rate budget and need no idempotency header. Wallet purchases use POST /traffic/buy with a stable Idempotency-Key and explicit terms acceptance.

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) {
  if (typeof header !== 'string') return false;
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300 || typeof parts.v1 !== 'string') return false;
  const expected = Buffer.from(createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex'));
  const received = Buffer.from(parts.v1);
  return received.length === expected.length && timingSafeEqual(received, expected);
}
// traffic events: usage.80pct, usage.cap_reached (once per purchase cycle, also sent by email)

MCP

Connection passwords are masked by default. A URL build does not make a proxy request; test_connection uses some existing traffic. Wallet purchases need the user's confirmation and terms acceptance.

jsonjson
// stdio MCP server. buy_traffic needs confirm:true, accept_terms:true and idempotency_key.
// Ask the user to accept the terms, acceptable-use policy and immediate start of service first.
{
  "mcpServers": {
    "portproof": {
      "command": "npx",
      "args": ["-y", "@portproof/mcp"],
      "env": { "PORTPROOF_API_KEY": "pk_...", "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

MCP setup, credentials and purchase checks →

Errors and limits

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

Fix a 407 Proxy Authentication Required response, troubleshoot CONNECT tunnel failures or diagnose a refused or timed-out connection.

Proxy gateway and API error codes
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
402insufficient_fundsAPI: the account balance does not cover a purchase from the API
403insufficient_scopeAPI: the key lacks the scope (see Scopes above)
403account_suspendedAPI: the account, or proxy traffic on it, is paused; no more GB can be bought until it is resumed. Write to support
409idempotency_conflictAPI: the same Idempotency-Key with a different body inside 24 h, or the same key sent twice at once (checkout and /traffic/buy)
409trial_already_usedAPI: the trial can be bought once per customer
409traffic_not_purchasedAPI: no traffic balance yet; buy GB first
429rate_limitedAPI: read 600 / 5 min, write 120 / 5 min per key; POST /traffic/test 6 per minute