Learn / Exchange APIs

Bybit testnet setup

Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs before acting on a number.

Bybit’s testnet is a separate hostname with separate credentials:

PurposeURL
Testnethttps://api-testnet.bybit.com
Mainnethttps://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 hostname between demo and production and switches with the x-simulated-trading: 1 header, where a dropped header means production.

Mainnet also has regional domains — .nl, .tr, .kz, .ge, .ae, .eu, .id, plus manepa.jp for Japan and 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 optional X-BAPI-RECV-WINDOW (default 5000 ms)
  • Signature: HMAC_SHA256 or RSA_SHA256
  • Pre-hash string: timestamp + api_key + recv_window + queryString for GET, or + jsonBodyString for 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

Account type. This is the Bybit-specific trap, because it changes what error codes mean rather than whether things work.

Under a Unified Trading Account, several codes differ from classic:

CodeClassicUTA
10003“Too many sessions under the same UID”“Your api key has expired” (spot)
33004“Your api key has expired” (derivatives)
10006“Too many visits. Exceeded the API Rate Limit.”
10018“Exceeded the IP Rate Limit.”

And two codes tell you which you are on:

  • 110028 — “The API can only be accessed by unified account users.” (you are not on UTA)
  • 100028 — “The API cannot be accessed by unified account users.” (you are)

If your testnet account and your mainnet account are different types, error handling validated on one will misinterpret the other. Check that they match 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 type verified to match what you tested against — check via 110028 / 100028 if unsure
  • IP binding configured if you intend to use it (10010 is its signature when it is wrong)
  • Key permissions confirmed — 10005 is “Permission denied, please check your API key permissions”
  • Rate limit handling exercised, and order retries not automatic
  • Region checked — HTTP 403 is documented for US IPs, 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.

Your testnet account type is chosen independently. Which is the mechanism behind the mismatch described above: it is entirely possible to have a classic testnet account and a Unified Trading Account on mainnet without ever having made a decision about it.

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 10006 versus 10018 — account-level versus IP-level — need different fixes.
  • Region restrictions. The HTTP 403 for US IPs 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 my error codes mean different things on mainnet?

Most likely your testnet and mainnet accounts are different types. Under a Unified Trading Account, 10003 on spot means an expired API key, while on classic accounts it means too many sessions under the same UID. Confirm which type each account is — 110028 and 100028 distinguish them — before trusting error handling you validated elsewhere.

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.