---
title: "Proxy · Scrape.ly Docs"
description: "Managed residential and datacenter proxies, your own proxies, and how browsers fit in."
url: https://scrape.ly/docs/proxy
markdown: https://scrape.ly/docs/proxy.md
---

# Proxy

> Managed residential and datacenter proxies, your own proxies, and how browsers fit in.

Every run can use a managed proxy with residential and datacenter IPs. There's no account or password to set up: each run gets its own credentials, and you pay per GB. You can also bring your own proxy URLs, or mix them with the managed proxy.

```js [JavaScript]
const proxyConfiguration = await Actor.createProxyConfiguration({ countryCode: 'US' });
```

```python [Python]
proxy_configuration = await Actor.create_proxy_configuration(country_code="US")
```

> [!NOTE]
> This page is about HTTP traffic. Browsers on the [Scrape.ly browser](https://scrape.ly/docs/browser.md) use that browser's own proxy instead. See [Browser crawlers](#browser-crawlers).

## How the managed proxy works

When a run starts, the platform injects these environment variables into the container:

| Variable | Value |
| --- | --- |
| `SCRAPELY_PROXY_HOSTNAME` | Proxy host reachable from inside the run |
| `SCRAPELY_PROXY_PORT` | Proxy port (HTTP and SOCKS5 on the same port) |
| `SCRAPELY_PROXY_USERNAME` | `scrapely` |
| `SCRAPELY_PROXY_PASSWORD` | The run ID |
| `SCRAPELY_PROXY_STATUS_URL` | Status endpoint used by the SDK access check |

The same values are also set as `APIFY_PROXY_*` so existing proxy code works unchanged.

The password only works from inside the run that owns it, so Actors never get a reusable proxy credential and there's no account-level password to leak. You can't use the managed proxy outside a run. The SDKs read these variables for you.

## Groups and countries

You pick a pool with `groups` and a location with `countryCode`:

| Group | Pool | Price |
| --- | --- | --- |
| `RESIDENTIAL` (default) | Residential IPs | $2–$4 per GB |
| `DATACENTER` | Datacenter IPs | $0.75–$1 per GB |

- These two are the only groups. No group means the residential pool.
- The legacy datacenter group names `SHADER` and `BUYPROXIES94952` are accepted and use the datacenter pool.
- Any other group, including groups Scrape.ly doesn't offer, such as `GOOGLE_SERP`, is refused. It is never routed through another pool or billed. See [Unsupported groups](#unsupported-groups).
- `countryCode` takes an ISO 3166-1 alpha-2 code such as `US` or `DE`. The SDKs also accept `subdivisionCode` (needs `countryCode`), but the proxy targets the country only.
- Traffic is billed per GB as run usage and counts toward the run's `maxTotalChargeUsd`. If you can't cover it, the run is aborted.
- Traffic to your own proxies is never billed as proxy usage.

### Unsupported groups

If you ask for a group that doesn't exist, the SDK's access check fails when you create the proxy configuration, and the proxy refuses every request that names it. The message is the same everywhere:

```text
Proxy group "GOOGLE_SERP" is not available on Scrapely. Available groups: RESIDENTIAL, DATACENTER.
```

| Where | What you see |
| --- | --- |
| Python SDK | `Actor.create_proxy_configuration()` raises `ConnectionError` with the message |
| JavaScript SDK | On the platform, `Actor.createProxyConfiguration()` throws an `Error` with the message. In local runs it logs the message as a warning |
| HTTP proxy, `CONNECT` or plain request | `407 Proxy Authentication Required`, with the message in the `X-Scrapely-Proxy-Error` header and the response body |
| SOCKS5 | Authentication fails |

This also applies to groups that come from an input field (`scrapelyProxyGroups`), and to a known group combined with an unknown one (`groups: ['RESIDENTIAL', 'GOOGLE_SERP']`). Several unknown groups are named together: `Proxy groups "GOOGLE_SERP", "UNBLOCKER" are not available on Scrapely. ...`.

To fall back to a pool that exists, catch the error and create the configuration again with `RESIDENTIAL` or `DATACENTER`. Actors built with an older SDK only log a warning at start; their requests then get the `407` above.

## Use it from code

On the platform, `Actor.createProxyConfiguration()` / `Actor.create_proxy_configuration()` with no options gives you the residential pool:

```js [JavaScript]
import { Actor, CheerioCrawler } from 'scrapely';

await Actor.main(async () => {
    const proxyConfiguration = await Actor.createProxyConfiguration({
        groups: ['DATACENTER'],
        countryCode: 'US',
    });

    const crawler = new CheerioCrawler({
        proxyConfiguration,
        requestHandler: async ({ request, $ }) => {
            await Actor.pushData({ url: request.url, title: $('title').text() });
        },
    });

    await crawler.run(['https://example.com']);
});
```

```python [Python]
import asyncio

from scrapely import Actor


async def main():
    proxy_configuration = await Actor.create_proxy_configuration(
        groups=["RESIDENTIAL"],
        country_code="US",
    )
    proxy_url = await proxy_configuration.new_url()
    Actor.log.info(f"Using {proxy_url}")


if __name__ == "__main__":
    asyncio.run(Actor.main(main))
```

To use your own proxies, pass `proxyUrls` / `proxy_urls`:

```js [JavaScript]
const proxyConfiguration = await Actor.createProxyConfiguration({
    proxyUrls: [
        'http://user:password@proxy.example:8000',
        'http://user:password@proxy-2.example:8000',
    ],
});

const proxyUrl = await proxyConfiguration.newUrl();
```

```python [Python]
proxy_configuration = await Actor.create_proxy_configuration(
    proxy_urls=[
        "http://user:password@proxy.example:8000",
        "http://user:password@proxy-2.example:8000",
    ],
)
proxy_url = await proxy_configuration.new_url()
```

Rules the SDKs enforce:

- `proxyUrls` can't be combined with `groups` or `countryCode`; the SDK throws.
- `'auto'` in `proxyUrls` (or `tieredProxyUrls`) reserves a slot for the managed proxy next to your own URLs.
- `newUrl(sessionId)` keeps related requests on one IP. Session IDs are at most 50 characters from `a-z`, `A-Z`, `0-9`, `.`, `_`, and `~`.
- `checkAccess` (default `true`) runs a quick status check at start. If the proxy refuses the configuration, for example an [unsupported group](#unsupported-groups), the SDK throws. If the check can't reach the proxy, it logs a warning and the run continues.
- Outside the platform, with no `proxyUrls`, the SDK warns and returns `undefined`.

| SDK | Options | Guide |
| --- | --- | --- |
| JavaScript | Also accepts the input-field keys `useScrapelyProxy`, `scrapelyProxyGroups`, `scrapelyProxyCountry`, and `scrapelyProxySubdivision`, and their [Apify aliases](#apify-names) | [Proxy & events](https://scrape.ly/docs/sdk-node-proxy-events.md#proxy-configuration) |
| Python | `groups`, `country_code`, `subdivision_code`, `proxy_urls`, `new_url_function`, `tiered_proxy_urls`, `tiered_proxy_config`, `password`, `check_access`, `actor_proxy_input` | [Environment & proxy](https://scrape.ly/docs/sdk-python-environment.md) |

## Let users choose in the input

Add a field with the `proxy` editor to your input schema:

```json
{
    "proxyConfiguration": {
        "title": "Proxy configuration",
        "type": "object",
        "editor": "proxy",
        "description": "Use the scrape.ly proxy or your own proxy URLs",
        "default": { "useScrapelyProxy": true }
    }
}
```

In the dashboard, this field renders a **Use Scrapely Proxy (recommended)** switch. When the switch is off, users can enter a list of custom proxy URLs. The value looks like `{"useScrapelyProxy": true}` or `{"useScrapelyProxy": false, "proxyUrls": ["http://..."]}`, and it can also carry `scrapelyProxyGroups`, `scrapelyProxyCountry`, and `scrapelyProxySubdivision`.

`useScrapelyProxy` decides whether the run uses Scrape.ly Proxy:

- `true`: requests go through Scrape.ly Proxy, with the groups and country from the same object.
- `false` with `proxyUrls`: requests go through your own proxy URLs.
- `false` without `proxyUrls`: no proxy. `Actor.createProxyConfiguration()` returns `undefined` (`None` in Python).

To pick the pool or the exit country, add them to the same object, for example as the field's default:

```json
{ "useScrapelyProxy": true, "scrapelyProxyGroups": ["DATACENTER"], "scrapelyProxyCountry": "US" }
```

| Key | Meaning |
| --- | --- |
| `scrapelyProxyGroups` | `["RESIDENTIAL"]` (default pool) or `["DATACENTER"]`. Other groups are [refused](#unsupported-groups) |
| `scrapelyProxyCountry` | Two-letter ISO country code, e.g. `"US"` |
| `scrapelyProxySubdivision` | ISO 3166-2 subdivision, requires `scrapelyProxyCountry`. The proxy currently routes by country only |

Without `scrapelyProxyGroups`, the run uses the residential pool.

### Apify names

Every key also has an Apify alias, so inputs, tasks and actors written for Apify keep working:

| Scrape.ly name | Apify alias |
| --- | --- |
| `useScrapelyProxy` | `useApifyProxy` |
| `scrapelyProxyGroups` | `apifyProxyGroups` |
| `scrapelyProxyCountry` | `apifyProxyCountry` |
| `scrapelyProxySubdivision` | `apifyProxySubdivision` |

Callers and input schemas can use either name. The run receives each proxy object under **both** names with the same values, whichever naming the caller used. So actor code can read `input.proxyConfiguration.useScrapelyProxy` or `useApifyProxy`, and both reflect the user's choice. When an object sets both names, the Scrape.ly name wins. The stored input, the run's **Input** tab and the API return the input exactly as it was sent.

One exception: an actor that installs the upstream `apify` JavaScript package itself (no Scrape.ly SDK swap) receives only the Apify names. Versions of that package up to 3.7.0 reject unknown proxy keys.

Pass the value straight to the SDK:

```js [JavaScript]
const input = await Actor.getInput();
const proxyConfiguration = await Actor.createProxyConfiguration(input?.proxyConfiguration);
```

```python [Python]
actor_input = await Actor.get_input() or {}
proxy_configuration = await Actor.create_proxy_configuration(
    actor_proxy_input=actor_input.get("proxyConfiguration"),
)
```

> [!TIP]
> Keep proxy passwords out of source control. Store them as [secret environment variables](https://scrape.ly/docs/security.md) or as CLI secrets referenced as `@secretName`.

## Browser crawlers

How browser traffic is proxied depends on the Actor's **Use Scrapely browser** setting:

| Setting | Where browsers run | Proxy for browser traffic | Billed as |
| --- | --- | --- | --- |
| Off (default) | In the run container | The `proxyConfiguration` you pass to the crawler, as for HTTP crawlers | Proxy GB |
| On | On the [Scrape.ly browser](https://scrape.ly/docs/browser.md) | The Scrape.ly browser's own residential proxy. Your proxy URLs are ignored, with a warning | Browser credits |

With the setting on, pick the browser's exit location with a browser configuration rather than a proxy:

```js [JavaScript]
const browserConfiguration = await Actor.createBrowserConfiguration({ countryCode: 'DE' });
const crawler = new PlaywrightCrawler({ browserConfiguration, requestHandler });
```

```python [Python]
browser_configuration = await Actor.create_browser_configuration(country_code="DE")
crawler = PlaywrightCrawler(browser_configuration=browser_configuration)
```

Without one, the browser uses the country of a platform `ProxyConfiguration` on the crawler, then the run default (`US`). All options, including `state`, `city`, `os`, and Cloudflare solving: [Choose the location and fingerprint](https://scrape.ly/docs/browser.md#choose-the-location-and-fingerprint).

## What's next

- [Security](https://scrape.ly/docs/security.md): keep proxy credentials out of Git
- [JavaScript SDK: proxy and events](https://scrape.ly/docs/sdk-node-proxy-events.md)
- [Python SDK: environment](https://scrape.ly/docs/sdk-python-environment.md)
- [Publish and monetize](https://scrape.ly/docs/publishing.md): how proxy usage counts toward a user's budget

---

- Previous: [Storage](https://scrape.ly/docs/storage.md)
- Next: [Scrape.ly Browser](https://scrape.ly/docs/browser.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
