Skip to main content
Staking reporting draws on different endpoints from staking itself, and the two API versions do not describe a position the same way. This page covers how to build a complete report on APIv2, which parts of it APIv3 replaces, and which reporting exists only on APIv3. APIv3 staking reads are available to clients staking SOL, or ETH on 0x02 validators, and require APIv3 staking to be enabled for your organization. Every APIv2 and APIv3 endpoint on this page needs the Read vault activity permission on the vaults you report on. For how to grant it, see Permission groups.

The APIv2 reporting surface

Four endpoints carry everything APIv2 reports about staking. A position row carries stakingPositionId, assetType, stakedAmount, inactiveStakedAmount, status, providerName, sourceAddress, withdrawalAddress, and transactionId. Position status is one of PENDING, ACTIVE, or EXITED. Wallet and vault asset details carry availableBalance, stakedBalance, totalBalance, unclaimedBalance, unvestedBalance, and unvestedUnstakeableBalance, each as an amount with quantity, currentPrice, and currentUSDValue.

Constructing a position report on APIv2

1

List positions for each wallet

Call GET /v2/wallets/{walletId}/staking/positions and follow page.next until it returns null.
2

Read staked and inactive amounts per position

Take stakedAmount and inactiveStakedAmount from each row, and group by assetType.
3

Read the wallet rollup

Read stakedBalance from GET /v2/wallets/{walletId}, which also supplies USD valuation. stakedBalance sums several underlying balances, so it need not equal your per-position total.
4

Attach rewards

Call GET /v2/wallets/{walletId}/staking/rewards and split by rewardType, which is CLAIMED or UNCLAIMED, using referenceDate for the period.

The pending and exiting stake gap on APIv2

APIv2 does not return non-zero values for pending or exiting stake. A position appears with status set to PENDING while its deposit is confirmed but not yet active, and during that window its amount is not reported anywhere on APIv2. The documented workaround was to recover the amount from the transaction that created the position:
  1. Call GET /v2/wallets/{walletId}/staking/positions and filter to rows where status is PENDING.
  2. For each row, take its transactionId and call GET /v2/transactions/{transactionId} to read that transaction’s amount.
  3. Sum those amounts by asset type, and treat the total as the pending stake missing from stakedBalance.
This does not work in practice. Staking transactions return amount.quantity as "0" on GET /v2/transactions/{transactionId}, so step 2 recovers nothing. They also carry transactionType "OTHER", because APIv2 reserves its specific types for deposits, withdrawals, and transfers. Identify a staking transaction by the position’s transactionId rather than by its type. The position’s status also stays PENDING after the transaction itself reads SUCCESS, so a reporting job that runs shortly after a stake request sees a pending position whose amount is unavailable from either endpoint.
If your reporting reads stakedBalance alone, stake that is activating or exiting is absent from the figure, and there is no APIv2 endpoint that recovers it. Report pending and exiting stake from APIv3 instead.

What APIv3 replaces

APIv3 reports staking balances as typed buckets rather than two amount fields plus a status. Per-position balances come from GET /v3/wallets/{walletId}/staking-positions/{positionId}/balances, and wallet-level figures for the same buckets come from GET /v3/wallets/{walletId}/balances. Each row below is the nearest equivalent, not a rename. The two versions build these amounts from different underlying balances, so a figure can change when you switch versions even where the concept matches. Field names and pagination also differ. An APIv2 amount names its value quantity, while an APIv3 amount names it amount. APIv2 paginates with page.next, while APIv3 uses page.endCursor fed into an after parameter, with a first page size of 25 by default and 100 at most.

ACTIVE_STAKE is narrower than stakedBalance

On Ethereum, stakedBalance sums five underlying balances and ACTIVE_STAKE carries one. Switching a report from one to the other can drop stake with nothing in the response to signal it.
The stake ACTIVE_STAKE leaves out on Ethereum includes restaked positions, stake restaked in the beacon chain, Alluvial staking, and delegated stake that remains usable. So an organization holding EigenLayer or Alluvial positions sees an ACTIVE_STAKE lower than its stakedBalance. Adding the four APIv3 staking buckets together does not recover stakedBalance either. ACTIVATING_STAKE and DEACTIVATING_STAKE cover stake that stakedBalance never included, so the two sets overlap on the principal and diverge in both directions. On assets that report claimable rewards as a position state, ACTIVE_STAKE folds those rewards in as well, where APIv2 reports them separately as unclaimedBalance. Each asset defines its own composition, and there is no shared definition holding the two versions in step, so confirm the make-up of a figure for every asset you report on rather than generalizing from Ethereum.

Absent buckets mean zero

A balance type missing from an APIv3 response means zero, not unknown, so summing is safe on both endpoints. A report that treats an absent bucket as unreported will mis-total. The two endpoints differ in whether a zero appears at all. GET /v3/wallets/{walletId}/balances drops zero-valued entries, so a wallet with no activating stake has no ACTIVATING_STAKE row. The per-position endpoint applies no such filter and emits what the position reports, zeros included. So on a position, an explicit zero is meaningful while absence is not, and you cannot read “this position has no active stake” from a missing ACTIVE_STAKE entry.

Wallet balances and staking positions answer different questions

The two APIv3 balance endpoints read from different sources by design, so the numbers they return can differ. GET /v3/wallets/{walletId}/balances reads the wallet’s named balances. It does not consult validators, so staked balance is counted whatever created it. GET /v3/wallets/{walletId}/staking-positions asks the Ethereum module for the validators it manages, and that lookup covers 0x02 validators only. This is deliberate. A wallet holding only 0x01 validators stays on the legacy Ethereum staking flow, and standardized staking applies to wallets that hold at least one 0x02 validator. On a wallet that holds 0x01 validators, that stake is included in the wallet’s ACTIVE_STAKE and has no row in the position list. Both endpoints return what they are designed to return. On such a wallet, the wallet figure and the sum of the per-position figures are not expected to agree. The difference is the 0x01 stake, and it reflects a difference in scope, not a shortfall. APIv2 presented these as positions by synthesizing one from the validator record. APIv3 reports the validators the staking flow manages, so if you are moving a report across and relied on those synthesized rows, expect a shorter position list for the same wallet.

Bringing legacy validators into position-level reporting

If you want stake currently held by 0x01 validators to appear in the APIv3 position list, consolidate those validators into a 0x02 validator. The stake then reports as an ordinary APIv3 staking position, and the wallet figure and the summed position figures line up again. Consolidation runs on APIv2 through POST /v2/transactions/consolidate-stake. See Pre-Pectra staking, under “Consolidate Stake from Pre-Pectra to Pectra”, and Pectra staking, under “Consolidate to Pectra validator”. You can consolidate from a non-Pectra validator into a Pectra validator, across wallets and vaults, but not into a non-Pectra validator and not across staking providers.
Consolidation is irreversible. Anchorage Digital performs it on request, so agree the scope before it runs. See Staking ETH for how consolidation is handled for your organization.

Organization enablement

APIv3 staking is enabled per organization. A wallet whose organization does not have it enabled returns an error from GET /v3/wallets/{walletId}/staking-positions rather than an empty list, so handle an error and an empty list as different outcomes. If you see the error, contact your client experience team to confirm enablement.

What APIv3 adds

These have no APIv2 equivalent.
  • ACTIVATING_STAKE and DEACTIVATING_STAKE report activating and exiting stake as amounts, per position and at wallet level. They close the APIv2 gap described above.
  • GET /v3/wallets/{walletId}/balances returns all four staking buckets for a wallet in one call, so a wallet report does not need a call per position.
  • HELD covers holds on the wallet’s primary address, such as pending withdrawals and settlements.
  • GET /v3/wallets/{walletId}/rewards returns individual reward events over a date range. Each event carries stakingPositionId and transactionHash where applicable, so a reward can be attributed to a validator or stake account.
  • GET /v3/wallets/{walletId}/daily-rewards returns one row per day per reward type, over a range of up to 31 days.
  • Both reward endpoints classify rewards by source rather than by claim state.
  • A Solana position reports stakeAuthority, withdrawAuthority, and validatorVoteAccount, which identifies positions delegated through a third-party staking product.

Querying rewards on APIv3

The two reward endpoints take their reward type differently, and accept different values. Both accept DELEGATION_REWARDS, DELEGATION_MEV_REWARDS, EXECUTION_LAYER_REWARDS, BLOCK_PROPOSER_REWARDS, ATTESTATION_REWARDS, and SYNC_COMMITTEE_REWARDS. The daily endpoint also accepts CONSENSUS_LAYER_REWARDS, which is an aggregate of BLOCK_PROPOSER_REWARDS, ATTESTATION_REWARDS, and SYNC_COMMITTEE_REWARDS.
Do not query an aggregate type and any of its component types in the same rewardTypes array. The components are already counted inside the aggregate, so the response double-counts them, and the API does not reject the combination. This applies to the daily endpoint only, since the per-event endpoint takes a single type and does not accept the aggregate.

What stays on APIv2

A complete report still calls APIv2 for the following.
  • currentPrice and currentUSDValue exist on APIv2 only. An APIv3 amount carries only amount and assetType, so USD valuation comes from APIv2.
  • APIv2 rewardType reports whether a reward is claimed. APIv3 reward types report where a reward came from, so one does not substitute for the other.
  • unclaimedBalance, unvestedBalance, and unvestedUnstakeableBalance have no APIv3 equivalent today.
  • GET /v2/vaults/{vaultId} reports vault-level rollups with vaultId and vaultName.
  • GET /v2/transactions/{transactionId} reports a transaction’s status, one of INITIATING, NEEDS_APPROVAL, INPROGRESS, SUCCESS, REJECTED, EXPIRED, and FAILURE, and its blockchainTxId, the on-chain hash. The APIv3 read endpoints on this page report neither.
  • An APIv2 position carries the transactionId of the operation that created it, and the positions endpoint accepts it as a filter. The filter applies on ETH but not on SOL. See Getting started with staking API for the polling pattern.
Because APIv3 currently exposes neither claim state nor an unclaimed balance, APIv2 is the only source for outstanding rewards on assets whose rewards must be claimed.

A mixed reporting pattern

Both versions report on the same wallets and staking positions, so one report can take each figure from whichever version reports it best.
SUCCESS on GET /v2/transactions/{transactionId} describes the transaction, not on-chain finality. Check blockchainTxId against your own node or a block explorer to confirm inclusion and depth before treating a position as settled in a report.