Bybit API key setup and request signing
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 V5 API is straightforward once the signature is right, and the signature is where almost everyone loses an hour. The reason is specific: Bybit’s pre-hash string is a different shape from Binance’s and OKX’s, so code adapted from another exchange produces a signature that is confidently wrong.
The headers
Four, on every private request:
| Header | Content |
|---|---|
X-BAPI-API-KEY | The API key |
X-BAPI-TIMESTAMP | UTC timestamp in milliseconds |
X-BAPI-SIGN | The derived signature |
X-BAPI-RECV-WINDOW | Optional; default 5000 ms |
Broker users additionally send X-Referer or Referer.
The signature
Algorithm: HMAC_SHA256 or RSA_SHA256.
The pre-hash string:
- GET —
timestamp + api_key + recv_window + queryString - POST —
timestamp + api_key + recv_window + jsonBodyString
Bybit’s own worked example:
1658384314791XXXXXXXXXX5000category=option&symbol=BTC-29JUL22-25000-C
which produces
410e0f387bafb7afd0f1722c068515e09945610124fa11774da1da857b72f30b
Note what is concatenated and what is not. There are no separators, the method is not included, and the request path is not included.
Why code from another exchange fails here
The three major venues each do this differently, and none of the differences are cosmetic:
| Venue | Pre-hash string |
|---|---|
| Bybit | timestamp + api_key + recv_window + queryString | body |
| Binance | Query string concatenated with the HTTP body |
| OKX | timestamp + method + requestPath + body |
So a signer ported from OKX will include the HTTP method and the path — both absent from Bybit’s construction. A signer ported from Binance will omit the timestamp, key and recv window from the signed material. Both produce a well-formed signature that Bybit will reject.
If you are adapting working code, rewrite the pre-hash construction from Bybit’s documentation rather than adjusting the one you have. It is faster and you will not be left wondering which of three differences you missed.
Getting the environment right
Testnet is a separate hostname:
| Purpose | URL |
|---|---|
| Mainnet | https://api.bybit.com (alternate https://api.bytick.com) |
| Testnet | https://api-testnet.bybit.com |
There are also regional mainnet domains — .nl, .tr, .kz, .ge, .ae,
.eu, .id, plus manepa.jp for Japan and spark-fintech.com for Hong Kong.
A separate testnet host is the safe arrangement: testnet credentials do not exist on mainnet, so pointing at the wrong host produces an authentication error rather than a live trade. Compare OKX, where demo and production share a hostname and the environment is carried by a header.
Telling failures apart
The useful skill is distinguishing signing problems from key problems from account-type problems, because they look similar and have nothing to do with each other.
Signing or transport
| Symptom | Likely cause |
|---|---|
HTTP 401 | Wrong key, or auth params not placed in the request header |
| Auth failure with correct-looking key | Pre-hash construction — see above |
| Intermittent failure, some endpoints only | Query-string ordering or encoding differing between signed and sent |
Key state
| Code | Meaning |
|---|---|
10010 | “Unmatched IP, please check your API key’s bound IP addresses.” |
10005 | “Permission denied, please check your API key permissions.” |
10003 | Classic: too many sessions under the same UID. UTA spot: your key has expired |
33004 | UTA derivatives: your key has expired |
Account type — these two are near-mirror images and genuinely confusing:
| Code | Meaning |
|---|---|
110028 | “The API can only be accessed by unified account users.” |
100028 | “The API cannot be accessed by unified account users.” |
If you are hitting either, the endpoint and your account type disagree. Check which you have before changing any code.
Region — HTTP 403 is documented for IP rate limit breaches, for GET
requests sent with an empty JSON body, and for requests from US IPs. Three
unrelated causes, one status code.
Practical setup notes
- Sync your clock. The timestamp is in the signature and
recvWindowdefaults to 5000 ms. Drift produces authentication failures that look like signing bugs. - Log the pre-hash string during development. Not the secret — the string being signed. Comparing it against the documented example is the fastest way to find a construction error.
- Start read-only. Permission failures then surface as
10005immediately, rather than as a surprise when you first place an order. - Bind an IP if you can.
10010exists because IP binding is enforced; it is the cheapest reduction in what a leaked key is worth.
FAQ
Why is my Bybit signature rejected when the same code works on another exchange?
Because the pre-hash string is different. Bybit signs
timestamp + api_key + recv_window + queryString (or the JSON body for POST),
with no separators, no HTTP method and no request path. OKX includes the method
and path; Binance signs the query string concatenated with the body. Rewrite the
construction from Bybit’s documentation rather than adapting.
What does Bybit error 10003 mean?
It depends on your account type, which is why it is confusing. On classic
accounts it means too many sessions under the same UID. Under a Unified Trading
Account on spot, it means your API key has expired — and the derivatives
equivalent is 33004. Check which account type you have before debugging.
What is X-BAPI-RECV-WINDOW for?
It is how long Bybit will accept the request after the signed timestamp, defaulting to 5000 ms. It exists to bound replay. If you see intermittent authentication failures on an otherwise correct setup, clock drift against this window is a strong suspect.
Is Bybit testnet a separate account?
Yes — separate hostname at https://api-testnet.bybit.com and separate
credentials. Testnet keys do not work against mainnet, which means an
environment mix-up fails with an auth error rather than placing a real order.