When you call an AI API, the question is not which node benchmarks fastest — it is three things: whether the egress IP stays fixed, whether concurrent connections hold up, and whether a failure can be retried cleanly. A hiccup in a browser chat is just a two-second spinner; a timeout in a script can force an entire batch to rerun. And if you switch nodes just to retry, the egress IP changes and you may trip the provider's risk controls. Below we take the three apart, then hand over a config you can copy.
Web chat and API calls: different network needs
Chatting with a model in a browser usually means one or two long-lived connections, with the front end reconnecting automatically if the line drops; a person barely notices a few hundred milliseconds of jitter. A script is a different model: every request walks through DNS resolution, a TCP handshake and a TLS handshake, and the connection may be torn down the moment the response arrives. In a batch of dozens or hundreds of concurrent requests, any failed connection surfaces as an exception in your code rather than a spinner in a UI.
| Aspect | Web chat | API calls from code |
|---|---|---|
| Connection pattern | A few long-lived connections, streamed over SSE / WebSocket | Many short-lived connections, rebuilt constantly |
| How failures surface | Spinner in the UI; the front end reconnects automatically | Exception or timeout; retry logic decides the outcome |
| Egress requirements | Just needs to connect and stay connected | Usually needs a stable egress IP for allowlists and debugging |
| Metric that matters | Time to first byte | Connection success rate and latency jitter |
| Typical workload | One question, one answer | Batch generation, embeddings, scheduled jobs |
The table explains a common puzzle: the browser works fine on a machine, yet scripts time out constantly. The browser rides a long-lived connection that is already established, while a script rebuilds the connection every time — far more exposure to jitter on the route. So 'the web page loads' is not proof that a route is good for API traffic.
How to choose between three route types: dedicated lines, relays and direct routes
Split routes roughly by path and you get three common types: IEPL dedicated lines, relays and direct connections. The names are just labels; the path decides the behaviour — where traffic exits the region, how many hops it crosses, and whether it shares an egress with ordinary traffic.
| Route type | Path characteristics | Behaviour | Best API workloads |
|---|---|---|---|
| IEPL dedicated line | International dedicated line, no public-internet detour | Stable latency, low jitter | Streaming output, interactive chat, long-lived connections |
| Relay | Traffic enters a relay node first, then exits through it | Quality depends on the relay node; moderate variation | Retryable batch jobs, offline workloads |
| Direct | Traffic exits straight from the local network | Heavily affected by the local egress; peak-hour slowdowns are clear | Backup route, low-frequency calls |
The idea behind split routing is to assign routes by domain: send API domains such as api.openai.com and api.anthropic.com over a dedicated line, and leave heavy traffic like pulling dependencies or syncing mirrors on a direct connection. The two routes back each other up, switching only after the primary fails repeatedly. VPNEM organises routes by region, covering 110+ countries and 210+ routes; when picking a node, prefer entries marked IEPL, then use measured jitter to decide whether to fall back to a relay.
- 110+Countries and regions covered
- 210+Routes online
- 60 daysMoney-back window
- UnlimitedDevices online at once
Conclusion: keep interactive requests and batch jobs on separate routes. Put streaming chat and low-jitter calls on an IEPL dedicated line; put offline batch processing on a relay or a direct connection; and keep one backup route that takes over only after the primary fails repeatedly, so retries do not change the egress every time.
Fixed egress, concurrent connections and timeout retries
A fixed egress IP matters more than being the fastest
What the provider sees is your egress IP. If one account fires requests from several egresses within a few minutes, you may be asked for extra verification, or throttled outright; and when the source addresses in the logs do not line up, you cannot tell whether the problem is the route or your code. The fix is simple: do not enable 'automatically pick the lowest-latency node' in the client — pin one node manually. When a team shares a subscription, agree on one egress, or split accounts by project.
Concurrency: the connection pool matters more than bandwidth
Opening a fresh TLS connection for every request multiplies handshake cost by request count, so once concurrency climbs, the bottleneck is usually connection setup rather than bandwidth. Turning on keep-alive, setting the pool ceiling to 1.2–2× your concurrency, and letting the client use HTTP/2 multiplexing usually beats switching to a 'faster' route. Monitor connection success rate and handshake time, not just peak bandwidth.
Timeouts and retries: configure the two kinds separately
Connect timeouts and read timeouts need separate values. Keep the connect timeout short (a few seconds) so an unreachable route fails fast and leaves time for a retry; keep the read timeout long, because a streaming response is meant to keep emitting for tens of seconds. Retry only idempotent requests, and only on 5xx or timeouts — retrying 4xx just burns quota. Backoff needs random jitter, otherwise a pool of workers retries in the same second and saturates the route that just recovered.
# Applies to the current shell session only, so the rest of the machine stays clean
export HTTPS_PROXY="http://127.0.0.1:7890"
export HTTP_PROXY="http://127.0.0.1:7890"
# Local and private addresses skip the proxy
export NO_PROXY="localhost,127.0.0.1,::1,10.0.0.0/8,.internal.example.com"
Environment variables such as HTTPS_PROXY are read directly by curl, Python requests, the Go standard library and others. Node does not read them by default — you have to hand undici an explicit ProxyAgent, though newer Node versions can enable experimental environment-variable proxy support with NODE_USE_ENV_PROXY=1.
import httpx
from openai import OpenAI
# Connection pool: max 32, keep-alive 16; separate connect and read timeouts
http_client = httpx.Client(
timeout=httpx.Timeout(30.0, connect=8.0),
limits=httpx.Limits(max_connections=32, max_keepalive_connections=16),
)
client = OpenAI(http_client=http_client, max_retries=3)
This config maps onto the three points above: the connection pool caps how many connections are opened at once, connect=8.0 fails fast when a route is unreachable, and max_retries=3 leaves retries to the SDK instead of a hand-written loop. The SDK already retries connection errors, 408, 409, 429 and 5xx with exponential backoff, so there is no need to reimplement it; wrapping another retry layer around it only turns one timeout into three.
Protocols and clients: from Shadowsocks to Hysteria2
A subscription link usually lists several nodes at once, and different protocols behave differently in the client. Here is where the common ones fit — and one rule to remember when choosing: a protocol that connects reliably beats a protocol that is faster on paper.
| Protocol | Characteristics | Works best when |
|---|---|---|
| Shadowsocks | Lightweight, AEAD encryption, broad client support | CPU-constrained machines; fewer settings is better |
| VMess | Requires a UUID; pairs with several transport layers | Migrating from an existing config |
| Trojan | Uses standard TLS; traffic resembles HTTPS | The network treats TLS well |
| VLESS | Not encrypted on its own; relies on TLS / REALITY | You want to skip one layer of encryption overhead |
| Hysteria2 | Built on QUIC / UDP with aggressive congestion control | Lossy links with high jitter |
| TUIC | Built on QUIC, multiplexed | Mobile use where the network switches constantly |
Importing is much the same across clients: copy the subscription link, choose 'Import from clipboard' or paste it into the subscription settings, then tap 'Update subscription' once. The import only counts as successful when the node list appears in the client. Desktop clients generally offer TUN mode (which takes over all traffic) and system proxy mode; on a machine that runs scripts, prefer system proxy with split rules rather than pushing every packet through the tunnel. On iOS, the system asks you to allow the VPN configuration after import — it will not take effect until you tap Allow.
A subscription link is itself a credential. Do not commit it to a public repository or paste it into chat logs. Inject it as a secret variable in CI, and keep it locally in a config file with tightened permissions.
Pre-launch checklist: timeouts, DNS and split rules
Run the list below against your client and your code; every item maps to a real failure mode.
- ✅ Fixed egress: requests from the same account always leave from the same egress, which makes allowlisting easier for the provider and debugging easier for you.
- ✅ Split rules send only the domains that need it over the international route:
api.openai.com,api.anthropic.comand similar go over a dedicated line, everything else goes direct. - ✅ DNS and requests travel the same route: turn on remote resolution in the client so domains are not resolved to a nearby node.
- ✅ Set the two timeouts separately: a few seconds for connect, and enough read time for the longest streaming response.
- ❌ Global proxy without
NO_PROXY: internal domains and local services get routed out too, which shows up as 'the API suddenly returns 502'. - ❌ Using 'auto-select node': every reconnect changes the egress IP, so the provider sees multiple sources.
- ❌ Judging a route with ping: ICMP getting through does not mean the TLS handshake will succeed — watch
time_connectandtime_appconnect.
You do not need extra tooling to check a route — a single curl command is enough:
curl -x http://127.0.0.1:7890 -sS --connect-timeout 8 -o /dev/null \
-w 'http=%{http_code} connect=%{time_connect}s tls=%{time_appconnect}s total=%{time_total}s\n' \
https://api.openai.com/v1/models
Run it a dozen times and watch the variation in the connect and tls columns rather than the absolute values. Small variation means the route suits interactive requests; if you see occasional multi-second spikes, the node is probably switching or the local egress is jittering, and that route is only good for retryable batch work.
A DNS leak typically shows up as 'it connects but it is slow' or 'the same domain is fast one minute and slow the next'. If the client only proxies TCP traffic and leaves DNS to the local resolver, the answer may point at the node nearest to you rather than the node nearest to the egress. To check, confirm in the client logs that name resolution goes through the proxy, and extend your split rules to cover DNS queries themselves.
Developer FAQ
I only develop on my own machine — do I still need a dedicated route?
Yes. A dev machine opens connections the same way production does; only the concurrency differs. Get split rules and timeout values right locally and you will not hit 'everything works here but the server times out on everything' after launch. Conversely, if the local setup only works because a global proxy papers over the gaps, that config will not transfer to a server.
Requests time out now and then — can I just retry?
First work out which timeout you are hitting. A connect timeout means the route or port is broken, and a retry will most likely fail too — switch routes first. A read timeout usually means the streaming response is simply long: raise the read timeout before considering a retry. Tracking the two kinds of timeout separately is far more useful than logging one vague 'failure rate'.
How many machines can share one subscription?
VPNEM allows unlimited simultaneous devices, so a dev machine, a test machine and a CI runner can share one subscription — as long as the egress policy is consistent: either pin them all to the same node, or split accounts by project. Do not mix several egresses on one account.
How can I tell whether a route is really a dedicated line?
Look at the data, not the name. Sample time_connect repeatedly: a dedicated line should trace a nearly flat curve. If it rises predictably at peak hours, it behaves more like a direct connection or a mediocre relay. VPNEM labels the type of every route on its routes page, so you can check the measured curve against the label.
If you are still comparing providers, ask these questions first: does it keep no logs, how long is the refund window, is an email address required to sign up, and are the payment methods convenient. VPNEM's answers: no logs, a 60-day money-back guarantee, no email address required, and payment via Alipay, WeChat Pay and USDT. Route and plan details are on the routes page and the plans page.
Conclusion: when choosing a route for AI API calls, fix the egress first, then the protocol, and tune timeouts and retries last. A fixed egress handles risk controls and allowlists, the protocol decides whether connections hold, and timeouts and retries decide whether one failure snowballs into a batch of them.