Route traffic through op.wtf.
One host, one port, every protocol. The username picks the exit and the API key is the password. Everything below builds on that.
Quick start
Get started with op.wtf proxies in minutes. Follow these simple steps to integrate our proxy network into your application.
Create an account
Sign up for a free account and top up your balance. No credit card required to get started.
Create an accountGet your credentials
Find your API key in the dashboard. This will be used as your proxy password.
Configure your proxy
Use our simple URL format to connect through our proxy network.
http://username:[email protected]:58996
Authentication
All requests are authenticated using your API key as the proxy password. Your username determines the proxy configuration.
Username format
# Rotating type-country # Sticky session (residential, datacenter & mobile) type-country-sid-session_id
| Parameter | Description | Example |
|---|---|---|
sr_ | Optional prefix — retry blocked responses on a fresh exit (€0.001, only when it works) | sr_res-us, sr_dc-de |
type | Proxy type for rotating: res, dc, mob, stc | res, dc, mob, stc |
country | Two-letter ISO country code | us, gb, de, jp |
-sid- | Sticky session suffix — append to any res, dc, or mob code to pin a session | res-us-sid-…, dc-de-sid-… |
session_id | Sticky session identifier after -sid- — alphanumeric | abc123, mc_session |
Endpoints & ports
| Service | Host | Port |
|---|---|---|
| SOCKS4 / SOCKS5 / SOCKS5h / HTTP proxy | proxy.op.wtf | 58996 |
| API + Dashboard | op.wtf | 443 |
The proxy port accepts any destination port — 80, 443, 22, 25565, anything. Outbound TCP only (no UDP relay).
Proxy types
Residential
Use res for residential IPs. Best for web scraping, social media, and bypassing geo-restrictions.
res-us:[email protected]:58996
Datacenter
Use dc for fast datacenter IPs — best for high-speed requests and bulk operations, at €0.10/GB, our lowest rate. 55+ countries, many with streaming-optimized exits, and sticky sessions with -sid-. The live country list is in the dashboard proxy builder.
# Rotating datacenter exit in the US dc-us:[email protected]:58996 # Sticky session — same exit across requests dc-de-sid-mysession:[email protected]:58996
Mobile
Use mob for mobile IPs. Best for social media automation and apps that require mobile IP addresses.
mob-us:[email protected]:58996
Static
Use stc for static residential IPs. Best when you need a stable long-lived IP across many sessions.
stc-us:[email protected]:58996
Smart Retry
Prefix any code with sr_ and a blocked response is retried on a fresh exit instead of being handed back to you. Works with any underlying code: sr_res-us, sr_dc-de, sr_res_sc-us_chicago. €0.001 per request that actually needed a retry — nothing if the first attempt worked, and nothing if none of them did.
sr_res-us:[email protected]:58996
Countries
195+ countries. Use the two-letter ISO code in your username.
Popular codes: us gb de fr jp au ca nl es it br in. The full live list is in the dashboard proxy builder. any connects through every country at once.
City & ASN targeting
For residential proxies you can target specific cities or ASNs for precise control.
City targeting
Append the city name to the country code (use underscores for spaces).
res_sc-country_city:[email protected]:58996
Examples: res_sc-us_new_york, res_sc-de_berlin, res_sc-gb_london.
ASN targeting
res-us-asn-7922:[email protected]:58996
Sticky sessions
Append -sid-<session_id> to a residential, datacenter, or mobile code to keep the same exit IP across requests:
res-us-sid-abc123:[email protected]:58996 dc-de-sid-abc123:[email protected]:58996 mob-gb-sid-abc123:[email protected]:58996
Session IDs are alphanumeric. The session stays sticky for ~10 minutes of activity, or until the upstream IP rotates.
Smart retry
Some sites answer a proxy request with a block page or an interstitial instead of the content you asked for. Prefix any code with sr_ and we check what came back: if it's a block rather than a page, we throw it away and try again on a fresh exit.
sr_res-us:[email protected]:58996
Works with any code
# Residential US sr_res-us # Datacenter Germany sr_dc-de # Residential, Chicago sr_res_sc-us_chicago # Keeps your sticky session on the first attempt sr_res-us-sid-abc123
You only pay for requests that work. A request that succeeds first time costs nothing extra. A request that needed a retry and then got through costs €0.001. A request where no attempt got through costs you no retry fee at all — just the bandwidth.
Scope: works on http:// targets, where we can see the response. An https:// tunnel is encrypted end to end, so there is nothing for us to inspect — use the Unlocker API for those, which does the same thing and handles TLS for you.
curl -x http://sr_res-us:[email protected]:58996 http://some-strict-site/
X-PR-LLS header
The HTTP proxy response carries an X-PR-LLS header containing the session ID that handled your request. Read it from the response, then reuse that ID inside a sticky-session username ({type}-{cc}-sid-<id>) on follow-up requests to keep the same exit IP.
This is the flow-based stickiness pattern: you don't have to pick a session ID up-front — let the proxy assign one, observe it, then pin to it.
curl -i -x http://res-us:[email protected]:58996 \ https://api.ipify.org # HTTP/1.1 200 OK # ... # X-PR-LLS: 8f2c1a9b # # Reuse the returned id to pin further requests to the same IP: curl -x http://res-us-sid-8f2c1a9b:[email protected]:58996 \ https://example.com
Unlocker
Send a URL, get the page back. No proxy configuration, no retry loop, no block handling on your side.
If the site answers with a block page instead of content, we discard it and try again on a fresh exit automatically. The per-request fee is charged only when we hand back real content — if nothing gets through you pay bandwidth and nothing else.
| Field | Description | Default |
|---|---|---|
url | Target URL — http:// or https:// | required |
type | Exit type: res, dc, mob, stc | res |
country | Two-letter country code | us |
city / asn / session | Finer targeting, same meaning as in a proxy username | — |
method | HTTP method | GET |
headers | Object of request headers to forward | — |
body | Request body | — |
retry | Retry blocked responses on a fresh exit | true |
follow_redirects | Follow up to 10 redirects | true |
timeout_ms | Per-attempt timeout, 1000–120000 | 30000 |
curl -X POST https://op.wtf/api/fetch \
-H "Authorization: Bearer op-yourkey" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/product/1","country":"de"}'{
"status": 200,
"final_url": "https://example.com/product/1",
"headers": { "content-type": "text/html; charset=utf-8" },
"body": "<!doctype html>…",
"body_base64": null,
"bytes": 48213,
"truncated": false,
"ttfb_ms": 412,
"total_ms": 690,
"attempts": 2,
"blocked": null,
"exit_type": "res",
"country": "de",
"charged": 0.00201
}body holds the response as text. If it isn't valid UTF-8, body is null and body_base64 carries the bytes instead. Bodies are capped at 8 MB; anything larger comes back with truncated: true.
Pricing: €0.001 per request that returns content, plus €0.001 if a retry was needed to get it, plus bandwidth at your exit type's rate. blocked non-null means no per-request fee was charged.
Geo check
Fetch one URL from many countries at once and see exactly what each of them got.
Built for anyone who has to answer "is it the same everywhere?" — ad verification, geo-pricing checks, compliance, localisation QA, SEO. One call, up to 15 countries.
curl -X POST https://op.wtf/api/geo-check \
-H "Authorization: Bearer op-yourkey" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","countries":["us","gb","de","jp"]}'{
"url": "https://example.com",
"exit_type": "res",
"consistent": false,
"checked": 4,
"charged": 0.008,
"results": [
{
"country": "us",
"ok": true,
"status": 200,
"ttfb_ms": 180,
"total_ms": 340,
"final_url": "https://example.com/us/",
"redirected": true,
"title": "Example — US",
"content_length": 20481,
"content_hash": "9f2c…",
"blocked": null,
"fingerprint": ["Cloudflare"]
}
]
}content_hash is a stable fingerprint of the body, so you can compare countries without diffing pages yourself. consistent is true when every country that got through saw identical content.
Pricing: €0.002 per country checked, plus bandwidth. Duplicate country codes are removed before you're charged.
Inspect
Headers, redirect chain, edge fingerprint and a security-header audit — from any country.
curl "https://op.wtf/api/inspect?url=https%3A%2F%2Fexample.com&country=gb" \ -H "Authorization: Bearer op-yourkey"
{
"url": "https://example.com",
"final_url": "https://www.example.com/",
"status": 200,
"hops": 2,
"redirects": [
{ "url": "https://example.com", "status": 301, "location": "https://www.example.com/" },
{ "url": "https://www.example.com/", "status": 200, "location": null }
],
"headers": { "server": "cloudflare" },
"fingerprint": ["Cloudflare", "Next.js"],
"security": {
"present": ["strict-transport-security"],
"missing": ["content-security-policy", "x-frame-options"],
"score": 16
},
"title": "Example Domain",
"blocked": null,
"ttfb_ms": 210,
"total_ms": 260,
"country": "gb",
"charged": 0.001
}Pricing: €0.001 per inspection plus bandwidth. A chain that never settles within 10 hops is free.
Geo DNS
Resolve a hostname as it is seen from another country.
Geo-steered DNS hands out different addresses depending on where the question comes from. This is how you check which one your users in a given market actually receive.
curl "https://op.wtf/api/dns?name=example.com&type=A&country=jp" \ -H "Authorization: Bearer op-yourkey"
{
"name": "example.com",
"type": "A",
"country": "jp",
"answer_count": 2,
"answers": [
{ "name": "example.com", "type": "A", "ttl": 300, "data": "93.184.216.34" }
],
"charged": 0.0002
}Supported record types: A, AAAA, CNAME, MX, TXT, NS, SOA, SRV, HTTPS.
Pricing: €0.0002 per lookup.
Exit IP
Which address does this targeting give me right now?
Takes the same targeting parameters as a proxy username and reports the live exit. Use it to confirm a sticky session really is sticky, or to record which address handled a job.
curl "https://op.wtf/api/exit-ip?type=res&country=us&session=abc123" \ -H "Authorization: Bearer op-yourkey"
{
"exit_ip": "104.28.91.7",
"exit_type": "res",
"country": "us",
"session": "abc123",
"latency_ms": 380,
"charged": 0.0002
}There is also GET /api/echo-ip, which needs no key and simply returns the address the request came from — point any client at it to see its own egress.
Pricing: €0.0002 per probe. A probe that doesn't come back is free.
Proxy tester
Test your own proxy list — alive, latency and real exit address for each line.
Accepts every format people actually have lying around: host:port, host:port:user:pass, user:pass@host:port and scheme://user:pass@host:port for http, https, socks4, socks5 and socks5h. Up to 200 lines per call.
curl -X POST https://op.wtf/api/proxy-test \
-H "Authorization: Bearer op-yourkey" \
-H "Content-Type: application/json" \
-d '{"proxies":["1.2.3.4:8080:user:pass","socks5://5.6.7.8:1080"]}'{
"tested": 2,
"alive": 1,
"dead": 1,
"charged": 0.001,
"results": [
{ "proxy": "1.2.3.4:8080", "scheme": "http", "ok": true, "alive": true,
"status": 200, "latency_ms": 640, "exit_ip": "1.2.3.4" },
{ "proxy": "5.6.7.8:1080", "scheme": "socks5", "ok": true, "alive": false,
"latency_ms": 10000, "error": "timeout" }
]
}Credentials you submit are never echoed back — the proxy field carries host and port only.
Pricing: €0.0005 per proxy that produced a verdict. "Dead" is a verdict; a line we couldn't parse is free.
Proxy detection
Check whether an IP belongs to a known proxy or VPN pool. Pass an IP and get back the provider name(s) that operate it — or unknown when we have no record of it. Useful for scoring signups, gating abuse, or vetting your own exit IPs.
Endpoint
GET https://op.wtf/api/detect?ip=<ip>
Authentication
Authenticate with your op-… API key — the same key you use for the proxy. Send it as a Bearer token, an X-Api-Key header, or a key= query parameter.
# Bearer token (recommended)
curl "https://op.wtf/api/detect?ip=1.2.3.4" \
-H "Authorization: Bearer op-yourkey"
# X-Api-Key header
curl "https://op.wtf/api/detect?ip=1.2.3.4" \
-H "X-Api-Key: op-yourkey"
# Query parameter
curl "https://op.wtf/api/detect?ip=1.2.3.4&key=op-yourkey"Response
A definitive answer for an IP that is in a known proxy pool:
{
"ip": "1.2.3.4",
"status": "proxy",
"proxy": true,
"providers": ["Provider A", "Provider B"],
"charged": 0.0001
}An IP that is not in any known pool:
{
"ip": "8.8.8.8",
"status": "clean",
"proxy": false,
"providers": [],
"charged": 0.0001
}When we can't determine the answer, you get status: "unknown" and nothing is charged:
{
"ip": "1.2.3.4",
"status": "unknown",
"proxy": false,
"providers": [],
"charged": 0
}| Field | Description |
|---|---|
status | proxy, clean, or unknown |
proxy | true if the IP is in a known proxy/VPN pool |
providers | Array of provider names operating the IP (empty if clean/unknown) |
charged | Amount debited for this check — 0.0001 or 0 |
Pricing
$0.0001 per check (one hundredth of a cent), billed to your credit balance — trial credits are used first. You are only charged when we return a definitive result (proxy or clean). An unknown result is always free. Private, loopback, and reserved IPs answer unknown instantly at no cost.
Errors
| Code | Meaning |
|---|---|
400 missing_ip | No ip parameter supplied. |
400 invalid_ip | The ip value isn't a valid IPv4/IPv6 address. |
401 invalid_api_key | No key supplied, or the key is not recognized. |
402 insufficient_balance | Your balance is too low. Top up in the dashboard. |
429 rate_limited | Too many checks — slow down (120/min per key). |
Bulk checks
Send up to 1000 addresses in one call instead of one request each. Duplicates are removed before billing, and the price per address is unchanged.
curl -X POST https://op.wtf/api/detect/bulk \
-H "Authorization: Bearer op-yourkey" \
-H "Content-Type: application/json" \
-d '{"ips":["1.2.3.4","5.6.7.8","2606:4700:4700::1111"]}'{
"checked": 2,
"proxies": 1,
"clean": 1,
"charged": 0.0002,
"results": [
{ "ip": "1.2.3.4", "status": "proxy", "proxy": true, "providers": ["ExampleVPN"] },
{ "ip": "5.6.7.8", "status": "clean", "proxy": false, "providers": [] },
{ "ip": "2606:4700:4700::1111", "status": "unknown", "proxy": false, "providers": [] }
]
}checked counts only the addresses you were billed for. unknown and malformed entries are free, exactly as on the single endpoint.
Sub-keys
Scoped credentials that spend from your balance, but only as far as you allow.
Give one to each client, machine or project. Every sub-key can carry its own spend cap, bandwidth cap, expiry date and list of exits it's allowed to use. Revoke one without touching your main key or disturbing anything else.
A sub-key starts with sk- and works everywhere your main key works — as the proxy password, as a Bearer token, as X-Api-Key, or as ?key=.
| Field | Description |
|---|---|
label | Your own name for the key |
spend_limit | Maximum € this key may consume. 0 = unlimited |
bytes_limit | Maximum bytes this key may move. 0 = unlimited |
allowed_types | Comma-separated exit types, e.g. res,dc. Blank = all |
allowed_countries | Comma-separated country codes. Blank = all |
expires_at | RFC 3339 timestamp after which the key stops working |
active | Set false to suspend without deleting |
GET /api/keys list your keys
POST /api/keys create one
PUT /api/keys/{key} change limits, scope or active state
POST /api/keys/{key}/reset zero its usage counters
DELETE /api/keys/{key} revoke itThese are managed from your signed-in dashboard session, not with an API key — so a leaked sub-key can never mint more keys. Manage them under Keys & Access in the dashboard.
A request from a key that has used its allowance returns 402 quota_exceeded; one aimed at an exit it wasn't granted returns 403 exit_not_allowed.
IP allowlist
Authorise a whole network to use the proxy with no credentials at all.
Add an address or a CIDR range and connections from it authenticate as you — no username, no password, nothing to leak on a machine you don't fully trust. Both IPv4 and IPv6 are supported.
GET /api/allowlist list authorised sources
POST /api/allowlist { "cidr": "203.0.113.0/24", "label": "office" }
POST /api/allowlist/remove { "cidr": "203.0.113.0/24" }Anything on this list can spend your balance. Keep the ranges as narrow as you can, and pair it with a spend limit.
Spend limits
A hard daily ceiling across everything on the account.
Set daily_spend_limit and once that much has been debited since midnight UTC, metered calls stop until the day rolls over. It covers proxy bandwidth and API calls alike — the guard rail against a runaway job quietly draining a balance overnight.
GET /api/limits { "daily_spend_limit": 25, "notify_below": 5, "spent_today": 3.9412 }
PUT /api/limits { "daily_spend_limit": 25, "notify_below": 5 }Calls refused by the cap return 402 spend_limit_reached. Set the limit back to 0 to remove it. notify_below raises a warning on your dashboard when the balance drops under that figure.
Python
import requests
proxies = {
"http": "socks5h://res-us:[email protected]:58996",
"https": "socks5h://res-us:[email protected]:58996",
}
r = requests.get("https://api.ipify.org", proxies=proxies, timeout=30)
print(r.text)Install with pip install requests[socks].
Node.js
import { SocksProxyAgent } from "socks-proxy-agent";
import fetch from "node-fetch";
const agent = new SocksProxyAgent(
"socks5h://res-us:[email protected]:58996"
);
const r = await fetch("https://api.ipify.org", { agent });
console.log(await r.text());Install with npm i socks-proxy-agent node-fetch.
cURL
# SOCKS5h curl -x socks5h://res-us:[email protected]:58996 https://api.ipify.org # HTTP CONNECT tunnel curl -x http://res-us:[email protected]:58996 https://api.ipify.org # Sticky session (canonical form generated by the dashboard) curl -x socks5h://res-us-sid-abc123:[email protected]:58996 https://api.ipify.org # Works on datacenter and mobile too curl -x socks5h://dc-de-sid-abc123:[email protected]:58996 https://api.ipify.org
Browsers
Configure your browser's SOCKS5 proxy to proxy.op.wtf:58996. Username: type-country (e.g. res-us). Password: your op-… API key.
Minecraft
op.wtf routes Minecraft traffic (port 25565) through dedicated gaming upstreams optimized for low latency. The handshake is preserved and version spoofing is handled so you can join any server.
Java Edition (Proxifier)
Install Proxifier
Download from proxifier.com and install.
Add op.wtf as a SOCKS5 server
- Server:
proxy.op.wtf - Port:
58996 - Protocol: SOCKS5
- Username:
res-us-sid-mc_session - Password: your
op-…API key
Create a rule for javaw.exe
Add a rule matching javaw.exe and apply the op.wtf proxy. Optionally restrict it to port 25565 only.
Launch Minecraft
Open the launcher, start the game, connect to your target server. Your IP will appear as a residential US address.
Country & city targeting
# Germany sticky res-de-sid-mc_session # Tokyo, Japan res_sc-jp_tokyo-sid-mc_session # Specific ISP via ASN (Comcast US) res-us-asn-7922-sid-mc_session
Bedrock Edition
Mobile clients don't support SOCKS5 directly. Use a SOCKS5-to-HTTP bridge or a VPN client that supports SOCKS5 (such as V2Ray) to route Bedrock traffic.
Latency tips
- Pick a country geographically close to the target server.
- Keep the same session ID across the play session — rotating exit IPs cause lag spikes.
- Residential adds 30–80 ms typical overhead. Datacenter (
dc-us) is 5–20 ms faster but more flag-prone.
Web scraping
import requests
from itertools import cycle
sessions = [f"scrape_{i}" for i in range(10)]
session_iter = cycle(sessions)
def fetch(url):
sid = next(session_iter)
proxies = {
"http": f"socks5h://res-us-sid-{sid}:[email protected]:58996",
"https": f"socks5h://res-us-sid-{sid}:[email protected]:58996",
}
for _ in range(3):
try:
return requests.get(url, proxies=proxies, timeout=20)
except requests.RequestException:
continue
return NoneFor high throughput use aiohttp with aiohttp_socks and 50–200 concurrent requests.
Selenium / Playwright
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(proxy={
"server": "http://proxy.op.wtf:58996",
"username": "res-us-sid-pw_session",
"password": "op-yourkey",
})
page = browser.new_page()
page.goto("https://api.ipify.org")
print(page.content())Billing
- Pay per GB — top up by card (Visa, Mastercard, Apple Pay, Google Pay and more, from €11) or crypto (50+ coins, from €5).
- Unlimited plans — fixed monthly rate at 1 / 10 / 100 Mbps. Bandwidth is uncharged within your speed allowance.
- Smart Retry (
sr_) — €0.001 per request that needed a retry and then got through. First-try successes and runs that never get through cost nothing extra. - Unlocker (
/api/fetch) — €0.001 per request that returns content, plus bandwidth. Nothing when we can't get through. - Geo Check (
/api/geo-check) — €0.002 per country checked, plus bandwidth. - Inspect (
/api/inspect) — €0.001 per inspection, plus bandwidth. - Geo DNS (
/api/dns) — €0.0002 per lookup. - Exit IP (
/api/exit-ip) — €0.0002 per probe. - Proxy Tester (
/api/proxy-test) — €0.0005 per proxy that produced a verdict. - Proxy Detection (
/api/detect) — $0.0001 per check. Charged only for a definitive result; anunknownanswer is free. - Trial credits — €0.20 free for connecting your Discord. Trial deducts before paid balance.
- Referral — €0.10 each for you and a new user when they sign up with your username.
Bandwidth is metered in real-time and batched to your account every 5 seconds.
Errors
| Code | Meaning |
|---|---|
SOCKS5 0x01 | General failure — upstream couldn't reach target. |
HTTP 407 | Missing or invalid API key. |
HTTP 502 | Upstream connection failed after all retries. |
SOCKS5 0x07 | Command not supported — UDP ASSOCIATE is unavailable. |
Persistent 502s usually mean zero balance — check your dashboard.
API errors
Every API endpoint answers with {"error": "<code>"} and a matching HTTP status.
| Code | HTTP | Meaning |
|---|---|---|
missing_api_key | 401 | No key in the header or query string. |
invalid_api_key | 401 | Key not recognised. |
key_disabled | 401 | That sub-key has been suspended. |
key_expired | 401 | That sub-key is past its expiry date. |
insufficient_balance | 402 | Account balance is too low. |
quota_exceeded | 402 | That sub-key has used its allowance. |
spend_limit_reached | 402 | Daily spend limit hit; resets at midnight UTC. |
exit_not_allowed | 403 | That sub-key may not use the requested exit. |
invalid_url · invalid_scheme | 400 | Target must be an absolute http(s) URL with a real host. |
invalid_country · invalid_type · invalid_city · invalid_asn · invalid_session | 400 | Malformed targeting parameter. |
too_many_countries · too_many_proxies · too_many_ips | 400 | Batch is over the per-call ceiling. |
rate_limited | 429 | Too many calls this minute. |
target_unreachable · timeout | 502 | The target didn't answer. Not charged. |