Assets, balances and issuers
Find an asset, read wallet balances and holders, and interpret issuer metadata.
Quickstart: find KALE, read balances and holders
These read-only examples use the same asset and sample wallet as the GraphQL runner. Replace the wallet with your own public G-address; no private key or wallet signature is needed.
Find the asset and confirm its issuer.
Examplecurl --fail-with-body 'https://api.stellaratlas.io/v1/assets?symbol=KALE&limit=10'Read
data[].idandissuer; a ticker can match multiple assets. This walkthrough usesKALE:GBDVX4VELCDSQ54KQJYTNHXAHFLBCA77ZY2USQBM4CSHTTV7DME7KALE. If discovery has another page, pass itsnextCursorascursorwith the same symbol and limit.Read one page of the wallet’s balance observations.
Example( trap - EXIT INT TERM trap 'exit 130' INT trap 'exit 143' TERM temporary=$(mktemp './wallet-balances.json.partial.XXXXXX') || exit trap 'rm -f -- "$temporary"' EXIT status=$(curl -q --fail-with-body --connect-timeout 5 --max-time 30 \ --write-out '%{http_code}:%header{x-stellaratlas-fallback}' \ 'https://api.stellaratlas.io/v1/analytics/accounts/GCX7LOMYJM54CXJPNTDZPMFSTQTQJTR5XR4DKZXLSWJG3R4B7GOADWXZ/balances?limit=25' --output "$temporary") || exit if [[ $status != 2[0-9][0-9]: ]]; then printf '%s\n' "Expected a live HTTP success, got $status" >&2 exit 22 fi mv -fT -- "$temporary" './wallet-balances.json' )Inspect
balances, matchingassetto the full KALE identity. The sample wallet is independent of the asset search; this example does not promise it holds KALE. A balance may be on a later page. See the balances reference.Read KALE holders, largest observed balance first.
Example( trap - EXIT INT TERM trap 'exit 130' INT trap 'exit 143' TERM temporary=$(mktemp './kale-holders.json.partial.XXXXXX') || exit trap 'rm -f -- "$temporary"' EXIT status=$(curl -q --fail-with-body --connect-timeout 5 --max-time 30 \ --write-out '%{http_code}:%header{x-stellaratlas-fallback}' \ 'https://api.stellaratlas.io/v1/assets/KALE%3AGBDVX4VELCDSQ54KQJYTNHXAHFLBCA77ZY2USQBM4CSHTTV7DME7KALE/holders?limit=25&sort=balance&order=desc' --output "$temporary") || exit if [[ $status != 2[0-9][0-9]: ]]; then printf '%s\n' "Expected a live HTTP success, got $status" >&2 exit 22 fi mv -fT -- "$temporary" './kale-holders.json' )Read
holdersas this page’s positive-balance observations. Its length is not the total holder count; inspectindexedSummaryandwatermark.indexStatusbefore using an indexed summary. See the holders reference.
These Bash examples use curl 7.84 or newer, mktemp and GNU mv. Each invocation creates its own temporary beside the saved page, promotes only a successful response without a fallback marker and cleans up its own temporary on exit. The parentheses scope variables and traps to that invocation. An HTTP or transport failure means the refresh is unavailable, not an empty balance or holder result. Stop this walkthrough on failure and do not continue pagination. If you retain the last successful response, label it as saved data rather than a fresh read. Its watermark.catalogGeneratedAt is the catalogue generation time, not the time you saved the response or a pinned balance snapshot.
After saving a first page successfully, use its nextCursor as after. With jq installed, run the matching command to save a separate second page. A missing, null or unusable cursor stops the command before it sends a request.
(
trap - EXIT INT TERM
trap 'exit 130' INT
trap 'exit 143' TERM
after=$(jq -er '.nextCursor | strings | select(length > 0)' './wallet-balances.json') || exit
temporary=$(mktemp './wallet-balances-next.json.partial.XXXXXX') || exit
trap 'rm -f -- "$temporary"' EXIT
status=$(curl -q --fail-with-body --connect-timeout 5 --max-time 30 \
--write-out '%{http_code}:%header{x-stellaratlas-fallback}' \
'https://api.stellaratlas.io/v1/analytics/accounts/GCX7LOMYJM54CXJPNTDZPMFSTQTQJTR5XR4DKZXLSWJG3R4B7GOADWXZ/balances?limit=25' --get --data-urlencode "after=$after" --output "$temporary") || exit
if [[ $status != 2[0-9][0-9]: ]]; then
printf '%s\n' "Expected a live HTTP success, got $status" >&2
exit 22
fi
mv -fT -- "$temporary" './wallet-balances-next.json'
)(
trap - EXIT INT TERM
trap 'exit 130' INT
trap 'exit 143' TERM
after=$(jq -er '.nextCursor | strings | select(length > 0)' './kale-holders.json') || exit
temporary=$(mktemp './kale-holders-next.json.partial.XXXXXX') || exit
trap 'rm -f -- "$temporary"' EXIT
status=$(curl -q --fail-with-body --connect-timeout 5 --max-time 30 \
--write-out '%{http_code}:%header{x-stellaratlas-fallback}' \
'https://api.stellaratlas.io/v1/assets/KALE%3AGBDVX4VELCDSQ54KQJYTNHXAHFLBCA77ZY2USQBM4CSHTTV7DME7KALE/holders?limit=25&sort=balance&order=desc' --get --data-urlencode "after=$after" --output "$temporary") || exit
if [[ $status != 2[0-9][0-9]: ]]; then
printf '%s\n' "Expected a live HTTP success, got $status" >&2
exit 22
fi
mv -fT -- "$temporary" './kale-holders-next.json'
)Keep the same account or asset, limit, sort and order. Stop on nextCursor: null; never exchange discovery, balance and holder cursors. These are latest-ingested observations, not current-chain balances or a frozen snapshot. Amounts retain source Float64 precision. The files preserve the original JSON bytes. Keep raw integer strings as strings to avoid rounding. Check each page’s coverage, watermark and precision fields. Treat findings as partial when the response reports coverage gaps or when more pages remain. An empty page alone does not prove a wallet never held an asset. See pagination and error handling.
For GraphQL, open the runner and choose Account balances (latest-ingested observations) or KALE holders by balance, with sponsorship. Set your account or asset in Variables, execute, then put that result’s cursor ininput.after without changing its other inputs. The presets use their own page sizes; the REST examples above use 25. See the GraphQL request format.
For freshness, WebSocket updates are origin-restricted first-party publication hints. They do not stream wallet balances or accept asset/account subscriptions; third-party clients should repeat bounded reads at an appropriate interval and handle failures with backoff.
Understand current account balances
The current account view reads classic balances from the configured Horizon provider. Its ledger can lag the network tip. The indexed balances in the quickstart above come from imported observations with separate coverage and precision. If the current provider fails, keep those observations labelled separately. A failed read or an unavailable amount is different from a reported zero.
curl -q --fail-with-body --connect-timeout 5 --max-time 70 'https://api.stellaratlas.io/v1/explorer/accounts/GCX7LOMYJM54CXJPNTDZPMFSTQTQJTR5XR4DKZXLSWJG3R4B7GOADWXZ'See the account API reference. Bind the response to the requested account and network. Use accounting.latestLedger and, when available, accounting.ledgerClosedAt for the provider snapshot. lastModifiedLedger describes the account’s last change. If the provider snapshot ledger is unavailable, leave freshness unconfirmed. Keep balances[].exact.balanceRaw and raw liability amounts as integer strings at scale seven, or use exact integer arithmetic. Treat null fields as missing evidence. The balanceInventoryComplete flag applies to the classic balance inventory and can be true while optional liability or authorization fields are missing.
- Offer commitments. Selling liabilities report the amount required for open sell offers. For a fully authorized issued-asset trustline, subtract those commitments from the balance to calculate availability. Buying liabilities reserve capacity to receive the asset and do not reduce the held balance.
- Native reserve. For XLM, also subtract the minimum reserve. Calculate it as
base reserve × (2 + subentries + sponsoring − sponsored). Useaccounting.baseReserveRawonly whenaccounting.baseReserveLedgermatches the snapshot ledger and all counts are available. For example, 10 XLM with 1.25 XLM committed to selling and a 3 XLM reserve leaves 5.75 XLM before fees. The required amount is 4.25 XLM. With only 1 XLM held, 1 XLM covers part of the minimum reserve first. The reserve shortfall is 2 XLM and the sell-offer shortfall is 1.25 XLM. Availability in that case is zero. If reserve evidence is missing, the reported selling liability can still be shown. Leave the combined required and covered amounts, offer allocation and availability uncalculated. - Receive headroom. For a fully authorized issued trustline, subtract the balance and buying liabilities from the trustline limit. A zero-balance trustline can still have receive headroom. Keep authorization restrictions visible, including authorization only to maintain liabilities. A restricted or unknown amount is different from an exact zero on an authorized trustline.
Claimable balances and contract funds are separate positions. Display them only with their own evidence. The principal in a claimable balance has already left the source balance, and reserve sponsorship is already included in the account counts. Avoid deducting either twice. A classic balance shown through a Stellar Asset Contract is the same holding. For classic liquidity-pool shares, show the exact share balance. Sell-offer reservations do not apply, and share count alone does not establish a withdrawable amount. The account’s reserve calculation already uses the protocol’s weighted counts.
Where a protocol position is supported by a retained observation, show it separately with its ledger and close time. Even a recent observation describes that ledger. Keep it out of current balance and availability totals. Missing evidence does not establish a zero position or complete account coverage.
Cached logos and collectible labels come from issuer metadata matched to the full asset identity. Use cacheOnly=true on the asset metadata batch route for an already-cached lookup that does not queue a refresh. An uncached logo can appear as a monogram. A collectible link or NFT-backing declaration is a publisher claim. Trustline evidence shows the quantity held, while scarcity and backing remain unverified. A federation name is display-only. Keep the full G-address primary and verify payment details separately.
Use an asset identity, not a ticker alone
An issued asset is identified by both its code and G-address issuer. The canonical form is CODE:GISSUER; the reversedGISSUER:CODE form and compatibility CODE_GISSUERand GISSUER_CODE forms resolve to the same identity. Usenative, XLM, - or _ for the native asset. URL-encode the colon in a path.
A symbol is not unique: searching issued asset code XLMreturns all matching issuers; the API does not pick one. Resolve C-address assets through the SAC reverse registry. An unmapped C-address remains an unclassified contract, not proof of a SEP-41 token.
Circle USDC is USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN; its SAC is CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75.
Search the published asset catalogue
curl --fail-with-body "https://api.stellaratlas.io/v1/assets?q=USDC&limit=20"The compact catalogue is built from published history, not a default recent 64-ledger window. Results report first and last observed ledgers; these are observations, not an asset's creation ledger or proof of complete network history.
Ledger and close-time bounds are explicit filters, separate from text search:
curl --fail-with-body "https://api.stellaratlas.io/v1/assets?min_ledger=60000000&max_ledger=61000000&limit=20"curl --fail-with-body "https://api.stellaratlas.io/v1/assets?start_time=2025-01-01T00%3A00%3A00Z&end_time=2025-02-01T00%3A00%3A00Z&limit=20"Continue with the returned opaque cursor and same filters and limit; do not treat the cursor as an asset ID.
Open an asset or issuer
curl --fail-with-body "https://api.stellaratlas.io/v1/assets/USDC%3AGA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"curl --fail-with-body "https://api.stellaratlas.io/v1/assets/USDC%3AGA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN/metadata"curl --fail-with-body 'https://api.stellaratlas.io/v1/issuers/GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN'
curl --fail-with-body 'https://api.stellaratlas.io/v1/issuers/GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN/assets?limit=20'Issuer metadata comes from its home domain's Stellar TOML. Asset metadata status is pending (bounded refresh queued),ready (available), or unavailable (could not be obtained). Logos are cached and served by StellarAtlas. Pending or unavailable does not prove that the asset does not exist.
Preserve attribution: show the issuer home domain and TOML/source link with issuer-supplied names, URLs and images. This is issuer-published metadata, not independently verified claims.