CoinGlass API Decrypt
Use this skill for CoinGlass web API responses that return encrypted payloads, commonly shaped like:
{"code":"0","msg":"success","data":"<base64 ciphertext>","success":true}
The goal is to reproduce the browser frontend's decrypt path, not to guess fields.
Quick Workflow
- Ask for or capture the full browser curl, including request headers and response headers.
- Preserve request headers exactly when possible. Current endpoints often require:
language: zh
encryption: true
cache-ts-v2: <milliseconds timestamp>
obe: <browser-generated token>
Referer: https://www.coinglass.com/
User-Agent: browser UA
Accept: application/json
- Confirm the response includes encrypted
data. If it only returns{"code":"0","msg":"success","success":true}, the request is missing a required runtime header, usuallyobeorcache-ts-v2. - Inspect response headers. Important headers include:
encryption
v
ev
user
time
- Implement the decrypt chain for the detected version.
Observed Decrypt Chains
v=0 / ev=2
This is the chain observed for:
GET https://capi.coinglass.com/api/home/v2/coinMarkets
Request side:
cache-ts-v2 = T
Key derivation:
initialKey = base64(T).slice(0, 16)
Decrypt response header user:
dataKeyGzip = AES-ECB-PKCS7-Decrypt(base64(user), initialKey)
dataKey = gzip_decompress(dataKeyGzip)
Decrypt response body data:
plaintextGzip = AES-ECB-PKCS7-Decrypt(base64(data), dataKey)
plaintextJson = JSON.parse(gzip_decompress(plaintextGzip))
Expected output:
{"total":857,"pageSize":20,"list":[...]}
v=1 / ev=2
As of 2026-05-21, the same endpoint may return v: 1, ev: 2,
user: <ciphertext>, and no time response header. Do not assume v=1
uses time; inspect the current frontend request/response interceptors.
For /api/home/v2/coinMarkets, the observed browser chain is:
urlSeed = "/api/home/v2/coinMarkets"
initialKey = base64(urlSeed).slice(0, 16)
dataKeyGzip = AES-ECB-PKCS7-Decrypt(base64(user), initialKey)
dataKey = gzip_decompress(dataKeyGzip)
plaintextGzip = AES-ECB-PKCS7-Decrypt(base64(data), dataKey)
plaintextJson = JSON.parse(gzip_decompress(plaintextGzip))
Important details from the regression:
- Keep
/apiin the URL seed for thisv=1branch. - Strip query parameters before deriving the key.
- Slice the base64 URL seed to the first 16 characters.
- Decrypt the response header
user, nottime, when the response has notimeheader. - Validate by trying candidate seeds against the
userheader first. The correct seed should pass PKCS7 unpadding and then decryptdatato JSON.
Static-key versions such as v=55 / v=66 / v=77
Some responses use a v value whose seed is a static key hidden in the current
obfuscated frontend bundle. In the 2026-05-24 regression, the same endpoint
returned v=66 or v=77; the current bundle mapped:
v=55 -> 170b070da9654622
v=66 -> d6537d845a964081
v=77 -> 863f08689c97435b
Use this workflow when a script says the current v uses a static key or when
the response version changes unexpectedly:
- Reproduce with the exact request headers and print a small response header
subset:
v,ev,user,time,encryption, plus the stored requestcache-ts-v2. - Fetch
https://www.coinglass.com/zhand locate the current Next.js app chunk, usually shaped like:
https://s3.coinglass.com/v1/cg/_next/static/chunks/pages/_app-<hash>.js
- Inspect the app bundle for the response interceptor and the key selector
function. Useful strings before obfuscation or nearby decoded references
include
headers.v,headers.encryption,headers.user,headers.time,AES.decrypt, andcache-ts-v2. - Find the function that switches on
response.headers.v. In the observed bundle it selected a seed, then returnedbtoa(seed). - For static versions, decode the obfuscated string table rather than guessing
the key. The observed bundle used a base64-like decoder plus RC4-style string
decoding. Pay attention to JavaScript operator precedence and
charCodeAtbehavior when porting this decoder to Python. - Derive the initial key as:
initialKey = base64(seed).slice(0, 16)
- Decrypt the second-stage key from response header
userwhen present; fall back totimeonly whenuseris absent. Then decrypt bodydatawith the second-stage key. - Validate with a small live request, for example
pageSize=1, so command output truncation does not mask a broken pipe or partial failure.
For reusable scripts, keep a local static mapping for currently observed
versions, but also implement a runtime bundle parser for unknown static-key
versions. CoinGlass may rotate the v branch or publish a new _app-<hash>.js
without changing the API path.
Algorithm Notes
- AES mode:
ECB - Padding:
PKCS7 - Ciphertext encoding: base64
- Payload compression: gzip for the current v=0/ev=2 endpoints
- Older or alternate endpoints may decrypt to a hex string containing zlib-compressed bytes. Detect by checking whether decrypted bytes start with gzip magic
1f 8b; otherwise trybytes.fromhex(...)then zlib inflate. - Frontend uses CryptoJS style calls such as
CryptoJS.AES.decrypt(ciphertext, CryptoJS.enc.Utf8.parse(key), { mode: ECB, padding: Pkcs7 }). Pythoncryptographyworks for 16/24/32-byte keys. If a CoinGlass branch uses non-standard CryptoJS WordArray key sizes, use or port the bundled script's CryptoJS-compatible AES helper.
Script Starter
When the user asks for a script, output or create a Python script for the specific API being worked on. Name it after the API path, not generically. Convert /api/home/v2/coinMarkets to:
api_home_v2_coinMarkets_decrypt.py
Use this naming convention:
<api path without leading slash, slashes replaced by underscores>_decrypt.py
If query parameters identify the data variant, append the meaningful variant before _decrypt.py, for example:
api_fundingRate_list_BTC_decrypt.py
Use scripts/coinglass_decrypt.py as the starting point/template when creating the endpoint-specific script. Typical command for the bundled example:
python scripts/coinglass_decrypt.py 1 --pretty
For a different CoinGlass endpoint:
- Change
COINGLASS_HOME_MARKETS_URL - Keep the browser curl headers aligned, especially
cache-ts-v2andobe - Keep
cache-ts-v2stored with the response because it is required to decrypt headeruser - If the response version changes, inspect the current frontend bundle and map the
vbranch before modifying key derivation - If a script fails with
Missing response header: time, print a small response header subset first:v,ev,user,time,encryption, and the stored requestcache-ts-v2. A current response withv=1,userpresent, andtimeabsent likely needs thev=1 / ev=2chain above. - Save the generated script with the endpoint-derived name, and include a short usage example in the final answer
Frontend Inspection Hints
When the version changes, search the frontend chunks for:
AES.decrypt
headers.encryption
headers.user
headers.time
cache-ts-v2
obe
If names are obfuscated, inspect the function that selects the initial key by
headers.v, plus the request interceptor that prepares the URL seed. In the
2026-05-21 regression, the request interceptor stripped query parameters, kept
the /api prefix, and applied .substring(0, 16) to the base64 seed. Avoid
relying on guessed field names.
For static-key versions, do not stop at "unsupported v". Fetch the current app
bundle, decode the string table around the headers.v key selector, recover
the version-to-seed mapping, and verify the chain against user before trying
to decrypt data.