Skip to main content

Unofficial Python SDK for the Proxy6.net API

Project description

proxy6-sdk

CI codecov PyPI version Python 3.13+ License: MIT

Unofficial — this project is a community-built client for Proxy6.net and is not affiliated with, endorsed by, or supported by Proxy6.net. All trademarks belong to their respective owners.

A typed Python SDK for the Proxy6.net API.

Covers all nine documented endpoints — getprice, getcount, getcountry, getproxy, setdescr, buy, prolong, delete, check — plus the keyless account call. Responses are parsed into dataclasses with proper types (datetime, int, bool, enums) instead of the raw strings the API returns.

Status: alpha (0.1.0a1). The public API may change before 0.1.0. See CHANGELOG.md for what shipped.

Install

uv add proxy6-sdk
# or
pip install proxy6-sdk

The distribution name is proxy6-sdk; the import name is proxy6:

from proxy6 import Proxy6Client

Requires Python 3.13+.

Quick start

from proxy6 import Proxy6Client, Version

# Pass the key explicitly...
with Proxy6Client(api_key="YOUR_KEY") as client:
    ...

# ...or set PROXY6_API_KEY in the environment and omit it.
with Proxy6Client() as client:
    info = client.account()
    print(f"Balance: {info.balance} {info.currency}")

    # How much for 10 Russian IPv6 proxies for 30 days?
    quote = client.get_price(count=10, period=30, version=Version.IPV6)
    print(quote.price, quote.price_single)

    # Buy them
    order = client.buy(
        count=10,
        period=30,
        country="ru",
        version=Version.IPV6,
        descr="scraper-pool",
    )
    for proxy in order.proxies:
        print(proxy.auth_url())  # http://user:pass@host:port

API surface

Method Purpose
account() Balance / currency (calls API with no method)
get_price(count?, period?, version?) Single-quote or full price table
get_count(country, version?) Available stock for a country
get_country(version) List of ISO2 codes for a version
get_proxy(state?, descr?, page?, limit?) List your proxies
set_descr(new, old?, ids?) Rename the technical comment
buy(count, period, country, version, descr?, ...) Purchase proxies
prolong(period, ids) Extend existing proxies
delete(ids?, descr?) Delete by id or by comment
check(ids?, proxy?) Liveness check via id or ip:port:user:pass
proxies(refresh?) Cached view of the full pool (filter locally)
invalidate_proxy_cache() Drop the pool cache
select_proxy(country?, version?, active?, ...) Pick one proxy from the pool, raises if nothing matches
requests_session(country?, version?, ...) One-shot: pick a proxy → ready requests.Session
httpx_client(country?, ..., **kwargs) One-shot: pick a proxy → ready httpx.Client
httpx_async_client(country?, ..., **kwargs) One-shot: pick a proxy → ready httpx.AsyncClient
aiohttp_kwargs(country?, ...) One-shot: pick a proxy → kwargs for aiohttp requests

Using your proxies in an HTTP client

The SDK ships convenience helpers at two levels so you don't have to stitch together proxy URLs by hand.

One-liner on the client — picks a proxy from the cached pool (auto-filtered to active=True by default) and hands back a ready-to-use HTTP client:

with Proxy6Client() as c:
    with c.requests_session(country="us") as s:
        r = s.get("https://api.ipify.org?format=json", timeout=10)

Per-proxy — when you want to pick the proxy yourself first (e.g. inspect it, reuse it across calls) and produce the client from there:

with Proxy6Client() as c:
    proxy = c.select_proxy(country="us", version=Version.IPV4)
    # equivalently: c.proxies().filter(country="us", version=Version.IPV4).random()
    with proxy.requests_session() as s:
        r = s.get("https://api.ipify.org?format=json", timeout=10)

Both forms share the cache — back-to-back calls don't re-hit /getproxy. Filter args (country, version, active, descr, type) are the same as :meth:ProxyList.filter. If nothing matches, a LookupError is raised with the criteria that failed.

Examples per library

requests

from proxy6 import Proxy6Client

with Proxy6Client() as c:
    # One-shot.
    with c.requests_session(country="us") as s:
        r = s.get("https://api.ipify.org?format=json", timeout=10)
        print(r.json()["ip"])

    # Per-proxy (lets you inspect/reuse `proxy`).
    proxy = c.select_proxy(country="us")
    with proxy.requests_session() as s:
        r = s.get("https://api.ipify.org?format=json", timeout=10)

httpx (sync)

from proxy6 import Proxy6Client

with Proxy6Client() as c:
    with c.httpx_client(country="us", timeout=10) as client:
        r = client.get("https://api.ipify.org?format=json")
        print(r.json()["ip"])

    # Per-proxy.
    proxy = c.select_proxy(country="us")
    with proxy.httpx_client(timeout=10) as client:
        r = client.get("https://api.ipify.org?format=json")

httpx (async)

import asyncio
from proxy6 import Proxy6Client

async def main() -> None:
    with Proxy6Client() as c:
        async with c.httpx_async_client(country="us", timeout=10) as client:
            r = await client.get("https://api.ipify.org?format=json")
            print(r.json()["ip"])

asyncio.run(main())

aiohttpaiohttp has no session-level proxy setting, so the proxy goes on each request. The helper returns a kwargs dict you spread in:

import asyncio
import aiohttp
from proxy6 import Proxy6Client

async def main() -> None:
    with Proxy6Client() as c:
        kw = c.aiohttp_kwargs(country="us")  # {"proxy": "http://u:p@host:port"}
        async with aiohttp.ClientSession() as s:
            async with s.get("https://api.ipify.org?format=json", **kw) as r:
                print((await r.json())["ip"])

asyncio.run(main())

subprocess / curl / wget — env-var format for shelling out:

import os
import subprocess
from proxy6 import Proxy6Client

with Proxy6Client() as c:
    proxy = c.select_proxy(country="us")
    subprocess.run(
        ["curl", "https://api.ipify.org"],
        env={**os.environ, **proxy.as_env()},
    )

Just give me the URL / dict — if you're plugging into another HTTP lib not covered above:

proxy.auth_url()              # "http://user:pass@host:port"
proxy.as_requests_dict()      # {"http": auth_url, "https": auth_url}
proxy.aiohttp_kwargs()        # {"proxy": auth_url}

httpx and aiohttp are imported lazily — they only need to be installed if you actually call the corresponding helper.

Caching the proxy pool

get_proxy() is the raw API call and always hits the wire. For the "give me my pool, I'll filter locally" workflow there's proxies(), which fetches the full pool once and reuses it for repeated calls:

pool = client.proxies()                       # one API call
us_v4 = pool.filter(country="us", version=Version.IPV4, active=True)
proxy = us_v4.random()
client.proxies()                              # served from cache, no HTTP
  • Default TTL is 24 hours. Override with Proxy6Client(proxy_cache_ttl=3600) (seconds) or pass None to disable caching entirely.
  • The cache is automatically invalidated after any state-changing call (buy, prolong, delete, set_descr), so newly-bought proxies show up on the next proxies() call.
  • Force a refresh with client.proxies(refresh=True) or drop the cache explicitly with client.invalidate_proxy_cache().
  • ProxyList is iterable / sized / indexable and supports filter(...) (country, version, active, descr, type) and random(*, rng=None) so most pool workflows don't need to touch client.proxies directly.

Authentication

The client resolves the API key in this order:

  1. The api_key= constructor argument
  2. The PROXY6_API_KEY environment variable

If neither is set, Proxy6Client() raises ValueError. The SDK does not load .env files itself — if you keep your key in .env, use python-dotenv in your application:

from dotenv import load_dotenv
from proxy6 import Proxy6Client

load_dotenv()
client = Proxy6Client()

Enums

from proxy6 import Version, State, ProxyType

Version.IPV4_SHARED  # 3
Version.IPV4         # 4
Version.MTPROTO      # 5
Version.IPV6         # 6

State.ACTIVE | State.EXPIRED | State.EXPIRING | State.ALL
ProxyType.HTTP | ProxyType.SOCKS

Errors

The API uses an envelope of {"status":"no","error_id":N,"error":"..."}. Each documented error code has its own exception subclass, so you can catch specific failures directly:

from proxy6 import (
    InsufficientBalanceError,
    NotEnoughProxiesError,
    Proxy6APIError,
)

try:
    client.buy(count=100, period=30, country="ru", version=Version.IPV6)
except InsufficientBalanceError:
    topup()
except NotEnoughProxiesError:
    wait_and_retry()
except Proxy6APIError as e:
    # Catch-all for anything else
    log.error("proxy6 %s: %s", e.error_id, e.error)
Code Exception Meaning
30 UnknownError Unknown error
100 AuthError Wrong API key
105 IPNotAllowedError IP restriction blocked the call
110 MethodError Wrong method name
200 InvalidCountError Bad proxies quantity
210 InvalidPeriodError Bad period (days)
220 InvalidCountryError Bad country code
230 InvalidIdsError Bad proxy id list
240 InvalidVersionError Bad proxy version
250 InvalidDescriptionError Bad technical description
260 InvalidProxyTypeError Bad proxy type/protocol
270 InvalidPortError Bad port
280 InvalidProxyStringError Bad ip:port:user:pass for check
300 NotEnoughProxiesError Stock too low
400 InsufficientBalanceError Zero / low balance
404 NotFoundError Element not found
410 PriceCalculationError Cost ≤ 0

The raw documented messages are also exposed as the proxy6.ERROR_CODES dict.

Rate limiting

The API allows 3 requests per second; over that limit it returns HTTP 429. The SDK ships with a thread-safe sliding-window limiter that all Proxy6Client instances share by default, so you don't have to think about it — concurrent calls just block long enough to stay under the cap.

from proxy6 import Proxy6Client, RateLimiter

# Default — uses the process-wide DEFAULT_RATE_LIMITER (3/s).
client = Proxy6Client()

# Custom limit (e.g. you have a higher-tier agreement).
client = Proxy6Client(rate_limiter=RateLimiter(max_requests=10, period=1.0))

# Disable entirely (you're handling throttling yourself).
client = Proxy6Client(rate_limiter=None)

Verifying a proxy against a live IP-check service

ProxyVerifier routes a request through a proxy and asks a public "what's my IP" service what it sees, so you can confirm the proxy is egressing as expected and your real IP isn't leaking. By default it walks through four built-in providers in order and returns the first success, so a single provider being down or blocking your IP doesn't break the check.

from proxy6 import Proxy6Client, ProxyVerifier

with Proxy6Client() as c:
    proxy = c.proxies().filter(country="us", active=True).random()

with ProxyVerifier() as v:                              # fallback enabled by default
    leak = v.check_leak(proxy)
    print(leak.result.ip, leak.result.country, leak.leaked, leak.result.provider)

check_leak() returns a LeakCheck whose leaked property is True whenever the IP the service saw differs from proxy.host — i.e. either the proxy isn't doing its job, or traffic bypassed it entirely.

Built-in providers (the default chain, in order):

Provider Endpoint Data returned
IpifyProvider api.ipify.org / api6.ipify.org IP only — smallest moving part
IcanhazipProvider ipv4.icanhazip.com / ipv6.icanhazip.com IP only, plain text
IfconfigCoProvider ipv4.ifconfig.co/json / ipv6.ifconfig.co/json IP + country + region + city + ASN
IpinfoIoProvider ipinfo.io/json IP + country + region + city + ASN (token optional for higher free-tier limits)

Pin a single provider (no fallback) or control the chain explicitly:

# Single provider — failures raise instead of falling back.
ProxyVerifier(provider=IpifyProvider()).check(proxy)

# Custom fallback chain, tried in order.
ProxyVerifier(providers=[MyChecker(), IpifyProvider(), IcanhazipProvider()]).check(proxy)

If every provider in the chain fails, check() raises a single VerificationError whose message lists each failure in order — no silent fallbacks, no truncated history. To use a custom provider or run your own endpoint, see docs/VERIFICATION.md.

Using a custom session

Inject any requests.Session (e.g. with retry policies or a proxy of your own):

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=Retry(
    total=5, backoff_factor=0.5, status_forcelist=(429, 500, 502, 503, 504)
)))
client = Proxy6Client(api_key="...", session=session)

Development

uv sync
uv run pytest                    # unit tests only (integration is gated)
uv run pytest -m integration     # hit the real API; needs PROXY6_API_KEY

Put your key in a local .env; tests/conftest.py loads it automatically.

User-visible changes are tracked in CHANGELOG.md — add an entry under [Unreleased] whenever you ship something that affects the public API.

License

MIT — see LICENSE. This is an unofficial project and ships with no warranty of fitness for any particular purpose.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

proxy6_sdk-0.1.0a2.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

proxy6_sdk-0.1.0a2-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

File details

Details for the file proxy6_sdk-0.1.0a2.tar.gz.

File metadata

  • Download URL: proxy6_sdk-0.1.0a2.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for proxy6_sdk-0.1.0a2.tar.gz
Algorithm Hash digest
SHA256 34ad1f8908b31af666e67ab177640c617e9a855d228fd3718fdcb5c6cd905453
MD5 b9d28afb70bf22ddff019303a337310e
BLAKE2b-256 9bd8eab0490a788d75ffade8d5fc39b746310b0ce5c2df5c6fafffa3ee130121

See more details on using hashes here.

Provenance

The following attestation bundles were made for proxy6_sdk-0.1.0a2.tar.gz:

Publisher: release.yml on AsyncAlchemist/proxy6-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file proxy6_sdk-0.1.0a2-py3-none-any.whl.

File metadata

  • Download URL: proxy6_sdk-0.1.0a2-py3-none-any.whl
  • Upload date:
  • Size: 23.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for proxy6_sdk-0.1.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 2b5d9252922bb2a9a800ef3c8d4eb66538f64f968458875ef341b99461231ae9
MD5 992754385da9c0d1cb647cc5f83db6c0
BLAKE2b-256 25a6b7011adfea3ef8a338a31cdcd4c787b0745d8ba9e69a0b6881bd37c155ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for proxy6_sdk-0.1.0a2-py3-none-any.whl:

Publisher: release.yml on AsyncAlchemist/proxy6-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page