# List credit blocks Source: https://developers.soax.com/api/account/credits api/openapi.json GET /v1/account/credits List your active credit blocks with their initial and remaining balances and expiry dates. Credits are consumed by proxy traffic according to the tier of the country the traffic routes through. Requires the `account:read` scope. # Get account Source: https://developers.soax.com/api/account/get api/openapi.json GET /v1/account Retrieve your organization's account information: name, status (`active` or `suspended`), current plan identifier, and account creation date. Requires the `account:read` scope. # Get subscription Source: https://developers.soax.com/api/account/subscription api/openapi.json GET /v1/account/subscription Retrieve your active subscription: plan, status, and current billing period. Returns 404 if the account has no active subscription. Requires the `account:read` scope. # Credits burned Source: https://developers.soax.com/api/analytics/credits-burned api/openapi.json POST /v1/proxy/analytics/credits-burned Retrieve credits burned per day, broken down by tier. Each item holds one date and a `data` array whose `key` is the tier number (`"1"`, `"2"`, `"3"`) and whose `value` is the credits burned on that tier. The date range may span at most 365 days. If the API key is restricted to specific packages, results (and any `package_ids` filter) are limited to those packages. Requires the `proxy:analytics:read` scope. # Usage summary Source: https://developers.soax.com/api/analytics/summary api/openapi.json POST /v1/proxy/analytics/summary Retrieve aggregate credits burned and traffic (GB) for the selected date range, compared against the previous period of the same length (`previous_period.to_date` is the day before `from_date`). If the API key is restricted to specific packages, only those packages are included. The date range may span at most 365 days. Requires the `proxy:analytics:read` scope. # Traffic breakdown Source: https://developers.soax.com/api/analytics/traffic api/openapi.json POST /v1/proxy/analytics/traffic Retrieve traffic (in GB) grouped by a dimension (`package`, `proxy_type`, `tier`, `country`, `domain`, or `ingress_ip`), as a series of interval buckets. When `group_by=package`, keys are package names. The date range may span at most 365 days. If `interval` is omitted it is chosen automatically from the range (< 24 h → `hour`, < 60 days → `day`, otherwise `month`). Explicit intervals are validated: `hour` needs a range of at most 24 hours, `day` between 24 hours and 180 days, `month` at least 60 days. If the API key is restricted to specific packages, results (and any `package_ids` filter) are limited to those packages. Requires the `proxy:analytics:read` scope. # API authentication Source: https://developers.soax.com/api/authentication How to authenticate with the SOAX API using API keys: the Authorization header, scopes, package restrictions, and key lifecycle. The SOAX API authenticates every request with an **API key** sent as a bearer token: ```bash theme={null} curl https://api.platform.soax.com/v1/account \ -H "Authorization: Bearer s_live_YOUR_API_KEY" ``` API key secrets start with `s_live_`. A missing, malformed, unknown, or revoked key returns: ```json theme={null} // 401 Unauthorized { "detail": "Invalid API key" } ``` **API key ≠ package key.** The API key (`s_live_…`) authenticates calls to `api.platform.soax.com`. The package key (a 10-character password, one per package) authenticates proxy traffic through `proxy.soax.com:1337` — that's the `password` field in [connection strings](/api/packages/connection-string) and the value rotated by [rotate package password](/api/packages/rotate-password). They are managed separately and are not interchangeable. ## Getting a key Create keys in the dashboard under [**Settings → API keys**](https://platform.soax.com/settings/api-keys): click **Generate new API key**, choose a name and scopes, and optionally restrict the key to specific packages. The secret is displayed **once**, at creation — store it securely; it cannot be retrieved later. (Only a hash is stored server-side.) You can also create keys programmatically with [`POST /v1/api-keys/`](/api/keys/create) — see [Key lifecycle](#key-lifecycle) below. ## Scopes Each key carries a set of scopes, and each endpoint requires one. Calling an endpoint without the required scope returns: ```json theme={null} // 403 Forbidden { "detail": "Missing required scope: proxy:packages:read" } ``` | Scope | Grants access to | | ------------------------- | ----------------------------------------------------------------------------------------------------------- | | `proxy:packages:read` | List/inspect packages, connection strings, and all location endpoints | | `proxy:packages:write` | [Rotate package passwords](/api/packages/rotate-password) | | `proxy:analytics:read` | All [analytics endpoints](/api/analytics/summary) | | `account:read` | [Account](/api/account/get), [credits](/api/account/credits), and [subscription](/api/account/subscription) | | `api-keys:read` | [List API keys](/api/keys/list) | | `api-keys:write` | [Create](/api/keys/create) and [revoke](/api/keys/revoke) API keys | | `team:read`, `team:write` | Reserved — no v1 endpoints use these yet | [`GET /v1/api-keys/me`](/api/keys/me) is the one exception: any active key can call it, regardless of scopes. Use it to check what a key can do. Grant each key only the scopes it needs. A monitoring job that reads analytics needs `proxy:analytics:read` and nothing else. ## Package restrictions A key can be restricted to specific packages (`package_ids`). A restricted key: * only sees those packages in [`GET /v1/proxy/packages`](/api/packages/list), * gets **404** (not 403) when requesting any other package — so the existence of packages outside the restriction is never revealed, * has its [analytics](/api/analytics/summary) results limited to those packages, even when it passes a broader `package_ids` filter. A key with `package_ids: null` is unrestricted and can access every package in the organization. ## Key lifecycle Keys can manage keys, which lets you automate rotation without touching the dashboard: * **Inspect** — [`GET /v1/api-keys/me`](/api/keys/me) shows the calling key's scopes, restriction, and rate-limit quota. * **Create** — [`POST /v1/api-keys/`](/api/keys/create) (requires `api-keys:write`). To prevent privilege escalation, the new key's scopes must be a subset of the caller's, and its package restriction a subset of the caller's restriction. * **Revoke** — [`DELETE /v1/api-keys/{key_id}`](/api/keys/revoke) (requires `api-keys:write`). Revocation is immediate and permanent: the key remains visible in listings with status `revoked`, but every request using it fails with 401. A key can revoke itself. To rotate a key with zero downtime: create the replacement, deploy it, then revoke the old key. ## Best practices * Store secrets in a secrets manager or environment variables, never in code or client-side bundles. * Use separate keys per service or environment, so one leak doesn't require rotating everything and usage stays attributable (each key tracks `last_used_at`). * Scope keys down: read-only keys for dashboards and monitoring, `api-keys:write` only where rotation is automated. * Enable [two-factor authentication](/dashboard/account-billing#two-factor-authentication-2fa) on dashboard accounts that can manage API keys. # Error handling Source: https://developers.soax.com/api/errors Every status code the SOAX API returns, the error response shapes, common error messages, and how to handle each one. The SOAX API uses conventional HTTP status codes. Anything in the `2xx` range is success, `4xx` means something is wrong with the request, and `5xx` means something went wrong on the SOAX side. ## Error response shapes Errors return a JSON object with the message under one of two keys, depending on which validation layer rejected the request. Endpoint-level checks (auth, scopes, not-found, pagination and count limits) use `detail`: ```json theme={null} { "detail": "Package not found" } ``` Deeper business-rule validation — connection-string rule combinations, analytics date ranges and filters, API key creation rules — and all 429s use `error`: ```json theme={null} { "error": "Cannot specify both 'rotate-time' and 'rotate-requests' simultaneously." } ``` The split is an implementation detail you shouldn't depend on — in your error handling, read `detail ?? error` and treat them the same. The tables below show which key each message actually uses. **422 validation errors** (a parameter or body field failed type/format validation) are the third shape: a `detail` **array**, one entry per problem, with the location of the offending field: ```json theme={null} { "detail": [ { "loc": ["query", "limit"], "msg": "Input should be less than or equal to 100", "type": "less_than_equal" } ] } ``` ## Status codes | Code | Meaning | How to handle | | ----- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `200` | Success | — | | `201` | Created (new API key) | Store the returned `secret` immediately — it is never shown again. | | `204` | Success, no body (key revoked) | — | | `400` | Invalid parameter value | Don't retry as-is. The `detail` message names the parameter and, where applicable, lists valid values. Fix the request. | | `401` | Missing, invalid, or revoked API key | Don't retry. Check the `Authorization: Bearer ` header and the key's status. If the key was revoked, create a new one. | | `403` | Key lacks the required scope, or your plan lacks a feature | Don't retry. Either use a key with the right scope (the `detail` names it), or — for ASN/ZIP location endpoints — upgrade to a plan with that targeting feature. | | `404` | Resource not found | Don't retry. The resource doesn't exist, belongs to another organization, or is outside the key's package restriction (restricted keys get 404, never 403, for packages they can't see). | | `422` | Request failed schema validation | Don't retry as-is. Fix the field named in `loc`. | | `429` | Rate limit exceeded | Wait the number of seconds in the `Retry-After` header, then retry. See [Rate limits](/api/rate-limits). | | `5xx` | Server error | Retry with exponential backoff. If it persists, contact support with the `x-request-id` response header value. | Every response includes an `x-request-id` header. Log it — support can trace exactly what happened to a request from that ID. ## Common error messages Messages under the `detail` key: | Status | `detail` | What it means | | ------ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 401 | `Invalid API key` | No key sent, the secret is wrong, or the key was revoked. | | 403 | `Missing required scope: ` | The key doesn't carry the scope this endpoint requires. See [scopes](/api/authentication#scopes). | | 403 | `Feature asn_targeting is not enabled` / `Feature zip_code_targeting is not enabled` | Your plan doesn't include ASN or ZIP targeting ([ASNs](/api/locations/package-asns), [ZIP codes](/api/locations/package-zip-codes)). | | 404 | `Package not found` | Wrong package ID, another organization's package, or outside the key's package restriction. | | 404 | `API key not found` | The `key_id` being revoked doesn't exist in your organization. | | 404 | `No active subscription found` | The account has no active subscription. | | 400 | `Invalid status value: … Valid values are: active, deleted, limited, paused, suspended` | Bad `status` filter on [list packages](/api/packages/list). One value only — comma-separated lists are rejected. | | 400 | `Count cannot exceed 1000.` | `count` on [connection strings](/api/packages/connection-string) is above the maximum. | | 400 | `Country code '…' is not available for this package.` | A [package location endpoint](/api/locations/package-regions) was called with a country the package can't target. List valid ones via [package countries](/api/locations/package-countries). | | 400 | `Network '…' is not available for this package. Available networks: …` | Bad `network` filter on a package location endpoint; the message lists what the package supports. | Messages under the `error` key: | Status | `error` | What it means | | ------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | 400 | `Country '…' is not available for this proxy package.` | [Connection strings](/api/packages/connection-string) requested for a country the package can't target. | | 400 | `Invalid network '…'. Must be one of: residential, mobile, any.` | Bad `network` value on connection strings. | | 400 | `Country must be specified if region, city, ISP, ASN, or ZIP is provided.` | Finer-grained targeting always needs `country`. | | 400 | `Cannot specify both 'rotate-time' and 'rotate-requests' simultaneously.` | Pick one rotation mode. | | 400 | `The 'retries' parameter is required when onerror='retry'.` | `on-error=retry` needs `retries`; `retries` is only valid with `on-error=retry`. | | 400 | `Session ID must be an alphanumeric lowercase string up to 16 characters.` | `session` must match `^[a-z0-9]{1,16}$`. | | 400 | `bind=node is incompatible with prefer=lookalike.` / `…with onerror=replace.` | A bound session can't be replaced, so replacement-based options don't apply. | | 400 | `Date range cannot be more than 1 year` | An [analytics](/api/analytics/summary) query spans more than 365 days. | | 400 | `hour interval requires a date range less than 24 hours` | See interval constraints in [Rate limits](/api/rate-limits#other-usage-constraints); `day` and `month` have analogous messages. | | 400 | `Invalid scopes: … Valid scopes are: …` | [Creating a key](/api/keys/create) with an unknown scope. | | 400 | `Requested scopes exceed the calling key's own scopes` | [Creating a key](/api/keys/create) with more scopes than the caller has. | | 429 | `Rate limit exceeded: …` | Too many requests. Wait `Retry-After` seconds. | ## Handling errors in code A pattern that covers the cases worth automating — retry `429` and `5xx`, surface everything else: ```python Python theme={null} import time import requests def soax_request(method, url, *, api_key, max_attempts=5, **kwargs): headers = {"Authorization": f"Bearer {api_key}"} for attempt in range(max_attempts): resp = requests.request(method, url, headers=headers, **kwargs) if resp.status_code == 429: time.sleep(int(resp.headers.get("Retry-After", 1))) continue if resp.status_code >= 500: time.sleep(min(2 ** attempt, 30)) # exponential backoff, capped continue if resp.status_code >= 400: body = resp.json() message = body.get("detail") or body.get("error") or body raise RuntimeError( f"SOAX API error {resp.status_code}: {message} " f"(x-request-id: {resp.headers.get('x-request-id')})" ) return resp.json() if resp.status_code != 204 else None raise RuntimeError(f"Gave up after {max_attempts} attempts: {url}") ``` ```javascript Node.js theme={null} async function soaxRequest(url, { apiKey, maxAttempts = 5, ...init } = {}) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const resp = await fetch(url, { ...init, headers: { Authorization: `Bearer ${apiKey}`, ...init.headers }, }); if (resp.status === 429) { const retryAfter = Number(resp.headers.get("Retry-After") ?? 1); await new Promise((r) => setTimeout(r, retryAfter * 1000)); continue; } if (resp.status >= 500) { await new Promise((r) => setTimeout(r, Math.min(2 ** attempt * 1000, 30000))); continue; } if (!resp.ok) { const body = await resp.json().catch(() => ({})); const message = body.detail ?? body.error ?? JSON.stringify(body); throw new Error( `SOAX API error ${resp.status}: ${message} ` + `(x-request-id: ${resp.headers.get("x-request-id")})` ); } return resp.status === 204 ? null : resp.json(); } throw new Error(`Gave up after ${maxAttempts} attempts: ${url}`); } ``` These are errors from the **API** (`api.platform.soax.com`). Errors from the **proxy gateway** (`proxy.soax.com:1337`, e.g. `407 AUTH_FAILED`) are a different system with different codes — see [Proxy error codes](/troubleshooting/error-codes). # Create an API key Source: https://developers.soax.com/api/keys/create api/openapi.json POST /v1/api-keys/ Create a new API key. To prevent privilege escalation, the new key's `scopes` must be a subset of the calling key's scopes, and `package_ids` (if provided) must be a subset of the calling key's package restriction. If `package_ids` is omitted, the new key inherits the calling key's restriction. The response includes the plaintext `secret` — this is the **only** time it is ever returned. Store it immediately in a secure location. Requires the `api-keys:write` scope. # List API keys Source: https://developers.soax.com/api/keys/list api/openapi.json GET /v1/api-keys/ Retrieve all API keys belonging to your organization, including revoked ones. Key secrets are never returned — only metadata. Requires the `api-keys:read` scope. # Get current API key Source: https://developers.soax.com/api/keys/me api/openapi.json GET /v1/api-keys/me Retrieve details about the API key used to authenticate the request: its scopes, package restriction, organization ID, and current rate-limit quota. Useful as a first call to verify a key works, and to check remaining rate-limit quota. This endpoint requires no scope — any active key can call it. The key secret is never returned. # Revoke an API key Source: https://developers.soax.com/api/keys/revoke api/openapi.json DELETE /v1/api-keys/{key_id} Revoke an API key belonging to your organization. Revocation is a soft delete: the key keeps appearing in `GET /v1/api-keys/` with status `revoked`, but any request using it is rejected with 401 from that point on. A key can revoke itself. Revocation cannot be undone — create a new key instead. Requires the `api-keys:write` scope. # List proxy countries Source: https://developers.soax.com/api/locations/countries api/openapi.json GET /v1/proxy/locations/countries List every country available on the SOAX network with its tier, regardless of package. To see what a specific package can target, use the package countries endpoint instead. Requires the `proxy:packages:read` scope. # List package ASNs Source: https://developers.soax.com/api/locations/package-asns api/openapi.json GET /v1/proxy/packages/{package_id}/locations/countries/{cc}/asns List ASNs available in a country for this package. Use `code` values as the `asn` parameter of the connection-string endpoint. **Feature gate**: returns 403 if your plan does not include ASN targeting. Requires the `proxy:packages:read` scope. # List package cities Source: https://developers.soax.com/api/locations/package-cities api/openapi.json GET /v1/proxy/packages/{package_id}/locations/countries/{cc}/cities List cities available in a country (optionally narrowed to a region) for this package. Use `code` values as the `city` parameter of the connection-string endpoint. Requires the `proxy:packages:read` scope. # List package countries Source: https://developers.soax.com/api/locations/package-countries api/openapi.json GET /v1/proxy/packages/{package_id}/locations/countries List the countries this package can route traffic through, with the tier of each country. Use the returned `code` values as the `country` parameter of the connection-string endpoint. Requires the `proxy:packages:read` scope. # List package ISPs Source: https://developers.soax.com/api/locations/package-isps api/openapi.json GET /v1/proxy/packages/{package_id}/locations/countries/{cc}/isps List ISPs available in a country (optionally narrowed by region and city) for this package. Use `code` values as the `isp` parameter of the connection-string endpoint. Requires the `proxy:packages:read` scope. # List package regions Source: https://developers.soax.com/api/locations/package-regions api/openapi.json GET /v1/proxy/packages/{package_id}/locations/countries/{cc}/regions List regions available in a country for this package. `rank` orders locations by available pool size (1 = largest) and `volume_level` is a coarse 1–5 indicator of relative IP volume. Use `code` values as the `region` parameter of the connection-string endpoint. Requires the `proxy:packages:read` scope. # List package ZIP codes Source: https://developers.soax.com/api/locations/package-zip-codes api/openapi.json GET /v1/proxy/packages/{package_id}/locations/countries/{cc}/zip-codes List ZIP codes available in a country (optionally narrowed by region and city) for this package. Use `code` values as the `zip` parameter of the connection-string endpoint. **Feature gate**: returns 403 if your plan does not include ZIP targeting. Requires the `proxy:packages:read` scope. # List proxy tiers Source: https://developers.soax.com/api/locations/tiers api/openapi.json GET /v1/proxy/locations/tiers List the proxy tiers SOAX offers. Tiers group countries by pool quality and pricing; each country belongs to one tier. Requires the `proxy:packages:read` scope. # API overview Source: https://developers.soax.com/api/overview Programmatic access to your SOAX account: packages, connection strings, locations, usage analytics, and API key management over a JSON REST API. The SOAX API gives you programmatic access to everything you'd otherwise manage in the [dashboard](https://platform.soax.com): list and inspect your proxy packages, generate ready-to-use connection strings, browse available locations, pull usage analytics, and manage API keys — all without logging in. The API manages your **account**. It is separate from the proxy gateway (`proxy.soax.com:1337`) that carries your actual proxy traffic. You authenticate to the API with an **API key** (`s_live_…`); you authenticate to the proxy gateway with a **package key** (a 10-character password, one per package). See [Authentication](/api/authentication) for the difference. ## Base URL All endpoints live under a single base URL and are versioned under `/v1`: ``` https://api.platform.soax.com/v1 ``` Requests and responses are JSON over HTTPS. Use paths exactly as documented — the API keys collection paths end with a trailing slash (`/v1/api-keys/`), and calling them without it triggers a 307 redirect that not every HTTP client follows with the `Authorization` header intact. ## Authentication Every request must carry an API key in the `Authorization` header: ```bash theme={null} curl https://api.platform.soax.com/v1/api-keys/me \ -H "Authorization: Bearer s_live_YOUR_API_KEY" ``` Keys are created in the dashboard under [**Settings → API keys**](https://platform.soax.com/settings/api-keys) and carry **scopes** that control which endpoints they can call. See [Authentication](/api/authentication) for the full scope reference. ## Endpoints at a glance | Group | What it covers | | ----------------------------------- | ------------------------------------------------------------------------------------ | | [API Keys](/api/keys/me) | Inspect the current key, list, create, and revoke API keys | | [Packages](/api/packages/list) | List and inspect proxy packages, build connection strings, rotate package passwords | | [Locations](/api/locations/tiers) | Tiers, countries, regions, cities, ISPs, ASNs, and ZIP codes available for targeting | | [Account](/api/account/get) | Account info, credit balances, subscription details | | [Analytics](/api/analytics/summary) | Usage summaries, traffic breakdowns, credits burned | ## Pagination `GET /v1/proxy/packages` uses cursor-based pagination. Each page returns `has_more` and `next_cursor`; pass `next_cursor` as the `cursor` query parameter to fetch the next page, and stop when `has_more` is `false`. Other list endpoints return complete lists without pagination. ## Rate limits Each API key can make **5 requests per second**, and each client IP **500 requests per minute**. Exceeding either returns `429`. See [Rate limits](/api/rate-limits) for details and retry guidance. ## Errors Errors use conventional HTTP status codes with a JSON body describing the problem. See [Error handling](/api/errors) for every status code, the response shapes, and how to handle each case. ## OpenAPI specification The API is described by an OpenAPI 3.1 specification, useful for generating clients or importing into tools like Postman: ``` https://api.platform.soax.com/v1/openapi.json ``` ## Next steps Create an API key and make your first request in a few minutes. API keys, scopes, package restrictions, and key lifecycle. Status codes, error bodies, and how to handle each. Limits, the 429 response, and how to check your remaining quota. # Get connection strings Source: https://developers.soax.com/api/packages/connection-string api/openapi.json GET /v1/proxy/packages/{package_id}/connection-string Build ready-to-use proxy connection strings for a package, with targeting and session rules encoded for you. The returned credentials are used against the proxy gateway (`proxy.soax.com:1337`) — not against this API. Location parameters are validated against what the package can actually target: an unavailable country, region, city, ISP, ASN, or ZIP returns 400. `region`, `city`, `isp`, `asn`, and `zip` all require `country` to be set. Rotation and error handling: `rotate-time` and `rotate-requests` are mutually exclusive; `on-error=retry` requires `retries`, and `retries` is only valid with `on-error=retry`; `bind=node` is incompatible with `prefer=lookalike` and `on-error=replace`. When any rotation/error/bind rule is used (or `count` > 1) and no `session` is given, a session ID is generated automatically; with `count` > 1 the session IDs are numbered so each connection string gets its own IP. For packages using IP allowlist authentication, rules are encoded in the hostname (HTTPS only), `username` is empty, and `password` is `null`. Requires the `proxy:packages:read` scope. # Get a package Source: https://developers.soax.com/api/packages/get api/openapi.json GET /v1/proxy/packages/{package_id} Retrieve full configuration for one proxy package: status, network types, tiers, traffic usage (in bytes), the package password used for proxy authentication, connection limits, allowed IPs, port and target rules, and enabled protocols. If the API key is restricted to specific packages, requesting a package outside the restriction returns 404 (not 403), so the existence of other packages is not revealed. Requires the `proxy:packages:read` scope. # List packages Source: https://developers.soax.com/api/packages/list api/openapi.json GET /v1/proxy/packages Retrieve a paginated list of proxy packages accessible to the API key. If the key is restricted to specific packages, only those are returned. Use cursor-based pagination: pass the `next_cursor` value from one response as the `cursor` parameter of the next request until `has_more` is `false`. Traffic values are in bytes. Requires the `proxy:packages:read` scope. # Rotate package password Source: https://developers.soax.com/api/packages/rotate-password api/openapi.json POST /v1/proxy/packages/{package_id}/rotate-password Generate a new package password (the 10-character key used to authenticate proxy requests — not the API key). The old password stops working immediately, so update every client that connects through this package. The new plaintext password is returned only in this response. Returns 400 if the package is deleted or suspended. Requires the `proxy:packages:write` scope. # API quickstart Source: https://developers.soax.com/api/quickstart Create an API key, verify it, list your packages, and generate a proxy connection string — with curl, Python, and Node.js examples. This guide takes you from zero to a working integration: you'll create an API key, verify it works, list your proxy packages, and generate a connection string you can plug straight into your proxy client. ## 1. Create an API key 1. Log in to the [dashboard](https://platform.soax.com). 2. Go to [**Settings → API keys**](https://platform.soax.com/settings/api-keys). 3. Click **Generate new API key**, give it a name, and pick its scopes. For this walkthrough you need at least `proxy:packages:read`. 4. Optionally restrict the key to specific packages — a restricted key can only see and act on those packages. 5. Copy the secret. It starts with `s_live_` and is **shown only once** — store it somewhere secure right away. Treat the secret like a password. Don't commit it to source control or embed it in client-side code. If a key leaks, [revoke it](/api/keys/revoke) and create a new one. ## 2. Verify the key Call [`GET /v1/api-keys/me`](/api/keys/me) — it needs no scope and echoes back what the key can do: ```bash theme={null} curl https://api.platform.soax.com/v1/api-keys/me \ -H "Authorization: Bearer s_live_YOUR_API_KEY" ``` ```json theme={null} { "id": "3f8a2c1e-5b6d-4e7f-8a9b-0c1d2e3f4a5b", "scopes": ["proxy:packages:read"], "package_ids": null, "org_id": "0b9c8d7e-6f5a-4b3c-2d1e-0f9a8b7c6d5e", "rate_limit": { "configured_limit": "5/second", "remaining_quota": 4 } } ``` If you get `401 {"detail": "Invalid API key"}` instead, check that the header is exactly `Authorization: Bearer ` and that the key hasn't been revoked. ## 3. List your packages ```bash curl theme={null} curl https://api.platform.soax.com/v1/proxy/packages \ -H "Authorization: Bearer s_live_YOUR_API_KEY" ``` ```python Python theme={null} import requests API_KEY = "s_live_YOUR_API_KEY" BASE_URL = "https://api.platform.soax.com/v1" resp = requests.get( f"{BASE_URL}/proxy/packages", headers={"Authorization": f"Bearer {API_KEY}"}, ) resp.raise_for_status() for pkg in resp.json()["data"]: print(pkg["id"], pkg["name"], pkg["status"]) ``` ```javascript Node.js theme={null} const API_KEY = "s_live_YOUR_API_KEY"; const BASE_URL = "https://api.platform.soax.com/v1"; const resp = await fetch(`${BASE_URL}/proxy/packages`, { headers: { Authorization: `Bearer ${API_KEY}` }, }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const { data } = await resp.json(); for (const pkg of data) console.log(pkg.id, pkg.name, pkg.status); ``` ```json theme={null} { "data": [ { "id": "b2f0c3d4-e5a6-4b7c-8d9e-0f1a2b3c4d5e", "name": "Residential - EU scraping", "status": "active", "status_reason": null, "created_at": "2026-03-15T12:00:00Z", "types": ["wifi"], "tiers": [1, 2, 3], "allowed_countries": null, "traffic_limit": 500000000000, "traffic_spent": 120000000000, "traffic_left": 380000000000 } ], "has_more": false, "next_cursor": null } ``` Grab the `id` of the package you want to use — you'll need it in the next step. ## 4. Generate a connection string The [connection-string endpoint](/api/packages/connection-string) builds proxy credentials for you, with targeting rules already encoded — no need to construct the username format by hand: ```bash curl theme={null} curl "https://api.platform.soax.com/v1/proxy/packages/b2f0c3d4-e5a6-4b7c-8d9e-0f1a2b3c4d5e/connection-string?protocol=http&country=us" \ -H "Authorization: Bearer s_live_YOUR_API_KEY" ``` ```python Python theme={null} resp = requests.get( f"{BASE_URL}/proxy/packages/{package_id}/connection-string", headers={"Authorization": f"Bearer {API_KEY}"}, params={"protocol": "http", "country": "us"}, ) resp.raise_for_status() conn = resp.json()[0] print(conn["uri"]) ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ protocol: "http", country: "us" }); const csResp = await fetch( `${BASE_URL}/proxy/packages/${packageId}/connection-string?${params}`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); if (!csResp.ok) throw new Error(`HTTP ${csResp.status}`); const [conn] = await csResp.json(); console.log(conn.uri); ``` ```json theme={null} [ { "uri": "http://country-us:aB3dE5fG7h@proxy.soax.com:1337", "username": "country-us", "password": "aB3dE5fG7h", "host": "proxy.soax.com", "port": 1337 } ] ``` ## 5. Use it The returned `uri` is a standard proxy URL — pass it to any HTTP client: ```bash curl theme={null} curl -x "http://country-us:aB3dE5fG7h@proxy.soax.com:1337" \ https://checker.soax.com/api/ipinfo ``` ```python Python theme={null} proxies = {"http": conn["uri"], "https": conn["uri"]} ip = requests.get("https://checker.soax.com/api/ipinfo", proxies=proxies) print(ip.json()) ``` You're routed through a US IP. From here you can add sessions, rotation, and finer targeting (region, city, ISP, ASN, ZIP) via the same endpoint's query parameters. ## Next steps All targeting, session, and rotation parameters. What each scope unlocks, and how to manage keys programmatically. Pull traffic and credit usage into your own reporting. Stay under the limits and handle 429s gracefully. # Rate limits Source: https://developers.soax.com/api/rate-limits SOAX API rate limits: 5 requests/second per API key and 500 requests/minute per IP, how the 429 response looks, how to check remaining quota, and other usage constraints. The SOAX API enforces two independent rate limits, and a request must pass **both**: | Limit | Applies to | Window | | ------------------------- | ---------------------- | --------------------- | | **5 requests / second** | each API key | fixed 1-second window | | **500 requests / minute** | each client IP address | fixed 1-minute window | Two keys used from the same machine share the IP budget; the same key used from two machines shares the key budget. Limits are fixed-window: the counter resets at each window boundary rather than sliding continuously. ## The 429 response Exceeding either limit returns `429 Too Many Requests`: ```json theme={null} { "error": "Rate limit exceeded: 5 per 1 second" } ``` The response carries standard rate-limit headers telling you exactly when to retry: | Header | Meaning | | --------------------- | -------------------------------------------------- | | `Retry-After` | Seconds to wait before retrying (at least 1) | | `RateLimit-Limit` | The bucket size (e.g. `5`) | | `RateLimit-Remaining` | Requests left in the current window (`0` on a 429) | | `RateLimit-Reset` | Unix timestamp when the window resets | Honor `Retry-After` rather than guessing: ```python theme={null} import time, requests def get_with_retry(url, headers, max_attempts=5): for _ in range(max_attempts): resp = requests.get(url, headers=headers) if resp.status_code != 429: return resp time.sleep(int(resp.headers.get("Retry-After", 1))) raise RuntimeError("still rate limited after retries") ``` For sustained workloads, spacing requests (a client-side throttle at \~4 requests/second per key) is more effective than reacting to 429s. ## Checking your quota Every authenticated response — not just 429s — includes the `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, and `Retry-After` headers for your key's bucket, so you can track quota passively from responses you're already making. If you want it in a body instead, [`GET /v1/api-keys/me`](/api/keys/me) returns the same information (note that this call itself counts against the limit): ```json theme={null} { "rate_limit": { "configured_limit": "5/second", "remaining_quota": 3 } } ``` ## Other usage constraints Beyond request rates, individual endpoints enforce these caps: | Constraint | Value | Where | | ------------------------------ | -------------------------------------- | --------------------------------------------------------- | | Packages per page | `limit` 1–100 (default 20) | [List packages](/api/packages/list) | | Connection strings per request | `count` 1–1000 | [Get connection strings](/api/packages/connection-string) | | Analytics date range | at most 365 days | all [analytics endpoints](/api/analytics/summary) | | `interval=hour` | range under 24 hours | [Traffic breakdown](/api/analytics/traffic) | | `interval=day` | range between 24 hours and 180 days | [Traffic breakdown](/api/analytics/traffic) | | `interval=month` | range of at least 60 days | [Traffic breakdown](/api/analytics/traffic) | | `session` ID format | lowercase letters/digits, max 16 chars | [Get connection strings](/api/packages/connection-string) | ## Tips for staying under the limits * **Cache reference data.** Tiers, countries, and package location lists change rarely — fetch them once and refresh occasionally, don't call them per request. * **Batch connection strings.** One call with `count=100` gives you 100 session-distinct connection strings; you don't need 100 calls. * **Use one key per service, not per request.** Each key gets its own 5 req/s budget, but creating keys in a hot path is an anti-pattern — create them ahead of time. These limits apply to the management API only. Proxy traffic through `proxy.soax.com:1337` is governed by your package's own limits (concurrent connections, traffic volume), not by these API rate limits. # Account & Billing Source: https://developers.soax.com/dashboard/account-billing Manage your subscription, payment methods, and invoices — and understand how traffic and overages are calculated. Everything you need to handle billing lives in **Settings**, accessible from the top navigation bar in the dashboard. This page covers what's there and how it works, so you can manage your account without needing to open a support ticket. ## Your subscription Go to [**Settings → Billing**](https://platform.soax.com/settings/billing) to see your current plan. You'll find: * **Plan name and status** — whether your subscription is active, paused, or cancelled * **Subscription ID** — useful if you ever need to reference your account with support * **Plan limits** — your monthly credit allowance, seat count, and number of packages * **Next billing date** — when your next charge will be processed To make changes — upgrade, downgrade, or cancel — click **Manage subscription** in the top right of the subscription card. ## Invoices Your invoice history is at the bottom of the **Billing** tab. Each invoice shows: * **Invoice ID** — a unique reference number * **Date and due date** * **Amount charged** * **Status** — Paid, Unpaid, or Pending You can filter by status or date range. To download an invoice, click the download icon on the right. To preview it in the browser first, click **Preview**. ## Payment methods Go to **[Settings → Billing](https://platform.soax.com/settings/billing) → Payment methods** to add or update a payment method. SOAX supports four payment options: * **Credit card** — Visa, Mastercard, Amex. Processed instantly. Adding a card enables automatic payments on your billing date. * **Cryptocurrency** — BTC, ETH, USDT, USDC, SOL, and more. Takes 5–30 minutes to process. Requires KYC verification before use — see Verification below. * **Bank transfer** — Wire transfer. Manual checkout; takes 1–3 business days to settle. * **Postpaid billing** — Enterprise plan only. Your subscription is billed upfront; any overage is invoiced at the end of each month. ## Billing information Go to **[Settings → Billing](https://platform.soax.com/settings/billing) → Edit billing info** to update your company name, billing address, and VAT ID. This is separate from your account email — invoices are addressed using whatever you set here, so make sure it reflects your company or finance team's details. ### VAT VAT applies to customers in the EU and UK. Customers outside these regions aren't charged VAT. * **If you're a business (B2B) with a valid VAT ID** — add it to your billing details and your invoices will be issued under the reverse charge mechanism. SOAX won't charge you VAT; you report it to your local tax office instead. If your VAT ID is missing or invalid, VAT is added automatically at your country's standard rate. * **If you're an individual (B2C)** — VAT is applied automatically based on your billing country at the local rate. VAT is shown during checkout before you confirm payment. You can add or update your VAT ID at any time from **Edit billing info**. ## How credits work SOAX measures usage in **credits**. 1 credit = \$1, and each credit buys a certain amount of proxy traffic depending on which country tier you're targeting. ### Proxy pricing by country tier The price per GB varies by location. Higher-tier countries (US, UK, Western Europe) cost more per GB than lower-tier ones. | Tier | Builder (\$200/mo) | Team (\$500/mo) | Scale (\$1500/mo) | Enterprise | | ---------------- | -------------------- | ------------------ | ------------------ | -------------- | | Tier 1 countries | \$3.00/GB \~66.7 GB | \$2.20/GB \~227 GB | \$1.50/GB \~1.0 TB | from \$0.50/GB | | Tier 2 countries | \$2.25/GB \~88.9 GB | \$1.65/GB \~303 GB | \$1.15/GB \~1.3 TB | from \$0.35/GB | | Tier 3 countries | \$1.20/GB \~166.7 GB | \$0.90/GB \~555 GB | \$0.60/GB \~2.5 TB | from \$0.25/GB | The approximate GB figures are what your included credits cover at that tier's rate. Actual usage will vary depending on which countries you target. ### Credit expiry Credits are added to your prepaid credit balance in separate blocks. Each block has its own expiry date, which does not change.

Credits added on Sandbox or a monthly plan expire 60 days after they are added. Credits added on an annual plan expire 12 months after they are added.

Renewing, changing, or cancelling your plan does not change the expiry dates of credits already in your balance. If credits are available in several unexpired blocks, we use the block that expires first. ### What counts as usage Traffic is counted when a request is successfully routed through a SOAX proxy node. Data transferred in both directions (request + response) counts toward your usage. Traffic is **not** counted for: * Failed connection attempts (e.g. authentication errors, timeouts before a connection is established) * Internal dashboard activity Check your current usage at any time in [**Settings → Credits**](https://platform.soax.com/settings/credits) or on the [Usage breakdown](https://platform.soax.com/usage) dashboard. ## How overages work When you run out of included credits, additional usage is charged at your plan's per-GB rate for the country tier you're targeting. Here's how it works in practice: 1. Your plan includes a set number of credits (shown under **Plan limits** in the subscription card). 2. Once you use them up, requests continue to go through — SOAX doesn't cut off your traffic mid-operation. 3. Additional usage is billed at the same per-GB rate as your plan. 4. The overage charge appears as a separate line on your next invoice. Postpaid billing — where you pay after usage rather than upfront — is only available on the Enterprise plan. If you're consistently going over your credits, upgrading your plan reduces your per-GB rate significantly. For example, Tier 1 traffic drops from $3.00/GB on Builder to $1.50/GB on Scale — so the same volume costs half as much. ## Verification Identity verification is found in [**Settings → Profile → Verification**](https://platform.soax.com/settings/profile). ### Personal identity (KYC) KYC verification unlocks: * Crypto payments * Restricted domain and port access To start, click **Start verification** under [**Profile → Verification**](https://platform.soax.com/settings/profile). You'll need to upload a government-issued ID. Once your documents are uploaded, the status changes to **Ready to verify** while our team processes them. ## Team and permissions Every account has two roles: **Owner** and **Member**. The Owner controls billing, packages, and account configuration; Members use the packages they're given. This keeps spend and configuration under the Owner's control while letting the rest of the team connect and work. | Capability | Owner | Member | | -------------------------------------------------------- | ----- | ------ | | Connect through assigned packages | Yes | Yes | | View usage and analytics | Yes | Yes | | Create, edit, pause, or delete packages | Yes | No | | Set or change package limits (RPS, connections, traffic) | Yes | No | | Purchase credits | Yes | No | | Manage the subscription, payment methods, and invoices | Yes | No | | Request access to restricted domains or ports | Yes | No | | Manage team members and account security | Yes | No | If you're a Member and need to do something only Owner can, ask an Owner on your team. ## Account security ### Two-factor authentication (2FA) 2FA is managed in [**Settings → Profile → Authentication**](https://platform.soax.com/settings/profile). It's disabled by default. To enable it, click **Add OTP device** and follow the setup steps with an authenticator app (e.g. Google Authenticator or Authy). We recommend enabling 2FA, especially if your account has API keys or active billing methods attached. ### Active sessions You can see all devices currently logged into your account under [Settings → Ptofile → Authentication → Active sessions](https://platform.soax.com/settings/profile). If you see a session you don't recognize, click **Revoke** to sign it out immediately. # Package Management Source: https://developers.soax.com/dashboard/package-management Create, configure, and manage proxy packages from the dashboard. Packages are the unit of access and billing — credentials, limits, and entitlements all live on a package. A **proxy package** is your entry point to the SOAX network. Every request you make runs inside a package and inherits its credentials, limits, and entitlements. You can create as many packages as you need — one per environment, one per team, one per workload — and manage them independently. All packages share your organization's credit pool, but each package controls who can spend from it and how. Only organization **Owners** can create, edit, pause, or delete packages and change their limits. **Members** can connect through the packages assigned to them, but can't change package configuration. See [Team and permissions](/dashboard/account-billing#team-and-permissions). ## What's on a package | Attribute | What it controls | | -------------------------------- | --------------------------------------------------------- | | **Package key** | Authentication credential (used as the proxy password). | | **Network types** | Which networks are allowed: residential, mobile, or both. | | **RPS limit** | Maximum requests per second for this package. | | **Concurrent connections limit** | Maximum simultaneous connections. | | **Traffic limit** | Optional cap on GB consumed by this package. | | **IP allowlist** | Authorized client IPs for IP Auth. | | **Feature entitlements** | Premium features enabled on the package. | Limits are enforced at two levels. Each package has its own RPS and concurrent connection limit. There's also a customer-level ceiling across all your packages, so creating more packages doesn't sidestep the global limit. ## Creating a package 1. In the dashboard, open [**Packages**](https://platform.soax.com/packages) in the sidebar. 2. Click **Create package**. 3. Set the package name, allowed network types, and any optional traffic limit. 4. Save. The package key is generated and shown on the package detail page. The package is immediately available for use. Open Quick Connect on the package page to start building a connection string. ## Managing the package key The package key is the password half of every connection string: ```text theme={null} {rules}:{package_key}@proxy.soax.com:1337 ``` Keys look like `pk_abc123…`. They're shown on the package detail page; copy the value exactly. ### Regenerating a key If a key is leaked or you want to rotate it on schedule, regenerate it from the package detail page. The old key stops working immediately, so update every integration that uses it before regenerating. ## Network types Residential and mobile are both enabled by default on new packages. You can disable one if you want a package restricted to a single network type — for example, to give a team residential-only access, or to keep a high-volume scraping workload on residential without accidentally consuming mobile credits. The network type is selected at runtime via the `network` rule (`network-res`, `network-mob`, `network-any`, or combinations like `network-res_mob`). If you don't include a `network` rule, the package's default is used (`res`). ## Limits and quotas ### RPS and concurrent connections Each package has an RPS limit and a concurrent-connections limit, both visible on the package detail page. On the Sandbox plan, the concurrent-connections limit is **4,000 per proxy gateway**. Hitting either limit returns: ```text theme={null} HTTP/1.1 429 Too Many Requests X-SOAX-Error: RATE_LIMIT_EXCEEDED ``` If your workload needs more headroom, ask an organization Owner to raise the package's limits. The customer-level ceiling across all packages is tied to your plan — to lift that, an Owner can contact support to have the limits evaluated and increased. ### Traffic limit The optional traffic limit lets you cap GB consumption on a single package without affecting other packages. Useful for ring-fencing a team or an experimental workload from the rest of the organization's budget. Hitting the cap returns: ```text theme={null} HTTP/1.1 429 Too Many Requests X-SOAX-Error: TRAFFIC_LIMIT_EXCEEDED ``` Update the cap from the package settings. ## IP allowlist (for IP Auth) If you want to authenticate by IP instead of sending the package key on every request, add your client's outbound public IP to the package's IP allowlist, found in the package's **Access** tab (`platform.soax.com/packages//access`). * The IP added must be the public outbound IP that SOAX sees, not your local/private IP. Check it without a proxy at [checker.soax.com/api/ipinfo](https://checker.soax.com/api/ipinfo). * You can keep both methods active simultaneously. Adding an IP to the allowlist doesn't disable username/password auth. * IP Auth works with HTTPS in Quick Connect (rules in the subdomain, subject to the 63-character DNS label limit). HTTP and SOCKS5 can't carry rules under IP Auth — use username/password authentication for those. See [Authentication](/getting-started/authentication) for full setup. ## Multiple packages Common reasons to split workloads into multiple packages: * **Per environment** — separate packages for staging and production, so usage and limits don't cross over. * **Per team or customer** — track traffic by team in [Usage breakdown](https://platform.soax.com/usage), and apply different limits to each. * **Per workload** — isolate a high-volume scrape from interactive use so one can't starve the other on shared concurrency. * **Per network type** — restrict one package to residential and another to mobile to make billing attribution clean. All packages share the organization's credit pool. The split only controls how that pool is allocated and tracked, not how much credit you have. ## Pausing and deleting a package * **Pause.** Temporarily disable a package without losing its settings. Requests fail with `407 PACKAGE_SUSPENDED` until resumed. * **Delete.** Permanently remove the package. The package key is invalidated immediately. There's no undo. ## Where to look next * **Current usage** for a package is in [Usage breakdown](https://platform.soax.com/usage), filtered by package. * **Errors against the package** surface in your application logs via [`X-SOAX-Error`](/troubleshooting/error-codes) response headers. * **Connection string format and rule reference** is in [Authentication](/getting-started/authentication), [Residential proxies](/proxies/residential), and [Mobile proxies](/proxies/mobile). ## Next steps Build a connection string for this package without memorising the rules format. Username/password and IP Auth, end-to-end. Per-package traffic, credits, and country-tier breakdowns. Subscription, payments, invoices, and credits. # Quick Connect Source: https://developers.soax.com/dashboard/quick-connect Build SOAX connection strings without memorising the rules format. Quick Connect is the dashboard's connection-string builder for residential and mobile proxies. Quick Connect is the dashboard's connection-string builder. You pick what you want — country, session type, rotation, error handling — and the dashboard generates the connection string you paste into your code or HTTP client. It's also the name of the underlying mode. Putting rules directly into the connection string (`country-us-session-job1-rotate-timed_300:pk_abc123@proxy.soax.com:1337`) is "Quick Connect" no matter where you typed the string. The dashboard tool just makes building one easier. ## When to use Quick Connect Quick Connect covers everything most integrations need: * Rotating proxies (no session) and ephemeral sessions * Full filtering (country, region, city, ISP, ASN, zip, network) * Rotation (`rotate-timed_N`, `rotate-requests_N`) * Error handling (`onerror-replace`, `onerror-retry_N`, `onerror-fail`) * Routing preferences (`prefer-lookalike`) * Node binding (`bind-node`) * Unlimited concurrent sessions ## Opening Quick Connect In the dashboard, click [**Packages**](https://platform.soax.com/packages) in the sidebar and open the package you want to use. The Quick Connect panel is on the package page. ## Building a connection string Quick Connect walks you through four groups of options, which match the four categories of rules under the hood: ### 1. Filtering — which nodes are eligible | Setting | What it does | | --------------------- | -------------------------------------------------------------------------------------------------------------------- | | **Country** | Limit to one country (lowercase ISO 3166-1 alpha-2, e.g. `us`, `gb`, `de`). Pick "any" for the full global pool. | | **Region / state** | Narrow to a region within the chosen country. | | **City** | Narrow further to a specific city. | | **ISP / Carrier** | Pick a specific ISP (residential) or mobile carrier. Available ISP/carrier names are listed in the package settings. | | **ASN** | Target a specific autonomous system number. | | **Zip / postal code** | Target a postal code (available on Scale and Enterprise plans). | | **Network** | Residential (`res`), mobile (`mob`), or a combination. Defaults to `res`. | The more specific your filter combination, the smaller the eligible pool. If you target a small city and a specific ISP, you may see slower allocation or `NODE_NOT_FOUND` errors. Start broad and narrow only when you need to. ### 2. Session — rotating or held | Setting | What it does | | ------------ | --------------------------------------------------------------------------- | | **Rotating** | No `session` parameter. Every request gets a new node. | | **Session** | Adds `session-{id}`. All requests with that session ID reuse the same node. | Session IDs are letters, digits, and underscores, up to 32 characters. Ephemeral sessions expire after **60 seconds of inactivity**. Rules are locked on the first request — sending the same session ID with different rules returns `409 SESSION_PARAMS_MISMATCH`. To change rules, use a new session ID. ### 3. Rotation — when the node is allowed to change Only meaningful with a session. | Setting | What it does | | ----------------- | ------------------------------------------------------------------- | | **Timed** | Rotate after N seconds. Accepts `s`, `m`, `h` suffixes. Max 1 week. | | **Request count** | Rotate after N requests. Max 1,000,000. | | **None** | Keep the same node until error, filter mismatch, or expiry. | ### 4. Error handling — what happens on node failure | Setting | What it does | | ----------------------- | --------------------------------------------------------------------------------------- | | **Replace** *(default)* | Get a new eligible node and continue. | | **Retry on same node** | `onerror-retry_N`, retries up to N times (max 10) on the current node before replacing. | | **Fail** | Return the error to your client. No retry, no replacement. | SOAX can only see infrastructure failures (node offline, connection refused, timeout). HTTP status codes from the target — 403, 429, CAPTCHA pages — are inside the HTTPS tunnel and aren't visible to SOAX. Detecting and responding to those is your application's job. See [CAPTCHA & ban rates](/troubleshooting/captcha-ban-rates). ### Routing and binding (optional) | Setting | What it does | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Prefer lookalike** | `prefer-lookalike`. On any node selection (allocation, rotation, replacement), prefer a node similar to the previous one. | | **Bind node** | `bind-node`. Lock the session to one node. Rotation is disabled; if the node fails, the request fails with `503 BOUND_NODE_FAILED` unless paired with `onerror-retry_N`. | `bind-node` requires a session and is incompatible with `rotate-timed_N`, `rotate-requests_N`, `onerror-replace`, and `prefer-lookalike`. ## Copy and test When your settings produce the connection string you want, click **Copy** to grab it. The format is: ``` {rules}:{package_key}@proxy.soax.com:1337 ``` Test it with one request: ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-session-job1:pk_abc123" https://checker.soax.com/api/ipinfo ``` A successful response includes a JSON body with the exit IP and its geolocation. If you targeted a country, the `country_code` should match. ## Worked examples **Rotating US residential, no session:** ``` country-us:pk_abc123@proxy.soax.com:1337 ``` **Session in the UK, rotate IP every 5 minutes:** ``` country-gb-session-uk1-rotate-timed_300:pk_abc123@proxy.soax.com:1337 ``` **Mobile in New York, retry 3 times on error, prefer a similar replacement:** ``` network-mob-country-us-city-new_york-session-m1-onerror-retry_3-prefer-lookalike:pk_abc123@proxy.soax.com:1337 ``` **Lock to a single node:** ``` country-us-session-locked1-bind-node:pk_abc123@proxy.soax.com:1337 ``` ## Next steps Username/password and IP Auth, end-to-end. Full rule reference for residential configuration. Full rule reference for mobile configuration. What each SOAX-level error means and how to fix it. # Usage Analytics Source: https://developers.soax.com/dashboard/usage-analytics Monitor your proxy usage and traffic patterns and spot issues before they become problems. The Usage Analytics dashboard gives you a detailed breakdown of how your proxies are being used. You can find it by clicking the bar chart icon in the left sidebar, or going to [Usage breakdown](https://platform.soax.com/usage). ## Filtering by date range Use the date picker at the top of the page to change the time window. The summary cards and chart both update to reflect whichever range you select. This is useful for comparing a specific week or day against your baseline. ## The summary cards At the top of the page, three cards give you a snapshot of activity for the selected time range. ### Total credits The total number of credits consumed in the selected period. Credits are deducted based on the volume of traffic you route through SOAX and the country tier you target. If this number is higher than expected, the usage log below will help you narrow down which package or country is driving it. ### Total traffic The total data transferred through your proxies, shown in GB. This is the raw volume your credits are spent on. A spike here is worth investigating in the chart. ### Credits/GB avg Your average credits spent per GB in the selected period. This reflects the blend of country tiers you've been targeting. A higher number means more traffic through Tier 1 countries (US, UK, Western Europe), which cost more per GB. A lower number means more Tier 2 or Tier 3 traffic. If it's trending up, check the **Country** and **Tier** filters in the usage insights to see what's shifted. ## Usage insights The chart section lets you slice your traffic data to understand exactly where consumption is coming from. ### Filters You can filter the chart and table by: * **Proxy package** — isolate traffic from a specific package if you're running multiple * **Proxy network** — filter by network type (e.g. residential) * **Tier** — see how much of your traffic is going through Tier 1, 2, or 3 countries * **Country** — drill down to a specific country * **Interval** — switch between daily, weekly, or hourly granularity Filters stack, so you can combine them — for example, residential traffic in Tier 1 countries over the last 7 days. ### Reading the chart The **Usage over time** chart shows traffic volume in GB or consumed credits (toggle with the **Gb / Credits** buttons in the top right) broken down by your chosen grouping. By default it groups by package, with each package shown as a separate colour in the stacked columns. Switch between **Columns** and **Lines** views depending on what you're trying to see: * **Columns** make it easy to compare total volume across days * **Lines** make it easier to see trends and rate of change over time Use **Group by** to pivot the view — grouping by **Country** or **Tier** is useful when you want to understand your geographic cost distribution. A sudden spike on a single day usually means a job ran with misconfigured session settings, or a retry loop sent far more requests than intended. Cross-reference the date in the usage log to find the package and egress IP involved. ## Usage log Below the chart, the usage log gives you a row-by-row record of traffic activity. Each row represents an aggregated record for a specific combination of package, network, tier, country, egress IP, and domain as well as month / date / hour. The columns are: | Column | What it tells you | | ------------- | --------------------------------------------------------------- | | **Date** | When the traffic occurred | | **Package** | Which proxy package was used (shown as a UUID) | | **Network** | The proxy network type (e.g. residential) | | **Tier** | The country tier (1, 2, or 3) — this determines the per-GB rate | | **Country** | The country code of the exit node used | | **Egress IP** | The specific proxy IP the traffic exited through | | **Domain** | The target domain that was requested | | **Traffic** | Data transferred for that record, in GB | ### Exporting data Click **Export CSV** in the top right of the usage log to download the full log for the current filter and date range. This is useful for reconciling usage against your own application logs, or sharing consumption data with your team. Use **All columns selected** to choose which columns to include in the export before downloading. ## How to spot issues A few patterns worth knowing: **Unexpectedly high traffic on a single day** — check the usage log filtered to that date. Look for a high-traffic egress IP or a domain you don't recognise. This often points to a misconfigured retry loop or a job that ran longer than expected. **Credits/GB avg is higher than your plan rate** — this means you're routing traffic through more expensive country tiers than you intended. Filter the chart by **Tier** to confirm, then check whether your proxy configuration is targeting the right countries. **Traffic spread across many egress IPs to the same domain** — this is normal for rotating proxy sessions. If you're seeing it on a session-based package, it may indicate your session duration is too short and connections are cycling more often than expected. **No traffic showing for a package** — if a package shows zero usage when you expect some, check that the package is active in **Package Management** and that your credentials are correct. Authentication failures don't generate usage records. # Advanced: Multi-Geo Monitoring in Go Source: https://developers.soax.com/examples/advanced/multi-geo-go Production-grade Go example that monitors a URL across multiple countries in parallel using SOAX residential proxies with a worker pool. This example demonstrates a real-world pattern: checking the same URL from multiple countries in parallel, collecting structured results, and handling failures gracefully. It's designed as a starting point for price monitoring, ad verification, or geo-targeted data collection. The program uses a worker pool where each worker gets its own session and country assignment. Workers retry on failure with exponential backoff, and the entire job can be cancelled with a context timeout. ## What this example covers * Configurable worker pool with controlled concurrency * One session per country (consistent IP per geo) * Structured result collection across all workers * Exponential backoff retry on failure * Context-based timeout and graceful shutdown * Clean separation between configuration, execution, and output ## Full code ```go theme={null} package main import ( "context" "encoding/json" "fmt" "io" "math" "net/http" "net/url" "os" "sync" "time" ) // Config holds the job configuration. type Config struct { PackageKey string TargetURL string Countries []string MaxConcurrency int MaxRetries int Timeout time.Duration } // ProxyResult holds the outcome of a single geo-targeted request. type ProxyResult struct { Country string `json:"country"` IP string `json:"ip,omitempty"` ISP string `json:"isp,omitempty"` City string `json:"city,omitempty"` StatusCode int `json:"status_code"` BodySize int `json:"body_size"` Duration time.Duration `json:"duration"` Attempts int `json:"attempts"` Error string `json:"error,omitempty"` } // CheckerResponse matches the SOAX checker JSON structure. type CheckerResponse struct { Status bool `json:"status"` Data struct { IP string `json:"ip"` CountryCode string `json:"country_code"` CountryName string `json:"country_name"` Region string `json:"region"` City string `json:"city"` ISP string `json:"isp"` Carrier string `json:"carrier"` } `json:"data"` } func main() { cfg := Config{ PackageKey: os.Getenv("SOAX_PACKAGE_KEY"), TargetURL: "https://checker.soax.com/api/ipinfo", Countries: []string{ "us", "gb", "de", "fr", "jp", "br", "au", "ca", "in", "kr", }, MaxConcurrency: 5, MaxRetries: 3, Timeout: 30 * time.Second, } if cfg.PackageKey == "" { fmt.Println("Set SOAX_PACKAGE_KEY environment variable") os.Exit(1) } ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout) defer cancel() results := runJob(ctx, cfg) output, _ := json.MarshalIndent(results, "", " ") fmt.Println(string(output)) // Summary succeeded := 0 for _, r := range results { if r.Error == "" { succeeded++ } } fmt.Printf("\n%d/%d countries succeeded\n", succeeded, len(results)) } // runJob dispatches workers and collects results. func runJob(ctx context.Context, cfg Config) []ProxyResult { jobs := make(chan string, len(cfg.Countries)) results := make(chan ProxyResult, len(cfg.Countries)) // Start worker pool var wg sync.WaitGroup for i := 0; i < cfg.MaxConcurrency; i++ { wg.Add(1) go func(workerID int) { defer wg.Done() worker(ctx, workerID, cfg, jobs, results) }(i) } // Send jobs for _, country := range cfg.Countries { jobs <- country } close(jobs) // Wait for all workers to finish, then close results go func() { wg.Wait() close(results) }() // Collect results var collected []ProxyResult for r := range results { collected = append(collected, r) } return collected } // worker pulls countries from the jobs channel and fetches each one. func worker(ctx context.Context, id int, cfg Config, jobs <-chan string, results chan<- ProxyResult) { for country := range jobs { select { case <-ctx.Done(): results <- ProxyResult{ Country: country, Error: "cancelled: timeout exceeded", } return default: result := fetchWithRetry(ctx, cfg, country) results <- result } } } // fetchWithRetry attempts the request up to MaxRetries with exponential backoff. func fetchWithRetry(ctx context.Context, cfg Config, country string) ProxyResult { var lastErr error for attempt := 1; attempt <= cfg.MaxRetries; attempt++ { result, err := fetchCountry(ctx, cfg, country) if err == nil { result.Attempts = attempt return result } lastErr = err // Don't sleep after the last attempt if attempt < cfg.MaxRetries { backoff := time.Duration(math.Pow(2, float64(attempt-1))) * 500 * time.Millisecond select { case <-ctx.Done(): return ProxyResult{ Country: country, Attempts: attempt, Error: "cancelled: timeout exceeded", } case <-time.After(backoff): // continue to next attempt } } } return ProxyResult{ Country: country, Attempts: cfg.MaxRetries, Error: lastErr.Error(), } } // fetchCountry makes a single proxied request for a given country. func fetchCountry(ctx context.Context, cfg Config, country string) (ProxyResult, error) { sessionID := fmt.Sprintf("geo_%s", country) proxyStr := fmt.Sprintf("http://country-%s-session-%s:%s@proxy.soax.com:1337", country, sessionID, cfg.PackageKey) proxyURL, err := url.Parse(proxyStr) if err != nil { return ProxyResult{}, fmt.Errorf("invalid proxy URL: %w", err) } client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: 10 * time.Second, } req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfg.TargetURL, nil) if err != nil { return ProxyResult{}, fmt.Errorf("request creation failed: %w", err) } start := time.Now() resp, err := client.Do(req) duration := time.Since(start) if err != nil { return ProxyResult{}, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return ProxyResult{}, fmt.Errorf("body read failed: %w", err) } result := ProxyResult{ Country: country, StatusCode: resp.StatusCode, BodySize: len(body), Duration: duration, } // Parse checker response to extract IP metadata var checker CheckerResponse if json.Unmarshal(body, &checker) == nil && checker.Status { result.IP = checker.Data.IP result.ISP = checker.Data.ISP result.City = checker.Data.City } return result, nil } ``` ## How to run it Set your package key as an environment variable: ```bash theme={null} export SOAX_PACKAGE_KEY="abc123" go run main.go ``` ## Example output ```json theme={null} [ { "country": "us", "ip": "104.28.55.12", "isp": "Comcast", "city": "Denver", "status_code": 200, "body_size": 203, "duration": 842000000, "attempts": 1 }, { "country": "gb", "ip": "86.12.145.78", "isp": "BT", "city": "Manchester", "status_code": 200, "body_size": 198, "duration": 1103000000, "attempts": 1 }, { "country": "de", "ip": "91.64.33.210", "isp": "Deutsche Telekom", "city": "Berlin", "status_code": 200, "body_size": 201, "duration": 967000000, "attempts": 1 } ] 10/10 countries succeeded ``` ## How it works **Worker pool.** The `MaxConcurrency` setting controls how many countries are checked at the same time. With 10 countries and 5 workers, the job runs in two waves. Increase concurrency to run all countries simultaneously, or decrease it to stay within your package's connection limits. **One session per country.** Each country gets a session ID (`geo_us`, `geo_gb`, etc.). This means the same IP is reused if you run the job multiple times within the session's 60-second inactivity window. For monitoring jobs that run on a schedule, this gives you consistent IPs across runs. **Exponential backoff.** If a request fails, the worker waits before retrying: 500ms after the first failure, 1s after the second, 2s after the third. This avoids hammering the proxy during transient issues. **Context timeout.** The entire job has a 30-second deadline. If any worker is still running when the timeout hits, it gets cancelled and returns a clear error. This prevents hung requests from blocking your pipeline. **Structured results.** Every result includes the country, exit IP, ISP, response timing, retry count, and any errors. This makes it easy to pipe into a database, alerting system, or dashboard. ## Adapting this for your use case **Change the target URL.** Replace `checker.soax.com/api/ipinfo` with the URL you're monitoring. The result struct will need updating to match your target's response format. **Add more countries.** The `Countries` slice controls which geos are checked. Add or remove country codes as needed. **Switch to mobile.** Change the proxy string to include `network-mob`: ```go theme={null} proxyStr := fmt.Sprintf("http://network-mob-country-%s-session-%s:%s@proxy.soax.com:1337", country, sessionID, cfg.PackageKey) ``` **Add rotation.** If you want a fresh IP on every run instead of reusing sessions, remove the `session` parameter from the proxy string, or append a timestamp to the session ID to force a new session each time: ```go theme={null} sessionID := fmt.Sprintf("geo_%s_%d", country, time.Now().Unix()) ``` **Write results to a file.** Replace the `fmt.Println` at the end with a file write for scheduled jobs: ```go theme={null} os.WriteFile("results.json", output, 0644) ``` ## Next steps Full parameter reference for session, rotation, and error handling options. Simpler Go examples for getting started. Understand what errors mean and how to handle them in your code. Troubleshoot timeouts, auth failures, and geo mismatches. # curl Source: https://developers.soax.com/examples/curl Working curl commands for SOAX residential and mobile proxies. Covers rotating and sessions with copy-paste examples. curl is the quickest way to test SOAX from your terminal. Every example on this page is a single command you can copy, paste, and run immediately. Replace `YOUR_PACKAGE_KEY` with your actual package key from the [dashboard](https://platform.soax.com). ## Residential proxies ### Rotating (new IP every request) ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` Run this multiple times and you'll get a different IP each time. ### Target a specific city ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-city-new_york:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ### Session (same IP across requests) ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-session-mysession1:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` Run this multiple times with the same session ID and you'll see the same IP, as long as the session is active. Ephemeral sessions expire after 60 seconds of inactivity. ### Session with timed rotation Rotate to a new IP every 5 minutes: ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-session-job1-rotate-timed_300:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ### Session with request-based rotation Rotate to a new IP every 10 requests: ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-session-job1-rotate-requests_10:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ### Session with error handling Retry 3 times on the same node before replacing: ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-session-job1-onerror-retry_3:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` Replace with a similar node on error: ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-session-job1-onerror-replace-prefer-lookalike:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` Fail immediately on error (no retry, no replacement): ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us-session-job1-onerror-fail:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ## Mobile proxies Mobile proxies use the same format. Add `network-mob` to your parameters. ### Rotating ```bash theme={null} curl -x proxy.soax.com:1337 -U "network-mob-country-us:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ### Session ```bash theme={null} curl -x proxy.soax.com:1337 -U "network-mob-country-us-session-mobile1:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ### Target a specific carrier ```bash theme={null} curl -x proxy.soax.com:1337 -U "network-mob-country-us-isp-verizon_wireless-session-vz1:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ### Mobile session with timed rotation ```bash theme={null} curl -x proxy.soax.com:1337 -U "network-mob-country-us-session-m1-rotate-timed_600:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ## IP Auth (HTTPS) If you've added your IP to the allowlist in the dashboard, you can connect without credentials. Rules go in the subdomain instead. ```bash theme={null} curl --proxy "https://country-us.proxy.soax.com:1337" https://checker.soax.com/api/ipinfo ``` With a session: ```bash theme={null} curl --proxy "https://country-us-session-job1.proxy.soax.com:1337" https://checker.soax.com/api/ipinfo ``` IP Auth via subdomain is limited to 63 characters (the DNS label limit). For complex rule combinations, use the [parameter shortcuts](/proxies/residential#parameter-shortcuts) (e.g. `c-us-ci-new_york-s-job1`) or switch to username/password authentication. ## SOCKS5 To connect via SOCKS5 instead of HTTP, use `--socks5`: ```bash theme={null} curl --socks5 proxy.soax.com:1337 --proxy-user "country-us:YOUR_PACKAGE_KEY" https://checker.soax.com/api/ipinfo ``` ## Expected response All examples above return a JSON response from the SOAX checker: ```json theme={null} { "status": true, "data": { "ip": "185.123.45.67", "country_code": "US", "country_name": "United States", "region": "California", "city": "Los Angeles", "isp": "Spectrum", "carrier": "" } } ``` The `ip` should be different from your real IP. If you targeted a specific country, the `country_code` should match. ## Next steps Working Python code for SOAX proxies. Working Node.js code for SOAX proxies. What to do when something goes wrong. Full parameter reference. # Go Source: https://developers.soax.com/examples/go Working Go code for SOAX residential and mobile proxies. Copy-paste examples using net/http. These examples use Go's standard `net/http` and `net/url` packages. No external dependencies needed. Replace `YOUR_PACKAGE_KEY` with your actual package key from the [dashboard](https://platform.soax.com). ## Residential proxies ### Rotating (new IP every request) ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://country-us:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` Each time you run this, you'll get a different IP. ### Session (same IP across requests) ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://country-us-session-go1:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } for i := 0; i < 3; i++ { resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) continue } body, _ := io.ReadAll(resp.Body) resp.Body.Close() fmt.Printf("Request %d: %s\n", i+1, string(body)) } } ``` All three requests will return the same IP. ### Session with timed rotation Rotate to a new IP every 5 minutes: ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://country-us-session-job1-rotate-timed_300:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ### Target a specific city and ISP ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://country-us-city-los_angeles-isp-comcast:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ### Session with error retry and lookalike replacement ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://country-us-session-scrape1-onerror-retry_3-prefer-lookalike:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ## Mobile proxies Add `network-mob` to the proxy URL. Everything else works the same way. ### Rotating ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://network-mob-country-us:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ### Session ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://network-mob-country-us-session-mobile1:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } for i := 0; i < 3; i++ { resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) continue } body, _ := io.ReadAll(resp.Body) resp.Body.Close() fmt.Printf("Request %d: %s\n", i+1, string(body)) } } ``` ### Target a specific carrier ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" ) func main() { proxyURL, _ := url.Parse("http://network-mob-country-us-isp-verizon_wireless-session-vz1:YOUR_PACKAGE_KEY@proxy.soax.com:1337") client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ## Multiple concurrent sessions Run several sessions in parallel using goroutines: ```go theme={null} package main import ( "fmt" "io" "net/http" "net/url" "sync" ) type target struct { session string country string } func main() { targets := []target{ {session: "target_a", country: "us"}, {session: "target_b", country: "gb"}, {session: "target_c", country: "de"}, } var wg sync.WaitGroup for _, t := range targets { wg.Add(1) go func(t target) { defer wg.Done() proxyStr := fmt.Sprintf("http://country-%s-session-%s:YOUR_PACKAGE_KEY@proxy.soax.com:1337", t.country, t.session) proxyURL, _ := url.Parse(proxyStr) client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, } resp, err := client.Get("https://checker.soax.com/api/ipinfo") if err != nil { fmt.Printf("%s: error: %s\n", t.session, err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Printf("%s: %s\n", t.session, string(body)) }(t) } wg.Wait() } ``` ## Expected response All examples return a JSON response from the SOAX checker: ```json theme={null} { "status": true, "data": { "ip": "185.123.45.67", "country_code": "US", "country_name": "United States", "region": "California", "city": "Los Angeles", "isp": "Spectrum", "carrier": "" } } ``` ## Next steps Working Python code for SOAX proxies. Working Node.js code for SOAX proxies. Full parameter reference. What to do when something goes wrong. # Node.js Source: https://developers.soax.com/examples/nodejs Working Node.js code for SOAX residential and mobile proxies. Copy-paste examples using axios and node-fetch. These examples use [axios](https://axios-http.com/) with [https-proxy-agent](https://www.npmjs.com/package/https-proxy-agent). Replace `YOUR_PACKAGE_KEY` with your actual package key from the [dashboard](https://platform.soax.com). Install the dependencies: ```bash theme={null} npm install axios https-proxy-agent ``` ## Residential proxies ### Rotating (new IP every request) ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://country-us:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }) .then(res => console.log(res.data)) .catch(err => console.error(err.message)); ``` Each time you run this, you'll get a different IP. ### Session (same IP across requests) ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://country-us-session-node1:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); async function run() { for (let i = 0; i < 3; i++) { const res = await axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }); const data = res.data.data; console.log(`Request ${i + 1}: ${data.ip} (${data.city}, ${data.country_code})`); } } run(); ``` All three requests will return the same IP. ### Session with timed rotation Rotate to a new IP every 5 minutes: ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://country-us-session-job1-rotate-timed_300:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }) .then(res => console.log(res.data)) .catch(err => console.error(err.message)); ``` ### Target a specific city and ISP ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://country-us-city-new_york-isp-comcast:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }) .then(res => console.log(res.data)) .catch(err => console.error(err.message)); ``` ### Session with error retry and lookalike replacement ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://country-us-session-scrape1-onerror-retry_3-prefer-lookalike:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }) .then(res => console.log(res.data)) .catch(err => console.error(err.message)); ``` ## Mobile proxies Add `network-mob` to the proxy URL. Everything else works the same way. ### Rotating ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://network-mob-country-us:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }) .then(res => console.log(res.data)) .catch(err => console.error(err.message)); ``` ### Session ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://network-mob-country-us-session-mobile1:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); async function run() { for (let i = 0; i < 3; i++) { const res = await axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }); const data = res.data.data; console.log(`Request ${i + 1}: ${data.ip} (${data.carrier})`); } } run(); ``` ### Target a specific carrier ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://network-mob-country-us-isp-t_mobile-session-tm1:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }) .then(res => console.log(res.data)) .catch(err => console.error(err.message)); ``` ## Multiple concurrent sessions Run several sessions in parallel with unique session IDs: ```javascript theme={null} const axios = require("axios"); const { HttpsProxyAgent } = require("https-proxy-agent"); const targets = [ { session: "target_a", country: "us" }, { session: "target_b", country: "gb" }, { session: "target_c", country: "de" }, ]; async function fetch(target) { const agent = new HttpsProxyAgent( `http://country-${target.country}-session-${target.session}:YOUR_PACKAGE_KEY@proxy.soax.com:1337` ); const res = await axios.get("https://checker.soax.com/api/ipinfo", { httpsAgent: agent }); const data = res.data.data; return `${target.session}: ${data.ip} (${data.country_code})`; } Promise.all(targets.map(fetch)).then(results => results.forEach(r => console.log(r))); ``` ## Using node-fetch If you prefer node-fetch over axios: ```bash theme={null} npm install node-fetch https-proxy-agent ``` ```javascript theme={null} const fetch = require("node-fetch"); const { HttpsProxyAgent } = require("https-proxy-agent"); const agent = new HttpsProxyAgent("http://country-us:YOUR_PACKAGE_KEY@proxy.soax.com:1337"); fetch("https://checker.soax.com/api/ipinfo", { agent }) .then(res => res.json()) .then(data => console.log(data)) .catch(err => console.error(err.message)); ``` ## Expected response All examples return a JSON response from the SOAX checker: ```json theme={null} { "status": true, "data": { "ip": "185.123.45.67", "country_code": "US", "country_name": "United States", "region": "California", "city": "Los Angeles", "isp": "Spectrum", "carrier": "" } } ``` ## Next steps Working Python code for SOAX proxies. Working Go code for SOAX proxies. Full parameter reference. What to do when something goes wrong. # Python Source: https://developers.soax.com/examples/python Working Python code for SOAX residential and mobile proxies. Copy-paste examples using the requests library. These examples use the [requests](https://docs.python-requests.org/) library. Replace `YOUR_PACKAGE_KEY` with your actual package key from the [dashboard](https://platform.soax.com). Install requests if you haven't already: ```bash theme={null} pip install requests ``` ## Residential proxies ### Rotating (new IP every request) ```python theme={null} import requests proxy_url = "http://country-us:YOUR_PACKAGE_KEY@proxy.soax.com:1337" response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) print(response.json()) ``` Each time you run this, you'll get a different IP. ### Session (same IP across requests) ```python theme={null} import requests proxy_url = "http://country-us-session-python1:YOUR_PACKAGE_KEY@proxy.soax.com:1337" for i in range(3): response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) data = response.json()["data"] print(f"Request {i + 1}: {data['ip']} ({data['city']}, {data['country_code']})") ``` All three requests will return the same IP. ### Session with timed rotation Rotate to a new IP every 5 minutes: ```python theme={null} import requests proxy_url = "http://country-us-session-job1-rotate-timed_300:YOUR_PACKAGE_KEY@proxy.soax.com:1337" response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) print(response.json()) ``` ### Target a specific city and ISP ```python theme={null} import requests proxy_url = "http://country-us-city-los_angeles-isp-comcast:YOUR_PACKAGE_KEY@proxy.soax.com:1337" response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) print(response.json()) ``` ### Session with error retry and lookalike replacement ```python theme={null} import requests proxy_url = "http://country-us-session-scrape1-onerror-retry_3-prefer-lookalike:YOUR_PACKAGE_KEY@proxy.soax.com:1337" response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) print(response.json()) ``` ## Mobile proxies Add `network-mob` to the proxy URL. Everything else works the same way. ### Rotating ```python theme={null} import requests proxy_url = "http://network-mob-country-us:YOUR_PACKAGE_KEY@proxy.soax.com:1337" response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) print(response.json()) ``` ### Session ```python theme={null} import requests proxy_url = "http://network-mob-country-us-session-mobile1:YOUR_PACKAGE_KEY@proxy.soax.com:1337" for i in range(3): response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) data = response.json()["data"] print(f"Request {i + 1}: {data['ip']} ({data['carrier']})") ``` ### Target a specific carrier ```python theme={null} import requests proxy_url = "http://network-mob-country-us-isp-t_mobile-session-tm1:YOUR_PACKAGE_KEY@proxy.soax.com:1337" response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) print(response.json()) ``` ## Multiple concurrent sessions If you need to run several sessions in parallel (for example, scraping different targets), give each one a unique session ID: ```python theme={null} import requests from concurrent.futures import ThreadPoolExecutor targets = [ {"session": "target_a", "country": "us"}, {"session": "target_b", "country": "gb"}, {"session": "target_c", "country": "de"}, ] def fetch(target): proxy_url = f"http://country-{target['country']}-session-{target['session']}:YOUR_PACKAGE_KEY@proxy.soax.com:1337" response = requests.get( "https://checker.soax.com/api/ipinfo", proxies={"http": proxy_url, "https": proxy_url}, ) data = response.json()["data"] return f"{target['session']}: {data['ip']} ({data['country_code']})" with ThreadPoolExecutor(max_workers=3) as pool: for result in pool.map(fetch, targets): print(result) ``` ## Expected response All examples return a JSON response from the SOAX checker: ```json theme={null} { "status": true, "data": { "ip": "185.123.45.67", "country_code": "US", "country_name": "United States", "region": "California", "city": "Los Angeles", "isp": "Spectrum", "carrier": "" } } ``` ## Next steps Working Node.js code for SOAX proxies. Working Go code for SOAX proxies. Full parameter reference. What to do when something goes wrong. # Authentication Source: https://developers.soax.com/getting-started/authentication How to authenticate with SOAX proxies using username/password or IP Auth. Includes connection string format, where to find credentials, and working examples. SOAX supports two ways to authenticate your proxy requests: **username/password** and **IP Auth**. Both are configured per proxy package in the dashboard. ## Where to find your credentials Each proxy package has its own credentials. To find them: 1. Log in to the [dashboard](https://platform.soax.com). 2. Open **Quick Connect** or go to your proxy package settings. 3. Your **package key** (password) is displayed there. Copy it exactly as shown. Package keys look like `pk_abc123…`. You can regenerate your package key at any time from the dashboard. This immediately invalidates the old key, so make sure to update it everywhere you use it before regenerating. ## Username/password authentication This is the most common way to connect. You send your credentials with every request as part of the proxy connection string. The format is: ``` {rules}:{package_key}@proxy.soax.com:1337 ``` The **username** field carries your rules (country, session, network type, rotation, error handling). The **password** field is your package key. They are separated by a colon. Here's a basic example that routes through a US residential proxy: ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-us:pk_abc123" https://checker.soax.com/api/ipinfo ``` You should get a response like this: ```json theme={null} { "status": true, "data": { "ip": "185.123.45.67", "country_code": "US", "country_name": "United States", "region": "California", "city": "Los Angeles", "isp": "Spectrum", "carrier": "" } } ``` You need at least one targeting rule in the username. If you want the full global pool without any geographic filter, use `country-any`: ```bash theme={null} curl -x proxy.soax.com:1337 -U "country-any:pk_abc123" https://checker.soax.com/api/ipinfo ``` ### Adding more rules You can chain multiple rules in the username field using hyphens as delimiters. Multi-word values use underscores instead of hyphens (the hyphen is the rule delimiter). Here are some examples: **Target a specific country and city:** ``` country-us-city-new_york:pk_abc123@proxy.soax.com:1337 ``` **Use mobile network in a specific region:** ``` network-mob-country-us-region-california:pk_abc123@proxy.soax.com:1337 ``` **Add a session to keep the same IP across requests:** ``` country-us-session-job42:pk_abc123@proxy.soax.com:1337 ``` Rules can appear in any order. The full list is covered in the [Residential proxies](/proxies/residential) and [Mobile proxies](/proxies/mobile) reference pages. ### Supported protocols Username/password authentication works with all proxy protocols on the same gateway port: | Protocol | Supported | Port | | -------- | --------- | ---- | | HTTP | Yes | 1337 | | HTTPS | Yes | 1337 | | SOCKS5 | Yes | 1337 | ## IP Auth If you don't want to send credentials with every request, you can authenticate by IP instead. Once your client IP is on the package's allowlist, SOAX authenticates based on where the request comes from. ### Setting up IP Auth 1. In the dashboard, open your proxy package settings. 2. Find the **IP Allowlist** section. 3. Add the public IP address of the machine that will send requests. Once added, you can connect without sending a username and password. With Quick Connect, the rules go in the subdomain instead. ### Quick Connect with IP Auth (HTTPS only) ``` https://{rules}.proxy.soax.com:1337 ``` For example, to route through a US proxy: ```bash theme={null} curl --proxy "https://country-us.proxy.soax.com:1337" https://checker.soax.com/api/ipinfo ``` This works at the start of the TLS handshake (SNI), which is why it's HTTPS-only — there is no equivalent field for SOAX to read with IP Auth on plain HTTP or SOCKS5. Subdomains are subject to a 63-character DNS label limit (RFC 1035). If your rule string is long (combining country, city, ISP, session, and rotation rules), you may hit this limit. Use the [parameter shortcuts](/proxies/residential#parameter-shortcuts) (e.g. `c-us-ci-new_york-s-job1` instead of `country-us-city-new_york-session-job1`) to keep it short, or switch to username/password authentication, which has no rule-length limit. ### IP Auth over HTTP and SOCKS5 There is no way to pass runtime rules with IP Auth on plain HTTP or SOCKS5. HTTP has no username field and no SNI, and SOCKS5 has no username field under IP Auth. IP Auth carries rules over HTTPS only, via the subdomain (SNI), as shown above. If you need runtime rules on HTTP or SOCKS5, use **username/password** authentication instead — the rules go in the username field, and all three protocols support it. ### Protocol support summary | Auth method | HTTP | HTTPS | SOCKS5 | | ---------------------------- | ---- | ----- | ------ | | Username / password | Yes | Yes | Yes | | IP Auth (rules in subdomain) | No | Yes | No | ## Which method should I use? For most use cases, **username/password** is the simplest option. It works with all protocols, has no rule-length limits, and doesn't need any dashboard setup beyond copying your package key. **IP Auth** is useful when your tool or integration doesn't support proxy credentials (some browser automation tools and legacy HTTP clients fall into this category), or when you want to avoid embedding credentials in configuration files. You can use both methods on the same package. Adding an IP to the allowlist doesn't disable username/password authentication. ## Errors If authentication fails, the proxy returns an error with two response headers: `X-SOAX-Error` (a machine-readable code) and `X-SOAX-Detail` (a plain explanation). The common ones: | HTTP | X-SOAX-Error | Meaning | | ---- | ------------------- | ------------------------------------- | | 407 | `AUTH_FAILED` | Invalid or missing package key | | 407 | `IP_NOT_ALLOWED` | Your client IP isn't on the allowlist | | 407 | `PACKAGE_SUSPENDED` | Account suspended or disabled | See [Error codes](/troubleshooting/error-codes) for the full list. ## Next steps Full rule reference for residential proxy configuration. Full rule reference for mobile proxy configuration. Generate connection strings from the dashboard without memorizing the format. What to do if you get a 407 AUTH\_FAILED or IP\_NOT\_ALLOWED error. # Choosing the Right Proxy Type Source: https://developers.soax.com/getting-started/choosing-proxy-type Understand the difference between residential and mobile proxies, when to use each, and how to configure them on your package. SOAX offers two network types for proxy traffic: **residential (WiFi)** and **mobile**. Both are included in every plan at the same credit rate, and both are enabled by default on new proxy packages. You don't need to pick one or the other upfront. You can use both at the same time, or disable one on a per-package basis if you want to control which network type your team uses. This page helps you understand when each network type works best. ## Residential (WiFi) Residential proxies route your traffic through real home internet connections. The IPs belong to genuine ISP subscribers, so websites see them as regular household traffic. SOAX's residential pool includes over 155 million IPs across 195+ countries. **When to use residential proxies:** * **Web scraping at scale.** Most anti-bot systems treat residential IPs as legitimate traffic. This gives you higher success rates on difficult targets like search engines, ecommerce platforms, and social media sites. * **Price monitoring.** Residential IPs let you check pricing from specific countries and cities without triggering geo-based blocking or CAPTCHA walls. * **Ad verification.** You can view ads as a real user would in any location, making it easier to detect fraud, misplacement, or geo-targeting issues. * **SEO monitoring.** Track search rankings from different locations using IPs that search engines treat as genuine users. * **General-purpose data collection.** If you're not sure which network type to use, start with residential. It covers the widest range of use cases. ## Mobile Mobile proxies route your traffic through real mobile carrier connections on 3G, 4G, and 5G networks. The IPs come from carriers like Verizon, AT\&T, Deutsche Telekom, and others worldwide. SOAX's mobile pool includes over 30 million IPs from carriers globally. **When to use mobile proxies:** * **Mobile-specific content.** Some websites and apps serve different content to mobile users. Mobile proxies let you see exactly what a mobile visitor would see. * **App testing and verification.** If you're verifying how your app or mobile ads appear across carriers and regions, mobile IPs give you an authentic testing environment. * **Highly protected targets.** Some websites that aggressively block residential IPs are more lenient with mobile traffic, because mobile carrier IPs are shared across many real users by design. This makes them harder to block without affecting legitimate visitors. * **Social media platforms.** Mobile IPs are common for social media access, so they tend to blend in well on platforms that scrutinize connection types. ## How they compare | | Residential (WiFi) | Mobile | | ------------------ | ---------------------------------------------------- | ------------------------------------------------------ | | IP source | Home ISP connections | Mobile carrier connections (3G/4G/5G) | | Pool size | 155M+ IPs | 30M+ IPs | | Best for | Web scraping, price monitoring, SEO, ad verification | Mobile content, app testing, heavily protected targets | | Detection risk | Low | Very low | | Credit cost | Same per tier | Same per tier | | Geo-targeting | Country, region, city, ISP, ASN, zip | Country, region, city, carrier (via `isp`), ASN, zip | | Enabled by default | Yes | Yes | ## You don't have to choose Both network types share the same credit pool and burn at the same rate per geographic tier. There's no price difference between residential and mobile traffic in the same country. When you create a proxy package, both are enabled by default. If you want to restrict a package to only residential or only mobile traffic, you can change this in the package settings. This is useful when you want to isolate use cases, for example giving your scraping team residential-only access and your mobile QA team mobile-only access. For details on setting up and configuring packages, see [Package Management](/dashboard/package-management). ## Other network types SOAX also offers datacenter and ISP proxies for specific use cases. These use different IP sources (data center infrastructure and static ISP-assigned IPs respectively) and are suited to tasks where speed or IP consistency matters more than appearing as a typical home or mobile user. ## Next steps Set up your credentials and start connecting. Full reference for residential proxy configuration and parameters. Full reference for mobile proxy configuration and parameters. Make your first request in under 5 minutes. # Core Concepts Source: https://developers.soax.com/getting-started/core-concepts How SOAX's proxy system is built. Understand packages, sessions, rules, and bindings so you can get the most out of the platform. Most proxy providers give you an IP and hope it works. SOAX works differently. We built a system where you describe what you need (where, how, for how long, what happens when something breaks) and the platform handles the rest. You don't manage IPs directly. You set rules, and the system executes them predictably, even at high concurrency and at scale. This page explains the building blocks that make this work: **packages**, **sessions**, **rules**, and **bindings**. Understanding these concepts will help you get more out of SOAX than just rotating through IPs. ## Packages A **proxy package** is your entry point to the proxy network. It's where credentials, limits, and access controls live. When you create a package in the dashboard, you're setting up a self-contained unit of proxy access. Each package has its own: * **Package key** for authentication * **Network types** (residential, mobile, or both) * **Traffic limits** (optional, set by the organization owner) * **RPS and connection limits** controlling throughput * **IP allowlist** for IP-based authentication * **Assigned users** who can use the package Packages exist so you can separate and control how different teams or use cases consume proxy traffic. For example, you might create one package for your scraping pipeline with residential-only access and a 500GB limit, and another for your QA team with mobile enabled and no limit. All usage is tracked per package, which means you can see exactly how much traffic each use case or team is generating. Credits are consumed from your organization's shared pool, but packages control who can spend them and how. For more on managing packages, see [Package Management](/dashboard/package-management). ## Sessions A **session** groups related requests and gives them a shared context. Without a session, every request is independent. The system picks a new node each time, with no memory of what happened before. This is rotating mode, and it's the right choice for high-volume work where you don't need IP continuity. But many workflows need continuity. You might need to log in, navigate to a page, and then scrape it. Or you might need to maintain the same IP for a few minutes while you collect data from a multi-page flow. That's what sessions are for. When you create a session (by adding a `session` parameter to your connection string), the system binds your session to a node and keeps using that node for subsequent requests. All the rules you set apply consistently across every request in that session. Changes to the node only happen when your rules allow it. ``` country-us-session-job1:YOUR_PACKAGE_KEY@proxy.soax.com:1337 ``` You pick the session ID (letters, digits, and underscores, up to 32 characters). The rules are set on the first request and apply for the lifetime of the session — if you send the same session ID later with different rules, the request is rejected with `409 SESSION_PARAMS_MISMATCH`. To change rules, use a new session ID. Sessions expire after 60 seconds of inactivity. The key principle: sessions give you controlled continuity. You're not locked to a random IP and hoping it stays up. You're telling the system what behavior you need, and it maintains that behavior across requests. ## Rules **Rules** are what set SOAX apart from most proxy providers. Instead of giving you a few toggles (country, rotation time), SOAX uses a composable rules system with four independent categories. Each category answers a different question about how your requests should be handled: ### Filtering rules: which nodes can be used? Filtering rules constrain the pool of eligible nodes based on their characteristics. You can filter by country, region, city, ISP, ASN, zip code, or network type. These filters are enforced continuously. If you're in a session and the bound node stops matching your filters (for example, the node's IP is reclassified from US to JP), the binding becomes invalid and the system replaces it. ``` country-us-city-new_york-isp-comcast ``` ### Routing rules: how does the system choose between eligible nodes? Once the system knows which nodes are eligible, routing rules influence which one it picks. The available option is `prefer-lookalike`, which tells the system to favor nodes with similar characteristics to the previous one when it needs to select a replacement. This matters when your node gets replaced (due to rotation, error, or reclassification). Without `prefer-lookalike`, you might jump from a Comcast IP in New York to a Verizon IP in Texas. With it, the system tries to find another Comcast IP in New York first. ``` prefer-lookalike ``` ### Rotation rules: when is the system allowed to change the node? Rotation rules control when the current node can be replaced during a session. You can rotate after a set number of seconds (`rotate-timed_N`, max 1 week, accepts `s`/`m`/`h` suffixes) or after a set number of requests (`rotate-requests_N`, max 1,000,000). Without a rotation rule, a session keeps the same node until it encounters an error or its filters stop matching. ``` rotate-timed_300 rotate-requests_50 ``` ### Error handling rules: what happens when something goes wrong? Error handling rules define how the system reacts to infrastructure failures (node goes offline, connection refused, timeout). You have three options: replace the node with a new one (default), retry the request on the same node a set number of times before replacing (max 10), or fail immediately and return the error to your client. ``` onerror-replace onerror-retry_3 onerror-fail ``` SOAX works at the transport layer, so the failures it can act on are infrastructure failures — node offline, connection refused, timeout. It can't see HTTP status codes or soft blocks inside an HTTPS tunnel, so target-side responses (403, 429, CAPTCHA pages) are your application's problem to detect and handle. ### Why this matters These four categories are independent. You can combine any filtering rule with any routing preference, any rotation trigger, and any error handling strategy. They compose together in a single connection string: ``` country-us-city-new_york-session-job1-rotate-timed_300-onerror-retry_3-prefer-lookalike ``` That string says: use a US node in New York, maintain a session called "job1", rotate to a new node every 300 seconds, retry 3 times on the same node before replacing on error, and when you do replace, prefer a node with similar characteristics. Most proxy providers make you choose between rotating and session-based connections and give you a country dropdown. SOAX lets you describe exact behavior for every scenario your requests might encounter. The system then executes that behavior deterministically, whether you're sending 10 requests or 10 million. ## Bindings A **binding** is the link between a session and a node. When your session's first request is processed, the system selects a node that matches your filtering rules and creates a binding. That node handles all subsequent requests in the session until something causes the binding to change. A binding can change when: * A rotation rule triggers (timed or request-count) * The node fails and error handling replaces it * The node no longer matches your filtering rules When a binding changes, the system selects a new node using your filtering and routing rules, and creates a new binding. The session continues without interruption. You normally don't interact with bindings directly. They're managed by the rules you set. But there's one exception: `bind-node`. When you use `bind-node`, you're telling the system to lock the session to exactly one node. No rotation, no automatic replacement. If that node goes offline, your requests fail with an explicit error (`503 BOUND_NODE_FAILED`) instead of silently switching to a different node. This is useful for workflows where using a different node mid-session would cause problems (for example, maintaining a logged-in state on a target that fingerprints by IP). `bind-node` requires a session and is incompatible with `rotate-timed_N`, `rotate-requests_N`, `onerror-replace`, and `prefer-lookalike`. You can pair it with `onerror-retry_N` to retry on the same node before failing. ## Nodes and IPs A **node** is a real device in the SOAX network that executes your requests. It has a physical location, a network connection, and an IP address that the target website sees. Nodes are not permanent. They're real devices on real networks, which means they can change IP, go offline, or shift characteristics over time. SOAX is designed to handle this as a normal operating condition, not as an exception. This is why continuity in SOAX doesn't live at the IP level. It lives in sessions and bindings. Your session's rules define what behavior you need, and the system delivers it using whatever nodes are currently available and eligible. If a node changes or disappears, the rules handle what happens next. This is a deliberate design choice. Systems that promise IP stability are making guarantees they can't always keep, especially at scale. SOAX instead gives you explicit control over what happens when things change, so your workflows stay predictable. ## How it all fits together 1. Your **organization** has a credit pool and one or more **packages**. 2. Each **package** defines who can connect, what network types are available, and what limits apply. 3. When you send a request, you include **rules** in your connection string that describe what you need. 4. If you include a **session** parameter, the system creates a session and binds it to a node that matches your rules. 5. The **binding** persists across requests, changing only when your rotation, error handling, or filtering rules require it. 6. If you don't include a session, each request is independent and gets a fresh node every time. The result is a system where you describe behavior, not infrastructure. You don't need to know which specific node or IP you're using. You just define the rules, and the system handles the rest. ## Next steps Make your first request in under 5 minutes. Full parameter reference for residential proxy rules. Full parameter reference for mobile proxy rules. How to set up credentials and connect. # Quickstart Source: https://developers.soax.com/getting-started/quickstart Go from signup to your first successful proxy request in under 5 minutes. Covers both the dashboard and terminal. This guide walks you through making your first proxy request with SOAX. By the end, you'll have a working connection that returns a response through one of our residential proxy IPs. ## 1. Create your account Go to [soax.com/signup](https://soax.com/signup) and create an account. You'll need to verify your email address before you can access the dashboard. Check your inbox (and spam folder) for the verification link. ## 2. Choose a package Before you can make requests, you need an active package. After verifying your email, log in to the [dashboard](https://platform.soax.com) and select a package that fits your needs. ## 3. Make your first request Once your package is active, you have two ways to make your first request: through the dashboard using **Quick Connect**, or from your **terminal** using curl. ### Option A: Quick Connect (dashboard) Quick Connect is the fastest way to test your connection without writing any code.