> ## Documentation Index
> Fetch the complete documentation index at: https://developers.soax.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stateful Sessions

> Long-lived proxy sessions you configure on the server. Change location, network or rotation at any time without touching your client.

A **stateful session** is a long-lived proxy identity that you configure through the API. You create it once, get a connection string, and then change its rules whenever you like. Your client keeps the same connection string; the next request picks up the change.

## When to use them

[Ephemeral sessions](https://developers.soax.com/proxies/residential.md#ephemeral-sessions-held-ip) put the rules in the connection string, lock them on the first request, and expire after 60 seconds of inactivity. That's the right fit for short, self-contained jobs.

Stateful sessions fit when the setup has to outlive a single job:

* **You want to change location or rotation without redeploying.** Move a session from the US to Germany with one API call. Every client using it follows.
* **You run a fleet of workers or browser profiles.** Each one gets its own named session, created in bulk with one call.
* **You authenticate by IP.** Each session gets its own port on IP allowlist packages, so you can use HTTP and SOCKS5 without sending credentials. See [Connecting](#connecting-to-a-session).
* **You need to know the exit IP up front.** A session is bound to a node when it's created, so the response already carries its exit IP.

| | Ephemeral session | Stateful session |
| - | - | - |
| Where the rules live | In the connection string | On the server |
| Created by | Your first request | An API call |
| Changing the rules | Use a new session ID | Update the session; the connection string stays the same |
| IP allowlist over HTTP and SOCKS5 | No | Yes, through a dedicated port per session |

## How it works

1. **Create a session** with the rules you want: where (country, region, city, ISP, ASN, ZIP), what kind of network, and how the exit node should change over time.
2. **SOAX binds it** to a matching exit node straight away and returns the exit IP and a connection string.
3. **Send traffic** through the connection string like any other proxy.
4. **Change the rules** at any time. The connection string doesn't change.
5. **The exit node changes** when your rules say so, or immediately when you [rotate](#rotating) the session.
6. **Disable** a session to pause it and enable it later, or **delete** it when you're done.

The examples below use the [SOAX API](https://developers.soax.com/api/overview.md). You need an API key with the `proxy:packages:write` scope (and `proxy:packages:read` to list and read sessions), plus the ID of the package the sessions belong to. See [API authentication](https://developers.soax.com/api/authentication.md).

## Create a session

```bash theme={null}
curl -X POST "https://api.platform.soax.com/v1/proxy/packages/YOUR_PACKAGE_ID/stateful-sessions" \
  -H "Authorization: Bearer s_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "checkout", "ruleset": {"country": "us", "region": "california"}}'
```

The response lists the sessions that were created, each with its current binding and connection string:

```json theme={null}
{
  "data": [
    {
      "id": "7c1e9a52-3b4d-4f6a-9e8b-2d5c0a1f6b3e",
      "name": "checkout",
      "status": "enabled",
      "ruleset": {
        "network_type": null,
        "country": "us",
        "region": "california",
        "city": null,
        "isp": null,
        "asn": null,
        "zip": null,
        "lookalike": false,
        "rotation_trigger": "timed:300",
        "error_handling": "replace"
      },
      "exit_ip": "203.0.113.42",
      "network_type": "wifi",
      "country": "us",
      "region": "california",
      "city": "los_angeles",
      "bound_since": "2026-10-27T10:15:04Z",
      "port": 1337,
      "connection_string": {
        "uri": "stateful-checkout:YOUR_PACKAGE_KEY@proxy.soax.com:1337",
        "username": "stateful-checkout",
        "password": "YOUR_PACKAGE_KEY",
        "host": "proxy.soax.com",
        "port": 1337
      }
    }
  ],
  "failed": 0,
  "reason": null
}
```

`ruleset` is what you asked for. The top-level `exit_ip`, `network_type` and geo fields describe the node the session is bound to right now.

The `ruleset` is optional. Leave it out to get a session with the defaults: any location the package allows, a new exit node every 300 seconds, and a replacement node if the current one fails.

### Creating many sessions at once

Set `count` (up to 1,000) to create a pool in one call. Each session gets the base name plus a number:

```json theme={null}
{ "name": "w", "count": 50, "ruleset": { "country": "de" } }
```

This creates `w1` to `w50`. Each session is bound on its own, and any that can't be bound are discarded, so the response can contain fewer sessions than you asked for. Always check `failed`: if it's above 0, `reason` explains why and you can create the rest with another call. If none can be bound, the request fails with `503` and a `Retry-After` header. Retry after that delay, or broaden the rules (for example, drop `city`).

## Connecting to a session

How you connect depends on how the package authenticates. Both methods work over HTTP, HTTPS and SOCKS5.

| Package authentication | Username | Password | Host and port |
| - | - | - | - |
| Username and password | `stateful-<name>` | Package key | `proxy.soax.com:1337` |
| IP allowlist | None | None | `proxy.soax.com` and a port assigned to the session |

```bash Username and password theme={null}
curl -x "http://stateful-checkout:YOUR_PACKAGE_KEY@proxy.soax.com:1337" https://checker.soax.com/api/ipinfo
```

```bash IP allowlist theme={null}
curl -x "http://proxy.soax.com:SESSION_PORT" https://checker.soax.com/api/ipinfo
```

Use the `connection_string` from the API response rather than building it yourself. On IP allowlist packages, each session's `port` identifies it, so your client's public IP must be on the package's [IP allowlist](https://developers.soax.com/dashboard/package-management.md#ip-allowlist-for-ip-auth).

> [!WARNING]
>
> Don't add rules such as `country-us` to a stateful session's username. A stateful session's rules live on the server; change them through the API.

## Rules

A session's `ruleset` uses the same [rules system](https://developers.soax.com/getting-started/core-concepts.md#rules) as connection strings, written as JSON fields:

| Field | Values | Connection-string equivalent |
| - | - | - |
| `network_type` | `wifi` (residential), `mobile`, `any` | `network-res`, `network-mob` |
| `country` | Two-letter code, or `any` | `country-us` |
| `region`, `city`, `isp` | Codes from the [location endpoints](https://developers.soax.com/api/locations/package-regions.md) | `region-…`, `city-…`, `isp-…` |
| `asn`, `zip` | Codes from the location endpoints. Need ASN or ZIP targeting on your plan | `asn-…`, `zip-…` |
| `lookalike` | `true`, `false` (default) | `prefer-lookalike` |
| `rotation_trigger` | `timed:<seconds>` (minimum 10, default `timed:300`), `request-count:<requests>`, `on-error`, `bind` | `rotate-timed_N`, `rotate-requests_N`, none, `bind-node` |
| `error_handling` | `replace` (default), `retry_<attempts>`, `fail` | `onerror-replace`, `onerror-retry_N`, `onerror-fail` |

A few things to know:

* **Geo fields need a country.** Setting `region`, `city`, `isp`, `asn` or `zip` requires a specific `country` (not `any`). Each value must belong to the one above it: a city inside its region, a region inside its country. Use the `code` values from the [location endpoints](https://developers.soax.com/api/locations/package-regions.md) so they always validate.
* **The package sets the limits.** `country` and `network_type` must be available on the package.
* **`on-error`** keeps the same node until it fails, with no timed or request-based rotation.
* **`bind`** pins the session to its current node. It can't be combined with `error_handling: "replace"` or `lookalike: true`, and its error handling defaults to `retry_3`. If the node goes offline, requests fail instead of moving to another node.

## Changing a session

Send only what you want to change. Your client doesn't need to do anything.

```bash theme={null}
curl -X PATCH "https://api.platform.soax.com/v1/proxy/packages/YOUR_PACKAGE_ID/stateful-sessions/SESSION_ID" \
  -H "Authorization: Bearer s_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ruleset": {"country": "de"}}'
```

* **Omitted fields keep their value.** Setting a geo field or `network_type` to `null` clears it. `lookalike`, `rotation_trigger` and `error_handling` always have a value and can't be set to `null`.
* **Changing a location clears what's beneath it.** Changing `country` clears `region`, `city`, `isp`, `asn` and `zip` unless you send new values in the same request. Changing `region` clears `city`, `isp`, `asn` and `zip`. Changing `city` clears `isp`, `asn` and `zip`. `isp`, `asn` and `zip` never clear one another.
* **Renaming changes the username.** The username is `stateful-<name>`, so update your client after a rename.

### Disabling and enabling

Set `status` to `"disabled"` to stop a session carrying traffic without losing its name, port or rules. Set it back to `"enabled"` to resume: the session is bound to a fresh node. If no node is available, the call returns `502` and the session stays disabled. Disabled sessions still count toward the package's session limit.

## Rotating

Ask for a new exit node at any time:

```bash theme={null}
curl -X POST "https://api.platform.soax.com/v1/proxy/packages/YOUR_PACKAGE_ID/stateful-sessions/SESSION_ID/rotate" \
  -H "Authorization: Bearer s_live_YOUR_API_KEY"
```

The response carries the new `exit_ip` and `bound_since`. You can rotate each session once per second; faster calls get `429` with `Retry-After: 1`.

## Stateful doesn't mean pinned

A stateful session keeps its **configuration and connection string**. Its **exit IP** can still change:

* when its `rotation_trigger` fires,
* when the bound node fails and `error_handling` replaces it,
* when the node no longer matches the rules, for example after you change them,
* when the node leaves the network.

To keep the same IP for as long as possible, use `rotation_trigger: "bind"` with `error_handling: "fail"` or `"retry_<attempts>"`. Even then, a node that goes offline can't be kept. Nodes are real devices; see [Nodes and IPs](https://developers.soax.com/getting-started/core-concepts.md#nodes-and-ips).

## Deleting

Delete one session with `DELETE …/stateful-sessions/SESSION_ID`, or up to 100 at once with [`POST …/stateful-sessions/delete`](https://developers.soax.com/api/stateful-sessions/bulk-delete). A deleted session stops working immediately, and its name and port can be reused.

## Limits

| Limit | Value |
| - | - |
| Sessions per package | 1,000 by default, counting enabled and disabled sessions. Contact support if you need more. |
| Sessions per create request | 1 to 1,000 |
| Session name | Lowercase letters and digits, up to 16 characters, unique in the package. In a batch, the number appended counts toward the 16. |
| Manual rotation | Once per second per session |
| Minimum timed rotation | `timed:10` |
| Sessions per bulk delete | 100 |
| Concurrent creates | One at a time per package. An overlapping create may get `429`. |

Everything the package already enforces applies to stateful traffic unchanged: traffic limits, RPS and connection limits, allowed countries and the firewall. Sessions themselves aren't billed; their traffic is billed as usual.

On a suspended or deleted package you can't create or rotate sessions, but you can still list, read, update and delete them.

## API errors

API errors return a JSON body. Endpoint-specific errors use `detail`, shared API rate-limit errors use `error`, and schema-validation errors use a `detail` array. See [Error handling](https://developers.soax.com/api/errors.md#error-response-shapes) for these response shapes. Common status codes:

| Status | When |
| - | - |
| `400` | Invalid name or rules, session limit reached, stateful sessions disabled on the package, or package suspended |
| `403` | The API key lacks the required scope |
| `404` | The package or session doesn't exist, or is outside the API key's package restriction |
| `422` | Malformed request body, such as `null` for `rotation_trigger` or a bulk-delete ID that isn't a UUID |
| `429` | Rotating faster than once per second, or an overlapping create on the same package |
| `502` | A create where every session failed unexpectedly, or a re-enable that couldn't bind a node |
| `503` | A create where no session could be bound to a node. Retry after `Retry-After`. |

See the [Stateful Sessions API reference](https://developers.soax.com/api/stateful-sessions/create.md) for every endpoint, and [Error handling](https://developers.soax.com/api/errors.md) for the general error format.

## Next steps

> [card] [Create stateful sessions](https://developers.soax.com/api/stateful-sessions/create.md)
>
> Full request and response reference.

> [card] [Core concepts](https://developers.soax.com/getting-started/core-concepts.md)
>
> Sessions, rules and bindings explained.

> [card] [Location endpoints](https://developers.soax.com/api/locations/package-regions.md)
>
> Look up valid region, city, ISP, ASN and ZIP codes.

> [card] [Authentication](https://developers.soax.com/getting-started/authentication.md)
>
> Username and password vs IP allowlist.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.