Holdings & Balances
The Holdings suite tells you what a wallet holds right now and what it cost. Current quantities and USD values come straight from authoritative on-chain balances, while cost basis is computed by replaying the wallet’s full transaction history through a FIFO tax-lot engine and rebasing the result onto those balances.
All examples use the production base URL:
https://api.qeychain.xyzEndpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/wallets/{address}/holdings |
Paginated list of current holdings |
GET |
/api/v1/wallets/{address}/holdings/summary |
Portfolio totals + per-chain breakdown |
GET |
/api/v1/wallets/{address}/holdings/{symbol} |
Single asset across all chains, lot by lot |
GET |
/api/v1/wallets/{address}/cost-basis |
Full lot-level cost basis report |
Every endpoint requires authentication. Send your access token as a Bearer token
in the Authorization header:
Authorization: Bearer <YOUR_ACCESS_TOKEN>A request with no valid credentials returns 401 UNAUTHORIZED.
How balances and cost basis fit together
Section titled “How balances and cost basis fit together”Two independent sources are combined for every holding:
- Current quantity and value are sourced from the wallet’s live on-chain
positions. These are authoritative for “what is held now” and are what the
dashboard’s
portfolio_valueis built from, so totals reconcile across endpoints. - Cost basis comes from the FIFO engine’s remaining tax lots. The engine’s raw remaining quantity can drift above the true balance whenever an outflow was not captured in transaction history (bridges, internal transactions, un-indexed transfers). To keep numbers meaningful, cost basis is rebased onto the actual on-chain quantity: the engine’s average cost per unit is multiplied by the balance you actually hold. When the FIFO remaining matches the on-chain balance, this equals the raw remaining cost exactly; when it has drifted, unrealized gain/loss stays consistent instead of showing a phantom total.
As a result, unrealized_gl = current_value - cost_basis, computed over the same
quantity on both sides.
Response envelope
Section titled “Response envelope”All Holdings endpoints return the standard v1 envelope. A successful response
wraps its payload in data and always includes meta:
{ "success": true, "data": { }, "meta": { "request_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "timestamp": "2026-07-29T14:03:11.482Z", "version": "1.0.0" }}List endpoints add a pagination block:
"pagination": { "page": 1, "per_page": 50, "total": 128, "total_pages": 3, "has_more": true}Errors use the same envelope with success: false:
{ "success": false, "error": { "code": "INVALID_ADDRESS", "message": "Invalid wallet address. Supported: Ethereum (0x…), Bitcoin (bc1… / 1… / 3…), Solana.", "details": { "provided": "not-an-address" }, "field": "address" }, "meta": { "request_id": "…", "timestamp": "…", "version": "1.0.0" }}Monetary and quantity fields are serialized as JSON strings (for example
"4500.00") to preserve decimal precision.
The partial / still_indexing degradation
Section titled “The partial / still_indexing degradation”Heavy wallets (thousands of transactions) can take a long time to replay through
the FIFO engine because the full history is fetched page by page from the data
provider. Balances, by contrast, come from a single fast call. To stay within the
gateway timeout, /holdings and /holdings/summary resolve balances first
and treat cost-basis enrichment as best-effort under a wall-clock time budget:
- On-chain balances are fetched — this alone is enough to return a valid response.
- The cost-basis engine build runs under the remaining budget. If it finishes in time, you get full holdings with cost basis. If it overruns, the response is returned with balances only, and the engine build continues in the background to warm the cache.
When this happens, two top-level flags are set:
{ "success": true, "data": [ ], "partial": true, "still_indexing": true, "meta": { }}Both flags are always present on /holdings and /holdings/summary
(false on a fully resolved response), so you can rely on the field. When
partial is true:
quantity,current_price, andcurrent_valueare complete and accurate.cost_basisandaverage_costcome back as"0.00"andunrealized_gl/unrealized_gl_pctarenull, because the engine hasn’t finished.
List holdings
Section titled “List holdings”GET /api/v1/wallets/{address}/holdingsReturns the wallet’s current holdings, one entry per (symbol, chain), with
quantity, value, cost basis, and unrealized gain/loss. Supports filtering,
sorting, and pagination.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
address |
string | yes | Wallet address (EVM, Bitcoin, or Solana). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
chain |
string | no | Filter to a single chain, e.g. ethereum, polygon, arbitrum. |
min_value |
number | no | Minimum current USD value. Must be >= 0. |
sort |
string | no | Sort order (default value_desc). See below. |
page |
integer | no | Page number, >= 1 (default 1). |
per_page |
integer | no | Items per page, 1–100 (default 50). |
owned_wallets |
string[] | no | All addresses owned by the user, for internal-transfer detection. |
Accepted sort values: value_desc (default), value_asc, cost_desc,
cost_asc, gain_desc, gain_asc, quantity_desc, quantity_asc,
symbol_asc, symbol_desc.
Example request
curl -sG "https://api.qeychain.xyz/api/v1/wallets/0x742d35cc6634c0532925a3b844bc454e4438f44e/holdings" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN" \ --data-urlencode "chain=ethereum" \ --data-urlencode "min_value=100" \ --data-urlencode "sort=value_desc" \ --data-urlencode "per_page=50"Example response
{ "success": true, "data": [ { "symbol": "ETH", "name": "Ethereum", "chain": "ethereum", "contract_address": null, "quantity": "12.481003", "current_price": "3612.44", "current_value": "45090.87", "cost_basis": "31204.10", "average_cost": "2500.12", "unrealized_gl": "13886.77", "unrealized_gl_pct": "44.50", "total_lots": 9, "open_lots": 7, "partial_lots": 2, "earliest_acquisition": "2022-08-14T09:31:07Z", "has_long_term_lots": true, "long_term_quantity": "10.000000", "short_term_quantity": "2.481003" }, { "symbol": "USDC", "name": "USD Coin", "chain": "ethereum", "contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "quantity": "8200.00", "current_price": "1.00", "current_value": "8200.00", "cost_basis": "8200.00", "average_cost": "1.00", "unrealized_gl": "0.00", "unrealized_gl_pct": "0.00", "total_lots": 4, "open_lots": 4, "partial_lots": 0, "earliest_acquisition": "2023-02-01T18:44:52Z", "has_long_term_lots": true, "long_term_quantity": "8200.00", "short_term_quantity": null } ], "pagination": { "page": 1, "per_page": 50, "total": 2, "total_pages": 1, "has_more": false }, "partial": false, "still_indexing": false, "meta": { "request_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "timestamp": "2026-07-29T14:03:11.482Z", "version": "1.0.0" }}Holding fields
| Field | Type | Description |
|---|---|---|
symbol |
string | Token symbol. |
name |
string | null | Token name. |
chain |
string | Blockchain network. |
contract_address |
string | null | Token contract; null for native coins. |
quantity |
string | Current on-chain quantity held. |
current_price |
string | null | Current market price per unit (USD). |
current_value |
string | null | Current market value (USD). |
cost_basis |
string | Total cost basis, rebased to the held quantity (USD). |
average_cost |
string | Average cost per unit (USD). |
unrealized_gl |
string | null | Unrealized gain/loss (USD). |
unrealized_gl_pct |
string | null | Unrealized gain/loss as a percentage. |
total_lots |
integer | Number of active tax lots backing this holding. |
open_lots |
integer | Fully unconsumed lots. |
partial_lots |
integer | Partially consumed lots. |
earliest_acquisition |
string | null | Earliest lot acquisition date (ISO 8601). |
has_long_term_lots |
boolean | Whether any lots qualify for long-term treatment. |
long_term_quantity |
string | null | Quantity in lots held longer than one year. |
short_term_quantity |
string | null | Quantity in lots held one year or less. |
Portfolio summary
Section titled “Portfolio summary”GET /api/v1/wallets/{address}/holdings/summaryReturns aggregate portfolio totals, a per-chain breakdown, the top holdings by
value, and a long-term/short-term value split. total_value is sourced from the
same on-chain balances as the dashboard, so the two always agree.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
address |
string | yes | Wallet address. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
owned_wallets |
string[] | no | All addresses owned by the user, for internal-transfer detection. |
Example request
curl -s "https://api.qeychain.xyz/api/v1/wallets/0x742d35cc6634c0532925a3b844bc454e4438f44e/holdings/summary" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response
{ "success": true, "data": { "address": "0x742d35cc6634c0532925a3b844bc454e4438f44e", "total_value": "53290.87", "total_cost_basis": "39404.10", "total_unrealized_gl": "13886.77", "total_unrealized_gl_pct": "35.24", "total_positions": 2, "positions_with_gain": 1, "positions_with_loss": 0, "chains": [ { "chain": "ethereum", "total_value": "53290.87", "cost_basis": "39404.10", "unrealized_gl": "13886.77", "position_count": 2, "percentage_of_portfolio": "100.00" } ], "top_holdings": [ { "symbol": "ETH", "name": "Ethereum", "chain": "ethereum", "contract_address": null, "quantity": "12.481003", "current_price": "3612.44", "current_value": "45090.87", "cost_basis": "31204.10", "average_cost": "2500.12", "unrealized_gl": "13886.77", "unrealized_gl_pct": "44.50", "total_lots": 9, "open_lots": 7, "partial_lots": 2, "earliest_acquisition": "2022-08-14T09:31:07Z", "has_long_term_lots": true, "long_term_quantity": "10.000000", "short_term_quantity": "2.481003" } ], "long_term_value": "44290.87", "short_term_value": "9000.00", "risk_metrics": null }, "partial": false, "still_indexing": false, "meta": { "request_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "timestamp": "2026-07-29T14:05:44.109Z", "version": "1.0.0" }}Summary fields
| Field | Type | Description |
|---|---|---|
total_value |
string | Total portfolio value (USD). |
total_cost_basis |
string | Total cost basis across holdings (USD). |
total_unrealized_gl |
string | Total unrealized gain/loss (USD). |
total_unrealized_gl_pct |
string | null | Total unrealized gain/loss percentage. |
total_positions |
integer | Number of unique holdings. |
positions_with_gain |
integer | Holdings currently in the green. |
positions_with_loss |
integer | Holdings currently in the red. |
chains |
array | Per-chain breakdown (below), sorted by value. |
top_holdings |
array | Top 5 holdings by value (same shape as /holdings entries). |
long_term_value |
string | null | Value attributable to long-term lots. |
short_term_value |
string | null | Value attributable to short-term lots. |
risk_metrics |
object | null | Reserved for future scoring; currently always null. |
Each entry in chains has: chain, total_value, cost_basis,
unrealized_gl, position_count, and percentage_of_portfolio.
Single-asset detail
Section titled “Single-asset detail”GET /api/v1/wallets/{address}/holdings/{symbol}Returns one asset aggregated across every chain it appears on, with a per-chain breakdown and the full list of tax lots. The headline quantity and value reflect the authoritative on-chain balance; lot-level detail comes from the FIFO engine.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
address |
string | yes | Wallet address. |
symbol |
string | yes | Token symbol (case-insensitive, e.g. eth). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
owned_wallets |
string[] | no | All addresses owned by the user, for internal-transfer detection. |
If the wallet has never held the symbol, the response is 404 RESOURCE_NOT_FOUND.
Example request
curl -s "https://api.qeychain.xyz/api/v1/wallets/0x742d35cc6634c0532925a3b844bc454e4438f44e/holdings/ETH" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response
{ "success": true, "data": { "address": "0x742d35cc6634c0532925a3b844bc454e4438f44e", "symbol": "ETH", "total_quantity": "12.481003", "total_current_value": "45090.87", "total_cost_basis": "31204.10", "total_unrealized_gl": "13886.77", "holdings_by_chain": [ { "symbol": "ETH", "name": "Ethereum", "chain": "ethereum", "contract_address": null, "quantity": "12.481003", "current_price": "3612.44", "current_value": "45090.87", "cost_basis": "31204.10", "average_cost": "2500.12", "unrealized_gl": "13886.77", "unrealized_gl_pct": "44.50", "total_lots": 9, "open_lots": 7, "partial_lots": 2, "earliest_acquisition": "2022-08-14T09:31:07Z", "has_long_term_lots": true, "long_term_quantity": "10.000000", "short_term_quantity": "2.481003" } ], "lots": [ { "lot_id": "ETH-ethereum-0001", "original_quantity": "5.000000", "remaining_quantity": "5.000000", "cost_per_unit": "1850.00", "total_cost": "9250.00", "remaining_cost": "9250.00", "acquisition_date": "2022-08-14T09:31:07Z", "holding_period_days": 1445, "is_long_term": true, "lot_type": "purchase", "price_source": "zerion", "status": "OPEN", "is_untracked": false, "needs_manual_cost_basis": false, "source_lots": [], "destination_lots": [], "origin_type": "purchase", "origin_chain": null, "origin_entity": null }, { "lot_id": "ETH-ethereum-0007", "original_quantity": "3.000000", "remaining_quantity": "2.481003", "cost_per_unit": "3100.00", "total_cost": "9300.00", "remaining_cost": "7691.11", "acquisition_date": "2025-11-02T21:12:40Z", "holding_period_days": 269, "is_long_term": false, "lot_type": "swap_in", "price_source": "zerion", "status": "PARTIAL", "is_untracked": false, "needs_manual_cost_basis": false, "source_lots": ["USDC-ethereum-0031"], "destination_lots": [], "origin_type": "swap", "origin_chain": null, "origin_entity": null } ], "total_lots": 2, "long_term_lots": 1, "short_term_lots": 1, "earliest_acquisition": "2022-08-14T09:31:07Z", "latest_acquisition": "2025-11-02T21:12:40Z" }, "meta": { "request_id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f", "timestamp": "2026-07-29T14:07:02.551Z", "version": "1.0.0" }}Top-level detail fields
| Field | Type | Description |
|---|---|---|
total_quantity |
string | On-chain quantity summed across chains. |
total_current_value |
string | null | Total current value (USD). |
total_cost_basis |
string | Total cost basis, rebased to held balances (USD). |
total_unrealized_gl |
string | null | Total unrealized gain/loss (USD). |
holdings_by_chain |
array | One holding entry per chain (same shape as /holdings). |
lots |
array | All tax lots for the asset, oldest first (fields below). |
total_lots |
integer | Number of lots returned. |
long_term_lots |
integer | Lots qualifying for long-term treatment. |
short_term_lots |
integer | Lots held one year or less. |
earliest_acquisition |
string | null | Oldest lot date. |
latest_acquisition |
string | null | Most recent lot date. |
Lot fields (lots[])
| Field | Type | Description |
|---|---|---|
lot_id |
string | Unique lot identifier. |
original_quantity |
string | Quantity acquired. |
remaining_quantity |
string | Quantity remaining after disposals. |
cost_per_unit |
string | Cost per unit at acquisition (USD). |
total_cost |
string | Original total cost basis (USD). |
remaining_cost |
string | Remaining cost basis (USD). |
acquisition_date |
string | null | When the lot was acquired. |
holding_period_days |
integer | null | Days held since acquisition. |
is_long_term |
boolean | Held longer than one year. |
lot_type |
string | Acquisition type, e.g. purchase, swap_in, reward. |
price_source |
string | null | Source of the acquisition price. |
status |
string | OPEN, PARTIAL, or CLOSED. |
is_untracked |
boolean | Cost basis unknown / needs manual entry. |
needs_manual_cost_basis |
boolean | Flagged for manual cost-basis entry. |
source_lots |
string[] | Lot IDs this lot was derived from (money trail). |
destination_lots |
string[] | Lot IDs this lot became. |
origin_type |
string | null | Origin, e.g. purchase, swap, bridge. |
origin_chain |
string | null | Origin chain for bridged assets. |
Cost-basis report
Section titled “Cost-basis report”GET /api/v1/wallets/{address}/cost-basisReturns a full, paginated, lot-level cost-basis report across all assets, with
summary counts and holding-period totals. The method parameter controls the
ordering of lots (and which order field is populated); it does not change the
lot data itself.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
address |
string | yes | Wallet address. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
method |
string | no | Ordering method: fifo (default), lifo, hifo, specific_id. |
symbol |
string | no | Filter to a single token symbol. |
status |
string | no | Filter by lot status: OPEN, PARTIAL, CLOSED, or ALL. |
page |
integer | no | Page number, >= 1 (default 1). |
per_page |
integer | no | Items per page, 1–100 (default 50). |
owned_wallets |
string[] | no | All addresses owned by the user, for internal-transfer detection. |
Example request
curl -sG "https://api.qeychain.xyz/api/v1/wallets/0x742d35cc6634c0532925a3b844bc454e4438f44e/cost-basis" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN" \ --data-urlencode "method=fifo" \ --data-urlencode "status=OPEN" \ --data-urlencode "per_page=50"Example response
{ "success": true, "data": { "address": "0x742d35cc6634c0532925a3b844bc454e4438f44e", "method": "fifo", "total_cost_basis": "39404.10", "total_current_value": "53290.87", "total_unrealized_gl": "13886.77", "total_lots": 13, "open_lots": 11, "partial_lots": 2, "closed_lots": 0, "long_term_lots": 8, "short_term_lots": 5, "long_term_cost_basis": "25204.10", "short_term_cost_basis": "14200.00", "lots": [ { "lot_id": "ETH-ethereum-0001", "symbol": "ETH", "chain": "ethereum", "quantity": "5.000000", "original_quantity": "5.000000", "cost_per_unit": "1850.00", "total_cost_basis": "9250.00", "original_cost_basis": "9250.00", "acquisition_date": "2022-08-14T09:31:07Z", "holding_period_days": 1445, "is_long_term": true, "current_price": "3612.44", "current_value": "18062.20", "unrealized_gl": "8812.20", "lot_type": "purchase", "status": "OPEN", "is_untracked": false, "fifo_order": 1, "lifo_order": null, "hifo_order": null } ] }, "pagination": { "page": 1, "per_page": 50, "total": 13, "total_pages": 1, "has_more": false }, "meta": { "request_id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f80", "timestamp": "2026-07-29T14:09:18.220Z", "version": "1.0.0" }}Report fields
| Field | Type | Description |
|---|---|---|
method |
string | Ordering method applied. |
total_cost_basis |
string | Remaining cost basis across all lots (USD). |
total_current_value |
string | null | Current value, reconciled to on-chain balances (USD). |
total_unrealized_gl |
string | null | Reconciled unrealized gain/loss (USD). |
total_lots |
integer | Total lots matching the filters. |
open_lots |
integer | Fully unconsumed lots. |
partial_lots |
integer | Partially consumed lots. |
closed_lots |
integer | Fully consumed lots. |
long_term_lots |
integer | Lots held longer than one year. |
short_term_lots |
integer | Lots held one year or less. |
long_term_cost_basis |
string | Cost basis in long-term lots (USD). |
short_term_cost_basis |
string | Cost basis in short-term lots (USD). |
lots |
array | Lot detail for the current page (fields below). |
Report lot fields (lots[])
| Field | Type | Description |
|---|---|---|
lot_id |
string | Unique lot identifier. |
symbol |
string | Token symbol. |
chain |
string | Blockchain network. |
quantity |
string | Remaining quantity. |
original_quantity |
string | Quantity at acquisition. |
cost_per_unit |
string | Cost per unit at acquisition (USD). |
total_cost_basis |
string | Remaining cost basis (USD). |
original_cost_basis |
string | Original total cost basis (USD). |
acquisition_date |
string | null | Acquisition date. |
holding_period_days |
integer | null | Days held. |
is_long_term |
boolean | Held longer than one year. |
current_price |
string | null | Current price (USD), if available. |
current_value |
string | null | Per-lot current value for display (USD). |
unrealized_gl |
string | null | Per-lot unrealized gain/loss for display (USD). |
lot_type |
string | Acquisition type. |
status |
string | OPEN, PARTIAL, or CLOSED. |
is_untracked |
boolean | Cost basis unknown. |
fifo_order |
integer | null | Consumption order under FIFO (1 = oldest). |
lifo_order |
integer | null | Consumption order under LIFO (1 = newest). |
hifo_order |
integer | null | Consumption order under HIFO (1 = highest cost). |
Errors
Section titled “Errors”| Code | HTTP | When |
|---|---|---|
INVALID_ADDRESS |
400 | {address} is not a valid EVM, Bitcoin, or Solana address. |
VALIDATION_ERROR |
400 | A query parameter is out of range or malformed. |
UNAUTHORIZED |
401 | Missing or invalid credentials. |
RESOURCE_NOT_FOUND |
404 | /holdings/{symbol} — the wallet has never held that symbol. |
DATA_PROVIDER_ERROR |
502 | The upstream data provider failed while fetching balances or history. |
PROVIDER_ENTITLEMENT_ERROR |
502 | The upstream provider rejected the request at the plan level (expired trial or exhausted quota) — an account issue, not an outage. |
