For resellers who run their own billing and their own panel. Your platform creates a customer, gives that customer traffic out of your prepaid pool, and fetches proxy lines for them. Your customer never visits pyproxy.com, never registers here and never sees our name — with a hostname of your own on the lines, there is nothing of ours in what they receive.
Two things before any of this works: gigabytes in your reseller pool, and a key. The pool is the commercial part — write to support@pyproxy.com or Telegram with the address of the account it should sit on, and we set the wholesale rate and turn reselling on. Wholesale here starts at 500 GB.
The key you make yourself, and it takes about ten seconds:
production, billing cron), and press Create key.Ten live keys per account, which is room for several environments without losing track of them. If the Generate CDKey screen is not in your sidebar, the account is not marked as a reseller yet — that is the one part you have to ask us for.
| Base address | https://pyproxy.com/api/v1/reseller |
|---|---|
| Authentication | Authorization: Bearer rk_…, or X-Api-Key: rk_… |
| Bodies | JSON in, JSON out, UTF-8 |
| Billing | gigabytes leave your pool the moment you allocate them, not when they are spent |
| Protocols | HTTP and HTTPS on rotating residential traffic |
A key is shown once, when it is issued, and stored here only as a hash. Lost keys are replaced, not recovered. Keys do not expire and are unaffected by password changes, so an integration keeps running.
GET /me
What you can spend and what the account is allowed to do. Worth calling once at startup.
curl https://pyproxy.com/api/v1/reseller/me \
-H "Authorization: Bearer rk_…"
{
"account": "you@yourbrand.com",
"poolGb": 300,
"gateway": "gw.pyproxy.com",
"gatewayIsYours": false,
"backend": "rain",
"customers": 12,
"limits": {"gbPerCall": 2000, "allocationsPerHour": 200, "gbPerDay": 1000,
"customers": 5000, "linesPerCall": 100}
}
POST /customers
Register one of your customers. It costs nothing, buys nothing and reserves the reference. The reference is yours: your own user id, your own order number, whatever you already key them by. Lowercase letters, digits and underscore, up to 41 characters — a dash or a dot is targeting syntax at the gateway and is refused here rather than breaking a line later.
curl -X POST https://pyproxy.com/api/v1/reseller/customers \
-H "Authorization: Bearer rk_…" -H "Content-Type: application/json" \
-d '{"ref": "user_8842", "label": "Acme Ltd"}'
{"ref": "user_8842", "label": "Acme Ltd", "gateway": "gw.pyproxy.com", "provisioned": false}
Allocating traffic to a reference that does not exist yet registers it on the way, so this call is optional.
POST /customers/{ref}/traffic
Debits your pool and provisions that many gigabytes on this customer's own upstream plan, with their own credentials and their own cap. One customer can never spend another's traffic. Calling it again on the same customer adds to what they have.
Send a requestId of your own, or the same value as an Idempotency-Key header.
It is what makes a retry safe: a repeat of an id we have seen returns the first outcome and moves nothing. Use
your order id, not a random value, or a timeout will cost you the traffic twice.
curl -X POST https://pyproxy.com/api/v1/reseller/customers/user_8842/traffic \
-H "Authorization: Bearer rk_…" -H "Content-Type: application/json" \
-d '{"gb": 5, "requestId": "order-99213"}'
{
"requestId": "order-99213", "ref": "user_8842", "gb": 5, "duplicate": false,
"poolGb": 295, "gbTotal": 5.0, "gbRemaining": 5.0, "gbUsed": 0.0,
"protocols": ["http"], "maxStickyMinutes": 30
}
Expect this call to take a few seconds: it buys upstream and waits for the plan to appear rather than answering optimistically. If the purchase fails, your pool is credited back before the error reaches you and nothing is charged — the response says so, and that request id is then spent, so retry with a new one.
GET /customers/{ref}
Read from the gateway, not from our bookkeeping: gbUsed is what has actually gone through.
curl https://pyproxy.com/api/v1/reseller/customers/user_8842 \
-H "Authorization: Bearer rk_…"
{"ref": "user_8842", "provisioned": true, "gbTotal": 5.0, "gbRemaining": 4.128,
"gbUsed": 0.872, "protocols": ["http"], "maxStickyMinutes": 30}
GET /customers?limit=100&offset=0
The whole book, without the per-customer usage read.
POST /customers/{ref}/lines
Returns ready lines in host:port:username:password order. Nothing is charged for generating them;
traffic is counted as it is used.
curl -X POST https://pyproxy.com/api/v1/reseller/customers/user_8842/lines \
-H "Authorization: Bearer rk_…" -H "Content-Type: application/json" \
-d '{"count": 2, "country": "de", "stickyMinutes": 10}'
{"ref": "user_8842", "protocol": "http", "lines": [
"gw.pyproxy.com:PORT:USER-country-de-session-8ca9b7b3:PASS",
"gw.pyproxy.com:PORT:USER-country-de-session-1bdbe83d:PASS"]}
| Field | Meaning |
|---|---|
count | 1–100. Rotating lines without a sticky session are identical by design: the gateway picks a fresh address per request. |
country | ISO alpha-2, lowercase or upper. Reliable on rotating residential; ask us before you promise it on datacenter or mobile, where we have measured it landing elsewhere. |
stickyMinutes | 1–30. Each line gets its own session tag, so ten sticky lines are ten different addresses. We cap this at half an hour because that is what we measured a session holding, whatever the supplier's metadata claims. |
protocol | Rotating residential answers HTTP(S) only. Asking for socks5 here is refused rather than handed to you as a line that closes on connect. |
By default a line says gw.pyproxy.com. Point a hostname of yours at us with a CNAME, tell us, and
every line we hand you afterwards carries yours instead:
gw.yourbrand.com. CNAME gw.pyproxy.com.
The gateway is a TCP relay and never reads the hostname, so nothing else changes: no certificate is involved, no TLS handshake happens against that name, and the port stays as returned. We tested this by dialling the gateway at its bare address, which is what a CNAME resolves to in the end.
| Code | What it means |
|---|---|
401 | Key missing, unknown or revoked. |
403 | Account suspended, or it has no reseller pool yet. |
402 | Your pool is short of what you asked for. The message says by how much. |
404 | No such customer on your account. |
409 | That reference already exists, or that request id already failed and needs a fresh one. |
429 | A limit: allocations per hour, gigabytes per day, or customers on the account.
The limits are in GET /me and we raise them on request. |
502 | Upstream did not deliver. Your pool was not charged. |
Every error is {"detail": "…"} with a sentence a human wrote. Read it before retrying: most of
them tell you exactly what to change.
Said plainly, so you can plan around it rather than discover it:
GET /customers/{ref} for usage; a minute apart is plenty.If one of these is in your way, say so — the order they get built in is decided by who asks.