🔥 LAUNCH SALE — up to +60% bonus traffic · Residential from $0.67/GB · code BACK15 Claim →
Log in Get proxies

PyProxy Reseller API

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:

  1. Sign in and open your reseller screenGenerate CDKey in the sidebar.
  2. Scroll to API access, name the key after whatever will hold it (production, billing cron), and press Create key.
  3. Copy it there and then. We store only a hash, so the key is shown once and never again; afterwards the table lists it by its first characters, how many calls it has made, and when it was last used.
  4. Lost it, or a laptop walked off with it? Revoke it in the same table and make another. Traffic already allocated to your customers keeps working — revoking a key stops new calls, not live proxies.

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.

Basics

Base addresshttps://pyproxy.com/api/v1/reseller
AuthenticationAuthorization: Bearer rk_…, or X-Api-Key: rk_…
BodiesJSON in, JSON out, UTF-8
Billinggigabytes leave your pool the moment you allocate them, not when they are spent
ProtocolsHTTP 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.

Your account

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}
}

A customer of yours

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.

Give them traffic

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.

Read what they have

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.

Proxy lines

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"]}
FieldMeaning
count1–100. Rotating lines without a sticky session are identical by design: the gateway picks a fresh address per request.
countryISO 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.
stickyMinutes1–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.
protocolRotating residential answers HTTP(S) only. Asking for socks5 here is refused rather than handed to you as a line that closes on connect.

Your own hostname on the lines

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.

When something goes wrong

CodeWhat it means
401Key missing, unknown or revoked.
403Account suspended, or it has no reseller pool yet.
402Your pool is short of what you asked for. The message says by how much.
404No such customer on your account.
409That reference already exists, or that request id already failed and needs a fresh one.
429A limit: allocations per hour, gigabytes per day, or customers on the account. The limits are in GET /me and we raise them on request.
502Upstream 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.

What we do not do yet

Said plainly, so you can plan around it rather than discover it:

If one of these is in your way, say so — the order they get built in is decided by who asks.