Managing Sub-Users for Your Clients with the API

Give each client its own proxy login with a monthly bandwidth cap, track its usage, raise the cap, and switch it off, all from your own code.

If you resell proxy access or run proxies for several clients, give each client its own sub-user: a separate proxy login with its own label, status and monthly bandwidth cap. One API call creates it:

curl -X POST https://node4.io/api/v1/sub-users \
  -H "Authorization: Bearer n4k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "acme-corp", "bandwidthLimitMb": 51200}'

That client now has a username and password that work on every product your account holds, and stop working once they move 50 GB in a UTC calendar month. This page walks through the full client lifecycle. The endpoint reference is in sub-users.

Before you start

Create a login per client

Send a label you will recognize in your own billing, and a cap in megabytes:

FieldAccepted values
labelUp to 128 characters, or null
bandwidthLimitMbA whole number, at least 1024 (1 GB), or null for no cap
maxConnectionsStored and returned, but advisory: nothing enforces it

The username and password are generated for you and returned in the 201 response:

{
  "success": true,
  "data": {
    "id": 412,
    "username": "node4_364512_Qm7rT2xa",
    "password": "c9HwLp4ZsY2kVn8e",
    "label": "acme-corp",
    "status": "active",
    "limits": {
      "bandwidthLimitMb": 51200,
      "bandwidthPeriod": "calendar_month_utc",
      "maxConnections": 100,
      "maxConnectionsEnforced": false
    },
    "usage": {
      "periodStart": "2026-10-01T00:00:00.000Z",
      "periodEnd": "2026-11-01T00:00:00.000Z",
      "bandwidthUsedMb": 0,
      "bandwidthRemainingMb": 51200
    },
    "lastUsedAt": null,
    "expiresAt": null,
    "createdAt": "2026-10-17T09:12:44.000Z"
  }
}

Store the id: every later call addresses the sub-user by it. In Node.js 18+:

const API = "https://node4.io/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.NODE4_API_KEY}`,
  "Content-Type": "application/json",
};

export async function createClientLogin(clientName, capGb) {
  const res = await fetch(`${API}/sub-users`, {
    method: "POST",
    headers,
    body: JSON.stringify({ label: clientName, bandwidthLimitMb: capGb * 1024 }),
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${res.status}: ${body.error}`);
  return body.data; // { id, username, password, ... }
}

To hand the client a connection string for a gateway product, pass the new id as credentialId to POST /gateways/endpoint, which returns the strings with targeting and sticky sessions already applied. Building proxy strings with the gateway API covers that call. For static datacenter proxies, the client uses its username and password with the hosts and ports from GET /proxies. If you replace one of those addresses later, the client needs the new host: automating IP replacement shows how to catch that from the proxy.replaced webhook.

List clients and their usage

GET /sub-users returns every sub-user with this month's usage, plus the plan's limits:

import os, requests

API = "https://node4.io/api/v1"
headers = {"Authorization": f"Bearer {os.environ['NODE4_API_KEY']}"}

d = requests.get(f"{API}/sub-users", headers=headers, timeout=30).json()["data"]
print(f"{d['plan']['used']} of {d['plan']['maxSubUsers']} sub-users, {d['plan']['active']} active")
for s in d["subUsers"]:
    cap = s["limits"]["bandwidthLimitMb"]
    used = s["usage"]["bandwidthUsedMb"]
    print(f"{s['label'] or s['username']:<20} {s['status']:<10} {used:>8.0f} MB / {cap or 'no cap'}")

The same logins appear in the dashboard under Account Settings → Auth & IP Access, in the Username / Password Authentication card, each with its label, monthly cap, usage and an Active or Disabled switch. Changes made in either place show up in the other.

Credentials card in Account Settings, Auth & IP Access tab: client logins with labels, monthly caps, usage and status.

status is one of four values:

StatusMeaning
activeAuthenticates normally
over_limitHas used this month's cap; refused until the cap is raised or the month ends
disabledSwitched off by you
expiredPast its expiresAt

GET /sub-users/{id}/usage (scope read:usage) returns one sub-user's figure for invoicing:

curl -H "Authorization: Bearer n4k_YOUR_KEY" https://node4.io/api/v1/sub-users/412/usage
{
  "success": true,
  "data": {
    "id": 412,
    "username": "node4_364512_Qm7rT2xa",
    "resolution": "calendar_month_utc",
    "seriesAvailable": false,
    "series": null,
    "period": {
      "start": "2026-10-01T00:00:00.000Z",
      "end": "2026-11-01T00:00:00.000Z",
      "bandwidthUsedMb": 18432,
      "bandwidthLimitMb": 51200,
      "bandwidthRemainingMb": 32768
    },
    "lastUsedAt": "2026-10-17T08:58:02.000Z"
  }
}

Per-sub-user usage is a running total for the current UTC month, the same counter the cap is enforced against. There is no hourly or daily series per sub-user (seriesAvailable is false); the account-wide series is GET /usage/bandwidth. To bill a client for a month, read its figure shortly before the month ends, because the counter starts again from zero on the 1st. lastUsedAt can trail real use by a few minutes.

Raise a cap, or switch a client off

PATCH /sub-users/{id} changes label, bandwidthLimitMb, maxConnections or status. Raising a cap brings an over_limit client back within about 2 minutes:

curl -X PATCH https://node4.io/api/v1/sub-users/412 \
  -H "Authorization: Bearer n4k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"bandwidthLimitMb": 102400}'

To pause a client that has not paid you, set {"status": "disabled"}, or call DELETE /sub-users/412, which also disables by default. A disabled sub-user keeps its username, its label and this month's counter, so you can turn it back on with {"status": "active"} and the client's configuration still works. Re-enabling counts against the plan's active limit, so a full account has to disable another login first.

requests.patch(f"{API}/sub-users/412", headers=headers, json={"status": "disabled"}, timeout=30)

Delete a client for good

DELETE /sub-users/{id}?permanent=true removes the login and frees its slot:

curl -X DELETE -H "Authorization: Bearer n4k_YOUR_KEY" \
  "https://node4.io/api/v1/sub-users/412?permanent=true"
{ "success": true, "data": { "id": 412, "deleted": true } }

Use it when the client has left. A disabled sub-user still counts toward the plan's total, so permanent deletion is what makes room for a new client once you reach the limit.

When changes take effect

A cap change, a disable or a delete reaches live traffic within about 2 minutes, because the gateways and proxy nodes cache authentication answers briefly. Plan for that window: disabling a client is not an instant cut-off, and connections already open when you disable it carry on until they close.

A cap is also not exact to the byte. Usage reaches the counter in short batches, so a client running at full speed can go a little past its cap before it is refused. Set the cap slightly below what you sell if the overrun matters to you.

What sub-users do not do

Some limits a reseller might expect cannot be enforced per sub-user, so the API refuses them with a 400 that explains why instead of storing a setting nothing would apply:

An unknown field is also a 400 listing the accepted ones, so a typo never passes silently.

Errors

StatusUsual causeFix
400Cap under 1024 MB, a refused field, an unknown field, the plan's sub-user limit reached, or an unverified email on a free accountRead the error string; delete a sub-user permanently to free a slot
403The key lacks the scope for that callCreate a key with write:credentials or read:usage as needed
404The id is not a sub-user on this accountCheck GET /sub-users
409Another change to this account's logins was in progressRetry after a moment

Room for more clients usually means more allowance rather than more logins: same-type plans stack, so a second plan adds its bandwidth to the pool every sub-user draws from. Plans and prices are on /pricing, and the API reference lists the bandwidth_threshold webhooks that warn you before the account-wide allowance runs out.