# Ludax proxies in Node.js: guide for AI assistants

> Give this file to your AI assistant (ChatGPT, Claude, Cursor, Copilot) and it can write working Node.js code for Ludax Proxy with the built-in fetch, undici, axios or got. Human-readable version: https://ludaxproxy.com/docs/nodejs

You are helping a developer route Node.js HTTP requests through Ludax residential proxies. Ludax is a standard HTTP proxy with a username and a password: no SDK, no API key. Follow the rules below exactly; they come from the gateways' behaviour and from tests with Node.js 24.18, undici 8.12, axios 1.21 and got 16.2 (2026-11-10).

## Rules

- The proxy URL is `http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD@<gateway>`, for example `gw.ludaxproxy.com:7777`.
- **Built-in fetch (Node.js 24):** set `HTTPS_PROXY` to the proxy URL and start Node with `NODE_USE_ENV_PROXY=1`. Node.js marks this switch experimental; undici's `ProxyAgent` is the stable way. Without the variable the built-in fetch ignores `HTTPS_PROXY`. It also ignores an `agent` option.
- **undici:** `import { fetch, ProxyAgent } from "undici"`, then `fetch(url, { dispatcher: new ProxyAgent(proxyUrl) })`. The credentials in the URL are sent as Basic proxy authentication.
- **axios:** the built-in `proxy` option: `{ protocol: "http", host, port, auth: { username, password } }`. axios 1.21 tunnels HTTPS through it; no extra package.
- **got:** `agent: { https: new HttpsProxyAgent({ proxy: proxyUrl }) }` with `HttpsProxyAgent` from `hpagent`.
- **SOCKS5:** `socks-proxy-agent` with `socks5h://` (the gateway resolves DNS), passed as `httpAgent` and `httpsAgent` in axios with `proxy: false`. SOCKS5 is on Residential `gw.ludaxproxy.com:7575` and Premium `gw.ludaxproxy.com:1080`, not on the TLS gateways.
- **One connection is one IP.** axios opens a new connection per request, so each request gets a new IP. undici keeps its connection open and reuses it: one `ProxyAgent` stays on one IP, so for rotation create a new `ProxyAgent` per request and `close()` it.
- **Same IP for a flow:** `-session-<TOKEN>-time-<MINUTES>` in the password (up to 1000 minutes). One token per flow.
- **Errors:** undici and the built-in fetch throw `TypeError: fetch failed`; the gateway's answer is down the `error.cause` chain as `Proxy response (407) !== 200 when HTTP Tunneling` (or 502). axios: `error.response.status` (407 or 502). got: `Bad response: 407`. Retry 502 with backoff; never retry 407 (30 failed logins in 5 minutes block the client IP for 10 minutes).
- **Blocked from a clean IP:** Node's TLS handshake is not a browser's. Use the TLS gateways (Residential `gw.ludaxproxy.com:9999`, Premium `gw.ludaxproxy.com:8081`) with `-AE-<profile>` at the end of the username (for example `-AE-chrome154`), and start Node with `NODE_EXTRA_CA_CERTS=ludax-ca.crt` (https://ludaxproxy.com/downloads/ludax-ca.crt). `NODE_EXTRA_CA_CERTS` works for every client; the `ca` option of https-proxy-agent does not reach axios. Never suggest `NODE_TLS_REJECT_UNAUTHORIZED=0`. On the TLS gateways a sticky IP needs `-sameip` after the time.

## Credentials

- **Username:** the product prefix (`resi-` or `premi-`), then the developer's account name from the dashboard (Generate), for example `resi-lx_k3m9q2p7xa`. The account name is `lx_` and 10 characters, the same for every plan. Never invent one: ask the developer, or use a placeholder such as `YOUR_USERNAME`.
- **Password:** the plan's own password (16 hexadecimal characters, one per plan), then the options, in this order: `-session-<TOKEN>-time-<MINUTES>`, then `-country-<cc>`, `-state-<code>`, `-city-<name>` (and `-asn-<number>`, `-os-<device>` on Premium). Use a placeholder such as `YOUR_PASSWORD`, or read it from an environment variable. Never put options in the username.
- **Sessions:** without `-session-` every new connection leaves from a new IP. `-session-<TOKEN>-time-<MINUTES>` keeps one IP for every connection that sends the same token (TOKEN: 3 to 24 letters or digits; MINUTES: 1 to 1000; Premium holds a session at least 3 minutes). Always send `-time-`: without it a session lasts 1 minute. On the TLS gateways add `-sameip` after the time, or only the browser profile is held.
- **Location:** `-country-` takes a lower-case ISO code (`us`, `de`, `jp`); `-state-` and `-city-` follow it (`-country-de-state-by-city-munich`); on Premium the state is its name without spaces (`-country-us-state-illinois-city-chicago`), in any of 219 countries. `-os-` (Premium) takes `windows`, `android`, `ios` or `macos`. With no free exit in that city or region the request falls back to the country. Codes for every country: https://ludaxproxy.com/locations
- **The port picks the product:** a plan's password works only on its own product's gateway, and the prefix must match it.

## Products and gateways in force

| Product | Username prefix | HTTP and HTTPS | SOCKS5 | Browser TLS | Sticky session |
| --- | --- | --- | --- | --- | --- |
| Residential | `resi-` | `gw.ludaxproxy.com:7777` | `gw.ludaxproxy.com:7575` | No | up to 1000 min |
| Residential TLS | `resi-` | `gw.ludaxproxy.com:9999` | none | Yes | up to 1000 min |
| Residential Premium | `premi-` | `gw.ludaxproxy.com:8080` | `gw.ludaxproxy.com:1080` | No | up to 1000 min |
| Residential Premium TLS | `premi-` | `gw.ludaxproxy.com:8081` | none | Yes | up to 1000 min |

## Check the connection

`GET https://api.ludaxproxy.com/whoami` through the proxy returns JSON: `ip`, `country` (full name), `type`, `tls_profile` (the browser the TLS handshake matches) and `latency_ms`. Add `?full=1` for a `debug` block with the JA4 fingerprint, the HTTP/2 fingerprint, the ASN and the User-Agent that arrived.

## Tested examples

The same code as on https://ludaxproxy.com/docs/nodejs. Replace `lx_k3m9q2p7xa` and `YOUR_PASSWORD` with the developer's credentials.

### First request

**Built-in fetch** (`first_request_fetch.mjs`)

```javascript
// run with: NODE_USE_ENV_PROXY=1 node first_request_fetch.mjs
// (set HTTPS_PROXY first, as in the install step)
const r = await fetch("https://api.ludaxproxy.com/whoami");
console.log(await r.json());
```

**undici** (`first_request_undici.mjs`)

```javascript
import { fetch, ProxyAgent } from "undici";

const proxy = new ProxyAgent(
  "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:7777",
);

const r = await fetch("https://api.ludaxproxy.com/whoami", { dispatcher: proxy });
console.log(await r.json());
```

**axios** (`first_request_axios.mjs`)

```javascript
import axios from "axios";

const r = await axios.get("https://api.ludaxproxy.com/whoami", {
  proxy: {
    protocol: "http",
    host: "gw.ludaxproxy.com",
    port: 7777,
    auth: { username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-country-us" },
  },
});
console.log(r.data);
```

**got** (`first_request_got.mjs`)

```javascript
import got from "got";
import { HttpsProxyAgent } from "hpagent";

const agent = new HttpsProxyAgent({
  proxy: "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:7777",
});

const data = await got("https://api.ludaxproxy.com/whoami", { agent: { https: agent } }).json();
console.log(data);
```

### A new IP on every request

**undici** (`rotate_undici.mjs`)

```javascript
import { fetch, ProxyAgent } from "undici";

const PROXY = "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:7777";

// undici keeps a connection open and reuses it, and one connection is one IP:
// a new ProxyAgent per request opens a new connection, so a new IP
async function getWithNewIp(url) {
  const proxy = new ProxyAgent(PROXY);
  try {
    const r = await fetch(url, { dispatcher: proxy });
    return await r.json();
  } finally {
    await proxy.close();
  }
}

for (let i = 0; i < 3; i++) {
  console.log((await getWithNewIp("https://api.ludaxproxy.com/whoami")).ip);
}
```

**axios** (`rotate_axios.mjs`)

```javascript
import axios from "axios";

const proxy = {
  protocol: "http",
  host: "gw.ludaxproxy.com",
  port: 7777,
  auth: { username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-country-us" },
};

// axios opens a new connection for every request, so every request gets a new IP
for (let i = 0; i < 3; i++) {
  const r = await axios.get("https://api.ludaxproxy.com/whoami", { proxy });
  console.log(r.data.ip);
}
```

### The same IP for a whole flow

**undici** (`sticky_undici.mjs`)

```javascript
import { fetch, ProxyAgent } from "undici";
import { randomBytes } from "node:crypto";

// one token = one IP, kept for 30 minutes; a new token = a new IP
const token = randomBytes(4).toString("hex");
const proxy = new ProxyAgent(
  `http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-session-${token}-time-30-country-us@gw.ludaxproxy.com:7777`,
);

for (const step of ["login", "search", "checkout"]) {
  const r = await fetch("https://api.ludaxproxy.com/whoami", { dispatcher: proxy });
  console.log(step, (await r.json()).ip);
}
await proxy.close();
```

**axios** (`sticky_axios.mjs`)

```javascript
import axios from "axios";
import { randomBytes } from "node:crypto";

// one token = one IP, kept for 30 minutes; a new token = a new IP
const token = randomBytes(4).toString("hex");
const client = axios.create({
  proxy: {
    protocol: "http",
    host: "gw.ludaxproxy.com",
    port: 7777,
    auth: {
      username: "resi-lx_k3m9q2p7xa",
      password: `YOUR_PASSWORD-session-${token}-time-30-country-us`,
    },
  },
});

for (const step of ["login", "search", "checkout"]) {
  const r = await client.get("https://api.ludaxproxy.com/whoami");
  console.log(step, r.data.ip);
}
```

### Geo-targeting

```javascript
import { fetch, ProxyAgent } from "undici";

// country, then state, then city: an IP in Munich, Germany
const proxy = new ProxyAgent(
  "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-session-muc01-time-10-country-de-state-by-city-munich@gw.ludaxproxy.com:7777",
);

const r = await fetch("https://api.ludaxproxy.com/whoami", { dispatcher: proxy });
console.log(await r.json());
await proxy.close();
```

### Many requests in parallel

```javascript
import axios from "axios";

const proxy = {
  protocol: "http",
  host: "gw.ludaxproxy.com",
  port: 7777,
  auth: { username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-country-us" },
};
const urls = Array.from({ length: 20 }, (_, n) => `https://api.ludaxproxy.com/whoami?page=${n}`);

// 5 requests at a time; each one leaves from its own IP
const ips = [];
for (let i = 0; i < urls.length; i += 5) {
  const batch = urls.slice(i, i + 5).map((url) => axios.get(url, { proxy, timeout: 30000 }));
  for (const r of await Promise.all(batch)) ips.push(r.data.ip);
}
console.log(ips.length, "requests from", new Set(ips).size, "IPs");
```

### SOCKS5 with socks5h://

```javascript
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";

// socks5h: the gateway resolves the hostname, so no DNS query leaves your machine
// Residential Premium over SOCKS5 (Residential: gw.ludaxproxy.com:7575)
const agent = new SocksProxyAgent(
  "socks5h://premi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:1080",
);

const r = await axios.get("https://api.ludaxproxy.com/whoami", {
  httpAgent: agent,
  httpsAgent: agent,
  proxy: false,
});
console.log(r.data);
```

### Retry a 502, stop on a 407

**undici** (`retry_undici.mjs`)

```javascript
import { fetch, ProxyAgent } from "undici";

const PROXY = "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:7777";

// the real reason sits at the end of the error's cause chain
function proxyStatus(error) {
  for (let e = error; e; e = e.cause) {
    const m = /Proxy response \((\d{3})\)/.exec(e.message ?? "");
    if (m) return Number(m[1]);
  }
  return null;
}

// retry a dropped exit (502), stop at once on a refused login (407)
async function get(url, tries = 3) {
  for (let attempt = 1; ; attempt++) {
    const proxy = new ProxyAgent(PROXY);
    try {
      return await (await fetch(url, { dispatcher: proxy })).json();
    } catch (error) {
      if (proxyStatus(error) === 407 || attempt === tries) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
    } finally {
      await proxy.close();
    }
  }
}

console.log(await get("https://api.ludaxproxy.com/whoami"));
```

**axios** (`retry_axios.mjs`)

```javascript
import axios from "axios";

const proxy = {
  protocol: "http",
  host: "gw.ludaxproxy.com",
  port: 7777,
  auth: { username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-country-us" },
};

// retry a dropped exit (502) or a timeout, stop at once on a refused login (407)
async function get(url, tries = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await axios.get(url, { proxy, timeout: 30000 });
    } catch (error) {
      const status = error.response?.status;
      const retry = status === 502 || error.code === "ECONNABORTED" || error.code === "ECONNRESET";
      if (!retry || attempt === tries) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
    }
  }
}

console.log((await get("https://api.ludaxproxy.com/whoami")).data);
```

### Browser TLS fingerprint through the TLS gateway

**undici** (`tls_check_undici.mjs`)

```javascript
import { fetch, ProxyAgent } from "undici";

// the Residential TLS port; -AE-chrome154 = look like Chrome 154
const proxy = new ProxyAgent(
  "http://resi-lx_k3m9q2p7xa-AE-chrome154:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:9999",
);

// run with: NODE_EXTRA_CA_CERTS=ludax-ca.crt node tls_check_undici.mjs
const r = await fetch("https://api.ludaxproxy.com/whoami", { dispatcher: proxy });
console.log((await r.json()).tls_profile);
```

**axios** (`tls_check_axios.mjs`)

```javascript
import axios from "axios";

// run with: NODE_EXTRA_CA_CERTS=ludax-ca.crt node tls_check_axios.mjs
const r = await axios.get("https://api.ludaxproxy.com/whoami", {
  proxy: {
    protocol: "http",
    host: "gw.ludaxproxy.com",
    port: 9999, // the Residential TLS port
    auth: {
      username: "resi-lx_k3m9q2p7xa-AE-chrome154", // + the browser to look like
      password: "YOUR_PASSWORD-country-us",
    },
  },
});
console.log(r.data.tls_profile);
```

## Plans and help

- Prepaid, no subscription: 1 GB of Residential Proxies costs $0.99. The smallest plan is the test; there is no free trial. Every plan: https://ludaxproxy.com/pricing
- Credentials: dashboard, Generate (https://ludaxproxy.com/dashboard). A plan's password can be reset there.
- Full reference: https://ludaxproxy.com/docs. Whole site for AI: https://ludaxproxy.com/llms.txt
- Support: Telegram https://t.me/LudaxProxy, email support@ludaxproxy.com.
