Skip to main content
Subscribe to real-time execution reports, trade captures, instrument state changes, and position updates for your firm using gRPC streaming.
gRPC Only - DropCopy endpoints are gRPC streaming only. There are no REST equivalents.

Service Definition

Service: polymarket.v1.DropCopyAPI Type: Server-side streaming (all endpoints)
service DropCopyAPI {
    rpc CreateDropCopySubscription(CreateDropCopySubscriptionRequest)
        returns (stream CreateDropCopySubscriptionResponse);

    rpc CreateTradeCaptureReportSubscription(CreateTradeCaptureReportSubscriptionRequest)
        returns (stream CreateTradeCaptureReportSubscriptionResponse);

    rpc CreateInstrumentStateChangeSubscription(CreateInstrumentStateChangeSubscriptionRequest)
        returns (stream CreateInstrumentStateChangeSubscriptionResponse);

    rpc CreatePositionChangeSubscription(CreatePositionChangeSubscriptionRequest)
        returns (stream CreatePositionChangeSubscriptionResponse);
}

Available Streams

StreamDescriptionUse Case
DropCopyExecution reports (fills, cancels)Real-time order execution monitoring
Trade Capture ReportCompleted tradesTrade reconciliation, compliance
Instrument State ChangeMarket state updatesTrading halts, market open/close
Position ChangePosition updatesReal-time P&L, risk monitoring

1. DropCopy Subscription

Stream execution reports as they occur for your firm.

Request Parameters

FieldTypeRequiredDescription
resume_tokenbytesNoResume from previous position
resume_timeTimestampNoResume from specific time
symbolslist[str]NoFilter by symbols. Empty = all symbols
firmslist[str]NoFilter by firms. Empty = authenticated firm

Response Fields

FieldTypeDescription
resume_tokenbytesStore for reconnection
executionslist[Execution]Execution reports in this batch

Example

import grpc
from datetime import datetime
from polymarket.v1 import dropcopy_pb2
from polymarket.v1 import dropcopy_pb2_grpc
from polymarket.v1 import enums_pb2


class DropCopyStreamer:
    def __init__(self, grpc_server: str = "grpc-api.preprod.polymarketexchange.com:443"):
        self.grpc_server = grpc_server
        self.access_token = None
        self.last_resume_token = None

    def stream_executions(self, symbols: list = None, resume_token: bytes = None):
        """Stream execution reports via DropCopy."""
        credentials = grpc.ssl_channel_credentials()
        channel = grpc.secure_channel(self.grpc_server, credentials)
        stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel)

        request = dropcopy_pb2.CreateDropCopySubscriptionRequest(
            symbols=symbols or []
        )
        if resume_token:
            request.resume_token = resume_token

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

        try:
            print("Starting DropCopy stream...")
            print(f"Symbols: {symbols or 'ALL'}")
            print("-" * 60)

            for response in stub.CreateDropCopySubscription(request, metadata=metadata):
                self.last_resume_token = response.resume_token

                for execution in response.executions:
                    self._display_execution(execution)

        except grpc.RpcError as e:
            print(f"gRPC error: {e.code()} - {e.details()}")
        finally:
            channel.close()

    def _display_execution(self, execution):
        """Display execution details."""
        print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Execution")
        print(f"  ID: {execution.id}")
        print(f"  Type: {enums_pb2.ExecutionType.Name(execution.type)}")

        if execution.HasField('order'):
            order = execution.order
            print(f"  Order ID: {order.id}")
            print(f"  Symbol: {order.symbol}")
            print(f"  Side: {enums_pb2.Side.Name(order.side)}")
            print(f"  State: {enums_pb2.OrderState.Name(order.state)}")

        if execution.last_shares > 0:
            print(f"  Fill Qty: {execution.last_shares}")
        if execution.last_px > 0:
            print(f"  Fill Price: {execution.last_px}")
        if execution.trade_id:
            print(f"  Trade ID: {execution.trade_id}")

        print("-" * 60)


# Usage
if __name__ == "__main__":
    streamer = DropCopyStreamer()
    # streamer.access_token = "your_token"
    streamer.stream_executions()

Sample Output

Starting DropCopy stream...
Symbols: ALL
------------------------------------------------------------

[14:30:15] Execution
  ID: exec_abc123
  Type: EXECUTION_TYPE_FILL
  Order ID: order_xyz789
  Symbol: tec-nfl-sbw-2026-02-08-kc
  Side: SIDE_BUY
  State: ORDER_STATE_FILLED
  Fill Qty: 100
  Fill Price: 525
  Trade ID: trade_def456
------------------------------------------------------------

[14:30:16] Execution
  ID: exec_ghi789
  Type: EXECUTION_TYPE_CANCELED
  Order ID: order_abc123
  Symbol: tec-nfl-sbw-2026-02-08-kc
  Side: SIDE_SELL
  State: ORDER_STATE_CANCELED
------------------------------------------------------------

2. Trade Capture Report Subscription

Stream completed trades for reconciliation and compliance.

Request Parameters

FieldTypeRequiredDescription
resume_tokenbytesNoResume from previous position
resume_timeTimestampNoResume from specific time
symbolslist[str]NoFilter by symbols
firmslist[str]NoFilter by firms

Response Fields

FieldTypeDescription
resume_tokenbytesStore for reconnection
trade_capture_reportslist[Trade]Trade records

Example

def stream_trade_captures(self, symbols: list = None):
    """Stream trade capture reports."""
    credentials = grpc.ssl_channel_credentials()
    channel = grpc.secure_channel(self.grpc_server, credentials)
    stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel)

    request = dropcopy_pb2.CreateTradeCaptureReportSubscriptionRequest(
        symbols=symbols or []
    )

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

    for response in stub.CreateTradeCaptureReportSubscription(request, metadata=metadata):
        for trade in response.trade_capture_reports:
            print(f"Trade ID: {trade.id}")
            print(f"  Aggressor: {trade.aggressor.order.id if trade.aggressor else 'N/A'}")
            print(f"  Passive: {trade.passive.order.id if trade.passive else 'N/A'}")
            print(f"  State: {trade.state}")

Trade Structure

Each trade contains two executions:
FieldDescription
idUnique trade ID
aggressorExecution for the incoming (taker) order
passiveExecution for the resting (maker) order
trade_typeType of trade (REGULAR, CROSS, etc.)
stateTrade state (NEW, CLEARED, etc.)

3. Instrument State Change Subscription

Stream market state changes (halts, opens, closes).
No Participant ID RequiredThis endpoint only requires Auth0 JWT authentication with read:instruments scope. You do not need to provide the x-participant-id header or complete KYC onboarding to access instrument state changes.

Request Parameters

FieldTypeRequiredDescription
resume_tokenbytesNoResume from previous position
resume_timeTimestampNoResume from specific time
symbolslist[str]NoFilter by symbols

Response Fields

FieldTypeDescription
resume_tokenbytesStore for reconnection
instrumentslist[Instrument]Updated instrument states

Instrument States

Primary State Flow

State                                              Description
INSTRUMENT_STATE_PENDINGInitial state for a newly created instrument which has not yet begun trading. Clients will receive a PENDING → OPEN state change notification but will not see PENDING in the order book.
INSTRUMENT_STATE_OPENIn this state, the instrument is open for continuous order entry and matching.
INSTRUMENT_STATE_CLOSEDIn this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired.
INSTRUMENT_STATE_EXPIREDAn instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered.
INSTRUMENT_STATE_TERMINATEDWhen an instrument’s Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers.

Exception States

State                                              Description
INSTRUMENT_STATE_SUSPENDEDOrders can be canceled but no matching occurs, and no order entry or modification is allowed.
INSTRUMENT_STATE_HALTEDThis state is similar to SUSPENDED, with the exception that orders cannot be canceled.

Other Possible States

State                                              Description
INSTRUMENT_STATE_PREOPENOrders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open.
INSTRUMENT_STATE_MATCH_AND_CLOSE_AUCTIONThis state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don’t want the instrument to be open after.

Example

def stream_instrument_states(self, symbols: list = None):
    """Stream instrument state changes."""
    credentials = grpc.ssl_channel_credentials()
    channel = grpc.secure_channel(self.grpc_server, credentials)
    stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel)

    request = dropcopy_pb2.CreateInstrumentStateChangeSubscriptionRequest(
        symbols=symbols or []
    )

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

    for response in stub.CreateInstrumentStateChangeSubscription(request, metadata=metadata):
        for instrument in response.instruments:
            print(f"Symbol: {instrument.symbol}")
            print(f"  State: {instrument.state}")
            print(f"  Description: {instrument.description}")

4. Position Change Subscription

Stream real-time position updates.

Request Parameters

FieldTypeRequiredDescription
resume_tokenbytesNoResume from previous position
resume_timeTimestampNoResume from specific time
symbolslist[str]NoFilter by symbols
firmslist[str]NoFilter by firms

Response Fields

FieldTypeDescription
resume_tokenbytesStore for reconnection
position_changeslist[PositionChange]Position updates

PositionChange Structure

FieldTypeDescription
positionPositionCurrent position state
change_timeTimestampWhen change occurred

Example

def stream_position_changes(self, symbols: list = None):
    """Stream position changes."""
    credentials = grpc.ssl_channel_credentials()
    channel = grpc.secure_channel(self.grpc_server, credentials)
    stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel)

    request = dropcopy_pb2.CreatePositionChangeSubscriptionRequest(
        symbols=symbols or []
    )

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

    for response in stub.CreatePositionChangeSubscription(request, metadata=metadata):
        for change in response.position_changes:
            pos = change.position
            print(f"Position Update: {pos.symbol}")
            print(f"  Account: {pos.account}")
            print(f"  Net Qty: {pos.net_position}")
            print(f"  Avg Price: {pos.average_price}")
            print(f"  Change Time: {change.change_time}")

Reconnection Handling

All DropCopy streams support resume tokens for seamless reconnection:
# Store resume_token from each response
last_token = response.resume_token

# On reconnect, pass the token
request = dropcopy_pb2.CreateDropCopySubscriptionRequest(
    resume_token=last_token
)
Resume Token ExpiryResume tokens may expire after extended disconnection periods. If resumption fails, start a fresh subscription and reconcile with the Report API for any missed data.

Comparing DropCopy vs Order Stream

FeatureDropCopyOrder Stream
ScopeFirm-wide executionsUser’s orders only
Use CaseBack-office, complianceTrading UI, order management
DataExecutions, tradesOrders, executions
AuthenticationFirm-level tokenUser token
When to Use DropCopyUse DropCopy when you need:
  • Firm-wide visibility across all users
  • Trade capture reports for reconciliation
  • Instrument state change notifications
  • Real-time position monitoring across accounts

Next Steps