Quickstart
A working proxy request in about two minutes. Your account includes 1 GiB of free traffic; it does not expire and there is no card on file.
Your credentials#
| Host | gate.busyip.com |
|---|---|
| HTTP | port 18080 (HTTP CONNECT) |
| SOCKS5 | port 11080 |
| Username | bi_k4m2xq7r |
| Password | •••••••••••• shown once, at signup — rotate if lost |
The examples on this page use bi_k4m2xq7r as a stand-in. Your own username is in your dashboard, and every sample below works with it unchanged.
First request#
curl -x http://bi_k4m2xq7r:YOUR_SECRET@gate.busyip.com:18080 https://api.ipify.org
That prints an IP address belonging to a real mobile or home connection, not ours. Run it twice and you may get a different one — see Sessions.
curl -x socks5h://bi_k4m2xq7r:YOUR_SECRET@gate.busyip.com:11080 https://api.ipify.org
Use socks5h, not socks5, so DNS is resolved at the exit rather
than on your machine. Without the h your own resolver decides, and the
exit's location stops matching what the site sees.
Targeting: the username is the control surface#
You select what you want by appending markers to your username. The password never changes. That is why every tool taking a proxy URL supports all of it with no plugin.
bi_k4m2xq7r[-session-{id}][-country-{cc}][-type-mobile|residential|any][-mode-sticky|rotate]
| You want | Username |
|---|---|
| Anything available | bi_k4m2xq7r |
| A mobile (cellular) exit | bi_k4m2xq7r-type-mobile |
| A home Wi-Fi exit | bi_k4m2xq7r-type-residential |
| Moldova | bi_k4m2xq7r-country-md |
| Moldova, mobile | bi_k4m2xq7r-country-md-type-mobile |
| A stable IP across requests | bi_k4m2xq7r-session-abc123 |
| A new IP on every request | bi_k4m2xq7r-mode-rotate |
-net- is accepted as a synonym for -type-, and
cellular, cell and wifi are accepted spellings.
An unknown type matches nothing rather than everything — if you typo
it you get no_capacity, never a silent fallback to the wrong pool.
Sessions — and the one behaviour to understand#
A -session-{id} pins you to a single exit device. A session
id with no -mode- is sticky by default. Sessions idle for 24 hours are
released.
Two things that sentence does not promise, because we measured both and neither is true on a fleet this size. The same session does not guarantee the same IP. It pins the phone; the phone's carrier can hand it a new address underneath you, and we have watched a session move between two addresses in the same /24 seconds apart. If your work is bound to an IP rather than to a device, treat a change as possible and check it. And a different session id does not guarantee a different IP. Stickiness is a promise about staying, never about differing — with a small pool two sessions frequently land on the same phone. If you need two addresses at once, ask us what is online rather than assuming two ids will do it.
If the phone behind your session goes offline, we tell you. We do not silently move you.
{"error":"sticky_device_offline","suggestedSession":"abc124"}
Read the reason from the headers, not the body. Every refusal carries
X-Proxy-Error and X-Proxy-Message, and a transient one also
carries X-Proxy-Retryable: true. Those survive the CONNECT
tunnel. The JSON body does not — essentially every HTTP client discards the body of a
failed CONNECT, including curl, requests, Go and OkHttp, so a client that
parses the body sees nothing at all and reports an empty response.
Most providers fail this open — you keep making requests and your IP has quietly changed underneath you, which for anything session-bound means you find out from the target site, not from the proxy. We would rather hand you an error you can branch on. Retry with the suggested session id to get a fresh pin.
Errors#
Every rejection is a 403 with a {"error","message"} JSON body
on the HTTP port, and SOCKS reply 0x02 (not allowed by ruleset) on
11080. The messages are safe to read and act on —
they name the rule you hit and never describe the exit's own network.
If you are new, concurrency_limit is the one you will meet
first. A trial credential allows 2 connections at once and
most scraping libraries open ten or more by default, so a first run can produce a
burst of denials that looks like something is broken. Nothing is — set your
connection pool to 2. A paid credential allows 8.
no_capacity is the one you will meet after that. Our
fleet is small and every exit is a real person's phone that they can close at any
moment. Build a retry with backoff and treat it as "try again in a moment", not as a
failure.
| Code | Means | Do |
|---|---|---|
invalid_credentials |
Wrong username or password. | Check the secret; rotate it from the dashboard if it is lost. |
quota_exhausted |
Your balance is spent. | Top up. Requests already in flight are given a short grace. |
no_capacity |
No exit matches your filters right now. | Retry with backoff, or widen — drop -country- or -type-. |
sticky_device_offline |
The exit you were pinned to dropped. | Retry with the suggestedSession we hand back. |
session_required |
-mode-sticky without a -session-. |
Add a session id. |
device_unavailable |
The exit accepted the request and then could not open a connection. | A transient failure, and the response says so: X-Proxy-Retryable: true. Retry immediately — a fresh request is usually handed a different exit. This is the one error worth automating a retry on. |
scope_not_permitted |
Your credential is limited to a country or type you did not ask for. | Use the credential's own scope. |
ip_not_allowed |
Your source IP is not on the credential’s allowlist. | Update the allowlist in the dashboard. |
concurrency_limit |
More connections at once than your credential allows. | Trial allows 2 at once; paid allows 8. Set your client’s connection pool to match — most scrapers default to 10 or 16. |
port_blocked |
The target port is not permitted. | We allow 80 and 443 only. The message names the permitted ports. |
host_blocked |
The target is a private or reserved address. | Private, loopback, link-local and CGNAT ranges are unreachable by design. |
bad_destination |
The address could not be parsed. | Check the host you asked for. |
resolve_failed |
DNS lookup for the target failed. | We fail closed on a resolution failure rather than guessing. |
credential_expired |
Unused for 180 days. | Rotate from the dashboard. |
disabled |
The credential is suspended. | Contact support@busyip.com. |
Code#
import requests
PROXY = "http://bi_k4m2xq7r:YOUR_SECRET@gate.busyip.com:18080"
r = requests.get("https://api.ipify.org",
proxies={"http": PROXY, "https": PROXY}, timeout=30)
print(r.text)
Pin a session by putting the marker in the username:
PROXY = "http://bi_k4m2xq7r-session-abc123-country-md:YOUR_SECRET@gate.busyip.com:18080"
import { ProxyAgent, request } from "undici";
const agent = new ProxyAgent("http://bi_k4m2xq7r:YOUR_SECRET@gate.busyip.com:18080");
const res = await request("https://api.ipify.org", { dispatcher: agent });
console.log(await res.body.text());
const browser = await chromium.launch({
proxy: {
server: "http://gate.busyip.com:18080",
username: "bi_k4m2xq7r-session-abc123",
password: "YOUR_SECRET",
},
});
Playwright takes the username and password as separate fields, so the markers go in the
username — not in the server URL.
Anything that takes a proxy URL — Scrapy, curl, yt-dlp, a browser extension — takes the same string. There is no SDK to install and there never will be; it is a standard proxy.
AI assistants (MCP)#
busyip exposes an MCP server at
https://busyip.com/mcp. Point an AI client at that URL and it can answer
questions about the service, and — once you connect an account — check your
balance, read your usage, and rotate your proxy password for you.
Six tools need no account at all
busyip_overview, busyip_pricing, busyip_capacity,
busyip_metering, busyip_targeting and busyip_legal
answer with no token. That is deliberate: an assistant deciding whether to recommend us
should be able to read the live capacity and the honest limits before anybody signs up.
busyip_capacity in particular reads the fleet live, so it will tell you a
country is unavailable rather than let you promise it.
Six more act on your account
Balance, usage, orders, credential, account state, and rotating the proxy secret. These need a token and are scoped to exactly one account — no tool takes an account identifier, so a token can only ever reach the account it was issued for. None of them can read your current proxy password, because it is not stored; the only way to obtain a working one is to rotate.
Sign-in
busyip is a standard remote MCP server — Streamable HTTP with OAuth 2.1 Dynamic Client Registration and PKCE. Point a client at the URL and it discovers and registers itself; there is no client ID or secret to paste. The first connection opens a browser, you sign in and press Approve, and the client keeps its own token from then on. Access is scoped to that one account.
Claude app (Desktop / web)
- Open Settings → Connectors
- Add custom connector
- Name it busyip and paste
https://busyip.com/mcp - Press Connect, then sign in and press Approve
Claude Code (CLI)
One command:
claude mcp add --transport http busyip https://busyip.com/mcp
OpenAI Codex CLI
Add a streamable-HTTP server to ~/.codex/config.toml:
[mcp_servers.busyip]
url = "https://busyip.com/mcp"
Or the equivalent command:
codex mcp add busyip --url https://busyip.com/mcp
Grok, or any other MCP client
Settings → Connectors / Integrations → Add MCP server → remote /
HTTP, paste https://busyip.com/mcp, authorise.
Clients that only take a header
Create a token at /app/mcp and send it as
Authorization: Bearer bimcp_…. Tokens are revocable from that page
and revoking takes effect on the next request, however the token was issued.
Discovery documents, if you want to look before connecting: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource.
What we count#
- 1 GB means 1 GiB = 1,073,741,824 bytes. We say GiB where it matters and never round in our favour.
- We bill wire bytes in both directions, including TLS handshakes and protocol overhead. Your application-level counter reads lower than ours, and the size of that gap is driven by how much you transfer per connection rather than by anything we choose. Measured on this network: +5.6% on a 120 KB fetch, and +46% on a 7 KB fetch that paid for its own TLS handshake. Reuse connections and the gap collapses toward the first number. All of it is real traffic a real person's phone carried, and they are paid for it. If your numbers do not fit that shape, tell us.
- Nothing is rounded per request or per connection, and there is no minimum billable unit.
- Enforcement is checked on a short interval, so a request in flight when your balance hits zero is allowed a bounded grace of up to 64 MiB rather than being cut mid-response. Those bytes are carried against your next top-up — they show as a negative balance until then, and at 64 MiB that is a few cents at most.
- Usage in the dashboard lags live traffic by up to a minute.
Limits on the trial#
| Trial | Paid | |
|---|---|---|
| Traffic | 1 GiB | Whatever you buy |
| Concurrent connections | 2 | 8 |
| Countries / types | All available | As sold |
| Bytes expire? | Never | Never |
Buying does not give you new credentials. The username and password above are yours for the life of the account — a purchase adds bytes to the same credential and lifts the concurrency limit. The code you just got working keeps working; you do not re-paste anything.
Honestly, what this is not#
We are a small fleet of real phones, not a 40-million-IP pool. Concretely:
- Availability is not yet guaranteed. There are hours when few or no exits are online.
- Do not build a job that must finish on a deadline against us today.
-
Country coverage is thin. If
-country-xxreturnsno_capacityconsistently, that country has no exit online — and we would rather you knew that than have us route you elsewhere and call it Moldova.
Every exit is a person who installed our app, agreed to share their connection, and is paid per gigabyte. On cellular they are a mobile IP; on Wi-Fi, a residential one. That is the whole product, and it is why the pool is small and why we can tell you exactly where your traffic came from.
If it will not connect at all#
- Timeouts on every request, no error body. Something between you and us is blocking port 18080 — corporate firewalls commonly allow only 80/443 outbound. Try the SOCKS5 port, or test from a different network.
-
DNS looks wrong, or sites resolve to the wrong region. Use
socks5h://, notsocks5://. -
It worked, then stopped. Check your balance first — an exhausted
balance returns
quota_exhausted, not a timeout.
Still stuck? Email support@busyip.com. One person reads it and they can see your account.