Automating Your Proxies with the Management REST API

Base URL, keys and scopes, then every endpoint group: proxies, purchasing, gateways, sub-users, billing, webhooks, rate limits and idempotency.

Jump to: buying plans and top-ups, gateways, sub-users, billing and settings, webhooks, retries and idempotency keys.

First request

The base URL for every call is https://node4.io/api/v1; the API is mounted on the main domain, with no separate hostname. Create a key under Account Settings → API & Webhooks, then verify it:

curl -H "Authorization: Bearer n4k_YOUR_KEY" https://node4.io/api/v1/profile

X-API-Key: n4k_YOUR_KEY works as an alternative header. Keys are prefixed n4k_ and shown once at creation: the server stores only a hash, so a lost key is replaced, not recovered. Call the API from your own server or scripts. It sends no CORS headers, so a web page cannot call it, and a key placed in browser code can be read by anyone who opens the page.

API Keys in Account Settings, API & Webhooks tab: Create Key and an existing key with its prefix, last use and permissions.

The full OpenAPI 3.1 document is published at https://node4.io/openapi.json, including every webhook event and its payload: load it into Postman or Insomnia, or generate a client from it, instead of typing the endpoints below by hand. Every operation in it has a stable operationId (listProxies, createOrder, getInvoicePdf and so on), which is also the method name in our client libraries.

Every successful response uses the envelope { "success": true, "data": ... }. Errors are { "error": "..." } with an HTTP status that says what kind of failure it was; the purchasing endpoints add a machine-readable code such as card_declined.

Scopes

A key carries the permission scopes you choose when creating it, and each endpoint needs exactly one:

ScopeGrants
read:proxiesProxies and their credentials, proxy health, status, gateways and gateway connection strings, live connections, credentials and sub-users, location coverage
write:proxiesRefresh or replace a static proxy's IP
read:usageBandwidth and usage history, per proxy and per sub-user
read:subscriptionThe subscription, purchasable plans and add-ons, auto top-up and alert settings
write:subscriptionChange auto top-up and usage alert settings
read:ordersOrder history and single-order status
write:ordersBuy plans, top-ups and IP slots (spends money)
read:billingAccount credit, invoices and invoice PDFs
read:profileProfile and the IP whitelist
write:ip-whitelistAdd and remove whitelist entries
write:credentialsCreate, change, disable and delete credentials and sub-users
read:notificationsAccount notifications
write:webhooksList, create and delete webhooks

Calling an endpoint the key is not scoped for returns a 403 whose error string names the missing permission, so a scope failure is distinguishable from a bad key (401) and from a revoked or expired one (also 403, with its own message).

Scope keys narrowly. A monitoring job needs read:usage and nothing else; a provisioning script needs write:ip-whitelist and should not also be able to read proxy credentials. Because every proxy's credentials flow through read:proxies, treat a key holding that scope as secret material of the same grade as the proxies, and give write:orders only to the one system that is meant to spend money. API key security covers storage and rotation.

One detail to know before it costs a debugging session: reading the IP whitelist requires read:profile, while adding and deleting entries require write:ip-whitelist. A key with only the write scope can modify the list but not read it back.

List your proxies

GET /proxies (scope read:proxies) is the complete inventory, every proxy on the account, active or not, with credentials attached:

curl -H "Authorization: Bearer n4k_YOUR_KEY" https://node4.io/api/v1/proxies
{
  "success": true,
  "data": {
    "total": 5,
    "active": 4,
    "proxies": [
      {
        "id": 1201,
        "type": "shared",
        "protocol": "http",
        "host": "…",
        "port": 4443,
        "username": "…",
        "password": "…",
        "status": "active",
        "country": "US",
        "expiresAt": "2026-09-01T00:00:00.000Z"
      }
    ]
  }
}

total and active can differ: expired and suspended proxies are included so your tooling can see why capacity dropped. Filter on status yourself. The protocol field says which listener each row describes; if a row says socks5 and your client has only spoken HTTP, the SOCKS5 guide explains what changes.

GET /proxies/list is the compact form for feeding other software. With ?format=txt it returns plain text, one active proxy per line as host:port:username:password, the format most scraping tools and proxy testers import directly:

curl -H "Authorization: Bearer n4k_YOUR_KEY" "https://node4.io/api/v1/proxies/list?format=txt" > proxies.txt

Without the parameter it returns the same active-only set as a JSON array. Use /proxies/list in pipelines and /proxies when you need statuses, IDs (required for refreshes) or expiry dates.

Usage and bandwidth

GET /usage returns the current calendar month's bandwidth (up, down, total, request count), all-time totals, and a page of recent usage log rows. ?limit= controls the page size, default 50, ceiling 200; asking for more silently returns 200, so never treat "fewer rows than requested" as the end of the data.

GET /usage/bandwidth is the time series: hourly snapshots over a trailing window set by ?days=, default 7, capped at 90.

curl -H "Authorization: Bearer n4k_YOUR_KEY" "https://node4.io/api/v1/usage/bandwidth?days=30"

Each snapshot carries an hour timestamp, bytes moved and request count, enough to chart consumption or to catch a runaway job at 3 a.m. GET /usage/analytics gives monthly totals, GET /usage/by-proxy, /usage/hourly and /usage/daily break usage down further, and GET /subscription returns the governing plan and its period dates; together they answer "will this month's usage fit?". When it will not, bandwidth top-ups add allowance without a plan change. The usage endpoints need read:usage; /subscription needs read:subscription.

IP whitelist

The IP whitelist is the credential-free authentication mode on every product: connections from a registered address are recognized without a username or password (what each product keeps in that mode is in authentication). Automating the list keeps it matching your servers as they come and go; the hygiene reasoning is in IP whitelisting best practices, and the mechanics are three calls.

List entries (scope read:profile):

curl -H "Authorization: Bearer n4k_YOUR_KEY" https://node4.io/api/v1/ip-whitelist

Add one (scope write:ip-whitelist):

curl -X POST -H "Authorization: Bearer n4k_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"ipAddress": "203.0.113.7", "label": "scraper-03"}' \
  https://node4.io/api/v1/ip-whitelist

The address must be a single, publicly routable IP. CIDR ranges are rejected, as are private, loopback and link-local addresses. Duplicates and over-limit additions return 400 with a readable error; your entry limit depends on tier plus any purchased slots, and the error text states the number in force. The optional label is for you.

Remove an entry by the id the GET returned:

curl -X DELETE -H "Authorization: Bearer n4k_YOUR_KEY" https://node4.io/api/v1/ip-whitelist/42

A boot-time pattern for elastic infrastructure: fetch the machine's public address, POST it with a hostname label, and DELETE it on graceful shutdown.

Refresh or replace a proxy's IP

Two endpoints (scope write:proxies) move a static proxy onto a new address, and both draw on the same monthly refresh allowance as the dashboard. POST /proxies/{id}/replace is the one to use in new code:

curl -X POST -H "Authorization: Bearer n4k_YOUR_KEY" https://node4.io/api/v1/proxies/1201/replace

It keeps the product and location, issues new credentials, and raises the proxy.replaced webhook. A 429 means this month's replacements are used up (Retry-After and resetsAt say when the allowance resets), a 409 means the proxy is not active, and a 503 means replacement is temporarily unavailable; nothing is used from the allowance in that case. The older POST /proxies/refresh takes {"proxyId": 1201} in the body and reports refreshesUsed against refreshLimit.

id is the numeric id from GET /proxies, and the proxy must be yours and active. The free tier and Shared Datacenter plans have no refresh allowance, and a 403 says so. Gateway products have nothing to refresh, since their exits change by design (see sticky versus rotating sessions), and after a replacement, connections open on the old address stop working. A replacement is not deduplicated by the server, so do not retry one blindly after a timeout: read GET /proxies first to see whether it happened.

Buy plans and top-ups

Four endpoints let a script buy without the dashboard. GET /plans lists the plans you can buy, with id, product family, price, billing cycle and what each includes; GET /addons lists the bandwidth top-ups and IP slots your account can add (both need read:subscription). Buying needs write:orders:

curl -X POST -H "Authorization: Bearer n4k_YOUR_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2b8e-3d4a-4e7f-9a0b-1c2d3e4f5a6b" \
  -d '{"planId": 12, "paymentMethod": "card"}' \
  https://node4.io/api/v1/orders
{
  "success": true,
  "data": {
    "paymentMethod": "card",
    "order": { "id": 9301, "status": "completed", "provisioning": { "status": "delivered" } }
  }
}

The two payment methods behave differently:

  • card charges the default saved card on the account, through the same checkout and fraud checks as the dashboard, at full price. It never spends account credit. With no saved card you get 402 no_saved_card; add one under Account Settings first. A declined card is 402 card_declined, and a payment held for review is 402 payment_held.
  • bitcoin creates an invoice and returns its checkoutUrl for you to pay (only Bitcoin responses carry checkoutUrl). Any account credit is applied automatically, and when credit covers the whole order it completes at once with no invoice.

POST /topups buys a bandwidth pack (addonId such as topup_50gb, or any whole number of GB inside the bounds GET /addons reports) or IP slots (ip_1, ip_5, ip_10). Top-ups are paid with Bitcoin; "paymentMethod": "card" is refused with payment_method_unsupported, because card top-ups happen only through auto top-up, configured in the dashboard.

Buying never changes or cancels an existing subscription. A Bitcoin order stays pending until its invoice is paid, so poll GET /orders/{id} (scope read:orders) for payment status, provisioning status and what was delivered, or subscribe to order.completed. Both purchase endpoints require an Idempotency-Key header; the next section but one explains it.

Gateways and live connections

Rotating and residential plans are used through a gateway hostname rather than a list of IPs. GET /gateways (scope read:proxies) returns one entry per gateway product on the account: hostname, HTTP and SOCKS5 ports, the usernames that authenticate, and which targeting options (country, state, city, ASN, sticky session) the product accepts.

POST /gateways/endpoint builds ready-to-use proxy strings, so you never assemble the username grammar by hand:

curl -X POST -H "Authorization: Bearer n4k_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"product": "residential", "country": "us", "state": "ny", "count": 3}' \
  https://node4.io/api/v1/gateways/endpoint

The response carries host, port, username and password, the http://, socks5:// and host:port:user:pass forms, and with count up to 100 distinct sticky sessions in endpoints. GET /gateways/endpoint does the same with query parameters. Targeting is checked against your plan and the exits that exist: a country with no exits is a 422 that lists the countries served, and a city name found in more than one state needs state. Rotation is per connection: without a session the gateway picks an exit for each tunnel, not for each request inside a kept-alive one.

For Rotating Unmetered plans, GET /connections/live returns the connections open right now against your concurrent-connection limit, plus refusals in the last few minutes, and GET /connections/history?range=24h (also 1h, 7d, 30d) returns the peak and refusal series the dashboard charts. History is kept 35 days.

Sub-users

Sub-users are separate proxy logins for each client or project, each with its own label, status and monthly bandwidth cap. GET /sub-users lists them with this month's usage and the plan's sub-user limits (scope read:proxies); creating and changing them needs write:credentials:

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

The username and password are generated and returned in the response. bandwidthLimitMb is a cap per UTC calendar month, at least 1024 (1 GB), or null for none. PATCH /sub-users/{id} changes the label, cap or status (active or disabled), and DELETE /sub-users/{id} disables a sub-user by default, or deletes it for good with ?permanent=true. GET /sub-users/{id}/usage (scope read:usage) returns its usage for the current month.

Changes reach live traffic within about 2 minutes, and a cap is not a to-the-byte cut-off: expect a little traffic past it while the gateways' usage counters catch up. Fields that cannot be enforced per sub-user, such as an IP allow-list or a connection limit, are refused with a 400 rather than silently stored.

Billing, settings and alerts

Three read endpoints cover money (scope read:billing): GET /billing/credit for the spendable account credit and recent credit movements, GET /billing/invoices (paged with limit and offset) and GET /billing/invoices/{id}/pdf, which returns the invoice as a PDF file, the same one the dashboard's Download button gives:

curl -H "Authorization: Bearer n4k_YOUR_KEY" -o invoice-1001.pdf \
  https://node4.io/api/v1/billing/invoices/1001/pdf

GET /auto-topup (scope read:subscription) shows the automatic top-up configuration and whether it can work right now; PUT /auto-topup (scope write:subscription) switches it on or off and sets the threshold, pack and per-period charge cap, under the same rules as the dashboard. It charges the account's default saved card, and card details are never readable over the API. GET /alerts and PUT /alerts read and change which channels fire at 80% and 100% of your bandwidth allowance: send only the switches to change, for example {"threshold80Webhook": true}.

Webhooks

Polling /usage every minute spends rate limit to learn mostly nothing; webhooks call you when something happens. All three webhook endpoints use write:webhooks.

curl -X POST -H "Authorization: Bearer n4k_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "ops-alerts",
    "url": "https://ops.example.com/hooks/node4",
    "events": ["usage.threshold_reached", "renewal.failed", "proxy.replaced", "order.completed"]
  }' https://node4.io/api/v1/webhooks

These events are delivered today:

EventSent when
bandwidth_threshold.warningUsage reaches 80% of the allowance (once per billing period)
bandwidth_threshold.exceededUsage reaches 100% of the allowance
usage.threshold_reachedEither threshold above, with threshold set to 80 or 100
usage.forecast_runoutAt your recent pace a metered allowance is estimated to run out within 5 days and before renewal. Once per billing period, not sent while auto top-up can still top up
connections.limit_reachedA Rotating Unmetered plan turned connections away at its limit in the last 24 hours (at most weekly)
renewal.failedA renewal payment failed: card declined or unusable, PayPal could not collect, or a Bitcoin renewal invoice went unpaid. provider is card, paypal or btcpay
proxy.replacedA static proxy moved to a new IP: by you (reason manual), by our support team (support) or automatically after downtime (auto)
order.completedA paid order settled

The threshold events honor the webhook switches in GET /alerts; usage.forecast_runout follows the 80% webhook switch. payment.succeeded, payment.failed, subscription.created, subscription.renewed, subscription.canceled and proxy.status_change are accepted in events but not sent yet; do not build on them. Use renewal.failed and order.completed for payment outcomes. An unknown event name returns 400 listing the valid set.

Every delivery is a POST with a JSON body { "id", "event", "data", "timestamp" } and four headers: X-Node4-Event (the event name), X-Node4-Event-Id (the same evt_ id as the body), X-Node4-Delivery (a different value on every attempt) and X-Node4-Signature, the hex HMAC-SHA256 of the raw body keyed with the webhook's secret. The API returns that secret once, in the creation response; the dashboard's Copy secret button under Account Settings → API & Webhooks shows it again whenever you need it. Verify the signature, because your endpoint is a public URL and without the check anyone who finds it can feed your systems fabricated events.

Answer with any 2xx. A 5xx, a timeout or a connection failure is retried, up to 3 attempts in all; a 4xx is treated as final. A retry carries the same event id, and so does the same event sent to each of your endpoints, so store the ids you have processed and skip repeats. Endpoints must be public https or http URLs; internal destinations are rejected at registration. Accounts are capped at 10 webhooks, and GET /webhooks and DELETE /webhooks/{id} complete the lifecycle. The API has no endpoint to edit a webhook, rotate its secret or switch a disabled one back on: re-enable it with its switch in the dashboard, and to change the secret, create a new webhook and delete the old one.

Rate limits

Every key gets 60 requests per minute, the same on every plan, bucketed per key. Give each subsystem its own key and a runaway retry loop in one cannot starve the others.

Every authenticated response reports the key's budget in two sets of headers. The standard names:

HeaderMeaning
RateLimit-LimitRequests allowed per window
RateLimit-RemainingRequests left in the current window
RateLimit-ResetSeconds until the window resets
RateLimit-PolicyThe quota itself, written limit;w=window-seconds

and the older X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, kept for existing integrations. Watch the one difference: RateLimit-Reset counts seconds from now, while X-RateLimit-Reset is a Unix timestamp. A client that reads one as the other either hammers the API or stalls for decades.

When the bucket is empty you get a 429 with a Retry-After header in whole seconds and a body that repeats the numbers:

{
  "error": "Rate limit exceeded. Please wait before making more requests.",
  "retryAfterSeconds": 42,
  "limit": 60,
  "windowSeconds": 60
}

Sleep for Retry-After and retry. Better, watch RateLimit-Remaining and slow down before the 429. A rate-limited request is refused before any handler runs, so retrying that 429 never repeats an action, even for a write.

Idempotency keys

A purchase over the network has an awkward failure: the request times out, and you cannot tell whether the order went through. Retrying blindly risks paying twice; not retrying risks never getting the proxies. Idempotency keys remove the guess.

POST /orders and POST /topups require an Idempotency-Key header, and they are the only endpoints that honor it. Send a unique value, 1 to 255 visible ASCII characters (a UUID is ideal), and send the same value on every retry of that purchase:

curl -X POST -H "Authorization: Bearer n4k_YOUR_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2b8e-3d4a-4e7f-9a0b-1c2d3e4f5a6b" \
  -d '{"addonId": "topup_50gb", "paymentMethod": "bitcoin"}' https://node4.io/api/v1/topups

What the server does with the key:

SituationAnswer
Same key, same body, first request finishedThe original response, replayed, with Idempotent-Replayed: true; nothing is charged again
Same key, same body, first request still running409 with code idempotency_in_flight: wait a few seconds and retry with the same key
Same key, different body422 with code idempotency_key_reused, nothing run
Missing or malformed key400 with code idempotency_key_required

A key is remembered for 24 hours from its first request, and keys are scoped to your account. After that a finished key is forgotten, and sending it again starts a new purchase, so retry within the day or check GET /orders first. Two answers mean "stop and look" rather than "retry": outcome_unknown (the first request with this key started a purchase whose outcome was never recorded) and payment_unconfirmed (an earlier card payment is still being confirmed). For both, check GET /orders and contact support if needed; never switch to a new key, which would be a second purchase. A request that repeats a purchase completed moments ago is answered 409 duplicate_recent_purchase with the existing orderId.

Three rules keep it working:

  1. One key per purchase. Generate a fresh UUID for each new order; reuse it only to retry that same order.
  2. Same body on retry. A key identifies one specific request, so change nothing but the attempt.
  3. Persist it when it matters. A key held only in memory is lost if your process crashes between attempts. Derive it from your own order ID, or store it before the first send.

Client libraries and Postman

Client libraries for Node.js and Python, and a ready-made Postman collection, are available on request from support while we prepare a public release. All three are generated from the OpenAPI document, so they cover every endpoint on this page. Both libraries:

  • send your key as Authorization: Bearer, read from NODE4_API_KEY when you do not pass one;
  • add an Idempotency-Key to createOrder and createTopup automatically, reuse it across their own retries, and wait out an idempotency_in_flight answer with the same key;
  • retry a 429 after the Retry-After delay, and retry server errors and dropped connections only for reads and purchases, so a write such as a proxy replacement is never sent twice;
  • return invoice PDFs as raw bytes;
  • raise a typed error per status, carrying the API's error code, so "not found" and "rate limited" are separate cases in your code.

The Node.js library has no dependencies, runs on Node.js 18 or later on your server, and ships TypeScript declarations. Install it from the archive support sends you:

// npm install ./node4-api
import { Node4Client } from "@node4/api";

const node4 = new Node4Client({ apiKey: process.env.NODE4_API_KEY });
const { active, proxies } = await node4.listProxies();
console.log(node4.lastRateLimit); // { limit, remaining, resetSeconds }

The Python library uses only the standard library and runs on Python 3.8 or later:

# pip install ./node4-python.tar.gz
from node4 import Node4Client, Node4RateLimitError

node4 = Node4Client()  # reads NODE4_API_KEY
print(node4.export_proxies(format="txt"))

The Postman collection has one request per endpoint, grouped like the sections above. After importing it, set the apiKey collection variable to your key; every request authenticates from that variable. Keep apiKey as a local value that you do not share, and check an exported or shared collection for the key before it leaves your machine (API key security covers this, including the VS Code extension's Current and Initial fields). Purchase requests generate a fresh Idempotency-Key from Postman's dynamic $guid variable on each send, so pressing Send again is a new purchase. Before you retry a purchase whose outcome you are unsure of, replace that header's value with a fixed variable you keep for that one purchase, and resend the same body.

Orders and notifications

GET /orders (scope read:orders) has offset pagination: ?limit= (default 20, ceiling 100) and ?offset= walk the full order history, useful for reconciling against your own accounting; understanding your invoice explains the line items. GET /notifications (scope read:notifications) takes the same limit with the same ceiling. All caps clamp silently rather than erroring, so the safe iteration pattern everywhere is: request your page size, and stop only when a page comes back empty, not when it comes back short.

A first integration

In order: verify the key with GET /profile (or getProfile() in either library), snapshot GET /proxies/list into your tool's config, register a webhook for usage.threshold_reached and renewal.failed, and put a daily GET /usage in cron as a backstop. Which endpoints matter most depends on the product: a fleet of static datacenter proxies leans on replacement and the whitelist, gateway plans on the endpoint builder and sub-users, and an agency running client work on sub-users and their caps (reselling access needs written authorization from Node4). The product line-up and each plan's allowances are on the features page.

A runnable script covering status, proxy list export, usage and IP whitelist management is in the API folder of our examples repository on GitHub.