curl --request GET \
--url https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance \
--header 'API-KEY: <api-key>' \
--header 'Api-Version: <api-version>'import requests
url = "https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance"
headers = {
"Api-Version": "<api-version>",
"API-KEY": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {'Api-Version': '<api-version>', 'API-KEY': '<api-key>'}
};
fetch('https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"API-KEY: <api-key>",
"Api-Version: <api-version>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Api-Version", "<api-version>")
req.Header.Add("API-KEY", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance")
.header("Api-Version", "<api-version>")
.header("API-KEY", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Api-Version"] = '<api-version>'
request["API-KEY"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"walletId": "wlt_eoGwAJpq8IWq1ZAbAUqz",
"address": "0x297774861c4985bc3100230fbee9e6865353d467",
"chain": "all",
"tokens": [
{
"chain": "Ethereum",
"symbol": "ETH",
"decimals": 18,
"balance": "0",
"formatted": "0.0",
"usd": null
},
{
"chain": "Ethereum",
"symbol": "USDC",
"contractAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"decimals": 6,
"balance": "0",
"formatted": "0.0",
"usd": 0
},
{
"chain": "BaseSepolia",
"symbol": "USDC",
"contractAddress": "0xfdaf25a361132d8fd8D6212D0C68D802a16f3B10",
"decimals": 6,
"balance": "100000000",
"formatted": "100.0",
"usd": 100
},
{
"chain": "BaseSepolia",
"symbol": "USDT",
"contractAddress": "0x6e189E7a850f38e1d649715B3EdBa0d132858f11",
"decimals": 6,
"balance": "100000000",
"formatted": "100.0",
"usd": 100
}
],
"totalUsd": 200,
"unpricedAssets": [
"Ethereum:ETH",
"Base:ETH",
"ArbitrumOne:ETH",
"Optimism:ETH",
"Polygon:POL",
"BaseSepolia:ETH"
],
"source": "rpc"
}{
"code": "ERRREF_1000",
"message": "'wallet-123' is not a valid wallet id",
"name": "The supplied reference id is blank, malformed, or of the wrong type"
}{
"code": "unauthorized",
"message": "Invalid API key"
}{
"code": "forbidden",
"message": "Source IP not allowed for this API key (observed: 203.0.113.42)"
}{
"code": "ERRWLT_1000",
"message": "Wallet 'wlt_eoGwAJpq8IWq1ZAbAUqz' not found",
"name": "The wallet does not exist or does not belong to this user"
}{
"code": "too_many_requests",
"message": "Rate limit exceeded, please retry later"
}{
"errors": [
{
"code": "unexpected",
"message": "An unexpected error occurred, you may try again later"
}
]
}Get wallet balance
Returns the on-chain balances of a single customer wallet — every token the wallet service tracks on every supported network, with raw and human-readable amounts plus USD valuation.
Read-only and safe to retry. There is no request body and no query parameters.
Behaviour worth knowing
Chain selection is automatic. EVM wallets are queried with the aggregated all scope, which returns per-network assets in a single call. TRON wallets are queried on their own network (Tron / TronNile), because the aggregate does not cover TRON. You cannot override this.
Zero balances are included. tokens is the full supported matrix for the wallet, not just funded holdings. Filter client-side if you only want non-empty assets.
It is a live read. source: "rpc" means each call hits chain nodes, which is why it takes roughly a second. Cache on your side rather than polling in a tight loop.
Ownership is enforced on both ids. A wallet that exists but belongs to another customer is reported as not found, never as forbidden — so a partner can never probe for another customer’s wallets.
Sandbox USD figures are derived from testnet balances and are not real value.
curl --request GET \
--url https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance \
--header 'API-KEY: <api-key>' \
--header 'Api-Version: <api-version>'import requests
url = "https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance"
headers = {
"Api-Version": "<api-version>",
"API-KEY": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {'Api-Version': '<api-version>', 'API-KEY': '<api-key>'}
};
fetch('https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"API-KEY: <api-key>",
"Api-Version: <api-version>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Api-Version", "<api-version>")
req.Header.Add("API-KEY", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance")
.header("Api-Version", "<api-version>")
.header("API-KEY", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.endl.io/api/v0/wallets/{userId}/{walletId}/balance")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Api-Version"] = '<api-version>'
request["API-KEY"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"walletId": "wlt_eoGwAJpq8IWq1ZAbAUqz",
"address": "0x297774861c4985bc3100230fbee9e6865353d467",
"chain": "all",
"tokens": [
{
"chain": "Ethereum",
"symbol": "ETH",
"decimals": 18,
"balance": "0",
"formatted": "0.0",
"usd": null
},
{
"chain": "Ethereum",
"symbol": "USDC",
"contractAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"decimals": 6,
"balance": "0",
"formatted": "0.0",
"usd": 0
},
{
"chain": "BaseSepolia",
"symbol": "USDC",
"contractAddress": "0xfdaf25a361132d8fd8D6212D0C68D802a16f3B10",
"decimals": 6,
"balance": "100000000",
"formatted": "100.0",
"usd": 100
},
{
"chain": "BaseSepolia",
"symbol": "USDT",
"contractAddress": "0x6e189E7a850f38e1d649715B3EdBa0d132858f11",
"decimals": 6,
"balance": "100000000",
"formatted": "100.0",
"usd": 100
}
],
"totalUsd": 200,
"unpricedAssets": [
"Ethereum:ETH",
"Base:ETH",
"ArbitrumOne:ETH",
"Optimism:ETH",
"Polygon:POL",
"BaseSepolia:ETH"
],
"source": "rpc"
}{
"code": "ERRREF_1000",
"message": "'wallet-123' is not a valid wallet id",
"name": "The supplied reference id is blank, malformed, or of the wrong type"
}{
"code": "unauthorized",
"message": "Invalid API key"
}{
"code": "forbidden",
"message": "Source IP not allowed for this API key (observed: 203.0.113.42)"
}{
"code": "ERRWLT_1000",
"message": "Wallet 'wlt_eoGwAJpq8IWq1ZAbAUqz' not found",
"name": "The wallet does not exist or does not belong to this user"
}{
"code": "too_many_requests",
"message": "Rate limit exceeded, please retry later"
}{
"errors": [
{
"code": "unexpected",
"message": "An unexpected error occurred, you may try again later"
}
]
}Response headers
| Header | Description |
|---|---|
api-version | The API version that served the request, echoed from your header. |
x-request-id | Correlation id. Quote it in every support ticket — it is the key to the server-side trace. |
content-type | application/json. |
X-RateLimit-Limit | Requests allowed per minute for this key. Sent on 429. |
X-RateLimit-Remaining | Requests left in the current window. Sent on 429. |
Operational notes
| Rate limiting | Per API key, in fixed one-minute windows, defaulting to 60 requests/minute. A partner or an individual key can be configured higher; changes take effect without a restart. |
| IP allow-listing | Optional per key. A key with no list configured accepts any source address. The 403 body echoes the observed IP so you can self-diagnose a mismatch. |
| No API secret | X-API-SECRET is accepted for backwards compatibility but ignored. Send Api-Key only. |
| Auditing | Every call is recorded against the partner as PARTNER_WALLET_BALANCE_GET, including calls that fail after authentication. |
Reading the numbers
balance, never from formatted. balance is the raw on-chain
amount in the smallest unit, sent as a string precisely so it does not lose
precision. formatted is for display only."balance": "100000000" with "decimals": 6 is 100 USDC.contractAddress means the asset is the network’s native gas token —
ETH, POL — rather than an ERC-20. Those are the assets that usually appear in
unpricedAssets with usd: null, and they contribute nothing to totalUsd.Authorizations
Your partner API key.
Headers
The API version this request targets. Required on every /api/v0/* call. Format YYYY-MM.<release>; 2026-09.1 is the latest. The version that served the request is echoed back on the response. Missing, malformed or unsupported values are rejected with 400.
^\d{4}-\d{2}\.\d+$Path Parameters
Customer reference id, format cus_<20 chars> — never an internal UUID. Must belong to the authenticated partner.
Wallet reference id, format wlt_<20 chars>. Must belong to the customer named in the path.
Response
The balance object. Unwrapped — the partner surface returns bare payloads, with no data/status envelope.
The wallet reference id that was requested.
The wallet's on-chain address. The same address is used across all EVM networks.
Scope of the query — all for the aggregated multi-network balance, or a single network name for TRON wallets.
One entry per tracked asset per network, including assets with a zero balance.
Show child attributes
Show child attributes
Sum of the USD value of all priced assets. Anything in unpricedAssets contributes nothing.
Chain:SYMBOL identifiers for assets with no USD price at read time, typically native gas tokens. Their usd is null.
Where the figures came from. rpc means read live from the chain node at request time.