Skip to main content

Overview

ETH does not enter or leave Ethereum staking instantly. Deposits wait in the deposit queue before becoming active stake (ETH in activation — not yet staked), and exiting stake passes through the exit queue, the withdrawal eligibility delay, and the withdrawal sweep before it can be redeemed (ETH in withdrawal — not yet redeemed). This pending ETH is often called ETH in transit. The Queue APIs expose both directions of this flow: network-wide totals and per-validator positions, each with estimated processing times. Use them to report pending balances to customers, forecast liquidity, or analyze staking supply dynamics.
API Endpoints: This guide uses the Queue API family:
EndpointBest For
/api/v2/ethereum/queuesNetwork-wide totals for inflow/outflow snapshots
/api/v2/ethereum/validators/queuesQueue overview for selected validators, with ETAs

Why Track Staking Flows?

Pending Balance Reporting

Show customers ETH that is deposited but not yet earning, or exiting but not yet redeemable, as distinct balance states.

Liquidity Forecasting

Predict when exiting ETH becomes liquid and when queued deposits start earning, down to the epoch.

Customer ETAs

Give per-validator activation and withdrawal estimates instead of network-wide averages.

Market & Supply Analytics

Track net pending flow (inflows minus outflows) as a leading indicator of staking supply changes.

Mapping “Pending ETH” to Queue Data

All balance fields are returned in wei (1 ETH = 10¹⁸ wei).
MetricDefinitionSource Fields
ETH in activationDeposited, not yet active stakedeposit_queue.deposit_balance (new validators) + deposit_queue.topup_balance (top-ups to existing validators)
ETH in exitExit requested, validator still activeexit_queue.balance
ETH in manual withdrawalWithdrawal requests queued on the execution layermanual_withdrawal_queue.balance
Pending inflowAll ETH entering stakingdeposit_balance + topup_balance
Pending outflowAll ETH leaving stakingexit_queue.balance + manual_withdrawal_queue.balance
Net pending flowDirection of staking supplyPending inflow − pending outflow
Don’t count consolidations as flows. The consolidation_queue and compounding_switch_queue track validators moving stake between validators or switching to compounding credentials — that ETH stays staked and is neither an inflow nor an outflow.
The last leg of an outflow: after a validator exits, its balance waits through the eligibility delay (~27 hours) and the withdrawal sweep before being credited. The network endpoint exposes the current sweep duration via withdrawal_sweep.estimated_sweep_delay, and the per-validator overview returns min/max sweep estimates for each exiting validator.

Network-Wide Snapshot

Get the total pending inflow and outflow for the entire network:
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/queues \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/vnd.beaconcha.in.v2.1+json' \
  --data '{"chain": "mainnet"}'

Response

{
  "data": {
    "deposit_queue": {
      "deposit_count": 1184,
      "deposit_balance": "37888000000000000000000",
      "topup_count": 243,
      "topup_balance": "1850000000000000000000",
      "estimated_processed_at": {
        "epoch": 454155,
        "timestamp": 1781099520
      },
      "churn": {
        "amount": "256000000000000000000",
        "interval_seconds": 384
      }
    },
    "exit_queue": {
      "count": 420,
      "balance": "13440000000000000000000",
      "estimated_processed_at": {
        "epoch": 454053,
        "timestamp": 1781060352
      },
      "churn": {
        "amount": "256000000000000000000",
        "interval_seconds": 384
      }
    },
    "manual_withdrawal_queue": {
      "count": 310,
      "balance": "5120000000000000000000",
      "estimated_processed_at": {
        "epoch": 454210,
        "timestamp": 1781120640
      }
    },
    "withdrawal_sweep": {
      "estimated_sweep_delay": 777600,
      "last_swept_validator_index": 1284503
    },
    "consolidation_queue": {
      "count": 18,
      "balance": "1152000000000000000000",
      "estimated_processed_at": {
        "epoch": 454060,
        "timestamp": 1781063040
      },
      "churn": {
        "amount": "256000000000000000000",
        "interval_seconds": 384
      }
    },
    "compounding_switch_queue": {
      "count": 25,
      "estimated_processed_at": {
        "epoch": 454048,
        "timestamp": 1781058432
      },
      "churn": {
        "amount": "256000000000000000000",
        "interval_seconds": 384
      }
    },
    "finality": "not_finalized"
  }
}
In this example: pending inflow = 37,888 + 1,850 = 39,738 ETH, pending outflow = 13,440 + 5,120 = 18,560 ETH, net pending flow = +21,178 ETH entering staking.

Inflow Fields (Deposit Queue)

FieldDescription
deposit_queue.deposit_countNumber of new validator deposits waiting (excludes top-ups)
deposit_queue.deposit_balanceTotal ETH of new validator deposits, in wei
deposit_queue.topup_countNumber of top-up deposits waiting (excludes new validator deposits)
deposit_queue.topup_balanceTotal ETH of top-ups, in wei
deposit_queue.estimated_processed_atEpoch and Unix timestamp when the entire deposit queue will be processed
deposit_queue.churnBalance the protocol can process per interval (e.g. wei per 384-second epoch)

Outflow Fields (Exit Queue, Manual Withdrawals, Sweep)

FieldDescription
exit_queue.countNumber of validators waiting to exit
exit_queue.balanceTotal ETH waiting to exit, in wei
manual_withdrawal_queue.countPending withdrawal requests (partial withdrawals and exits triggered via withdrawal request)
manual_withdrawal_queue.balanceTotal ETH in pending manual withdrawals, in wei
withdrawal_sweep.estimated_sweep_delayEstimated maximum seconds for the sweep to complete a full rotation and credit withdrawable ETH
withdrawal_sweep.last_swept_validator_indexCurrent position of the sweep clock
estimated_processed_at always refers to the very last item in the queue — i.e. when the queue as it stands right now would be fully drained. Queues are null when empty.

Per-Validator Tracking

Track queue positions for your own validator set — including validators that are still in the deposit queue and have no index yet (select them by public key):
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/validators/queues \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "validator": {
    "validator_identifiers": ["0x93247f22...09abcaf5", 1284503]
  },
  "page_size": 10
}
'

Response

{
  "data": [
    {
      "validator": {
        "index": null,
        "public_key": "0x93247f22...09abcaf5"
      },
      "deposit_queue": {
        "deposit_count": 1,
        "deposit_balance": "32000000000000000000",
        "topup_count": 0,
        "topup_balance": "0",
        "estimated_processed_at": {
          "epoch": 454102,
          "timestamp": 1781079168
        },
        "churn": {
          "amount": "256000000000000000000",
          "interval_seconds": 384
        }
      },
      "exit_queue": null,
      "manual_withdrawal_queue": null,
      "withdrawal_sweep": null,
      "consolidation_queue": null,
      "compounding_switch_queue": null,
      "finality": "not_finalized"
    },
    {
      "validator": {
        "index": 1284503,
        "public_key": "0xb5a3c901...77fe12d4"
      },
      "deposit_queue": null,
      "exit_queue": {
        "count": 1,
        "balance": "32890000000000000000",
        "estimated_processed_at": {
          "epoch": 454031,
          "timestamp": 1781051904
        },
        "churn": {
          "amount": "256000000000000000000",
          "interval_seconds": 384
        }
      },
      "manual_withdrawal_queue": null,
      "withdrawal_sweep": {
        "min": {
          "estimated_processed_at": {
            "epoch": 454800,
            "timestamp": 1781347200
          }
        },
        "max": {
          "estimated_processed_at": {
            "epoch": 455400,
            "timestamp": 1781577600
          }
        }
      },
      "consolidation_queue": null,
      "compounding_switch_queue": null,
      "finality": "not_finalized"
    }
  ],
  "paging": {
    "next_cursor": ""
  }
}
How to read this:
  • The first validator is in activation: 32 ETH deposited, expected to be processed at epoch 454102. Its index is null because it has not been activated yet.
  • The second validator is in withdrawal: its exit is expected at epoch 454031, and the sweep is expected to credit its balance between epochs 454800 and 455400.
  • Queues a validator is not part of are returned as null.
  • If a validator has multiple pending items (e.g. several queued top-ups), estimated_processed_at refers to its last one.
Track whole fleets without index lists: on Scale & Enterprise plans, the validator selector also accepts a withdrawal credential/address, a deposit_address, or an entity name — convenient when your validators all share withdrawal credentials.

Building Historical Snapshots

The Queue APIs return the current queue state. To analyze trends — daily net flow, queue growth, average wait times — poll on a schedule and store each snapshot:
import csv
import requests
from datetime import datetime, timezone

API_KEY = "<YOUR_API_KEY>"
URL = "https://beaconcha.in/api/v2/ethereum/queues"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/vnd.beaconcha.in.v2.1+json",  # pin the unified schema
}

data = requests.post(URL, headers=headers, json={"chain": "mainnet"}).json()["data"]

def balance(queue, *fields):
    """Sum wei string fields of a possibly-null queue object."""
    if queue is None:
        return 0
    return sum(int(queue.get(field) or 0) for field in fields)

def eta(queue):
    if queue and queue.get("estimated_processed_at"):
        return queue["estimated_processed_at"]["timestamp"]
    return None

inflow_wei = balance(data["deposit_queue"], "deposit_balance", "topup_balance")
outflow_wei = balance(data["exit_queue"], "balance") + balance(
    data["manual_withdrawal_queue"], "balance"
)

with open("staking_flow_snapshots.csv", "a", newline="") as f:
    csv.writer(f).writerow([
        datetime.now(timezone.utc).isoformat(),
        inflow_wei,
        outflow_wei,
        inflow_wei - outflow_wei,  # net pending flow
        eta(data["deposit_queue"]),
        eta(data["exit_queue"]),
    ])
Run it from a scheduler (cron, Airflow, etc.). Queue state changes every epoch (~6.4 minutes), so per-epoch polling is the maximum useful resolution; hourly snapshots are enough for most reporting, and daily snapshots already reveal supply trends.
Snapshots only exist from the moment you start collecting them — set up polling before you need the history.

Best Practices

Treat ETAs as Estimates

Estimates assume the current churn rate and queue composition. Re-poll regularly and present time ranges to customers, not exact times.

Sum in Wei, Not Floats

Wei values exceed float64 precision. Parse the string amounts with arbitrary-precision integers and convert to ETH only for display.

Expect Nulls

Empty or not-applicable queues are returned as null. Treat them as zero when aggregating.