We say the meter is honest. You should not take that on trust, and you do not have to: every request you make through us is logged with the bytes we billed for it, and you can export the log. This guide is the hands-on version of that promise. You count your own bytes, pull ours for the same window, and diff them request by request.
If you want the background first (the three ways providers inflate a meter, and a quick curl-only test that works on any provider), read how proxy providers inflate your usage. This post is the full reconciliation against our log.
The rules you are checking
- We meter request bytes plus response bytes, including headers and protocol overhead on the tunnelled connection.
- Connections that fail before transferring data are not billed.
- Retries that you start are billed, and each one appears as its own entry in your usage log.
- If our figure is more than you moved, we refund the difference.
Those four lines are paraphrased from the metering section of our terms, which is the binding version. The dashboard shows each request with its timestamp, host, status code, bytes in, bytes out and cost, and exports the lot as CSV.
Step 1: one request, one row
Start small. Fetch a known number of bytes, then ask the usage API what that request cost:
curl -s -o /dev/null \
-w 'sent %{size_request} received %{size_header} + %{size_download}\n' \
-x http://USER:[email protected]:8000 \
https://httpbin.org/bytes/10000
curl -H "Authorization: Bearer $PM_KEY" \
https://api.proxymonkey.io/v1/usage?last=1httpbin.org/bytes/10000 returns exactly ten thousand bytes of body, so you know what to expect. The API answers with bytes_in, bytes_out and cost_usd for your last request. Ours should come out a little above curl’s numbers: curl does not see the TLS records inside the tunnel, and we do.
Step 2: log your side of a real run
One request proves the plumbing. A few hundred prove the meter. This wrapper fetches a list of URLs through the proxy and writes one row per request with the bytes that crossed the wire in each direction. Redirects are not followed and nothing is retried, so every row in your log should match exactly one row in ours.
import csv
import sys
import time
from datetime import datetime, timezone
from urllib.parse import urlsplit
import requests
PROXY = "http://USER:[email protected]:8000"
PROXIES = {"http": PROXY, "https": PROXY}
def header_bytes(start_line, headers):
lines = [start_line] + [f"{k}: {v}" for k, v in headers.items()]
return sum(len(line) + 2 for line in lines) + 2
def fetch(url):
with requests.get(url, proxies=PROXIES, stream=True, timeout=30,
allow_redirects=False) as r:
body = sum(len(c) for c in r.raw.stream(65536, decode_content=False))
sent = header_bytes(f"GET {r.request.path_url} HTTP/1.1", r.request.headers)
received = header_bytes(f"HTTP/1.1 {r.status_code} {r.reason}", r.raw.headers) + body
return r.status_code, sent, received
with open(sys.argv[1]) as urls, open("mylog.csv", "w", newline="") as out:
log = csv.writer(out)
log.writerow(["time", "host", "status", "bytes_out", "bytes_in"])
for url in (line.strip() for line in urls):
if not url:
continue
started = datetime.now(timezone.utc).isoformat(timespec="seconds")
try:
status, sent, received = fetch(url)
except requests.RequestException as exc:
print(f"failed, not logged: {url} ({exc})", file=sys.stderr)
continue
log.writerow([started, urlsplit(url).hostname, status, sent, received])
time.sleep(1)Run it on an otherwise idle account, or at least with nothing else using the same credentials, so the window you export holds only this job. Note the start and end time.
Step 3: diff the two logs
Export the same window from the dashboard as CSV, then run this with the export’s path. The column names are the ones the usage API uses; if the header row of your export says something different, change the three constants to match it.
import csv
import sys
from collections import defaultdict
OUR_HOST, OUR_IN, OUR_OUT = "host", "bytes_in", "bytes_out"
def load(path, host_col, in_col, out_col):
totals = defaultdict(lambda: [0, 0])
with open(path, newline="") as f:
for row in csv.DictReader(f):
entry = totals[row[host_col]]
entry[0] += 1
entry[1] += int(row[in_col]) + int(row[out_col])
return totals
mine = load("mylog.csv", "host", "bytes_in", "bytes_out")
ours = load(sys.argv[1], OUR_HOST, OUR_IN, OUR_OUT)
print(f"{'host':<34}{'your reqs':>10}{'our reqs':>10}{'your bytes':>14}{'our bytes':>14}{'gap':>9}")
for host in sorted(set(mine) | set(ours)):
my_reqs, my_bytes = mine.get(host, [0, 0])
our_reqs, our_bytes = ours.get(host, [0, 0])
gap = f"{(our_bytes - my_bytes) / my_bytes:+.1%}" if my_bytes else "n/a"
print(f"{host:<34}{my_reqs:>10}{our_reqs:>10}{my_bytes:>14,}{our_bytes:>14,}{gap:>9}")Reading the result
- Request counts first. They should match per host. More rows on our side than requests on yours means we billed something you did not send, which is one of the classic ways meters get inflated. That is a bug and we want the two files.
- Bytes a few percent above yours: expected. TLS handshakes and record overhead are real bytes on the tunnel, and the script cannot see them.
- Bytes approaching double digits above yours: we are wrong. Send us both files and the window.
- Below yours: unusual. Check that your window covers the whole run before celebrating.
Step 4: check the price as well
Bytes are half the bill. The other half is the rate, and you can check that from the same export. Add up the cost column and the bytes, and work out the implied price per gigabyte both ways a gigabyte gets defined:
import csv
import sys
rows = list(csv.DictReader(open(sys.argv[1], newline="")))
cost = sum(float(r["cost_usd"]) for r in rows)
moved = sum(int(r["bytes_in"]) + int(r["bytes_out"]) for r in rows)
print(f"USD per 10^9 bytes: {cost / moved * 1e9:.4f}")
print(f"USD per 2^30 bytes: {cost / moved * 2**30:.4f}")One of the two should land on the per-gigabyte rate your order bought, and that tells you which gigabyte your bill uses. For reference, our residential rate when this page was built ran from $5.50/GB for a single gigabyte to $1.75/GB from 1,000 GB. If neither figure matches your rate, that is worth a message too.
This is not only a residential thing
ISP and datacenter addresses are rented per IP, but the meter still matters on a shared plan: it includes 1 GB, and traffic past that is billed from $0.35/GB. The same log and the same diff tell you whether you crossed the allowance.
What to send us
Both CSVs, the start and end of the window, and the output of the diff. Post it in Discord or email it. If our figure is higher than protocol overhead explains, we refund the difference, and the honesty page says what happens in public after that. If your figure turns out to be the wrong one, we will show you why, and you are welcome to stay suspicious.
Top-ups start at $5.
One shared datacenter IP for 30 days is $2.10. A single gigabyte of residential is $5.50. The balance never expires.