> For the complete documentation index, see [llms.txt](https://fleet.hackmap.win/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://fleet.hackmap.win/guides/proxy-providers.md).

# Proxy providers

A flat `proxy_url` works fine when you have one HTTP proxy and you want every slot to use it. It does not work when your proxy is a residential gateway with credentials, country routing, sticky sessions, and per-account session limits encoded into the URL itself. For that, Fleet has a proxy provider system: pluggable, installed as separate pip packages, configured per worker.

## How it fits together

A provider is a Python class that builds a proxy URL on demand. It lives in a pip-installable package with a `fleet.providers.proxy` entry-point. The master and worker both load installed providers at startup, the same way they load automation plugins.

When a worker spawns a slot, the framework looks at the worker's config:

* If `proxy_provider` is set, the framework instantiates that provider with `proxy_provider_config`, calls `acquire(slot_id=N, sticky=True)`, and exposes the resulting URL as `ctx.proxy_url` to the slot.
* If only `proxy_url` is set, the framework wraps it in the built-in `static` provider so the same code path serves both cases.
* Inside the slot, `ctx.proxy_url` is a plain string. `ctx.proxy` is a richer handle for explicit session control: `async with ctx.proxy.session(sticky=False, country="us") as sess: ...`

The framework caches the provider instance per worker. Reconcile recycles re-acquire fresh sessions without rebuilding the provider class.

## Configuring a provider on a worker

```bash
curl -X PATCH http://master/api/v1/automations/<type>/workers/<id>/config \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "slots": 4,
    "proxy_provider": "evomi",
    "proxy_provider_config": {
      "username": "your-evomi-user",
      "password": "your-evomi-pass",
      "countries": "EU+US"
    }
  }'
```

Each provider's config schema is defined inside its package. The master doesn't validate provider configs centrally — that happens when the worker instantiates the provider. If the config is malformed, the worker logs "failed to build proxy provider 'evomi'" and runs without a proxy.

## Country presets

Both shipped providers accept the same country syntax for the `countries` field:

| Value                   | Resolves to                                    |
| ----------------------- | ---------------------------------------------- |
| `"US"`                  | `["US"]`                                       |
| `"TR,DE"`               | `["TR", "DE"]`                                 |
| `["TR", "DE"]`          | `["TR", "DE"]`                                 |
| `"EU"`                  | All 27 EU members                              |
| `"EU+UK"`               | EU 27 plus GB                                  |
| `"EU+US"`               | EU 27 plus US                                  |
| `"EU+NA"`               | EU 27 plus US, CA                              |
| `"NA"`                  | US, CA                                         |
| `"LATAM"`               | AR, BR, CL, CO, MX, PE, UY                     |
| `"APAC"`                | AU, JP, KR, SG, HK, TW, NZ, MY, TH, ID, PH, VN |
| `"WORLDWIDE"` (or omit) | No restriction                                 |

Presets live in `fleet.core.country_presets`. Add more there if you need them.

## Built-in providers

`static` — wraps a single URL. Used implicitly when only `proxy_url` is set.

```json
{"proxy_provider": "static", "proxy_provider_config": {"url": "http://user:pass@host:port"}}
```

`rotating-list` — round-robins a list of URLs. Each slot gets a deterministic URL based on its slot id when `sticky=True`; the rotating cursor advances when `sticky=False`.

```json
{"proxy_provider": "rotating-list", "proxy_provider_config": {
  "urls": ["http://u:p@a:1", "http://u:p@b:1", "http://u:p@c:1"]
}}
```

## Evomi

Bundled with the framework wheel — no extra install needed. The Evomi provider is auto-registered via entry-points when you install `fleet-framework`.

Config:

```json
{
  "proxy_provider": "evomi",
  "proxy_provider_config": {
    "username": "your-evomi-user",
    "password": "your-evomi-pass",
    "host": "core-residential.evomi.com",
    "scheme": "http",
    "countries": "EU+US",
    "sticky_lifetime_minutes": 60
  }
}
```

Scheme `http` uses port 1000; `socks5` uses port 1002. Sticky sessions get a random 10-character session ID. If you set `sticky_lifetime_minutes` to anything other than 60, the framework adds an explicit `_lifetime-N` directive. Default is 60 (Evomi's own default), in which case the directive is omitted.

Generated URL shape:

```
<scheme>://<user>:<pass>[_country-CC[,CC...]][_session-ID][_lifetime-N]@<host>:<port>
```

Examples (with `username=u` and `password=p`):

| Inputs                                                 | URL                                                                                    |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `scheme=http`, no country, rotating                    | `http://u:p@core-residential.evomi.com:1000`                                           |
| `scheme=http`, `country=TR`                            | `http://u:p_country-TR@core-residential.evomi.com:1000`                                |
| `scheme=socks5`, `country=TR,DE`, sticky               | `socks5://u:p_country-TR,DE_session-XXXX@core-residential.evomi.com:1002`              |
| `scheme=socks5`, `country=TR,DE`, sticky, lifetime=500 | `socks5://u:p_country-TR,DE_session-XXXX_lifetime-500@core-residential.evomi.com:1002` |

## Dataimpulse

Bundled with the framework wheel — no extra install needed. Auto-registered via entry-points.

Config:

```json
{
  "proxy_provider": "dataimpulse",
  "proxy_provider_config": {
    "username": "your-dataimpulse-user",
    "password": "your-dataimpulse-pass",
    "host": "gw.dataimpulse.com",
    "scheme": "http",
    "countries": "EU+US",
    "sticky_port_range": [10000, 10004]
  }
}
```

Dataimpulse handles session stickiness differently than Evomi. Country routing goes into the username (`user__cr.au,be`), and stickiness comes from picking a specific port out of a reserved range. Each port maps to a long-lived session (about 120 minutes by Dataimpulse's defaults).

`sticky_port_range` is inclusive `[low, high]`. The framework picks `low + (slot_id % span)`, so two workers on the same host with 5 slots each get the same 5 sticky IPs deterministically. Bump the range if you want more unique sticky IPs.

For rotating mode (when you call `ctx.proxy.session(sticky=False)` explicitly), the framework uses port 823 instead. Dataimpulse uses the same port for HTTP and SOCKS5; only the URL scheme changes.

Generated URL shape:

```
<scheme>://<user>[__cr.cc1,cc2...]:<pass>@<host>:<port>
```

Examples:

| Inputs                                | URL                                                 |
| ------------------------------------- | --------------------------------------------------- |
| Rotating, no country                  | `http://f0..:b6..@gw.dataimpulse.com:823`           |
| Rotating, `country=AU,BE`             | `http://f0..__cr.au,be:b6..@gw.dataimpulse.com:823` |
| Sticky slot 0                         | `http://f0..:b6..@gw.dataimpulse.com:10000`         |
| Sticky slot 5, range `[10000, 10004]` | `http://f0..:b6..@gw.dataimpulse.com:10000` (wraps) |
| `scheme=socks5`, sticky               | `socks5://f0..:b6..@gw.dataimpulse.com:10000`       |

## Flameproxies

Bundled with the framework wheel — no extra install needed. Auto-registered via entry-points.

Config:

```json
{
  "proxy_provider": "flameproxies",
  "proxy_provider_config": {
    "username": "flm8c8dc769",
    "password": "your-flameproxies-pass",
    "host": "proxy.flameproxies.com",
    "scheme": "http",
    "package": "standard",
    "countries": "US",
    "sticky_lifetime_minutes": 10
  }
}
```

Scheme `http` uses port 8989; `socks5` uses port 1080. Flameproxies encodes everything in the **username**: account ID, package tier, country, session, and lifetime are joined with single dashes. A fresh 10-character lowercase-alphanumeric session ID is generated per `acquire`. Flameproxies sessions accept exactly one country; if you pass a multi-country preset (`EU+US`), the first resolved code wins.

Generated URL shape:

```
<scheme>://<account>-package-<pkg>[-country-<cc>]-session-<sid>-time-<min>:<pass>@<host>:<port>
```

Examples (with `username=flm8c8dc769` and `password=p`):

| Inputs                                               | URL                                                                                                         |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `scheme=http`, no country, sticky                    | `http://flm8c8dc769-package-standard-session-XXXXXXXXXX-time-10:p@proxy.flameproxies.com:8989`              |
| `scheme=http`, `country=US`, sticky                  | `http://flm8c8dc769-package-standard-country-us-session-XXXXXXXXXX-time-10:p@proxy.flameproxies.com:8989`   |
| `scheme=socks5`, `country=DE`, sticky, `lifetime=30` | `socks5://flm8c8dc769-package-standard-country-de-session-XXXXXXXXXX-time-30:p@proxy.flameproxies.com:1080` |
| Rotating (sticky=False)                              | `http://flm8c8dc769-package-standard:p@proxy.flameproxies.com:8989`                                         |

The default 10-minute sticky lifetime matches Flameproxies' own server-side default. Set `sticky_lifetime_minutes` higher only if your task needs the same exit IP for longer; the provider does not enforce a cap — that's the upstream's job.

## Inside a slot

The simple path: read `ctx.proxy_url`, pass it to your HTTP client.

```python
async def run_slot(self, ctx):
    async with httpx.AsyncClient(proxy=ctx.proxy_url) as client:
        while not ctx.shutdown.is_set():
            r = await client.get(ctx.config.target_url)
            await ctx.emit({"status": r.status_code})
```

The richer path: ask the handle for a session per request, switch countries, or force rotation.

```python
async def run_slot(self, ctx):
    while not ctx.shutdown.is_set():
        async with ctx.proxy.session(sticky=False, country="us") as sess:
            async with httpx.AsyncClient(proxy=sess.url) as client:
                r = await client.get(ctx.config.target_url)
                if r.status_code == 403:
                    await sess.report_bad("403 from target")
                else:
                    await ctx.emit({"status": r.status_code})
```

`session.report_bad()` is a hook for providers that want to cycle the underlying session. The two shipped providers don't do anything with it yet (each acquire builds a fresh URL anyway), but a provider that hits a real "give me a new session" API would.

## Writing your own provider

Implement the protocol and register with `@register_provider("name")`. Ship as a pip package with an entry-point:

```python
from fleet.core import ProxySession, register_provider, resolve_countries

@register_provider("my-provider")
class MyProvider:
    def __init__(self, cfg): self._cfg = cfg

    @classmethod
    def from_config(cls, cfg: dict):
        return cls(cfg)

    async def acquire(self, *, slot_id, sticky=True, country=None):
        codes = resolve_countries(country or self._cfg.get("countries"))
        # build a URL from self._cfg, codes, sticky, slot_id
        return ProxySession(url="...", metadata={"sticky": sticky, "country": codes})
```

```toml
[project.entry-points."fleet.providers.proxy"]
my-provider = "my_pkg:MyProvider"
```

That's the whole surface. Use `resolve_countries` from `fleet.core` to keep preset handling consistent across providers.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://fleet.hackmap.win/guides/proxy-providers.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
