Skip to content
SteamLabs API

Endpoints

Proxies

Manage your proxy pool and proxy groups from code: import, test, filter, and act on thousands at once.

Everything the Proxies page does, over HTTP: add proxies one at a time or paste in ten thousand, group them, test them, and change them in bulk.

Two scopes cover the whole domain. proxies.read for the listings, proxies.write for everything that changes the pool. Groups share them, because a group is just a way of organizing proxies.

Passwords are write-only

No response ever contains a proxy password, not even when you read a single proxy. You get has_password instead.

That is deliberate. You already have the credential (you pasted it in), so returning it buys you nothing, while a leaked read-only key would otherwise hand over a working set of third-party credentials. Send a new password to change one, or null to clear it.

List proxies

GET/api/v1/proxies

Your proxy pool, paginated and filterable.

API key required
Bash
curl "https://dashboard.steamlabs.dev/api/v1/proxies?status=healthy&per_page=2" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "data": [
        {
            "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
            "protocol": "http",
            "host": "res-eu-01.example.net",
            "port": 8080,
            "type": "static",
            "username": "slabs",
            "has_password": true,
            "max_logins_per_minute": 12,
            "is_active": true,
            "notes": null,
            "status": "healthy",
            "latency_ms": 184,
            "exit_ip": "185.22.10.4",
            "country": "NL",
            "exit_ip_samples": null,
            "distinct_exit_ips": null,
            "rotation_mismatch": false,
            "consecutive_failures": 0,
            "last_error": null,
            "last_checked_at": "2026-07-30T14:02:11+00:00",
            "last_used_at": "2026-07-30T15:41:52+00:00",
            "created_at": "2026-07-01T09:15:00+00:00",
            "updated_at": "2026-07-30T14:02:11+00:00",
            "groups": [
                { "id": "019fb430-1c22-73a4-9f0e-2b7c5d1e8a44", "name": "EU pool", "color": "#2563eb" }
            ]
        }
    ],
    "meta": { "page": 1, "per_page": 2, "total": 1, "last_page": 1 }
}

Filters

Parameter Values
search Host, host:port, or a bare port
status untested, checking, healthy, unreachable, steam_blocked. Repeat it (status[]=) to match several
protocol http, socks5
type static, rotating
group A group id, or ungrouped
is_active true or false
rotation_mismatch true for proxies that claim to rotate but measured as a single exit IP
sort newest (default), oldest, host, latency, last_checked

rotation_mismatch is worth a look now and then. A proxy labelled rotating that returns the same exit IP on every connection funnels your whole fleet through one address, and Steam throttles the lot.

Read, create, update, delete

GET/api/v1/proxies/{id}

One proxy.

API key required
POST/api/v1/proxies

Add a single proxy.

API key required
JSON
{
    "protocol": "socks5",
    "host": "res-eu-02.example.net",
    "port": 1080,
    "type": "static",
    "username": "slabs",
    "password": "hunter2",
    "max_logins_per_minute": 12,
    "is_active": true,
    "notes": "second EU block",
    "group_ids": ["019fb430-1c22-73a4-9f0e-2b7c5d1e8a44"]
}

Returns 201 with the proxy. A duplicate endpoint (same protocol, host, port and username) is refused with 422, so re-posting the same proxy never quietly creates a second one.

Creating goes through your plan's proxy cap. Over it, you get 403 with plan_limit_reached and the upgrade path:

JSON
{
    "message": "Your plan covers 500 proxies.",
    "code": "plan_limit_reached",
    "plan": {
        "heading": "Proxy limit reached",
        "upgrade_url": "https://steamlabs.example/subscription",
        "max_proxies": 500,
        "remaining": 0
    }
}
PATCH/api/v1/proxies/{id}

Change a proxy. Every field is optional. PUT does the same.

API key required

Omitted fields are left alone, including password. Send "password": null to clear it. Sending group_ids replaces the proxy's groups, so an empty array removes it from all of them.

DELETE/api/v1/proxies/{id}

Delete a proxy. Returns 204.

API key required

Import a list

POST/api/v1/proxies/import

Paste a proxy list, one per line.

API key required
JSON
{
    "proxy_list": "1.1.1.1:8000\n2.2.2.2:8000:user:pass\nsocks5://3.3.3.3:1080",
    "default_protocol": "http",
    "type": "static",
    "is_active": true,
    "group_ids": ["019fb430-1c22-73a4-9f0e-2b7c5d1e8a44"]
}

Accepted line formats, each with an optional http:// or socks5:// prefix that overrides default_protocol:

  • host:port
  • host:port:user:pass
  • host:port@user:pass
  • user:pass@host:port

Blank lines and lines starting with # are ignored. The response tells you what happened:

JSON
{
    "imported": 2,
    "skipped": 1,
    "invalid_count": 1,
    "invalid": [{ "line": 4, "value": "definitely-not-a-proxy" }]
}

skipped is endpoints your pool already had. A line the parser cannot read does not fail the batch: a 60,000-line paste with three typos imports 59,997 proxies and hands you the three. Only the first 50 unreadable lines come back, invalid_count has the total.

Re-importing yesterday's list is safe. You can also send an Idempotency-Key header for exact replay of the original response.

The import also goes through your plan's proxy cap, and is refused whole: if the new endpoints in the list would take you past the cap, nothing is written and you get the same 403 plan_limit_reached shape as the create endpoint. Endpoints your pool already holds do not count against the batch.

Test proxies

Checks run on the worker fleet, so these endpoints hand you a task to follow rather than a result. Both need an Idempotency-Key header.

POST/api/v1/proxies/{id}/test

Queue a health check for one proxy.

API key required
POST/api/v1/proxies/test

Queue a health check across a selection.

API key required
JSON
{ "filters": { "status": ["untested", "unreachable"] } }

Both answer 202:

JSON
{
    "task_id": "019fb457-d631-71a5-9042-d663a53bc51e",
    "accounts_affected": 34,
    "proxies_affected": 34
}

Proxies already mid-check are skipped, so proxies_affected can be lower than your selection. If nothing needed checking you get 409 nothing_to_check.

Both test endpoints refuse with 503 maintenance_mode during platform maintenance, like every other endpoint that queues new work on the fleet. See Errors.

Bulk changes

Every bulk endpoint takes either proxy_ids (up to 1,000, 422 bulk_limit_exceeded above that) or a filters object using the exact keys the listing accepts. Narrow the list until it looks right, then post that same filter object.

Send one or the other, never both: they describe two different selections, and guessing which you meant is not a risk worth taking.

Endpoint What it does
POST /api/v1/proxies/bulk/activate Sets is_active to true
POST /api/v1/proxies/bulk/deactivate Sets is_active to false
POST /api/v1/proxies/bulk/login-limit Sets max_logins_per_minute, or clears it with null
POST /api/v1/proxies/bulk/groups/add Adds the selection to every group in group_ids
POST /api/v1/proxies/bulk/groups/remove Removes it from them
POST /api/v1/proxies/bulk/delete Deletes the selection
JSON
{
    "filters": { "status": "unreachable", "is_active": false },
    "max_logins_per_minute": null
}

These answer 200 with the size of the selection:

JSON
{ "proxies_affected": 128 }

No task id, because there is nothing to wait for. However large the selection, one statement covers it.

An empty "filters": {} means every proxy you own. That is the deliberate equivalent of "select all" in the dashboard, so read it twice before sending it to the delete endpoint.

Proxy groups

Groups are named buckets, and a proxy can be in several. Accounts can pin a group, in which case they log in only through its members, so editing a group takes effect on those accounts immediately.

GET/api/v1/proxy-groups

Your groups, with member and account counts.

API key required
JSON
{
    "data": [
        {
            "id": "019fb430-1c22-73a4-9f0e-2b7c5d1e8a44",
            "name": "EU pool",
            "color": "#2563eb",
            "proxies_count": 240,
            "accounts_count": 18,
            "created_at": "2026-07-01T09:15:00+00:00",
            "updated_at": "2026-07-20T11:00:00+00:00"
        }
    ],
    "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

Takes search, sort (name by default, plus newest, oldest, proxies_count) and per_page.

GET/api/v1/proxy-groups/{id}

One group, with its member and account counts.

API key required

Returned bare, with no envelope, in the same shape a list row has.

POST/api/v1/proxy-groups

Create a group. Only name is required.

API key required
PATCH/api/v1/proxy-groups/{id}

Rename or recolor a group. PUT does the same.

API key required
DELETE/api/v1/proxy-groups/{id}

Delete the group. Its proxies are untouched. Returns 204.

API key required

Names are unique per account, and color is a #rrggbb hex string.

Members

POST/api/v1/proxy-groups/{id}/proxies

Add a selection of proxies to the group.

API key required
POST/api/v1/proxy-groups/{id}/proxies/remove

Remove a selection from the group.

API key required

Both take the same proxy_ids or filters selection as the bulk endpoints, and both answer { "proxies_affected": n }. Adding is idempotent: proxies already in the group are left alone rather than duplicated.

To read a group's members, list proxies filtered by it: GET /api/v1/proxies?group={id}. That way paging, sorting and filtering work exactly as they do everywhere else.