# How to bind an IP address to a Bybit API key

> A Bybit API key without a bound IP address stops working after 90 days; a key with at least one whitelisted address stays valid indefinitely. Since February 10, 2026, that list can only be edited in a browser — no API call changes it. Here’s the full path: creating the key, scoping its permissions, binding an address that Bybit actually accepts, and signing V5 requests.

- Source: https://papaproxy.net/blog/bybit-api-key-ip-whitelist.php
- Published: 2026-08-06
- Author: Alex Young
- Category: Security & access · PapaProxy.net Blog

---

## Key takeaways

- A Bybit API key with no bound IP becomes invalid after 90 days; bind at least one address and the expiration is removed entirely.
- Since February 10, 2026, whitelist entries on master API keys can’t be added or removed through the API — the edit is browser-only, so provisioning scripts must hand that step to a human.
- Error 10010 `Unmatched IP` means the request arrived from an address outside the key’s list; verify the egress IP through the same route your bot uses.
- Bybit answers with 403 to requests from US and mainland China IPs before authentication even starts, so check that Bybit responds from an address before you bind it.
- We ran that check on our own pools: a 1,000-IP datacenter package, a 1,000-IP ISP package, and 50 randomly sampled dedicated proxies — zero blocked subnets in all three sets.
- A V5 signature covers the string `timestamp + api_key + recv_window + parameters`, signed with HMAC SHA256 (lowercase hex) or RSA SHA256 (base64); the timestamp must fall within `[server_time − recv_window; server_time + 1000)`.

## How to create an API key on Bybit

Key creation lives in the website only: log in from a desktop browser, open Account → API Management, and click to create a new key. Two conditions catch people at this step. The mobile app doesn’t create API keys at all, and on a fresh account the option may not appear for the first 48 hours — Bybit’s Help Center guide to API keys notes that creation can be restricted after registration for risk control.

The form asks you to pick a key type. System-generated keys use HMAC: Bybit issues both the key and the secret, and the secret is shown once, at creation — store it before closing the page. Self-generated keys use RSA: you create the key pair locally and upload only the public part, so Bybit never holds your private key. Creation is confirmed with 2FA and an email code.

One more thing to fix at this stage is the environment. Mainnet, testnet, and the two demo environments use separate hosts and separate keys. Error 10003 `API key is invalid` often isn’t about the key at all — it’s a testnet key sent to `api.bybit.com` or the reverse.

## Choosing API key permissions

Permissions are set per product area — contract trading, spot trading, wallet transfers, convert, earn — on top of the base read-only or read-and-write choice. The working rule is the minimum set: a dashboard that only tracks positions needs read-only access; a spot bot needs read-and-write plus the Spot scope and nothing else; transfer and withdrawal rights belong only on keys whose entire job is moving funds, and those keys deserve the strictest IP list of all.

Give each integration its own key instead of reusing one everywhere. Scoped keys limit the damage of a single leak and let you revoke one tool without stopping the rest. What a complete permission set looks like for an automated strategy — and what else a bot needs beyond a working order call — we broke down in [the guide to building a trading bot on the Binance API](/blog/binance-trading-bot.php); the exchange differs, the reasoning transfers as is.

## Binding an IP address: what the whitelist changes

The whitelist documentation puts the main rule in one line: an API key with no bound IP becomes invalid after 90 days. Bind at least one address and the countdown disappears — the key no longer expires. There’s a second, less-known timer: after an account password change, a key without a bound IP is invalidated within 7 days.

You can watch the countdown from the API itself. `GET /v5/user/query-api` returns `deadlineDay` and `expiredAt` — and only for keys with no bound IP or after a password change; a bound key simply carries no expiry. When an unbound key does lapse, Spot requests return error -2015 `Your api key has expired` and derivatives requests return 33004.

Editing the list changed in 2026. The V5 changelog states it directly: effective February 10, 2026, users can no longer add or remove IP whitelist entries via the Modify Master API Key endpoint. An API attempt now returns error 141019, which says to go to API Management and select Edit. In practice this means whitelist changes are a manual, browser-based step — deployment pipelines that re-provisioned servers and updated key whitelists automatically need a human in the loop now. That’s one more argument for binding an address that never changes.

If a request comes from an address outside the list, Bybit returns error 10010 `Unmatched IP, please check your API key’s bound IP addresses` — the most common failure on freshly bound keys, and almost always an egress mismatch rather than a Bybit-side problem.

Binance runs the same 90-day clock, with a different consequence: there, an unrestricted key loses its spot trading permission rather than dying outright. We covered [how the Binance API key IP whitelist works](/blog/binance-api-key-ip-whitelist.php) separately; if you run bots on both exchanges, the two lists are maintained independently and both point at the same static egress address.

## Which address to bind — and our reachability test

The address on the list must be your egress IP — the one Bybit sees, not the one your machine shows locally. Behind NAT the local address is `192.168.x.x` while the exchange sees the router; a container platform can change egress between deployments; a residential connection can get a new address from the ISP overnight. Check it through the exact route the bot uses:

PythonCopy code

```python
import requests

# Plain route — the egress IP of the machine itself:
print(requests.get("https://api.ipify.org").text)

# Through the bot's proxy — the address Bybit will actually see:
proxy = {"https": "http://login:password@PROXY_IP:port"}
print(requests.get("https://api.ipify.org", proxies=proxy).text)
```

Whatever the second call prints is what belongs in the whitelist. And since every list edit now costs a browser login, a stable address stopped being a convenience and became the design requirement: one static egress IP per key, bound once.

Stability isn’t the only constraint — Bybit must be willing to answer that address at all. The integration guide warns that requests from IPs in the US or mainland China get 403 Forbidden, and several countries are served from their own regional hosts (`api.bybit.nl`, `api.bybit.tr`, `api.bybit.kz`, and others). Beyond geography, Bybit’s edge infrastructure is known to reject requests from IP ranges of major cloud platforms — a 403 before authentication even runs, which is easy to misread as a key problem.

So before binding an address, confirm the exchange responds from it. We ran that check across our own pools — a request to the public V5 API from every address, counting subnets Bybit refused:

| Address set | Addresses checked | Subnets blocked by Bybit |
| --- | --- | --- |
| Datacenter package | 1,000 | 0 |
| ISP (static residential) package | 1,000 | 0 |
| Dedicated proxies, random sample | 50 | 0 |

Every address received a normal API response. The scope is deliberately narrow — this is a reachability check, not a stress test of rate limits or account bans — but it answers the question that matters for a whitelist: an address you bind from these pools is an address Bybit talks to.

That’s the setup our [crypto proxies](/crypto-proxy.php) are built for: dedicated IPv4 addresses that stay static for the plan term, one per key or bot, in locations outside Bybit’s restricted regions, with HTTP, HTTPS, SOCKS4, and SOCKS5 on every address and access by IP whitelisting. Bind the same proxy IP in Bybit’s API Management once, and the key neither expires nor trips 10010.

## Signing requests: V5 authentication

Binding an IP doesn’t change how requests are signed — V5 authentication rides in four HTTP headers: `X-BAPI-API-KEY`, `X-BAPI-TIMESTAMP` (UTC, milliseconds), `X-BAPI-RECV-WINDOW` (defaults to 5,000 ms), and `X-BAPI-SIGN`. The string you sign is the concatenation `timestamp + api_key + recv_window + queryString` for GET requests, with the raw JSON body in place of the query string for POST. HMAC keys sign it with SHA256 and send lowercase hex; RSA keys send base64.

Here’s a complete signature example in Python — the call requests your own key’s state, so it doubles as a whitelist check:

PythonCopy code

```python
import hmac, hashlib, time, requests

API_KEY = "YOUR_API_KEY"
API_SECRET = "YOUR_API_SECRET"
RECV_WINDOW = "5000"

def signed_get(path, params):
    ts = str(int(time.time() * 1000))
    query = "&".join(f"{k}={v}" for k, v in params.items())
    payload = ts + API_KEY + RECV_WINDOW + query
    sign = hmac.new(API_SECRET.encode(), payload.encode(),
                    hashlib.sha256).hexdigest()
    return requests.get(
        "https://api.bybit.com" + path,
        params=params,
        headers={
            "X-BAPI-API-KEY": API_KEY,
            "X-BAPI-TIMESTAMP": ts,
            "X-BAPI-RECV-WINDOW": RECV_WINDOW,
            "X-BAPI-SIGN": sign,
        },
    )

# Returns the key's permissions, bound IPs, and — for unbound keys — expiredAt:
print(signed_get("/v5/user/query-api", {}).json())
```

Two errors dominate this stage. Error 10004 `Error sign` means the signed string didn’t byte-match what was sent — the query string in the payload must be exactly the one that goes on the wire, including parameter order. Error 10002 points at the clock: the timestamp must satisfy `server_time − recv_window ≤ timestamp < server_time + 1000`, so a machine that drifts a second ahead of the exchange starts failing. Keep the clock NTP-synced; the [clock-sync practice we described for the Binance API](/blog/binance-api-server-time.php) applies to Bybit unchanged.
