Short answers
Can I connect using the proxy name?
The name identifies a saved configuration through the Portproof API. It is not a gateway username. Look up an exact name, choose its configuration ID, then export the complete connection. Duplicate names require an explicit choice.
What does a saved SID guarantee?
A SID identifies a logical binding preference. Repeated exports retain it, but it does not reserve a device or guarantee an unchanged address. Different SIDs do not guarantee different addresses.
Does an edit invalidate an earlier export?
A name-only edit keeps the SID. Changed routing settings, protocol, rotation mode or TTL create a new version and SID. Previously exported URLs are unchanged and may still bind; they are not isolated retroactively or revoked by a local edit.
Keep four different identifiers separate
- Name
- Your human label, such as Research browser. Names are trimmed, case-sensitive and not necessarily unique. Renaming changes name lookup; it does not change the connection settings or password.
- Configuration ID
- The saved record’s UUID. Persist this in your application after creation or selection. Read, export, edit, test and retire the configuration through this ID. Each API action checks your customer ownership.
- SID
- An opaque identifier allocated by the server and persisted with a configuration version. It contains one to 64 lowercase letters, digits or underscores. Do not substitute an email, name, password or a shared example SID. The server prevents collisions across the shared account.
- Account username and password
- Gateway authentication. The exported username combines the account username, pool, country and routing tokens, including the SID. The proxy password is separate from your Portproof API key and never belongs in the name or SID.
Create a customer API key in the dashboard with the ports:write scope for creation, export, editing and diagnostics. The ports:read scope can list and inspect configuration metadata, but cannot export a password. Set PORTPROOF_API_URL to https://api.portproof.org/v1 and provide PORTPROOF_API_KEY through your application’s secret manager. Do not put either API keys or exported URLs in frontend code, analytics, screenshots or logs.
Choose live stock and save once
Read GET /traffic/countries before choosing a country. Counts describe reported routable devices, not a count of distinct addresses or reserved capacity. Inspect the observation time and status: an unavailable response or a stale last observation does not establish zero stock. Peer is a mixed network. The API value residential selects that peer pool for compatibility; it does not enforce residential-only addressing.
For a specific mobile or peer country, query GET /traffic/facets?pool=residential&country=US, replacing both values with your selection. Use the returned carrier, city, state, ASN and IP-class values. Choose the pool explicitly. A known zero-stock filter is refused; unknown stock is labelled separately. Individual facet counts do not guarantee that every combined filter has a match when you connect.
US is an example, not a stock claim. Replace it with a currently available selection. Set protocol to socks5 when creating a SOCKS5 configuration; create a separate record if both protocols must coexist.
{
"name": "Research browser",
"settings": {
"pool": "residential",
"country": "US",
"rotation": "auto10",
"protocol": "http",
"ttl_seconds": 600
}
}The response contains metadata, including id, version, sid and original settings. Persist those values. Do not create a configuration on every request. After a lost creation response, inspect your saved list before deciding whether another local record is needed.
curl --fail --request POST "$PORTPROOF_API_URL/traffic/connections" \
-H "Authorization: Bearer $PORTPROOF_API_KEY" -H 'Content-Type: application/json' \
--data-binary @connection.jsonFind the exact name or current SID
# PORTPROOF_API_URL includes /v1; use your customer API key.
# Choose ONE exact lookup. Names are case-sensitive and may match several records.
curl --fail --get "$PORTPROOF_API_URL/traffic/connections" \
-H "Authorization: Bearer $PORTPROOF_API_KEY" \
--data-urlencode "name=$PROXY_NAME"
# Or find the current saved configuration by its server-allocated SID.
curl --fail --get "$PORTPROOF_API_URL/traffic/connections" \
-H "Authorization: Bearer $PORTPROOF_API_KEY" \
--data-urlencode "sid=$PROXY_SID"
# Keep the chosen record's id. Never silently select the first duplicate name.Use either name or sid, not both. URL-encode the value, as the examples do. Both filters return active configurations owned by the authenticated customer. The SID filter matches the current version only; an earlier SID no longer finds the edited configuration even if its old exported URL still works. A missing match is a local catalogue result, not evidence that a gateway binding or an existing socket has disappeared.
If a name matches several records, compare their IDs and settings and explicitly choose one. Never silently take the first result. Persist the selected ID as PROXY_CONNECTION_ID for subsequent runs, or set exactly one of PROXY_NAME and PROXY_SID to repeat a lookup. No provider record ID, account-wide session key or another customer’s SID grants access.
Export and use the saved HTTP configuration
Install Undici in your application, then run the following as an ES module. The control requests go directly to Portproof; the destination request uses only the saved export. The example checks the version and SID before connecting, disables redirects, bounds each request, and prints only the destination HTTP status. A successful destination status is not proof of country, network class or future capacity.
import { ProxyAgent, fetch as proxyFetch } from 'undici';
async function main() {
// Set PORTPROOF_API_URL (including /v1), PORTPROOF_API_KEY,
// and exactly one of PROXY_CONNECTION_ID, PROXY_NAME or PROXY_SID.
const API = process.env.PORTPROOF_API_URL.replace(/\/$/, '');
const selectors = ['PROXY_CONNECTION_ID', 'PROXY_NAME', 'PROXY_SID']
.filter((key) => process.env[key]);
if (selectors.length !== 1) throw new Error('Choose one saved configuration selector');
async function api(path, method = 'GET') {
const response = await fetch(API + path, {
method,
headers: { authorization: 'Bearer ' + process.env.PORTPROOF_API_KEY,
'content-type': 'application/json' },
...(method === 'POST' ? { body: '{}' } : {}),
redirect: 'error', signal: AbortSignal.timeout(15000), cache: 'no-store',
});
// Do not print response bodies or nested errors: they may contain credentials.
if (!response.ok) throw new Error('Portproof API status ' + response.status);
return response.json();
}
let id = process.env.PROXY_CONNECTION_ID;
if (!id) {
const query = new URLSearchParams(process.env.PROXY_NAME
? { name: process.env.PROXY_NAME } : { sid: process.env.PROXY_SID });
const { connections } = await api('/traffic/connections?' + query);
if (connections.length !== 1) {
throw new Error('No unique match: inspect saved configurations and choose an ID');
}
id = connections[0].id;
}
const path = '/traffic/connections/' + encodeURIComponent(id);
const saved = await api(path);
if (saved.settings.protocol !== 'http') throw new Error('Choose an HTTP configuration');
const exported = await api(path + '/export', 'POST');
if (exported.configuration_version !== saved.version || exported.session !== saved.sid) {
throw new Error('Configuration changed: review its current version before reconnecting');
}
// Use the exact saved export in memory. Never print or commit exported.url.
const dispatcher = new ProxyAgent(exported.url);
try {
const response = await proxyFetch('https://example.com/', {
dispatcher, redirect: 'manual', signal: AbortSignal.timeout(20000),
});
console.log({ status: response.status });
await response.body?.cancel();
} finally {
await dispatcher.close();
}
}
// Covers control requests, JSON parsing, agent construction and cleanup as well.
await main().catch(() => {
console.error('Saved proxy check failed; review the configuration and API state');
process.exitCode = 1;
});Use SOCKS5 with destination DNS at the proxy
Save a configuration with protocol socks5 and retain its ID. Python Requests needs its SOCKS extra installed. The socks5h spelling is a client instruction to resolve the destination remotely; it leaves the saved username, SID, gateway and port intact. Do not change the protocol or port in an HTTP export and call it the original saved configuration.
# pip install 'requests[socks]'
import os
from urllib.parse import quote
import requests
api = os.environ["PORTPROOF_API_URL"].rstrip("/") # includes /v1
connection_id = quote(os.environ["PROXY_CONNECTION_ID"], safe="")
path = f"{api}/traffic/connections/{connection_id}"
try:
with requests.Session() as control:
control.trust_env = False
control.headers["Authorization"] = "Bearer " + os.environ["PORTPROOF_API_KEY"]
saved_response = control.get(path, timeout=(5, 15), allow_redirects=False)
if saved_response.status_code != 200:
raise ValueError("Saved configuration unavailable")
saved = saved_response.json()
if saved["settings"]["protocol"] != "socks5":
raise ValueError("Choose a saved SOCKS5 configuration")
response = control.post(path + "/export", json={}, timeout=(5, 15), allow_redirects=False)
if response.status_code != 200:
raise ValueError("Export unavailable; review the dashboard state")
exported = response.json()
if exported["configuration_version"] != saved["version"] or exported["session"] != saved["sid"]:
raise ValueError("Configuration changed; review its current version")
# socks5h keeps destination DNS at the proxy. Preserve all saved routing tokens.
proxy = exported["url"].replace("socks5://", "socks5h://", 1)
with requests.Session() as client:
client.trust_env = False
with client.get("https://example.com/", proxies={"http": proxy, "https": proxy},
timeout=(5, 15), allow_redirects=False, stream=True) as result:
print("HTTP status:", result.status_code)
except (requests.RequestException, ValueError, KeyError):
# Do not print exception text, full URLs, headers or exported passwords.
raise SystemExit("Check failed; inspect the saved configuration diagnostics") from NoneUse the full username from the corresponding saved export. curl prompts for the password. These checks consume traffic and report status only.
# Copy the FULL exported username into PROXY_USERNAME, without altering its SID.
# curl prompts for the password; keep it out of shell history and process arguments.
curl --disable --connect-timeout 10 --max-time 20 \
--proxy 'http://gw.portproof.org:7000' --proxy-user "$PROXY_USERNAME" \
--output /dev/null --write-out 'HTTP status: %{http_code}\n' 'https://example.com/'
# For a saved SOCKS5 configuration, use its own exported username and remote DNS.
curl --disable --connect-timeout 10 --max-time 20 \
--proxy 'socks5h://gw.portproof.org:7001' --proxy-user "$PROXY_USERNAME" \
--output /dev/null --write-out 'HTTP status: %{http_code}\n' 'https://example.com/'Understand the exported username tokens
Use the complete server export. The placeholders describe its parts; do not send the literal capitalised words to the gateway. Passwords in URLs are encoded by the exporter.
http://ACCOUNT-POOL-COUNTRY-sid-SAVED_SID-rot-MODE:PASSWORD@gw.portproof.org:7000
socks5://ACCOUNT-POOL-COUNTRY-sid-SAVED_SID-rot-MODE:PASSWORD@gw.portproof.org:7001| Saved setting | Username token | Meaning |
|---|---|---|
| pool | mbl · peer · best | mobile maps to mbl; residential maps to mixed peer; best permits either pool. Always choose explicitly. |
| country | Two-letter lowercase code | A specific live country, or any when an unrestricted country is intended. No fixed country list is promised. |
| sid (server allocated) | -sid-{id} | The persisted identifier accompanies every saved configuration, including timed modes. Keep it unchanged between exports. |
| rotation | -rot-auto5 / auto10 / auto20 / auto60 | Minutes before rotation is considered on a subsequent connection using this SID. No exact deadline, forced socket disconnect or different address is guaranteed. |
| rotation | -rot-sticky / ondemand | sticky requests binding reuse; ondemand selects for each new connection. Neither mode guarantees a particular device or a distinct address. |
| ttl_seconds | -ttl-{seconds} | 60–2592000 seconds of sliding binding lifetime, separate from timed rotation. Elapsed TTL does not prove that an earlier URL cannot bind again. |
| carrier | -carrier-{name} | A supported live carrier or network slug; a hard filter, not a fallback preference. Use an exact ASN when that better identifies the network. |
| city / state | -city-{name} / -state-{name} | Supported live slugs for a specific peer country. Mobile or best-pool combinations are rejected. |
| asn | -asn-{number} | An integer from 1 to 9999999, selected from live network facets. |
| ip_type | -iptype-mobile / residential / datacenter | Reported IP class, separate from the peer pool category. A residential filter is explicit; the peer label alone does not apply it. |
Bodies and queries reject unsupported settings. The shared-pool API does not offer an isp token, an isp IP class, device pinning or a hard rotation mode. Do not append unrecognised tokens to an exported URL: a gateway may ignore them. Preserve every original option in the saved configuration; never reconstruct it from a session-list URL.
Edit, inspect and recover conservatively
| Request | Behaviour |
|---|---|
| GET /traffic/connections/{id} | Read its current name, version, SID and complete saved settings. |
| POST /traffic/connections/{id}/export with {} | Return the current connection in a no-store response. Repeated exports preserve the SID; a changed password can change the secret. |
| PATCH /traffic/connections/{id} | Send expected_version plus name and/or the complete settings object. A name-only edit retains the SID; changed settings allocate a new SID/version. A conflict requires rereading and review. |
| POST /traffic/connections/{id}/test with {} | Test the exact saved protocol, targeting and SID. This consumes traffic. Treat ok:false as failure even with HTTP 200. |
| GET /traffic/connections/{id}/sessions | Read a bounded, scoped observation. Empty, truncated or unavailable results never prove absence. |
| POST /traffic/connections/{id}/rotate with {} | Request binding release using a persisted UUID Idempotency-Key. This does not close existing sockets or guarantee a different address. |
| GET /traffic/rotations/by-key/{requestKey} | Read the original release outcome after a timeout or pending result. Do not resend a release to verify it. |
| DELETE /traffic/connections/{id} | Retire the local record. It does not revoke credentials, disable old exports, release a binding or terminate sockets. |
After an uncertain password change, exports can be blocked while the current secret is reconciled. Do not request another password change to fix a lost response, and do not assume the old password still works. A read observes the current password at that moment; other authorised writers can change it later. Keep such outcomes visible in your job state rather than reporting success or zero usage.
These API examples use no automatic mutation retries. Read the error status without printing nested error objects, authentication headers, full connection URLs or passwords. A provider-account funding problem is different from your own traffic allowance. For transport-specific troubleshooting, use the HTTP and SOCKS5 guide and the connection errors guide.
What is not allowed
Use these examples only for destinations and activity you are authorised to access, within the acceptable-use policy.
Read next
HTTP proxy or SOCKS5: which to use
The real difference between the two ports, and which one your client should open.
Read the guide
Proxy connection refused or timing out
Separate a refused or unreachable gateway from a timeout, a wrong port and an answered tunnel, using curl’s code and message.
Read the guide