GET /v1/balances
Returns the caller's per-currency balance snapshot — one row per (currency, network) for which a balance record exists on this account.
| Method & path | GET /v1/balances |
| Required permission | read_balance |
| Auth | HMAC — see Authentication |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
currency | string | optional | Filter to a single currency symbol (case-insensitive on the server, but sign with the exact case you'll send). |
network | string | optional | Filter to a single network code (e.g. ETH, BSC, TRX). |
Pass both to drill down to a single (currency, network) row.
Response
{
"success": true,
"message": "OK",
"data": [
{
"currency": "USDT",
"network": "ETH",
"balance": "1500.000000",
"held": "200.000000",
"available": "1300.000000"
}
],
"data_count": 1
}
| Field | Type | Description |
|---|---|---|
currency | string | Currency symbol. |
network | string | Network code. |
balance | string | Confirmed credited balance at this currency's full decimal precision. Always a string — never parse as a JS number. |
held | string | Amount currently reserved by in-flight withdrawals (not yet confirmed). |
available | string | balance - held. Spendable amount, the right number to display in your UI as "available". |
Decimals are strings across the entire API so there's no float precision loss. Parse with decimal.Decimal (Python), BigNumber.js (Node), or similar.
Errors
Standard middleware errors only — no business-logic failures on this read endpoint. See the Errors page for the full catalog.
| Status | Cause |
|---|---|
401 | Missing or invalid signature / timestamp / nonce. |
403 | Key lacks read_balance, or source IP not whitelisted. |
429 | Rate limit exceeded. |
Code samples
Each sample assumes the sign_request(...) helper from the Authentication page is in scope. Only the request itself is shown.
- curl + bash
- Python
- Node.js
- PHP
METHOD="GET"
ENDPOINT="/v1/balances"
QUERY="currency=USDT&network=ETH"
BODY=""
# ... sign exactly as on the Authentication page (same TS / BODY_HASH / CANONICAL / SIG block) ...
curl -sS "${BASE_URL}${ENDPOINT}?${QUERY}" \
-H "X-CPG-Key: ${API_KEY}" \
-H "X-CPG-Timestamp: ${TS}" \
-H "X-CPG-Signature: ${SIG}"
query = "currency=USDT&network=ETH"
headers = sign_request("GET", "/v1/balances", query=query)
resp = requests.get(f"{BASE_URL}/v1/balances?{query}", headers=headers, timeout=10)
resp.raise_for_status()
print(resp.json())
const query = 'currency=USDT&network=ETH';
const headers = signRequest('GET', '/v1/balances', '', query);
const resp = await fetch(`${BASE_URL}/v1/balances?${query}`, { headers });
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
console.log(await resp.json());
$query = 'currency=USDT&network=ETH';
$headers = sign_request('GET', '/v1/balances', '', $query);
$ch = curl_init("$baseUrl/v1/balances?$query");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
echo curl_exec($ch);
curl_close($ch);
Notes
- Not paginated. The response is one row per
(currency, network)balance record — usually a small set (fewer than 20 rows). The envelope carriesdata+data_countonly; there are nocursor/next_cursor/has_morefields. heldis updated optimistically. It bumps the moment you submit a withdrawal (POST /v1/withdrawals) and clears when that withdrawal either confirms (becomes a realbalancedecrement) or fails (released back). Soavailablein this response is a true spendable number — you can callPOST /v1/withdrawalsagain immediately for anyavailableamount without risk of double-spending.- Sign the exact query string you'll send. The middleware reconstructs the canonical string from
request.url.query. If you signcurrency=USDT&network=ETHbut your HTTP client transmitsnetwork=ETH¤cy=USDT, the signature won't match.