URnetwork free-tier farm: API-only accounts, keys, hosted residential proxies + measured limits
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
bank.jsonl
|
||||||
|
pool.jsonl
|
||||||
|
px.txt
|
||||||
|
verify.json
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# ur-network-farm
|
||||||
|
|
||||||
|
Free-tier **residential** egress from URnetwork (ur.io) without a card, an email, or a browser.
|
||||||
|
Everything runs over the public API — an account, an API key and up to five hosted proxies in
|
||||||
|
about 20 seconds.
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /auth/network-create {"terms":true,"product_updates":false} -> network.by_jwt
|
||||||
|
POST /auth/regenerate-seedphrase -> seedphrase (24 words)
|
||||||
|
POST /account/api-key {"name":"farm-1"} -> urn_... key
|
||||||
|
POST /network/auth-client {"device_spec":"resident proxy",
|
||||||
|
"proxy_config":{"initial_device_state":{"country_code":"us"}}}
|
||||||
|
-> https_proxy_url (creds inside)
|
||||||
|
GET /network/proxies -> every proxy of the account
|
||||||
|
GET /subscription/balance -> bytes left today
|
||||||
|
```
|
||||||
|
|
||||||
|
Base: `https://api.bringyour.com`. Auth: `Authorization: Bearer <by_jwt | urn_key>`.
|
||||||
|
|
||||||
|
## Limits (measured 2026-11-13)
|
||||||
|
|
||||||
|
| limit | value | proof |
|
||||||
|
|---|---|---|
|
||||||
|
| traffic per account | **30 GiB / day**, resets 00:00 UTC | `start_balance_byte_count: 32212254720`, `start_time 2026-11-13T00:00:00Z → end_time 2026-11-14T01:00:00Z` |
|
||||||
|
| active proxies | **5 per account** | 6th call → `{"error":{"client_limit_exceeded":true,"upgrade_required":true,"message":"Your plan's limit of 5 active proxies is reached..."}}` |
|
||||||
|
| proxy creation rate | ~5 / minute / account | 429 on bursts; script sleeps 1.3 s |
|
||||||
|
| instant accounts per IP | ~4, then 429 | `Too many accounts have been created recently from your network address. This limit is scoped to the address you are connecting from and is shared with everyone` |
|
||||||
|
| countries | 20+ residential providers | 15 verified by `ipinfo` in one evening (US DE NL FR GB CA BR SE PL CH JP SG IT ES AT) |
|
||||||
|
|
||||||
|
Bypass for the IP cap: create the first ~4 accounts directly, then feed their own proxy URLs back
|
||||||
|
into the farm as rotating egress (`--proxy-file px.txt`). 8 more accounts went through with zero 429.
|
||||||
|
|
||||||
|
Pro tier ($3 first year, USDC/Zcash/Monero or card) raises it to 10 TiB/month and 1000 proxies.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 5 accounts, 5 proxies each, direct connection
|
||||||
|
python ur_farm.py --count 5 --proxies 5 --bank bank.jsonl
|
||||||
|
|
||||||
|
# more accounts, rotating egress through proxies we already own
|
||||||
|
python ur_farm.py --count 20 --proxies 5 --proxy-file px.txt --bank bank.jsonl --start 500
|
||||||
|
|
||||||
|
# export every proxy URL into a flat pool
|
||||||
|
python collect.py bank.jsonl > pool.jsonl
|
||||||
|
|
||||||
|
# liveness pass over the pool (ipinfo through each proxy)
|
||||||
|
python verify.py pool.jsonl --limit 20
|
||||||
|
```
|
||||||
|
|
||||||
|
`bank.jsonl` line shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"idx":300,"network_id":"...","name":"february-exhaust-83","jwt":"...","seed":"24 words",
|
||||||
|
"urn":"urn_...","proxies":[{"cc":"us","url":"https://<token>@host:7155","socks":"socks5h://..."}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proxy URL shape
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<40-char-token>.crisp.bringyour.com:7155 # HTTPS CONNECT, token is the credential
|
||||||
|
socks5h://crisp.bringyour.com:7153 # SOCKS5 with username/password auth
|
||||||
|
http://crisp.bringyour.com:7154 # plain HTTP
|
||||||
|
```
|
||||||
|
|
||||||
|
`https_proxy_url` carries the token inline, so `curl --proxy "$url"` works as-is.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- **`country_code` is mandatory** in `initial_device_state` — omit it and the call answers `Invalid location`.
|
||||||
|
- **Exit IP is not pinned.** The same proxy URL returned `188.103.48.237 AS3209 Vodafone Augsburg`
|
||||||
|
and later `80.171.40.200 AS6805 Telefonica Essen`. Enable *Fixed IP* in the proxy options when a
|
||||||
|
stable address matters, or treat stability as a known risk.
|
||||||
|
- **Save the seed before anything else.** The account has no email and no password recovery; losing
|
||||||
|
the JWT/seed loses the account.
|
||||||
|
- **Do not drive the browser for this.** UI proxy creation needs clipboard reads that fail when the
|
||||||
|
document is not focused, and copies the URL of the wrong card. `GET /network/proxies` returns every
|
||||||
|
URL directly.
|
||||||
|
- Proxies are residential but shared: several accounts can land on the same provider
|
||||||
|
(two `Vodafone Hildesheim` exits were seen), so `unique_ip` is the metric, not `alive`.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Free-tier testing of a public API on accounts we create ourselves. No card data, no third-party
|
||||||
|
accounts, no payment flows. Respect the provider's terms — this is a field note, not a licence.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
proxies: 34 (unique) from 34 bank lines
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Flatten a bank.jsonl into a proxy pool: one JSON line per proxy URL.
|
||||||
|
|
||||||
|
Usage: python collect.py [bank.jsonl] > pool.jsonl
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
src = sys.argv[1] if len(sys.argv) > 1 else "bank.jsonl"
|
||||||
|
seen = set()
|
||||||
|
out = sys.stdout
|
||||||
|
n = 0
|
||||||
|
for line in open(src, encoding="utf-8"):
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
rec = json.loads(line)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
for p in rec.get("proxies") or []:
|
||||||
|
url = p.get("url")
|
||||||
|
if not url or url in seen:
|
||||||
|
continue
|
||||||
|
seen.add(url)
|
||||||
|
row = {"acct": rec.get("idx"), "name": rec.get("name"), "cc": p.get("cc"),
|
||||||
|
"url": url, "socks": p.get("socks"), "client_id": p.get("client_id")}
|
||||||
|
out.write(json.dumps(row, ensure_ascii=False) + "\n")
|
||||||
|
n += 1
|
||||||
|
sys.stderr.write("proxies: %d (unique) from %d bank lines\n" % (n, len(seen)))
|
||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""URnetwork farm — instant networks, API keys, hosted proxies. No browser.
|
||||||
|
|
||||||
|
POST /auth/network-create {"terms": true} -> by_jwt (an account with no email)
|
||||||
|
then: seedphrase, urn_ key, N hosted HTTPS proxies by country.
|
||||||
|
|
||||||
|
Usage: python ur_farm.py --count 5 [--proxies 5] [--bank bank.jsonl]
|
||||||
|
"""
|
||||||
|
import argparse, json, os, ssl, subprocess, sys, time, urllib.request, urllib.error
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
BASE = "https://api.bringyour.com"
|
||||||
|
CTX = ssl.create_default_context()
|
||||||
|
CTX.check_hostname = False
|
||||||
|
CTX.verify_mode = ssl.CERT_NONE
|
||||||
|
CODES = ["us", "de", "nl", "fr", "gb", "ca", "br", "se", "pl", "ch", "jp", "sg", "it", "es", "at"]
|
||||||
|
|
||||||
|
|
||||||
|
PROXY = None
|
||||||
|
|
||||||
|
|
||||||
|
def call(path, method="GET", payload=None, tok=None, timeout=60):
|
||||||
|
if PROXY:
|
||||||
|
return call_curl(path, method, payload, tok, timeout)
|
||||||
|
data = json.dumps(payload).encode() if payload is not None else None
|
||||||
|
h = {"Accept": "application/json", "User-Agent": "Mozilla/5.0"}
|
||||||
|
if data:
|
||||||
|
h["Content-Type"] = "application/json"
|
||||||
|
if tok:
|
||||||
|
h["Authorization"] = "Bearer " + tok
|
||||||
|
req = urllib.request.Request(BASE + path, data=data, headers=h, method=method)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout, context=CTX) as r:
|
||||||
|
body = r.read(60000).decode("utf-8", "replace")
|
||||||
|
try:
|
||||||
|
return r.status, json.loads(body)
|
||||||
|
except Exception:
|
||||||
|
return r.status, body
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
return e.code, e.read(2000).decode("utf-8", "replace")[:400]
|
||||||
|
except Exception as e:
|
||||||
|
return 0, "%s: %s" % (type(e).__name__, str(e)[:120])
|
||||||
|
|
||||||
|
|
||||||
|
def call_curl(path, method, payload, tok, timeout):
|
||||||
|
cmd = ["curl", "-s", "-m", str(timeout), "--proxy", PROXY, "-X", method,
|
||||||
|
BASE + path, "-H", "Accept: application/json",
|
||||||
|
"-H", "User-Agent: Mozilla/5.0"]
|
||||||
|
if tok:
|
||||||
|
cmd += ["-H", "Authorization: Bearer " + tok]
|
||||||
|
if payload is not None:
|
||||||
|
cmd += ["-H", "Content-Type: application/json", "-d", json.dumps(payload)]
|
||||||
|
try:
|
||||||
|
out = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout + 20).stdout
|
||||||
|
try:
|
||||||
|
return 200, json.loads(out)
|
||||||
|
except Exception:
|
||||||
|
return 0, out[:400]
|
||||||
|
except Exception as e:
|
||||||
|
return 0, "%s: %s" % (type(e).__name__, str(e)[:100])
|
||||||
|
|
||||||
|
|
||||||
|
def one(idx, nproxy, country_offset):
|
||||||
|
st, d = call("/auth/network-create", "POST",
|
||||||
|
{"terms": True, "product_updates": False})
|
||||||
|
if not isinstance(d, dict) or "network" not in d:
|
||||||
|
return {"idx": idx, "error": "create %s %s" % (st, str(d)[:160])}
|
||||||
|
net = d["network"]
|
||||||
|
jwt = net.get("by_jwt")
|
||||||
|
if not jwt:
|
||||||
|
return {"idx": idx, "error": "no jwt"}
|
||||||
|
rec = {"idx": idx, "network_id": net.get("network_id"),
|
||||||
|
"name": net.get("network_name"), "jwt": jwt, "proxies": []}
|
||||||
|
|
||||||
|
st, sd = call("/auth/regenerate-seedphrase", "POST", {}, jwt)
|
||||||
|
seed = sd.get("seedphrase") if isinstance(sd, dict) else None
|
||||||
|
if isinstance(seed, list):
|
||||||
|
seed = " ".join(seed)
|
||||||
|
rec["seed"] = seed
|
||||||
|
|
||||||
|
st, kd = call("/account/api-key", "POST", {"name": "farm-%d" % idx}, jwt)
|
||||||
|
key = kd.get("api_key") if isinstance(kd, dict) else None
|
||||||
|
if not key:
|
||||||
|
rec["error"] = "api-key %s %s" % (st, str(kd)[:120])
|
||||||
|
return rec
|
||||||
|
rec["urn"] = key
|
||||||
|
|
||||||
|
for i in range(nproxy):
|
||||||
|
cc = CODES[(country_offset + i) % len(CODES)]
|
||||||
|
st, pd = call("/network/auth-client", "POST", {
|
||||||
|
"description": "HTTPS proxy · %s" % cc.upper(),
|
||||||
|
"device_spec": "resident proxy",
|
||||||
|
"proxy_config": {"initial_device_state": {"country_code": cc}}}, key)
|
||||||
|
r = (pd or {}).get("proxy_config_result") if isinstance(pd, dict) else None
|
||||||
|
if r and r.get("https_proxy_url"):
|
||||||
|
rec["proxies"].append({"cc": cc, "url": r["https_proxy_url"],
|
||||||
|
"socks": r.get("socks_proxy_url"),
|
||||||
|
"client_id": pd.get("client_id")})
|
||||||
|
else:
|
||||||
|
err = (pd or {}).get("error") if isinstance(pd, dict) else pd
|
||||||
|
rec.setdefault("proxy_errors", []).append("%s: %s" % (cc, str(err)[:80]))
|
||||||
|
time.sleep(1.3)
|
||||||
|
return rec
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--count", type=int, default=5)
|
||||||
|
ap.add_argument("--proxies", type=int, default=5)
|
||||||
|
ap.add_argument("--bank", default="bank.jsonl")
|
||||||
|
ap.add_argument("--start", type=int, default=100)
|
||||||
|
ap.add_argument("--proxy", default="", help="http(s) proxy URL for the whole run")
|
||||||
|
ap.add_argument("--proxy-file", default="", help="file with one proxy URL per line (rotates)")
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
global PROXY
|
||||||
|
pool = []
|
||||||
|
if a.proxy_file and os.path.exists(a.proxy_file):
|
||||||
|
pool = [l.strip() for l in open(a.proxy_file, encoding="utf-8") if l.strip()]
|
||||||
|
made, okp = 0, 0
|
||||||
|
for n in range(a.count):
|
||||||
|
if a.proxy:
|
||||||
|
PROXY = a.proxy
|
||||||
|
elif pool:
|
||||||
|
PROXY = pool[n % len(pool)]
|
||||||
|
idx = a.start + n
|
||||||
|
rec = one(idx, a.proxies, n * 3)
|
||||||
|
with open(a.bank, "a", encoding="utf-8") as fh:
|
||||||
|
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
||||||
|
if rec.get("urn"):
|
||||||
|
made += 1
|
||||||
|
okp += len(rec.get("proxies", []))
|
||||||
|
print("acct%d %-16s proxies=%d %s" % (idx, rec.get("name"),
|
||||||
|
len(rec.get("proxies", [])), ",".join(p["cc"] for p in rec.get("proxies", []))))
|
||||||
|
else:
|
||||||
|
print("acct%d FAIL %s" % (idx, rec.get("error")))
|
||||||
|
time.sleep(1.5)
|
||||||
|
print(json.dumps({"accounts_ok": made, "proxies_ok": okp, "bank": a.bank}))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Liveness pass over a proxy pool: curl ipinfo through each URL.
|
||||||
|
|
||||||
|
Usage: python verify.py pool.jsonl [--limit N] [--timeout SEC]
|
||||||
|
Prints one line per proxy and a summary: alive / unique_ip / countries.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("pool")
|
||||||
|
ap.add_argument("--limit", type=int, default=0, help="0 = all")
|
||||||
|
ap.add_argument("--timeout", type=int, default=35)
|
||||||
|
ap.add_argument("--out", default="verify.json")
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
rows = [json.loads(l) for l in open(a.pool, encoding="utf-8") if l.strip()]
|
||||||
|
if a.limit:
|
||||||
|
rows = rows[:a.limit]
|
||||||
|
|
||||||
|
alive, ips, ccs = 0, set(), set()
|
||||||
|
for r in rows:
|
||||||
|
out = subprocess.run(["curl", "-s", "-m", str(a.timeout), "--proxy", r["url"],
|
||||||
|
"https://ipinfo.io/json"],
|
||||||
|
capture_output=True, text=True, timeout=a.timeout + 25).stdout
|
||||||
|
try:
|
||||||
|
d = json.loads(out)
|
||||||
|
except Exception:
|
||||||
|
d = {}
|
||||||
|
r.update({"ip": d.get("ip"), "country": d.get("country"),
|
||||||
|
"city": d.get("city"), "org": d.get("org")})
|
||||||
|
if d.get("ip"):
|
||||||
|
alive += 1
|
||||||
|
ips.add(d["ip"])
|
||||||
|
ccs.add(d.get("country"))
|
||||||
|
print("%-4s %-3s %-16s %-3s %-14s %s" % (r.get("acct"), r.get("cc"), d.get("ip", "FAIL"),
|
||||||
|
d.get("country", "--"), (d.get("city") or "")[:14], (d.get("org") or "")[:26]))
|
||||||
|
|
||||||
|
json.dump(rows, open(a.out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||||
|
print("\nTOTAL %d | alive %d | unique_ip %d | countries %d %s"
|
||||||
|
% (len(rows), alive, len(ips), len(ccs), sorted(c for c in ccs if c)))
|
||||||
Reference in New Issue
Block a user