Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.polymarket.us/llms.txt

Use this file to discover all available pages before exploring further.

Subscribe to real-time balance ledger entries (deposits, withdrawals, fills, fees, adjustments) using gRPC streaming. The stream first replays any entries since resume_time, then pushes new entries as they happen. For paginated historical queries and CSV exports of the same data, see the Balance Ledger REST API.
This stream is distinct from Funding Transaction Streaming: that stream tracks transaction state changes (PENDING → COMPLETED, etc.) for deposits/withdrawals; this stream tracks the balance impact of every cash event in the ledger.

Service Definition

Service: polymarket.v1.FundingAPI RPC: CreateBalanceLedgerSubscription Type: Server-side streaming Required Scope: read:positions
service FundingAPI {
    rpc CreateBalanceLedgerSubscription(CreateBalanceLedgerSubscriptionRequest)
        returns (stream CreateBalanceLedgerSubscriptionResponse);
}

Request Parameters

CreateBalanceLedgerSubscriptionRequest

FieldTypeRequiredDescription
accountstrYesFully qualified account name. Example: firms/ISV-Alice/accounts/alice-trading.
currencystrNoISO currency code (e.g., USD). Empty = all currencies for the account.
entry_typeslist[LedgerEntryType]NoFilter by one or more allowlisted entry types. Empty = all allowlisted types. Suppressed types are filtered server-side regardless.
resume_timeTimestampNoReplay entries with update_time >= resume_time before switching to live push. Clamped upstream to 2026-05-01T00:00:00Z.
Cross-firm access is rejected. The account must belong to the firm in the JWT firm_id claim. Cross-firm subscriptions return PERMISSION_DENIED immediately.

Response Messages

The stream returns CreateBalanceLedgerSubscriptionResponse messages.

CreateBalanceLedgerSubscriptionResponse

FieldTypeDescription
entrieslist[BalanceLedgerEntry]Zero or more ledger entries in this batch. Empty messages function as heartbeats.

BalanceLedgerEntry

FieldTypeDescription
idstrUnique entry identifier.
accountstrAccount this entry belongs to.
currencystrISO currency code.
before_balancestrBalance immediately before this change (decimal).
after_balancestrBalance immediately after this change (decimal).
descriptionstrHuman-readable reason for the change.
update_timeTimestampTimestamp of the balance change. Persist for use as the next resume_time.
modified_security_idstrSecurity ID associated with the change, if any.
entry_typeLedgerEntryTypeOne of the allowlisted entry types (see below).
symbolstrInstrument symbol associated with the change, if any.
update_business_datestrBusiness date in YYYY-MM-DD.

Stream Behavior

  1. Replay phase. On connect, the server first delivers entries with update_time >= resume_time (clamped to the 2026-05-01 floor). If resume_time is omitted, only live entries are delivered.
  2. Live phase. After the replay drains, the server pushes new entries as they are committed.
  3. Suppressed entry types are filtered server-side and never reach clients.
  4. Empty entries messages are heartbeats and should be passed through (do not treat as termination).

LedgerEntryType Allowlist

Wire ValueName
1DEPOSIT
2WITHDRAWAL
3ORDER_EXECUTION
4CORRECTION
6RESOLUTION
7MANUAL_ADJUSTMENT
10ACCOUNT_PROPERTY_ADJUSTMENT
11COMMISSION
16WITHDRAWAL_REJECTION
17MANUAL_TRANSFER
22PENDING_WITHDRAWAL_CREATION
The full allowlist plus suppressed (internal) types are documented on the Balance Ledger REST overview.

Stream Limits

LimitValue
Concurrent streams per firm (across all gRPC subscriptions)20
Historical floor on resume_time2026-05-01T00:00:00Z (clamped upstream)
Exceeding the per-firm concurrent stream cap returns ResourceExhausted.

Metrics

The gateway exposes Prometheus metrics for balance ledger subscriptions:
MetricTypeLabelsDescription
balance_ledger_stream_activegaugefirm_idNumber of active subscriptions per firm
balance_ledger_stream_events_totalcounterfirm_id, entry_typeTotal entries delivered per firm and entry type

Complete Python Example

import grpc
from datetime import datetime
from google.protobuf import timestamp_pb2
from polymarket.v1 import funding_pb2
from polymarket.v1 import funding_pb2_grpc


class BalanceLedgerStreamer:
    def __init__(self, grpc_server: str = "grpc-api.preprod.polymarketexchange.com:443"):
        self.grpc_server = grpc_server
        self.access_token = None
        self.last_update_time: timestamp_pb2.Timestamp | None = None  # for resume_time

    def stream_balance_ledger(
        self,
        account: str,
        currency: str = "",
        entry_types: list | None = None,
        resume_time: timestamp_pb2.Timestamp | None = None,
    ):
        """Stream balance ledger entries for one account."""
        if not self.access_token:
            raise ValueError("Not authenticated. Please login first.")

        credentials = grpc.ssl_channel_credentials()
        channel = grpc.secure_channel(self.grpc_server, credentials)
        stub = funding_pb2_grpc.FundingAPIStub(channel)

        request = funding_pb2.CreateBalanceLedgerSubscriptionRequest(
            account=account,
            currency=currency,
            entry_types=entry_types or [],
        )
        if resume_time is not None:
            request.resume_time.CopyFrom(resume_time)

        metadata = [("authorization", f"Bearer {self.access_token}")]

        try:
            print(f"Subscribing to balance ledger for {account} (currency={currency or 'ALL'})")
            response_stream = stub.CreateBalanceLedgerSubscription(
                request, metadata=metadata
            )

            for response in response_stream:
                if not response.entries:
                    continue
                for entry in response.entries:
                    self._process_entry(entry)
                    self.last_update_time = entry.update_time

        except grpc.RpcError as e:
            print(f"gRPC error: {e.code()} - {e.details()}")
            raise
        except KeyboardInterrupt:
            print("\nStream interrupted by user")
        finally:
            channel.close()

    def _process_entry(self, entry):
        ts = entry.update_time.ToDatetime().isoformat()
        entry_type_name = funding_pb2.LedgerEntryType.Name(entry.entry_type)
        delta = float(entry.after_balance) - float(entry.before_balance)
        print(
            f"[{ts}] {entry_type_name:30s} "
            f"{entry.before_balance} -> {entry.after_balance} "
            f"(delta={delta:+.2f} {entry.currency}) "
            f"{entry.description}"
        )


if __name__ == "__main__":
    streamer = BalanceLedgerStreamer()
    # streamer.access_token = "your_access_token"  # see Authentication docs

    streamer.stream_balance_ledger(
        account="firms/ISV-Alice/accounts/alice-trading",
        currency="USD",
    )

Sample Output

Subscribing to balance ledger for firms/ISV-Alice/accounts/alice-trading (currency=USD)
[2026-05-02T14:30:15.123000] ORDER_EXECUTION                10000.00 -> 9474.03 (delta=-525.97 USD) Buy 100 @ 525 + commission
[2026-05-02T14:30:15.123000] COMMISSION                     9474.03 -> 9472.53 (delta=-1.50 USD) Maker fee
[2026-05-02T14:32:01.001000] DEPOSIT                        9472.53 -> 14472.53 (delta=+5000.00 USD) ACH deposit

Reconnection Handling

Persist the most recent update_time so reconnections resume without gaps:
if streamer.last_update_time is not None:
    streamer.stream_balance_ledger(
        account="firms/ISV-Alice/accounts/alice-trading",
        currency="USD",
        resume_time=streamer.last_update_time,
    )
resume_time is clamped upstream to 2026-05-01T00:00:00Z; passing an earlier value is allowed but only entries from the floor forward are replayed.

REST vs Streaming

AspectREST /v1/funding/balance-ledgergRPC CreateBalanceLedgerSubscription
Delivery modelPolled, paginated queryPush-based stream
ReplayVia start_time / end_time + pagingVia resume_time (clamped to floor)
CSV exportYes (/download endpoint)No
Best forReconciliation, audit reports, ad-hoc queriesReal-time balance dashboards, automated alerts

Error Codes

gRPC CodeCause
PERMISSION_DENIEDAccount belongs to a different firm, or token is missing the read:positions scope.
UNAUTHENTICATEDMissing or invalid JWT.
FAILED_PRECONDITIONISV credentials not configured.
UNAVAILABLEUpstream exchange service not connected.
RESOURCE_EXHAUSTEDPer-firm 20 concurrent stream cap exceeded.

Next Steps

Balance Ledger REST

Paginated query and CSV download

Position Ledger

Position changes (quantity, cost, realized P&L)

Funding Transactions

Deposit / withdrawal state changes

Authentication

gRPC authentication setup