Bybit testnet setup
Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs.
Bybit’s testnet is a separate hostname with separate credentials:
| Purpose | URL |
|---|---|
| Testnet | https://api-testnet.bybit.com |
| Mainnet | https://api.bybit.com (alternate https://api.bytick.com) |
Testnet keys do not exist on mainnet, so pointing at the wrong host produces an
authentication error rather than a live order. That is the arrangement you want,
and it is worth appreciating — OKX, by contrast, shares a REST hostname between
demo and production and switches with the x-simulated-trading: 1 header,
where a dropped header means production.
Mainnet also has regional hosts — api.bybit.tr, api.bybit.kz,
api.bybitgeorgia.ge, api.bybit.ae, api.bybit.eu, api.bybit.id, plus
api.manepa.jp for Japan and api.spark-fintech.com for Hong Kong. Which one
applies to you affects the host, not the mechanics.
What carries over
The request mechanics are identical, which is the point of testing there:
- Headers:
X-BAPI-API-KEY,X-BAPI-TIMESTAMP(milliseconds),X-BAPI-SIGN, and optionalX-BAPI-RECV-WINDOW(default 5000 ms) - Signature: HMAC_SHA256 or RSA_SHA256
- Pre-hash string:
timestamp + api_key + recv_window + queryStringfor GET, or+ jsonBodyStringfor POST
So the signing code you get working on testnet is the signing code that runs on mainnet. Since Bybit’s pre-hash construction differs from both Binance’s and OKX’s — no method, no request path, no separators — testnet is the right place to get that right.
The difference that does not carry over
The key. A key belongs to exactly one environment, and Bybit has four:
mainnet, testnet, mainnet-demo and testnet-demo. Over REST, sending a key to
the wrong environment’s host returns 10003, documented as “API key is
invalid. Check whether the key and domain are matched, there are 4 env:
mainnet, testnet, mainnet-demo, testnet-demo”. Bybit’s own FAQ calls a
key/domain mismatch the most likely cause of 10003.
Codes that look similar but mean something else:
| Code | Meaning |
|---|---|
10003 (WebSocket order entry) | “Too many sessions under the same UID” |
-2015 | “(Spot) Your api key has expired” |
33004 | “(Derivatives) Your api key has expired” |
10006 | “Too many visits. Exceeded the API Rate Limit.” |
10018 | “Exceeded the IP Rate Limit.” |
Account mode is a separate question from environment. Two near-mirror codes flag an endpoint that requires, or excludes, a unified account:
10028— “The API can only be accessed by unified account users.”100028— “The API cannot be accessed by unified account users.”
Bybit reports the mode as unifiedMarginStatus from Get Account Info. If you
see either code on one environment and not the other, compare that field on
both before you conclude your error handling works.
Migration checklist
- Base URL changed to mainnet, and logged at startup so the environment is visible rather than inferred
- Mainnet credentials in place; testnet credentials removed from the environment entirely
- Account mode verified to match what you tested against — compare
unifiedMarginStatusfrom Get Account Info on both - IP binding configured if you intend to use it (
10010is its signature when it is wrong) - Key permissions confirmed —
10005is “Permission denied, please check your API key permissions” - Rate limit handling exercised, and order retries not automatic
- Region checked — HTTP
403is documented for restricted regions (the integration guide names the US and Mainland China), as well as for IP rate limit breaches and GET requests with an empty JSON body - First live orders sized small
Deriving both from one switch
The failure mode worth engineering against is not testnet credentials on mainnet — that fails immediately and loudly. It is mainnet credentials with a testnet URL still set in a code path you did not exercise.
Derive both from a single setting so they cannot disagree:
BASE, KEY, SECRET = (
("https://api-testnet.bybit.com", TESTNET_KEY, TESTNET_SECRET)
if ENV == "testnet"
else ("https://api.bybit.com", LIVE_KEY, LIVE_SECRET)
)
log.info("bybit environment=%s base=%s", ENV, BASE)
One switch, two values, one log line.
Testnet accounts are their own thing
Testnet credentials are issued from the testnet site, not from your mainnet account, and the two are unrelated. Two things follow that trip people up:
Testnet environments get reset. Balances and keys can be wiped as part of maintenance. Do not build anything that assumes a testnet key is permanent, and do not spend time debugging a testnet key that stopped working — regenerate it first and see whether that was the whole problem.
Demo Trading is not testnet. Bybit’s Demo Trading runs on
https://api-demo.bybit.com, and its key is created on production while in
Demo Trading mode. It is a different environment from testnet, with different
keys — Bybit advises against using Demo Trading on Testnet, and mixing the two
is exactly the key/domain mismatch that returns 10003.
What testnet cannot tell you
- Fills and slippage. Testnet liquidity is not real; execution conclusions drawn from it are not evidence.
- Rate limits in anger. Testnet traffic rarely reproduces production
pressure, and
10006versus10018— account-level versus IP-level — need different fixes. - Region restrictions. The HTTP
403for restricted regions (US, Mainland China per the integration guide) is a production concern. - Your own behaviour. A workflow that felt fine on testnet feels different when the number is real. That is not a flaw in testnet; it is outside what it simulates.
FAQ
Do Bybit testnet keys work on mainnet?
No. Testnet credentials exist only on api-testnet.bybit.com and mainnet
credentials only on api.bybit.com. This is a safety property — an environment
mix-up gives you an authentication error rather than a real trade.
Why do I get 10003 after switching to mainnet?
Over REST, 10003 means the API key is invalid for the host you sent it to —
Bybit’s FAQ names a key/domain mismatch as the most likely cause. Check that
the base URL and the credentials were switched together, and that a Demo
Trading key is not being sent to testnet or mainnet. (On the WebSocket
order-entry channel the same code means too many sessions; an expired key is
-2015 or 33004, not 10003.)
Is the signature the same on testnet and mainnet?
Yes. Same headers, same HMAC_SHA256 or RSA_SHA256, same pre-hash string of
timestamp + api_key + recv_window + queryString or body. Testnet is the right
place to get the construction right, since Bybit’s differs from Binance’s and
OKX’s.
Can I use one key for testnet and mainnet?
No, and that is deliberate. Separate credentials per environment are what make pointing at the wrong host fail safely.