Skip to main content

Holdings

Token balances across every connected account, with cost basis, live valuation and per-wallet allocation.

Base URL: https://api-v2.kryptos.io · Required Permission: portfolios:read

Endpoint
GET/v1/holdings
GET/v1/holdings/{assetId}
GET/v1/holdings/portfolio/{portfolioId}
GET/v1/holdings/portfolio/{portfolioId}/holdings/{assetId}
GET/v1/calculated-balances
GET/v1/calculated-balances/portfolio/{portfolioId}

Request

curl -X GET "https://api-v2.kryptos.io/v1/holdings" \
-H "Authorization: Bearer ACCESS_TOKEN"

Query Parameters

Shared by /v1/holdings, /v1/holdings/portfolio/{portfolioId} and both /v1/calculated-balances endpoints.

ParameterTypeDefaultDescription
portfolioIdstringScope to one portfolio. Prefer the /portfolio/{portfolioId} path form.
integrationIdstringScope to a single connected account.
assetIdsstringComma-separated asset ids.
searchstringFree-text match on asset symbol and name.
minValuenumberDrop holdings worth less than this. Minimum 0.
includeSpambooleanfalseInclude assets flagged as spam. Accepts true or 1.
typestringallEcosystem filter. all or xstocks.
sortBystringvaluevalue, quantity, name, 24hrChange or txnCount.
sortOrderstringdescasc or desc.
offsetinteger0Rows to skip.
limitinteger50Rows to return, 1–1000.
ratesstringtruefalse skips the live price fetch — much faster, and omits every market field. See below.
calculatedBalancesbooleanfalseReturn ledger-derived balances instead of provider-reported ones.
excludeCalculatedBalancesbooleanfalseReturn only provider-reported balances, dropping ledger-derived rows.

Provider-reported vs. calculated balances

Two kinds of balance exist side by side, and three parameters select between them:

  • A provider-reported balance is what the exchange API or the chain says you hold right now.
  • A calculated balance is reconstructed by replaying the transaction ledger. Accounts fed only by CSV, and custom wallets, have no provider to ask — so their balances are always calculated.

By default /v1/holdings returns both, which is the number a user expects to see. Then:

You wantUse
Only what providers report?excludeCalculatedBalances=true
Only ledger-derived balances, for every account?calculatedBalances=true, or /v1/calculated-balances

/v1/calculated-balances is the dedicated form and returns two extra fields per row — isMissingTransactionHistory and lastLedgerTimestamp — which is what makes it useful for reconciliation. A true on the first means the running balance went negative, so acquisition history is missing: the ledger records a disposal with no matching purchase, which overstates gains. The transaction behind it is findable with GET /v1/transactions?isMissingTransaction=true.

Skipping prices with rates=false

rates=false returns quantities and cost basis without contacting the price service. The response then omits marketPrice, marketValue, costPerUnit, unrealizedPnL, roiPercentage, change24h, change24hPercentage and the top-level totalValue. Use it when you only need positions.

Response

{
"success": true,
"data": [
{
"assetId": "a3f1c8e0-9d42-4b17-8c55-6e0b2f7a1d34",
"asset": {
"tokenId": "bitcoin",
"symbol": "BTC",
"publicName": "Bitcoin",
"logoUrl": "https://...",
"type": "crypto",
"chainId": "bitcoin"
},
"totalQuantity": 2.5,
"costBasis": 100000,
"costPerUnit": 40000,
"marketPrice": 50000,
"marketValue": 125000,
"unrealizedPnL": 25000,
"roiPercentage": 25,
"change24h": 4200,
"change24hPercentage": 3.5,
"baseCurrency": "USD",
"isSpam": false,
"transactionCount": 18,
"assetDistribution": [
{
"integrationId": "int_9f2c",
"quantity": 1.5,
"account": {
"provider": "ledger",
"providerPublicName": "Ledger",
"walletId": "int_9f2c",
"publicAddress": "bc1q...",
"logoUrl": "https://...",
"alias": "Hardware wallet"
},
"allocationPercentage": 60,
"transactionCount": 11,
"portfolioId": "pf_main",
"portfolioName": "Main"
}
]
}
],
"totalCount": 42,
"offset": 0,
"limit": 50,
"hasMore": false,
"totalValue": 250000
}

Note the shape: pagination and totalValue sit at the top level, beside success, not inside data. See API Overview.

The single-asset endpoints (/v1/holdings/{assetId} and its portfolio form) return one object as { success, data }, or 404 { success: false, error: "Not found" }.

Response Fields

Holding

FieldTypeDescription
assetIdstringKryptos asset id — use this for assetIds filters and transaction writes
assetobjectAsset descriptor, see below
totalQuantitynumberQuantity held across all accounts
costBasisnumberTotal acquisition cost
costPerUnitnumberAverage cost per unit (costBasis / totalQuantity); 0 when there is no cost basis
marketPricenumberCurrent unit price
marketValuenumbertotalQuantity × marketPrice
unrealizedPnLnumbermarketValue − costBasis
roiPercentagenumber | nullReturn as a percentage of cost basis. null when cost basis is 0 — that means "no basis recorded", not "0% return"
change24hnumberAbsolute value change over 24h, in baseCurrency
change24hPercentagenumberPercentage price change over 24h
baseCurrencystringCurrency all monetary fields are denominated in
isSpambooleanFlagged as a spam or scam asset
transactionCountnumberTransactions touching this asset
assetDistributionarrayPer-account breakdown, see below

Treat roiPercentage: null explicitly. Coercing it to 0 reports a break-even position for an asset whose basis is simply unknown.

Asset

FieldTypeDescription
tokenIdstringCanonical token identifier, e.g. bitcoin
symbolstringTicker, e.g. BTC
publicNamestringDisplay name
logoUrlstringIcon URL
typestringcrypto, nft or fiat
chainIdstringChain the asset lives on, when applicable
contractAddressstringToken contract, for on-chain tokens

Asset Distribution

FieldTypeDescription
integrationIdstringThe connected account holding this slice
quantitynumberQuantity in this account
accountobjectprovider, providerPublicName, publicAddress, walletId, logoUrl, alias
allocationPercentagenumberShare of this asset's total value held here
transactionCountnumberTransactions for this asset in this account
portfolioIdstringPortfolio the account belongs to
portfolioNamestringPortfolio display name

Calculated balances — extra fields

/v1/calculated-balances and ?calculatedBalances=true add:

FieldTypeDescription
isMissingTransactionHistorybooleanThe replayed balance went negative — acquisitions are missing from the ledger
lastLedgerTimestampnumber | nullUnix ms of the most recent ledger entry feeding this balance

Portfolio totals

totalValue is the summed value of the whole filtered set, not just the returned page — so it stays correct as you page, and you should not add up marketValue yourself.

It covers token holdings only. NFT and DeFi value are separate: see NFTs for totalEstimatedValueUsd and DeFi for /v1/defi/totals.