Every public API on Coinglass is encrypted. The response body is ciphertext — but the key is handed to you in the response headers.
Encryption Scheme
Coinglass uses AES-128-ECB plus double gzip; the decryption key arrives via response header:
Request → GET /api/spot/rsi/list (with header encryption: true)
Response → Headers: { user: <base64 token>, v: "55" }
Decryption:
1. v=55 → look up constant "170b070da9654622"
2. base64(constant)[:16] → Key0
3. AES-ECB-decrypt(user_token, Key0) → gzip(actual_key)
4. Gunzip → 16-char hex key
5. AES-ECB-decrypt(encrypted_body, actual_key) → gzip(JSON)
6. Gunzip → plaintext JSON
No API key, no cookie, no session. Anyone who can make an HTTP request can decrypt any endpoint.
Key Constants
The v header selects which constant derives the first-layer key:
| v | Constant | Source |
|---|---|---|
| 55 | 170b070da9654622 | webpack module 12471, Kt[22] |
| 66 | d6537d845a964081 | webpack module 12471, Kt[38] |
| 77 | 863f08689c97435b | webpack module 12471, BatcW |
| 1 | URL path | btoa(url_path)[:16] |
All constants are hardcoded in the frontend webpack bundle. v=55/66/77 are fixed; v=1 is dynamic — derived from the request path.
How It Was Reversed
The logic lives in Next.js webpack module 12471, publicly fetchable from S3:
https://s3.coinglass.com/v1/cg/_next/static/chunks/pages/_app-f75bb33a408a04d3.js
Module 12471 contains:
- AES-ECB encrypt/decrypt — CryptoJS
- FP — GET + encrypt + decrypt response
- xW — plain request, no encryption
- Zl — POST + encryption
- Request interceptor — auto-injects
encryption: true,cache-ts-v2, etc. - String obfuscation —
Qt(offset, seed)lookup
Key derivation is in function Yt. The three constants map to Kt[22], Kt[38], and BatcW. Obfuscation is shallow — strings are directly visible.
Implementation
# decrypt.py — core logic
_KEY_TABLE = {
"55": "170b070da9654622",
"66": "d6537d845a964081",
"77": "863f08689c97435b",
}
def decrypt(encrypted_body, user_token_b64, v, url=""):
outer = json.loads(encrypted_body)
payload = base64.b64decode(outer["data"])
token = base64.b64decode(user_token_b64)
# Derive Key0
constant = _KEY_TABLE.get(v) if v != "1" else urlparse(url).path
key0 = base64.b64encode(constant.encode()).decode()[:16]
# Layer 1: decrypt user token → actual key
step1 = unpad(AES.new(key0.encode(), AES.MODE_ECB).decrypt(token), 16)
actual_key = gzip.decompress(step1).decode()
# Layer 2: decrypt body → plaintext
step2 = unpad(AES.new(actual_key.encode(), AES.MODE_ECB).decrypt(payload), 16)
return json.loads(gzip.decompress(step2).decode())
Usage:
from decrypt import fetch_and_decrypt
data = fetch_and_decrypt(
"https://capi.coinglass.com/api/spot/rsi/list",
{"pageSize": 500, "pageNum": 1},
)
print(data["list"][0]["rsi4h"]) # → "62.34"
Scale
More than 280 encrypted endpoints were uncovered, covering:
- Spot — RSI, market cap, price moves
- Futures — funding rates, open interest, liquidations, long/short ratios
- Options — OI, volume, Max Pain
- ETF flows — BTC/ETH/SOL/XRP
- On-chain metrics — SOPR, MVRV, active addresses, CDD and 50+ more
- On-chain indices — CGDI, CGRI, Pi Cycle, AHR999, Puell Multiple
- Macro — economic calendar, TradFi overview
- Hyperliquid — vaults, whale positions, user distribution
Full list in discovered_endpoints.py
Disclaimer
For education and research only. The scheme was reversed from publicly accessible frontend code. Please respect Coinglass’s Terms of Service.