NullRoute docs
Proxy gateway and tools API
One endpoint for residential, datacenter and mobile exits. The username picks the exit, the password is your API key.
- Host
- nullroute.176-97-210-16.sslip.io
- Port
- 32424
- Protocols
- HTTP, HTTPS, SOCKS5, SOCKS4
- Login
- res-us : op-… or sk-…
Port 32443 is the same gateway inside TLS. The raw IP 176.97.210.16 also works on plain port 32424, but not for TLS: the certificate is issued for the hostname only.
По-русски
Быстрый старт
Ключ. Зарегистрируйтесь и скопируйте ключ
op-…в Dashboard → Settings. Для клиентов и сотрудников делайте отдельные ключиsk-…в API keys.Подключение. Хост
nullroute.176-97-210-16.sslip.io, порт32424(то же в TLS —32443). Протокол HTTP или SOCKS5 — порт один и тот же.Логин и пароль. Логин — какой IP нужен (
res-us,dc-de,mob-gb), пароль — ваш ключ.Проверка. Должен вернуться IP из нужной страны:
curlcurl -x "http://res-us:op-ВАШ-КЛЮЧ@nullroute.176-97-210-16.sslip.io:32424" https://api.ipify.org
| Нужно | Логин |
|---|---|
| Новый IP на каждое соединение | res-us |
| Один IP надолго (сессия) | res-us-sid-4821 |
| Город | res-us-city-new_york |
| Штат (США) | res-us-st-texas или res-us-st-tx |
| ZIP (США) | res-us-zip-10001 |
| ОС | res-us-os-windows |
| Несколько фильтров | res-us-st-texas-os-windows |
| Провайдер (ASN) | res-us-asn-7922 |
| Любая страна | res-any |
- Проверенные фильтры.
-st-<штат>(res-us-st-texasилиres-us-st-tx),-city-<город>(res-us-city-new_york),-zip-<5 цифр>— только США (res-us-zip-10001),-asn-<номер>(res-us-asn-7922),-os-<windows|android|linux|macos>(res-us-os-windows). Фильтры можно совмещать в одном логине:res-us-st-texas-os-windows. - Проверка до выдачи. Шлюз сверяет фильтр с выходом до передачи трафика. Если подходящего выхода нет, вернётся
502 no exit matching filter— списания не будет. ZIP проверяется по радиусу: выход геолоцирован не дальше 25 км от центра индекса, поэтому может оказаться в соседнем ZIP; точный адрес проверяйте через/api/exit-ip. Город тоже привязан к геолокации выхода и покрывает агломерацию (до 30 км): ждите город или его пригороды, а не адрес. - Несовместимо с
-sid-. Липкую сессию и проверенные фильтры в одном логине не совместить: шлюз ответит400 sid cannot be combined with geo filters. Выберите что-то одно.
Шифрованное соединение (TLS)
Порт 32443 — тот же шлюз, но клиент сначала поднимает TLS. Логин и пароль те же, меняются только схема и порт. Подключайтесь по имени хоста: сертификат выпущен для nullroute.176-97-210-16.sslip.io, по IP проверка сертификата не пройдёт.
curl --proxy https://nullroute.176-97-210-16.sslip.io:32443 --proxy-user "res-us:op-ВАШ-КЛЮЧ" https://api.ipify.orgДля антидетекта и браузерных расширений строка такая: nullroute.176-97-210-16.sslip.io:32424:res-us-sid-4821:op-ВАШ-КЛЮЧ. Сессия держит один IP; если он умер, NullRoute сам переведёт её на новый. Соединение без трафика закрывается через 3 минуты. Ошибка 407 — ключ неверный, истёк или кончился лимит; 502 — выход не смог достучаться до сайта, повторите запрос.
Start
Quickstart
-
Get your API key. Create an account, then copy the master key (
op-…) from Dashboard → Settings. Keep it in an environment variable, not in source code.shellexport NULLROUTE_KEY="op-your-key" -
Build the proxy URL. Username = the exit you want, password = the key.
formathttp://<username>:<api-key>@nullroute.176-97-210-16.sslip.io:32424 -
Send a request through a residential US exit and check the IP it left from.
curlcurl -x "http://res-us:$NULLROUTE_KEY@nullroute.176-97-210-16.sslip.io:32424" \ "https://api.ipify.org?format=json" # {"ip":"203.0.113.24"}
Proxy
Endpoint and login
Every protocol uses the same host and port; the gateway detects the protocol from the first byte. Port 32443 carries the same four protocols inside TLS.
| Protocol | Proxy URL | Notes |
|---|---|---|
| HTTP / HTTPS | http://res-us:KEY@nullroute.176-97-210-16.sslip.io:32424 | HTTPS sites go through CONNECT, so TLS stays end-to-end. The proxy URL itself is always http://. |
| SOCKS5 | socks5h://res-us:KEY@nullroute.176-97-210-16.sslip.io:32424 | Username/password auth only. socks5h resolves DNS on the exit. TCP only, no UDP. |
| SOCKS4 / 4a | socks4a://res-us%3AKEY@nullroute.176-97-210-16.sslip.io:32424socks4a://KEY@nullroute.176-97-210-16.sslip.io:32424 | SOCKS4 has no password field, so the key must travel inside the user id: percent-encode the colon (res-us%3AKEY) or send the key alone. The plain res-us:KEY@ form does not work in curl — it splits userinfo at the colon and the gateway sees only res-us. |
Encrypted connection (TLS)
Port 32443 speaks HTTP, HTTPS CONNECT, SOCKS4/4a and SOCKS5 inside TLS. The login is the same; only the proxy scheme and port change. Connect by hostname, not by IP: the certificate is issued for nullroute.176-97-210-16.sslip.io, so a client that dials the raw address fails certificate validation.
# https:// scheme = TLS to the gateway first, then the normal request
curl --proxy https://nullroute.176-97-210-16.sslip.io:32443 --proxy-user "res-us:$NULLROUTE_KEY" https://api.ipify.orgCredentials
| Username | Password | Result |
|---|---|---|
res-us | op-… or sk-… | Standard form. Username selects the exit. |
op-… or sk-… | empty | Rotating residential exit in a random country — the same as res-any, not US. For tools that only have one login field. |
res-us | anything | Only from an allowlisted IP. No key needed. |
op- is your account's master key. sk- keys are sub-keys with their own limits; give those to teammates, scripts and clients.
Proxy
Targeting
The username is <type>-<country>, then any of the verified filters: -st-, -city-, -zip-, -asn-, -os-. A sticky session (-sid-) replaces the filters.
| Parameter | Format | Example | Notes |
|---|---|---|---|
| Exit type | res · dc · mob | dc-us | Residential $0.60/GB, datacenter $0.20/GB, mobile $1.00/GB. |
| Country | <type>-<cc> | res-de | ISO 3166 two-letter code, lowercase. any picks a random country. |
| State | <type>-<cc>-st-<state> | res-us-st-texas or res-us-st-tx | US states. Full name or two-letter code, lowercase. |
| City | <type>-<cc>-city-<city> | res-us-city-new_york | City in lowercase, spaces become underscores. Matched on the exit's geolocation, which is metro-wide: expect the city or its suburbs, not a street-level address. |
| ZIP | <type>-<cc>-zip-<zip> | res-us-zip-10001 | 5 digits, US only. Matched by distance: the exit is geolocated within 25 km of the ZIP's centre, so it may sit in a neighbouring ZIP. Confirm with /api/exit-ip if the exact ZIP matters. |
| ASN | <type>-<cc>-asn-<n> | res-us-asn-7922 | One network only, e.g. AS7922 (Comcast). Digits, no AS prefix. |
| OS | <type>-<cc>-os-<os> | res-us-os-windows | windows, android, linux or macos. |
| Combined | filters joined in one username | res-us-st-texas-os-windows | Every filter must match the same exit. |
| Session | <type>-<cc>-sid-<id> | res-us-sid-4821 | Same id keeps the same IP. Letters and digits. Can't be combined with the filters above. See sessions. |
| Smart retry | sr_ prefix | sr_res-us | Retries blocked plain-HTTP responses. See smart retry. |
res-us # residential, United States, new IP per connection
dc-de # datacenter, Germany
mob-gb # mobile carrier IP, United Kingdom
res-any # residential, random country
res-us-city-chicago # residential, Chicago
res-us-st-texas-os-windows # residential, Texas, Windows exits
res-us-zip-10001 # residential, US ZIP 10001
res-us-asn-7922 # residential, AS7922 only
res-us-os-android # residential, Android exits
res-us-sid-4821 # sticky: same IP while you reuse sid 4821
sr_res-us # smart retry on blocked responses- Can I combine filters? Yes:
res-us-st-texas-os-windows,res-us-city-new_york-asn-7922. Every filter in the username must match the same exit. - What if nothing matches? The gateway verifies the filter against the exit before it hands traffic over. With no matching exit the connection gets
502 no exit matching filterand nothing is charged. - Filters and sticky sessions? Not together.
-sid-pins one exit, so a username can have either filters or a session id.
In the tools API, where city, ASN and session are separate fields, precedence is city > ASN > session: if you send several, only the first one applies.
Proxy
Sessions
| Mode | Username | Behaviour |
|---|---|---|
| Rotating | res-us | Every new connection gets a new exit IP. Requests on one keep-alive connection or tunnel keep its IP. |
| Sticky | res-us-sid-4821 | Every connection with the same id uses the same exit IP for as long as that exit stays online. |
- New IP: change the id, e.g.
res-us-sid-4822. Use random ids (8+ digits) so parallel workers don't share an exit by accident. - Self-healing: if a sticky exit dies (two failed connects in a row, or a tunnel that drops before any data), NullRoute moves the session to a new IP. The id keeps that new IP afterwards, so your session stays stable instead of failing.
- Private ids: ids are scoped to your account. Your
sid-1and another customer'ssid-1are different exits. - Idle timeout: a connection with no traffic in either direction for 3 minutes is closed. A single connection lives at most 24 hours. Reconnecting with the same id gets the same IP.
- Header instead of username: HTTP clients can send
X-Session-Id: 4821. With curl and HTTPS sites use--proxy-header "X-Session-Id: 4821".
Proxy
Code examples
Replace op-your-key or $NULLROUTE_KEY with your key. Each tab is copy-paste ready.
# HTTP proxy
curl -x "http://res-us:$NULLROUTE_KEY@nullroute.176-97-210-16.sslip.io:32424" https://api.ipify.org
# SOCKS5, DNS resolved on the exit
curl -x "socks5h://res-us:$NULLROUTE_KEY@nullroute.176-97-210-16.sslip.io:32424" https://api.ipify.org
# Sticky session in Germany
curl -x "http://res-de-sid-4821:$NULLROUTE_KEY@nullroute.176-97-210-16.sslip.io:32424" https://api.ipify.org# PowerShell: type curl.exe, not curl (curl is an alias for Invoke-WebRequest).
# Always pass the proxy with -x; curl.exe ignores Windows proxy settings.
$env:NULLROUTE_KEY = "op-your-key"
curl.exe -x "http://res-us:$env:NULLROUTE_KEY@nullroute.176-97-210-16.sslip.io:32424" https://api.ipify.org
curl.exe -x "socks5h://res-us:$env:NULLROUTE_KEY@nullroute.176-97-210-16.sslip.io:32424" https://api.ipify.orgimport os, requests
key = os.environ["NULLROUTE_KEY"]
proxy = f"http://res-us:{key}@nullroute.176-97-210-16.sslip.io:32424"
# SOCKS5: pip install "requests[socks]" and use
# proxy = f"socks5h://res-us:{key}@nullroute.176-97-210-16.sslip.io:32424"
r = requests.get("https://api.ipify.org?format=json",
proxies={"http": proxy, "https": proxy}, timeout=30)
print(r.json())// npm i undici
import { fetch, ProxyAgent } from "undici";
const agent = new ProxyAgent({
uri: "http://nullroute.176-97-210-16.sslip.io:32424",
token: "Basic " + Buffer.from(`res-us:${process.env.NULLROUTE_KEY}`).toString("base64"),
});
const res = await fetch("https://api.ipify.org?format=json", { dispatcher: agent });
console.log(await res.json());// Chromium can't authenticate to SOCKS5 proxies: use the HTTP proxy.
import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: {
server: "http://nullroute.176-97-210-16.sslip.io:32424",
username: "res-us-sid-4821",
password: process.env.NULLROUTE_KEY,
},
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org");
console.log(await page.textContent("body"));
await browser.close();# Antidetect browsers, proxy extensions, bots: host:port:user:pass
nullroute.176-97-210-16.sslip.io:32424:res-us-sid-4821:op-your-key
# Or fill the fields separately
Type HTTP (or SOCKS5)
Host nullroute.176-97-210-16.sslip.io
Port 32424
Username res-us-sid-4821
Password op-your-keyFor a browser profile use a sticky username (-sid-), one id per profile, so the IP doesn't change between page loads.
Proxy
Smart retry
Prefix the username with sr_. When a response looks blocked, NullRoute sends the request again from a fresh exit in the same country, up to 3 attempts, and returns the first clean response.
- Counts as blocked: Cloudflare challenge (
cf-mitigated: challenge, or 503 from Cloudflare), DataDome block, captcha or "access denied" pages, HTTP429. - Costs $0.001 per request that needed a retry and then succeeded. First-try successes cost bandwidth only.
- Works for plain
http://requests through the HTTP proxy. HTTPS tunnels and SOCKS are end-to-end encrypted, so the gateway can't read the response. For HTTPS targets, usePOST /api/fetch, where retry is on by default.
curl -x "http://sr_res-us:$NULLROUTE_KEY@nullroute.176-97-210-16.sslip.io:32424" http://example.com/Proxy
Keys and limits
Sub-keys
Create sk-… keys in Dashboard → API keys and use them anywhere the master key works: as the proxy password or as the API bearer token. Disable or delete one without touching the master key. Each sub-key can have:
| Limit | What happens |
|---|---|
| Expiry date | Stops working at the end of that day, UTC. |
| Traffic cap (GB) | New connections are refused once the key has used its GB. |
| Spend cap ($) | Same, by money spent instead of traffic. |
| Exit types and countries | Usernames outside the list are refused, e.g. mob-gb on a key limited to res. |
| Speed cap (Mbps) | Shared by all connections of the key. Set on keys issued by NullRoute. |
Limits are checked when a connection opens; a connection already running finishes. The account's daily spend limit (in API keys) applies to every key.
IP allowlist
Connections from an allowlisted IP or CIDR range don't need a key. Send only the exit as the username:
# from an allowlisted server
curl -x http://res-us@nullroute.176-97-210-16.sslip.io:32424 https://api.ipify.orgIPv4 or IPv6, single address or CIDR, up to 50 entries. SOCKS5 still needs a username and a password field: send res-us and any non-key password. Allowlisted traffic bills the master balance.
Proxy
Troubleshooting
| You see | Cause | What to do |
|---|---|---|
407 Proxy Authentication Required (HTTP), or the connection closes right after login (SOCKS) | Key missing, mistyped, disabled or expired; sub-key out of traffic or spend; no balance; daily spend limit hit; exit type or country not allowed for the sub-key. Over HTTP the reason is in the response body and the X-Proxy-Error header (invalid_credentials, insufficient_balance, quota_exceeded, key_expired, exit_not_allowed…). | Check the key in API keys, top up in Billing, or raise the cap. The key goes in the password, not the username. |
400 unknown exit type / unknown country code | The username doesn't start with res, dc or mob, or the country isn't an ISO code. | Use e.g. res-us, dc-de, mob-gb or res-any. |
502 Bad Gateway, or SOCKS "general failure" | No exit could reach the site after several attempts: site down, port closed, or no IPs left for that city / ASN. | Retry. If it repeats, widen targeting (drop the filter, try another country). |
502 no exit matching filter | A verified filter (-st-, -city-, -zip-, -asn-, -os-) has no matching exit right now. | Widen or drop the filter, or retry later. The request is not charged. |
502 no exit available for this location right now (X-Proxy-Error: no_exit_for_location), or an HTTPS tunnel that closes right after the handshake | The network has no exits in that country at the moment. Small territories are often not covered. After one refusal the gateway answers this for 10 minutes without trying again. | Pick a nearby country or another network type. The request is not charged. |
curl: (97) Can't complete SOCKS4 connection (reply 91) | The SOCKS4 user id was cut at the colon: socks4a://res-us:KEY@… and --proxy-user "res-us:KEY" both send only res-us, because SOCKS4 has no password field. | Percent-encode the colon in the URL (socks4a://res-us%3AKEY@…) or send the key alone (socks4a://KEY@…). |
| Requests go to the US although you wanted another country | The username has no country: an empty username (http://:KEY@…, http://KEY:@…), the key alone, or a bare res / dc / mob defaults to <type>-us. | Put the exit first: http://res-de:KEY@…, or res-any for a random country. An empty password with a real username (res-us:) is rejected with 407. |
Username with -sid- and a filter is refused | Sticky sessions and verified filters can't be combined in one username. The gateway answers 400 with the body sid cannot be combined with geo filters. | Send either the filters or -sid-<id>. |
403 Forbidden, or SOCKS "not allowed" | The destination host or port is blocked by network policy (e.g. SMTP). | Not available through NullRoute. |
| IP changes on every request | Rotating username, or your client opens a new connection per request. | Add -sid-<id> to the username. |
| Sticky IP changed | The exit went offline and the session was moved to a new IP. | Expected. Check the current IP with /api/exit-ip if you need it. |
| Connection drops after a pause | 3-minute idle timeout (or the 24-hour connection limit). | Reconnect; same sid means same IP. |
| Your own IP shows on Windows | curl in PowerShell is Invoke-WebRequest, or -x was left out. | Use curl.exe -x …. |
Tools API
Authentication
The tools API runs checks through NullRoute exits and bills your balance per call. Send your key as a bearer token. Master keys (op-…) and sub-keys (sk-…) both work; sub-key scope rules apply.
Authorization: Bearer op-your-key- Base URL:
https://nullroute.176-97-210-16.sslip.io. JSON in, JSON out. X-API-Key: op-your-keyalso works. Akeyquery or body field is accepted but ends up in logs, so prefer the header.- Every successful response has a
chargedfield: what the call cost in USD. - Exit targeting fields (
type,country,city,asn,session) mirror the username parameters. Defaults:res,us.
Tools API
Endpoints
/api/fetch
$0.001 + bandwidthFetches a URL through an exit and returns status, headers, body and timings. Retries blocked responses on a new exit by default.
| Field | Type | Description |
|---|---|---|
urlrequired | string | http or https URL. |
method | string | Default GET. |
headers | object | Request headers, up to 32. |
body | string | Request body. |
type, country, city, asn, session | string | Exit targeting. Default res, us. |
retry | boolean | Default true. Up to 3 attempts on fresh sticky exits; +$0.001 when a retry was needed. |
follow_redirects | boolean | Default true, up to 10 hops. |
timeout_ms | number | Default 30000, range 1000–120000. |
curl https://nullroute.176-97-210-16.sslip.io/api/fetch \
-H "Authorization: Bearer $NULLROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","country":"de"}'{
"status": 200,
"final_url": "https://example.com/",
"headers": { "content-type": "text/html", "server": "cloudflare" },
"body": "<!doctype html>…",
"body_base64": null,
"bytes": 1712,
"truncated": false,
"ttfb_ms": 412,
"total_ms": 468,
"attempts": 1,
"blocked": null,
"exit_type": "res",
"country": "de",
"charged": 0.0010005
}Binary bodies come back in body_base64. Bodies over 8 MB are cut and flagged truncated. If every attempt is blocked you get the last response with blocked set (challenge, captcha, blocked, rate_limited) and pay bandwidth only.
/api/geo-check
$0.002 / country + bandwidthLoads one URL from up to 15 countries in parallel and tells you whether they all got the same content. Use it to spot geo-blocks and localized pages.
| Field | Type | Description |
|---|---|---|
urlrequired | string | http or https URL. |
countriesrequired | string[] | 1–15 country codes, e.g. ["us","de","jp"]. |
type | string | Exit type for every country. Default res. |
method | string | Default GET. |
timeout_ms | number | Per country. Default 20000, max 60000. |
curl https://nullroute.176-97-210-16.sslip.io/api/geo-check \
-H "Authorization: Bearer $NULLROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","countries":["us","de","jp"]}'{
"url": "https://example.com",
"exit_type": "res",
"results": [
{ "country": "us", "ok": true, "status": 200, "ttfb_ms": 380, "total_ms": 402,
"final_url": "https://example.com/", "redirected": false, "title": "Example Domain",
"content_length": 1256, "content_hash": "84238dfc8092e5d9c0dac8ef93371a07",
"blocked": null, "fingerprint": ["Cloudflare"] },
{ "country": "jp", "ok": false, "error": "upstream_timeout" }
],
"checked": 2,
"consistent": true,
"charged": 0.0040008
}Only countries that answered are billed. consistent compares content_hash across countries that weren't blocked.
/api/inspect
$0.001 + bandwidthFollows redirects one hop at a time (up to 10) and reports the chain, the CDN or WAF in front of the site, and which security headers are set.
| Query | Type | Description |
|---|---|---|
urlrequired | string | URL-encoded http or https URL. |
type, country, city, asn, session | string | Exit targeting. |
curl -G https://nullroute.176-97-210-16.sslip.io/api/inspect \
-H "Authorization: Bearer $NULLROUTE_KEY" \
--data-urlencode "url=http://vercel.com" -d country=us{
"url": "http://vercel.com",
"final_url": "https://vercel.com/",
"status": 200,
"redirects": [
{ "url": "http://vercel.com/", "status": 308, "location": "https://vercel.com/" },
{ "url": "https://vercel.com/", "status": 200, "location": null }
],
"hops": 2,
"headers": { "server": "Vercel", "x-vercel-id": "fra1::…", … },
"fingerprint": ["Vercel"],
"security": {
"present": ["strict-transport-security", "content-security-policy", "x-content-type-options",
"x-frame-options", "referrer-policy"],
"missing": ["permissions-policy"],
"score": 83
},
"title": "Vercel",
"blocked": null,
"ttfb_ms": 290,
"total_ms": 351,
"country": "us",
"charged": 0.0010732
}/api/dns
$0.0002Resolves a hostname over DNS-over-HTTPS through an exit, so you see the answers a visitor in that country gets.
| Query | Type | Description |
|---|---|---|
namerequired | string | Hostname, e.g. example.com. |
type | string | A (default), AAAA, CNAME, MX, TXT, NS, SOA, SRV, HTTPS. |
country | string | Exit country. Default us. |
curl -H "Authorization: Bearer $NULLROUTE_KEY" \
"https://nullroute.176-97-210-16.sslip.io/api/dns?name=example.com&type=A&country=de"{
"name": "example.com",
"type": "A",
"country": "de",
"answers": [
{ "name": "example.com", "type": "A", "ttl": 214, "data": "23.192.228.80" },
{ "name": "example.com", "type": "A", "ttl": 214, "data": "23.215.0.136" }
],
"answer_count": 2,
"charged": 0.0002
}/api/exit-ip
$0.0002Returns the public IP a request with these targeting options leaves from. Pass session to check which IP a sticky session holds.
| Query | Type | Description |
|---|---|---|
type, country, city, asn, session | string | Exit targeting. Default res, us. |
curl -H "Authorization: Bearer $NULLROUTE_KEY" \
"https://nullroute.176-97-210-16.sslip.io/api/exit-ip?type=res&country=gb&session=4821"{
"exit_ip": "203.0.113.24",
"exit_type": "res",
"country": "gb",
"session": "4821",
"latency_ms": 640,
"charged": 0.0002
}/api/proxy-test
$0.0005 / proxyChecks whether proxies are alive, how fast they answer and which IP they exit from. Works with any provider's proxies, not just NullRoute.
| Field | Type | Description |
|---|---|---|
proxiesrequired | string[] | 1–200 lines: host:port, host:port:user:pass or scheme://user:pass@host:port. Schemes: http, https, socks4, socks5, socks5h. |
timeout_ms | number | Per proxy. Default 10000, max 30000. |
curl https://nullroute.176-97-210-16.sslip.io/api/proxy-test \
-H "Authorization: Bearer $NULLROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{"proxies":["socks5://user:pass@198.51.100.7:1080","198.51.100.9:8080"]}'{
"results": [
{ "proxy": "socks5://198.51.100.7:1080", "scheme": "socks5", "ok": true, "alive": true,
"status": 200, "latency_ms": 312, "exit_ip": "198.51.100.7" },
{ "proxy": "http://198.51.100.9:8080", "scheme": "http", "ok": true, "alive": false,
"latency_ms": 10004, "error": "timeout" }
],
"tested": 2,
"alive": 1,
"dead": 1,
"charged": 0.001
}Lines that can't be parsed come back with "ok": false and aren't billed.
/api/detect
$0.0001 / IPTells you whether an IP belongs to a known proxy or VPN provider, and which one. Use the bulk form for lists up to 1,000 addresses.
| Request | Parameters | Description |
|---|---|---|
GET /api/detect | ip | One address. Master key only. |
POST /api/detect/bulk | ips: string[] | Up to 1,000 addresses, de-duplicated before billing. 10 calls per minute. |
curl https://nullroute.176-97-210-16.sslip.io/api/detect/bulk \
-H "Authorization: Bearer $NULLROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{"ips":["198.51.100.7","203.0.113.24","10.0.0.1"]}'{
"results": [
{ "ip": "198.51.100.7", "status": "proxy", "proxy": true, "providers": ["ExampleVPN"] },
{ "ip": "203.0.113.24", "status": "clean", "proxy": false, "providers": [] },
{ "ip": "10.0.0.1", "status": "unknown", "proxy": false, "providers": [] }
],
"checked": 2,
"proxies": 1,
"clean": 1,
"charged": 0.0002
}Status is proxy, clean, unknown (private address or no data, free) or invalid (not an IP, free).
Resellers
Reseller API
Для реселлеров: выпускайте ключи клиентам из своего бота или сайта — срок, скорость и лимит трафика задаются в одном запросе, трафик списывается с вашего баланса по оптовой цене.
Issue proxy keys to your customers from your own shop or bot. Each key gets an expiry, a speed cap and an optional traffic cap; its traffic is charged to your balance at your wholesale price.
- Base URL:
https://nullroute.176-97-210-16.sslip.io/api/v1/reseller. JSON in, JSON out. - Auth:
Authorization: Bearer op-…, your master key from Settings. Sub-keys are refused with401 use_master_key. - Accounts without reseller access get
403 not_a_reseller; a paused reseller account gets403 reseller_disabled. - No access yet? Unlock it from Billing for a one-off $170 — that buys the rights only (API, wholesale rates, client keys); traffic keeps billing your balance. An admin can re-price it or attach your domain afterwards.
- When your balance reaches $0, every key you issued stops authenticating. When a key expires, is disabled or runs out of traffic, its open tunnels close within about 30 seconds.
export PROXY_API_KEY="op-your-master-key"
export API="https://nullroute.176-97-210-16.sslip.io/api/v1/reseller"| Request | Does |
|---|---|
GET /balance | Balance, wholesale price per GB and GB left per exit type. |
POST /keys | Create a key. Returns 201 and the key object. |
GET /keys | List keys. Query: status=active|expired|disabled, limit (default 100, max 1000), offset. |
GET /keys/:key | One key. 404 not_found if it isn't yours. |
PATCH /keys/:key | Extend, disable, re-enable, change speed, traffic cap or label. |
DELETE /keys/:key | Delete the key. Returns {"success": true}. |
GET /keys/:key/usage | Hourly traffic. Query hours, default 24, max 2160. |
Create a key
| Field | Type | Description |
|---|---|---|
hours or days | number | Lifetime. Default 1 day, max 365 days. If both are sent, hours wins. |
speed_mbps | number | Shared by all the key's connections. 0 = no cap, max 10000. |
traffic_gb | number | Traffic cap. 0 or absent = unlimited. |
allowed_types | string | Comma list of res, dc, mob. Empty = all. |
label | string | Your note, e.g. the customer's order id. |
curl -X POST $API/keys \
-H "Authorization: Bearer $PROXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hours":2,"speed_mbps":500,"label":"order-1042"}'{
"key": "sk-3f9c…",
"label": "order-1042",
"active": true,
"expired": false,
"created_at": "2026-10-05T12:00:00Z",
"expires_at": "2026-10-05T14:00:00Z",
"speed_mbps": 500,
"traffic_limit_gb": null,
"traffic_used_gb": 0.0,
"cost_usd": 0.0,
"allowed_types": "",
"online": false,
"now_mbps": 0.0,
"connect": {
"host": "nullroute.176-97-210-16.sslip.io",
"port": 32424,
"username_example": "res-any",
"http": "http://res-any:sk-3f9c…@nullroute.176-97-210-16.sslip.io:32424",
"socks5": "socks5://res-any:sk-3f9c…@nullroute.176-97-210-16.sslip.io:32424",
"line": "nullroute.176-97-210-16.sslip.io:32424:res-any:sk-3f9c…"
}
}Send connect.line or connect.http to the customer as is. They can change res-any to any targeting username. Errors: 400 invalid_duration, invalid_speed, invalid_traffic_gb, invalid_allowed_types; 402 insufficient_balance when your balance is $0 or less.
Manage keys
PATCH takes any of extend_hours (added to the current expiry, or to now if the key already expired), active, speed_mbps, traffic_gb, label, and returns the updated key object.
K=sk-3f9c…
# Extend by 24 hours
curl -X PATCH $API/keys/$K -H "Authorization: Bearer $PROXY_API_KEY" \
-H "Content-Type: application/json" -d '{"extend_hours":24}'
# Disable now (re-enable with {"active":true})
curl -X PATCH $API/keys/$K -H "Authorization: Bearer $PROXY_API_KEY" \
-H "Content-Type: application/json" -d '{"active":false}'
# Delete
curl -X DELETE $API/keys/$K -H "Authorization: Bearer $PROXY_API_KEY"
# Balance and hourly usage
curl $API/balance -H "Authorization: Bearer $PROXY_API_KEY"
curl "$API/keys/$K/usage?hours=24" -H "Authorization: Bearer $PROXY_API_KEY"{
"balance_usd": 42.5,
"wholesale_per_gb": { "res": 0.35, "dc": 0.12, "mob": 0.6 },
"gb_left": { "res": 121.43, "dc": 354.17, "mob": 70.83 },
"gateway": "nullroute.176-97-210-16.sslip.io"
}{
"points": [
{ "hour": "2026-10-05T12:00:00Z", "bytes": 73400320 },
{ "hour": "2026-10-05T13:00:00Z", "bytes": 18874368 }
]
}Hours are UTC. Hours without traffic are left out.
Example: sell a key from a bot
import os, requests
API = "https://nullroute.176-97-210-16.sslip.io/api/v1/reseller"
HEADERS = {"Authorization": f"Bearer {os.environ['PROXY_API_KEY']}"}
def sell_key(order_id: str, hours: int, mbps: int = 0, gb: float = 0) -> str:
"""Call after payment. Returns the text to send to the customer."""
r = requests.post(f"{API}/keys", headers=HEADERS, timeout=15, json={
"label": order_id, "hours": hours,
"speed_mbps": mbps, "traffic_gb": gb,
})
if r.status_code == 402:
raise RuntimeError("Reseller balance is empty: top up before selling")
r.raise_for_status()
k = r.json()
return (f"Your proxy (valid until {k['expires_at']} UTC):\n"
f"{k['connect']['line']}\n"
f"HTTP: {k['connect']['http']}")
# bot.send_message(chat_id, sell_key("tg-551-1042", hours=24, mbps=100))Reference
Pricing and limits
Prepaid balance in USD; top up from $5 in the dashboard. Proxy traffic is billed per byte in both directions at the exit type's rate.
| Exit type | Username | Per GB |
|---|---|---|
| Residential | res | $0.60 |
| Datacenter | dc | $0.20 |
| Mobile | mob | $1.00 |
| Tool | Price | Per minute | Batch limit |
|---|---|---|---|
POST /api/fetch | $0.001 + bandwidth | 300 | 8 MB body |
| Smart retry | +$0.001 when a retry succeeds | — | 3 attempts |
POST /api/geo-check | $0.002 / country + bandwidth | 60 | 15 countries |
GET /api/inspect | $0.001 + bandwidth | 120 | 10 redirects |
GET /api/dns | $0.0002 | 300 | — |
GET /api/exit-ip | $0.0002 | 120 | — |
POST /api/proxy-test | $0.0005 / proxy | 30 | 200 proxies |
/api/detect | $0.0001 / IP | 120 · bulk 10 | 1,000 IPs |
"Bandwidth" is the response size at the exit type's per-GB rate. Rate limits are per account; going over returns 429 rate_limited. A daily spend limit and per-key caps are set in Dashboard → API keys.
Reference
API errors
API errors are JSON with an HTTP status and a stable code:
HTTP/1.1 402 Payment Required
{ "error": "insufficient_balance" }| Status | Code | What to do |
|---|---|---|
| 400 | invalid_url, invalid_scheme | Send a full http or https URL. |
| 400 | invalid_type, invalid_country, invalid_city, invalid_asn, invalid_session | Check the targeting fields against the username format. |
| 400 | invalid_name, invalid_ip, missing_ip | Send a valid hostname or IP address. |
| 400 | no_countries, too_many_countries, no_proxies, too_many_proxies, no_ips, too_many_ips | Stay within the batch limits in pricing and limits. |
| 401 | missing_api_key, invalid_api_key | Send Authorization: Bearer <key> with a current key. |
| 401 | key_disabled, key_expired | Re-enable the sub-key or create a new one. |
| 402 | insufficient_balance | Top up in Billing. |
| 402 | spend_limit_reached, quota_exceeded | Daily limit or sub-key cap hit. Raise it, or wait for 00:00 UTC. |
| 403 | exit_not_allowed | The sub-key doesn't allow that exit type or country. |
| 429 | rate_limited | Back off and retry after a few seconds. |
| 502 | target_unreachable, probe_failed, resolve_failed, too_many_redirects | The exit couldn't complete the request. Retry; you aren't charged. |
Proxy connection errors (407, 502, 403) are covered in troubleshooting.