Guides
Bring your own proxy
The residential unblocking tier sends requests through a residential proxy that your team chooses and pays for. Save its URL once and every request with unblock: "residential" (or auto, when it gets that far) goes through it.
Why bring your own
- Hard sites. Some sites block datacenter IPs whatever the browser looks like. A residential exit IP, optionally in a
countryyou pick, gets through more of them. - You choose the provider and pay it directly. Use any provider you like, at the price and pool you negotiated. We do not resell proxy bandwidth.
- No surcharge, on every plan. With a team proxy saved, the residential tier adds +0 credits and works on the free plan too. You pay the normal price of the request: a browser render through your proxy costs 5 credits. See Credits.
Set it up
- Open Settings → Proxy in the dashboard. Team owners and admins can change it; other members see whether one is set.
- Pick your provider. The URL field is filled with that provider's template (below): replace the UPPER-CASE parts with your account's values and leave
{country}and{session}in place. - Save, then press Test proxy. It fetches an IP-echo page through your proxy and shows the exit IP and country it got, or what went wrong.
- Send requests with
unblock: "residential"and, if you like, acountry.
A team has one proxy. Saving a new URL replaces the old one, and removing it turns the residential tier off for the team (requests that need it get 403 proxy_not_configured). Changes reach the API within about a minute.
URL format
scheme://USERNAME:PASSWORD@host:port- Scheme:
http,httpsorsocks5. The port defaults to 80, 443 or 1080. Up to 2,048 characters. - Credentials: percent-encode special characters in the password (
@→%40,:→%3A,#→%23,/→%2F). - Host: a public hostname or IP. Private, loopback, link-local and cloud-metadata addresses,
localhostand.local/.internalnames are refused when you save, and the hostname is checked again each time we connect.
Most providers take targeting options in the username (some in the password). Put these placeholders where your provider expects them; we fill them in for every request:
| Name | Type | Description |
|---|---|---|
{country} | lower-case ISO 3166-1 alpha-2 | The request's country (uk is sent as gb), or us when the request has none. {COUNTRY} gives the same code in upper case. The browser's locale and timezone follow the country, so the page sees a consistent visitor. |
{session} | 8 lower-case letters and digits | A new random sticky-session id for every attempt, so all the requests one page makes (scripts, images, API calls) leave from the same exit IP, and a retry gets a different one. Always include it: without it a rotating provider may give every connection of one page a different IP. |
If a placeholder has no value, its whole segment is removed (user-country-{country} becomes user), along with session-only options such as _lifetime-10m or -sesstime-10. Any other {placeholder} (a typo such as {sesion}) is refused when you save rather than sent to your provider.
Provider templates
UPPER-CASE parts are your account's values. Check the provider's own docs for the current format, then use Test proxy.
Decodo
http://user-USERNAME-country-{country}-session-{session}-sessionduration-10:PASSWORD@gate.decodo.com:7000Use your proxy sub-user, not your dashboard login. Keep the "user-" prefix: Decodo requires it once any parameter (country, session) is in the username. Sessions last sessionduration minutes (1–1440). Decodo docs.
Oxylabs
http://customer-USERNAME-cc-{country}-sessid-{session}-sesstime-10:PASSWORD@pr.oxylabs.io:7777Use your residential proxy user (without the "customer-" prefix, which the template already has). The cc country code is not case-sensitive. A session lasts sesstime minutes, or ends after 60 s idle. Oxylabs docs.
Bright Data
http://brd-customer-CUSTOMER_ID-zone-ZONE-country-{country}-session-{session}:PASSWORD@brd.superproxy.io:44445CUSTOMER_ID is your account id (hl_…), ZONE the name of your residential zone, PASSWORD that zone's password. Use port 44445: ports 22225 and 33335 stopped working on 2026-09-25. New accounts need Bright Data's KYC before residential traffic is allowed. Bright Data docs.
IPRoyal
http://USERNAME:PASSWORD_country-{country}_session-{session}_lifetime-10m@geo.iproyal.com:12321Targeting goes in the password, after your password. IPRoyal requires the session id to be exactly 8 letters or digits, which is what Pageteer generates. _lifetime-10m keeps each session for 10 minutes. IPRoyal docs.
SOAX
http://country-{country}-session-{session}:PACKAGE_KEY@proxy.soax.com:1337Current SOAX format: the username is only targeting rules and the password is your package key. Do not use the older package-ID-…@proxy.soax.com:5000 format. A session expires after 60 s without requests. SOAX docs.
Evomi
http://USERNAME:PASSWORD_country-{COUNTRY}_session-{session}_lifetime-10@rp.evomi.com:1000Targeting goes in the password, after your password. Evomi's docs show upper-case country codes, so the template uses {COUNTRY}. Session ids are 6–10 letters or digits (Pageteer sends 8); _lifetime is in minutes. This format comes from Evomi's documentation and has not been tested against a live account yet: use "Test proxy" after saving. Evomi docs.
Other provider
http://USERNAME:PASSWORD@proxy.example.com:8080Any HTTP, HTTPS or SOCKS5 proxy. Put {country} (or {COUNTRY}) and {session} where your provider expects a country code and a sticky-session id, usually in the username: without {session} a rotating pool may give every connection of one page a different IP. Percent-encode special characters in the password (@ → %40, : → %3A, # → %23, / → %2F).
Using it
curl -X POST https://api.104-236-84-15.sslip.io/v1/render \
-H "Authorization: Bearer $PAGETEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/","unblock":"residential","country":"de","formats":["markdown"]}'const res = await fetch('https://api.104-236-84-15.sslip.io/v1/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAGETEER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://example.com/",
"unblock": "residential",
"country": "de",
"formats": [
"markdown"
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data, res.headers.get('x-credits-remaining'));import os
import requests
headers = {"Authorization": f"Bearer {os.environ['PAGETEER_API_KEY']}"}
res = requests.post(
"https://api.104-236-84-15.sslip.io/v1/render",
headers=headers,
json={
"url": "https://example.com/",
"unblock": "residential",
"country": "de",
"formats": ["markdown"],
},
timeout=120,
)
res.raise_for_status()
print(res.json(), res.headers["x-credits-remaining"])When the residential tier served the page, the response says so in tier_used and adds proxy_source: "customer" (the x-proxy-source header on binary screenshots and PDFs). With unblock: "auto" we try none, then stealth, then your proxy, and charge only for the tier that worked (tier_used). Render, screenshot, PDF, extract, crawl and batch all use it.
Testing
Fetches an IP-echo page through your team's saved proxy and reports the exit IP and country. Free (0 credits), any API key works, limited to 10 tests a minute per team. The result also appears in Settings → Proxy.
| Name | Type | Description |
|---|---|---|
country | string (2 letters) | Fills {country} for this test, to check country targeting. |
curl -X POST https://api.104-236-84-15.sslip.io/v1/proxy/test \
-H "Authorization: Bearer $PAGETEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"country":"de"}'const res = await fetch('https://api.104-236-84-15.sslip.io/v1/proxy/test', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAGETEER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"country": "de"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data, res.headers.get('x-credits-remaining'));import os
import requests
headers = {"Authorization": f"Bearer {os.environ['PAGETEER_API_KEY']}"}
res = requests.post(
"https://api.104-236-84-15.sslip.io/v1/proxy/test",
headers=headers,
json={
"country": "de",
},
timeout=120,
)
res.raise_for_status()
print(res.json(), res.headers["x-credits-remaining"]){
"ok": true,
"ip": "203.0.113.42",
"country": "DE",
"ms": 1840,
"error": null
}{
"ok": false,
"ip": null,
"country": null,
"ms": 612,
"error": {
"code": "proxy_failed",
"reason": "auth",
"message": "team proxy refused the credentials (HTTP 407)"
}
}A failed test is still a 200: check ok. Without a saved proxy the endpoint answers 404 proxy_not_configured. The JavaScript client has proxyTest().
Errors
| Status | Code | When | Charged | What to do |
|---|---|---|---|---|
| 403 | proxy_not_configured | unblock "residential" without a team proxy saved in Settings → Proxy (404 from POST /v1/proxy/test). | No | Save your proxy URL in Settings → Proxy (see Bring your own proxy), or use another tier. |
| 502 | proxy_failed | Your proxy failed: reason "auth" (it rejected the credentials), "unreachable", "status" (it refused the connection) or "blocked" (its host resolves to a private address). | No | Fix the URL or credentials in Settings → Proxy and test it. "unreachable" and "status" may pass on retry. |
| Name | Type | Description |
|---|---|---|
auth | reason | The proxy answered 407: wrong username or password, or an expired plan. Not retried. |
unreachable | reason | We could not connect to the proxy host or port (DNS, refused, timed out). |
status | reason | The proxy refused to open the connection to the site (proxy_status has its HTTP status), for example because of a provider-side block or exhausted bandwidth. |
blocked | reason | The proxy hostname resolves to a private, loopback, link-local or metadata address. We never connect to it. |
Proxy failures are never charged, and neither are the attempts before them. Check Settings → Proxy and run a test.
Estimate what it costs you
We count the bytes each request sends and receives through your proxy, per key, project and day. To turn that into money:
- Open Usage in the dashboard.
- Enter Your proxy price ($/GB): what your provider charges per GB on your plan. Team owners and admins can set it; it applies to the whole team.
- The usage table then shows proxy MB and an estimated cost for each project and key: bytes ÷ 1,000,000,000 × your price (providers bill in decimal GB).
It is an estimate: providers meter traffic their own way (some add protocol overhead or round per request), so your provider's invoice is the real number. The same byte counts are available from code as totals.proxy_bytes in GET /v1/usage?group=key.
Security
- Encrypted at rest. The URL (with its credentials) is stored encrypted with AES-256-GCM, bound to your team, and decrypted only to send your requests through it.
- Write-only. After you save it, no page or API returns it in full: the dashboard shows a masked form such as
http://user-sp12345…:••••@gate.decodo.com:7000. To change it, paste a new one. - Never logged. The URL and its credentials are kept out of our logs and error messages; errors name the proxy host only.
- Deleted with it. Removing the proxy, or deleting the team, deletes the stored URL. See the Privacy Policy.
Limits
- Search never uses it.
/v1/searchqueries go out from our own servers. - Bandwidth cap per page. Each render through a residential proxy may transfer at most about 12 MB; images, fonts and media are skipped unless you ask for a screenshot or PDF. A page over the cap stops and the request is not charged.
- Same rules as every request. Your proxy changes the exit IP, nothing else: denylisted sites, private addresses and login or checkout pages stay off limits, CAPTCHAs are never solved, and the Acceptable Use Policy applies. You are also responsible for following your proxy provider's terms.
Your provider bills you for the bandwidth these requests use. Watch its dashboard (or the estimate on the Usage page), and use unblock: "auto" so pages that load without the proxy never touch it.