Documentation
¶
Overview ¶
Request signing for the authenticated endpoints, matching the official polymarket-us SDKs: the message timestampMs + METHOD + path — path EXCLUDES query parameters — is signed with Ed25519 and sent base64-encoded in three X-PM-* headers. The same headers on the HTTP upgrade request (method "GET", the stream path) authenticate WebSocket connections.
Client construction, public market-data endpoints, and the shared HTTP plumbing. Trading endpoints live in trading.go, WebSocket streams in ws.go, request signing in auth.go. See doc.go for the package overview.
Package polymarket is an unofficial Go client for the Polymarket US exchange API (api.polymarket.us). It is not affiliated with Polymarket.
Without a Signer the client is strictly read-only public market data (NewClient — no credentials, nothing is ever sent to trading endpoints). With one (NewAuthedClient) the trading endpoints and the WebSocket streams become available:
c := polymarket.NewClient(nil)
bbo, err := c.FetchBBO(ctx, "some-market-slug")
signer, err := polymarket.NewSigner(keyID, secretKey)
c = polymarket.NewAuthedClient(nil, signer, "", "")
res, err := c.CreateOrder(ctx, polymarket.Order{...})
The wire protocol follows the official polymarket-us TypeScript and Python SDKs (v0.1.x): requests are authenticated with Ed25519-signed X-PM-* headers, public market data is served from a gateway host, and trading plus both WebSocket streams use the api host.
Prices and quantities ¶
Prices cross the wire as decimal-dollar Amount objects ({"value":"0.55", "currency":"USD"}). For convenience this package parses prices to integer cents (and quantities to float64) wherever it decodes a response; fields set by the exchange also carry the raw decimal strings (Px, Qty, AvgPx, ...) so no precision is ever lost to the conversion.
Errors ¶
A non-2xx response surfaces as *APIError, carrying the HTTP status and the exchange's reason; match it with errors.As. Order rejections arrive inside 2xx execution streams and surface as plain errors from CreateOrder/ClosePosition with the reject reason.
Retries ¶
Reads and cancels retry 429s with linear backoff. Order placement makes exactly one attempt: the API has no client order id, so a retried create would be a brand-new order. After an ambiguous failure, reconcile with OpenOrders/Positions before re-sending.
Authenticated trading endpoints: orders, positions, balance. All of these require a Signer (NewAuthedClient); the read-only market-data client never touches them. They hit the api host, not the public gateway.
The exchange has four order intents because LONG and SHORT are separate instruments: BUY_LONG/SELL_LONG open and close a long (yes) position, BUY_SHORT/SELL_SHORT the short side. Prices always quote the named instrument — there is no 100−p inversion anywhere.
Order placement does NOT retry. This API has no client order id, so a retried create is a brand-new order to the exchange — after a 429 or an ambiguous transport failure the only safe recovery is to reconcile via OpenOrders/Positions, which is the caller's job. Cancels and reads are idempotent and keep the retry loop.
WebSocket streams: market data (books, BBOs, trades) and private data (order executions, positions, balances), both on the api host and both requiring a Signer — the exchange authenticates even the market stream. Auth rides the HTTP upgrade request as the same three X-PM-* headers, signed over "GET" + the stream path.
The shape is pull-based: Dial, Subscribe*, then call Next in a loop. Next returns one parsed message; an error from Next about one message's content leaves the stream usable, a transport error does not (redial to recover — subscriptions do not survive a reconnect).
Concurrency: at most one goroutine may call Next at a time. Subscribe*, Unsubscribe, and Close are safe to call concurrently with Next and with each other (the underlying library serializes writes).
Index ¶
- Constants
- type APIError
- type Amount
- type BBO
- type Balance
- type Book
- type Client
- func (c *Client) Balance(ctx context.Context) (Balance, error)
- func (c *Client) CancelAllOrders(ctx context.Context, slugs ...string) ([]string, error)
- func (c *Client) CancelOrder(ctx context.Context, orderID, marketSlug string) error
- func (c *Client) ClosePosition(ctx context.Context, marketSlug string) (OrderResult, error)
- func (c *Client) CreateOrder(ctx context.Context, o Order) (OrderResult, error)
- func (c *Client) DialMarkets(ctx context.Context) (*MarketsStream, error)
- func (c *Client) DialPrivate(ctx context.Context) (*PrivateStream, error)
- func (c *Client) FetchBBO(ctx context.Context, slug string) (BBO, error)
- func (c *Client) FetchBook(ctx context.Context, slug string) (Book, error)
- func (c *Client) FetchEvent(ctx context.Context, slug string) (Event, error)
- func (c *Client) FetchMarket(ctx context.Context, slug string) (Market, error)
- func (c *Client) FetchOrder(ctx context.Context, orderID string) (OpenOrder, error)
- func (c *Client) FetchSettlement(ctx context.Context, slug string) (Settlement, error)
- func (c *Client) ListMarkets(ctx context.Context, f MarketFilter) ([]Market, error)
- func (c *Client) OpenOrders(ctx context.Context, slugs ...string) ([]OpenOrder, error)
- func (c *Client) Positions(ctx context.Context) (map[string]Position, error)
- type Event
- type Level
- type Market
- type MarketFilter
- type MarketsMessage
- type MarketsStream
- func (s *MarketsStream) Close() error
- func (s *MarketsStream) Next(ctx context.Context) (MarketsMessage, error)
- func (s *MarketsStream) SubscribeBBOs(ctx context.Context, requestID string, slugs ...string) error
- func (s *MarketsStream) SubscribeBooks(ctx context.Context, requestID string, slugs ...string) error
- func (s *MarketsStream) SubscribeTrades(ctx context.Context, requestID string, slugs ...string) error
- func (s *MarketsStream) Unsubscribe(ctx context.Context, requestID string) error
- type OpenOrder
- type Order
- type OrderResult
- type OrderSnapshot
- type Position
- type PositionSnapshot
- type PositionUpdate
- type PrivateMessage
- type PrivateStream
- func (s *PrivateStream) Close() error
- func (s *PrivateStream) Next(ctx context.Context) (PrivateMessage, error)
- func (s *PrivateStream) SubscribeBalance(ctx context.Context, requestID string) error
- func (s *PrivateStream) SubscribeOrders(ctx context.Context, requestID string, slugs ...string) error
- func (s *PrivateStream) SubscribePositions(ctx context.Context, requestID string, slugs ...string) error
- func (s *PrivateStream) Unsubscribe(ctx context.Context, requestID string) error
- type Settlement
- type Signer
- type StreamExecution
- type StreamTrade
Examples ¶
Constants ¶
const ( DefaultGatewayURL = "https://gateway.polymarket.us" DefaultAPIURL = "https://api.polymarket.us" )
Production hosts. Public market data and authenticated trading are served from different bases; the client routes by endpoint, callers never choose.
const ( IntentBuyLong = "ORDER_INTENT_BUY_LONG" IntentSellLong = "ORDER_INTENT_SELL_LONG" IntentBuyShort = "ORDER_INTENT_BUY_SHORT" IntentSellShort = "ORDER_INTENT_SELL_SHORT" )
Order intents. BUY_LONG opens/extends a long (yes) position; SELL_LONG unwinds one. The SHORT pair does the same for the short (no) side.
const ( TIFImmediateOrCancel = "TIME_IN_FORCE_IMMEDIATE_OR_CANCEL" TIFGoodTillCancel = "TIME_IN_FORCE_GOOD_TILL_CANCEL" TIFFillOrKill = "TIME_IN_FORCE_FILL_OR_KILL" )
Time-in-force values (the API also offers GOOD_TILL_DATE, which this client does not support).
const ( StateNew = "ORDER_STATE_NEW" StatePartiallyFilled = "ORDER_STATE_PARTIALLY_FILLED" StateFilled = "ORDER_STATE_FILLED" StateCanceled = "ORDER_STATE_CANCELED" StateRejected = "ORDER_STATE_REJECTED" StateExpired = "ORDER_STATE_EXPIRED" )
Order states (subset — the full machine includes PENDING_* transients).
const ( SubMarketData = "SUBSCRIPTION_TYPE_MARKET_DATA" // full book refreshes SubMarketDataLite = "SUBSCRIPTION_TYPE_MARKET_DATA_LITE" // best bid/ask only SubTrade = "SUBSCRIPTION_TYPE_TRADE" SubOrder = "SUBSCRIPTION_TYPE_ORDER" SubPosition = "SUBSCRIPTION_TYPE_POSITION" SubAccountBalance = "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE" )
Subscription types, matching the exchange's vocabulary.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type APIError ¶
type APIError struct {
StatusCode int // HTTP status
What string // request label, e.g. "create order btc-100k"
Body string // response body (truncated) — the exchange's reason
}
APIError is a non-2xx response from the exchange. Match with errors.As to branch on StatusCode:
var apiErr *polymarket.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == 429 { ... }
Order rejections are NOT APIErrors — the exchange reports them inside a 2xx execution stream, and CreateOrder surfaces them as plain errors carrying the reject reason.
type Amount ¶
Amount is the API's money shape: a decimal-dollar string plus currency.
type BBO ¶
type BBO struct {
MarketSlug string
BidC int
AskC int
LastTradeC int
BidPx string
AskPx string
LastTradePx string
}
BBO is a market's top of book. The cents fields are 0 when that side is empty; the Px fields carry the exchange's exact decimals.
type Book ¶
type Book struct {
MarketSlug string
Bids []Level
Offers []Level
State string // MARKET_STATE_OPEN | _SUSPENDED | _HALTED | ...
}
Book is a market's full displayed depth. Both sides quote the same instrument (the market's LONG contract): bids are resting buys, offers resting sells. Best-priced level first on each side.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client reads public market data, and — when built by NewAuthedClient — places orders and reads the portfolio. The zero value is not usable.
func NewAuthedClient ¶
NewAuthedClient returns a Client that signs trading requests with signer. gatewayURL/apiURL "" mean production; tests pass their own.
func NewClient ¶
NewClient returns a read-only Client using hc (or a sane default if hc is nil).
Example ¶
Read public market data with no credentials.
package main
import (
"context"
"fmt"
"log"
polymarket "github.com/jaracah/polymarket-us-go"
)
func main() {
c := polymarket.NewClient(nil)
bbo, err := c.FetchBBO(context.Background(), "some-market-slug")
if err != nil {
log.Fatal(err)
}
fmt.Printf("bid %d¢ ask %d¢ (raw %s/%s)\n", bbo.BidC, bbo.AskC, bbo.BidPx, bbo.AskPx)
}
Output:
func (*Client) Balance ¶
Balance returns the account's first (USD) balance. It also serves as a cheap authenticated connectivity probe at startup.
func (*Client) CancelAllOrders ¶
CancelAllOrders cancels every open order (or, with slugs, every open order in those markets) and returns the canceled ids.
func (*Client) CancelOrder ¶
CancelOrder cancels one resting order. The API requires the order's market slug alongside its id.
func (*Client) ClosePosition ¶
ClosePosition asks the exchange to flatten the position in one market at market price and reports what executed. Like CreateOrder it is a single attempt — it places an order.
func (*Client) CreateOrder ¶
CreateOrder places o (always a limit order — market orders need slippage-tolerance machinery this client does not implement) and reports what executed synchronously. Single attempt, never retried: with no client order id, a retry is a second order (see the package comment in this file).
Example ¶
Place a limit order with an authenticated client.
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
polymarket "github.com/jaracah/polymarket-us-go"
)
func main() {
signer, err := polymarket.NewSigner(
os.Getenv("POLYMARKET_KEY_ID"),
os.Getenv("POLYMARKET_SECRET_KEY"),
)
if err != nil {
log.Fatal(err)
}
c := polymarket.NewAuthedClient(nil, signer, "", "")
res, err := c.CreateOrder(context.Background(), polymarket.Order{
MarketSlug: "some-market-slug",
Intent: polymarket.IntentBuyLong,
Quantity: 100,
PriceC: 55, // 55¢ limit, zero TimeInForce = immediate-or-cancel
})
if err != nil {
var apiErr *polymarket.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusTooManyRequests {
// Rate limited before the order reached the book. Do NOT blindly
// re-send after an ambiguous failure — reconcile with
// OpenOrders/Positions first (there is no client order id).
}
log.Fatal(err)
}
fmt.Printf("order %s: filled %d @ %d¢, %d resting\n",
res.OrderID, res.FillCount, res.AvgPriceC, res.Remaining)
}
Output:
func (*Client) DialMarkets ¶
func (c *Client) DialMarkets(ctx context.Context) (*MarketsStream, error)
DialMarkets opens the market-data stream.
Example ¶
Consume live books and trades from the market-data stream.
package main
import (
"context"
"fmt"
"log"
"os"
polymarket "github.com/jaracah/polymarket-us-go"
)
func main() {
signer, err := polymarket.NewSigner(
os.Getenv("POLYMARKET_KEY_ID"),
os.Getenv("POLYMARKET_SECRET_KEY"),
)
if err != nil {
log.Fatal(err)
}
c := polymarket.NewAuthedClient(nil, signer, "", "")
ctx := context.Background()
st, err := c.DialMarkets(ctx)
if err != nil {
log.Fatal(err)
}
defer st.Close()
if err := st.SubscribeBooks(ctx, "books-1", "some-market-slug"); err != nil {
log.Fatal(err)
}
for {
msg, err := st.Next(ctx)
if err != nil {
log.Fatal(err) // transport error: redial and re-subscribe
}
switch {
case msg.Book != nil:
fmt.Printf("%s: %d bids, %d offers\n",
msg.Book.MarketSlug, len(msg.Book.Bids), len(msg.Book.Offers))
case msg.Err != "":
log.Printf("subscription %s failed: %s", msg.RequestID, msg.Err)
}
}
}
Output:
func (*Client) DialPrivate ¶
func (c *Client) DialPrivate(ctx context.Context) (*PrivateStream, error)
DialPrivate opens the private stream.
func (*Client) FetchEvent ¶
FetchEvent returns one event (with its nested markets) by slug.
func (*Client) FetchMarket ¶
FetchMarket returns one market by slug.
func (*Client) FetchOrder ¶
FetchOrder returns one order by id.
func (*Client) FetchSettlement ¶
FetchSettlement returns a settled market's settlement price and time.
func (*Client) ListMarkets ¶
ListMarkets returns markets matching f.
func (*Client) OpenOrders ¶
OpenOrders returns open orders, optionally filtered to the given market slugs.
type Event ¶
type Event struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Title string `json:"title"`
StartTime string `json:"startTime"` // ISO-8601
EndTime string `json:"endTime"` // ISO-8601
Active bool `json:"active"`
Closed bool `json:"closed"`
Markets []Market `json:"markets"`
}
Event groups related markets (e.g. one game, one date's question).
type Level ¶
type Level struct {
PriceC int // price in integer cents
Size float64 // contracts
Px string // raw decimal-dollar price, e.g. "0.55"
Qty string // raw decimal quantity
}
Level is one resting orderbook level. PriceC and Size are parsed for convenience; Px and Qty are the exchange's exact decimal strings.
type Market ¶
type Market struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Title string `json:"title"`
Outcome string `json:"outcome"`
Description string `json:"description"`
Active bool `json:"active"`
Closed bool `json:"closed"`
Liquidity float64 `json:"liquidity"`
Volume float64 `json:"volume"`
EventSlug string `json:"eventSlug"`
}
Market is one tradeable outcome; an Event groups the related markets.
type MarketFilter ¶
type MarketFilter struct {
EventSlug string
ActiveOnly bool
Limit int // 0 = server default
Offset int
}
MarketFilter narrows ListMarkets. Zero fields are omitted; ActiveOnly maps to active=true&closed=false.
type MarketsMessage ¶
type MarketsMessage struct {
RequestID string
Heartbeat bool
Err string // server-reported subscription error
Book *Book
BBO *BBO
Trade *StreamTrade
}
MarketsMessage is one parsed frame off the market stream. Exactly one of Book, BBO, Trade is non-nil unless Heartbeat is set or Err is non-empty.
type MarketsStream ¶
type MarketsStream struct {
// contains filtered or unexported fields
}
MarketsStream is the market-data stream (path /v1/ws/markets).
func (*MarketsStream) Next ¶
func (s *MarketsStream) Next(ctx context.Context) (MarketsMessage, error)
Next blocks for one frame and parses it.
func (*MarketsStream) SubscribeBBOs ¶
SubscribeBBOs streams top-of-book updates for slugs.
func (*MarketsStream) SubscribeBooks ¶
func (s *MarketsStream) SubscribeBooks(ctx context.Context, requestID string, slugs ...string) error
SubscribeBooks streams full-depth Book refreshes for slugs.
func (*MarketsStream) SubscribeTrades ¶
func (s *MarketsStream) SubscribeTrades(ctx context.Context, requestID string, slugs ...string) error
SubscribeTrades streams the tape for slugs.
type OpenOrder ¶
type OpenOrder struct {
ID string
MarketSlug string
Intent string
PriceC int
Quantity int
CumQuantity int
Remaining int
TimeInForce string
State string
AvgPriceC int
AvgPx string // raw decimal average fill price ("" when no fill)
CreateTime string // ISO-8601
}
OpenOrder is one resting or recently-terminal order.
type Order ¶
type Order struct {
MarketSlug string
Intent string // IntentBuyLong | IntentSellLong | IntentBuyShort | IntentSellShort
Quantity int // whole contracts, > 0
PriceC int // limit price in cents, 1..99
TimeInForce string // "" (= TIFImmediateOrCancel), TIFGoodTillCancel, or TIFFillOrKill
PostOnly bool
}
Order is a limit order in integer cents and whole contracts. The zero TimeInForce is IOC. PostOnly (the API's participateDontInitiate) makes the exchange reject a placement that would cross instead of taking; it requires a resting time-in-force.
type OrderResult ¶
type OrderResult struct {
OrderID string
FillCount int // contracts filled so far (cumQuantity)
Remaining int // contracts still open (leavesQuantity)
AvgPriceC int // volume-weighted average fill price, cents (0 when no fill)
AvgPx string // raw decimal average fill price ("" when no fill)
State string // final ORDER_STATE_* seen, "" if the response carried none
}
OrderResult is the exchange's synchronous answer to an order (the client sends synchronousExecution so IOC outcomes come back in the response).
type OrderSnapshot ¶
OrderSnapshot is the subscription-open replay of resting orders. EOF marks the last snapshot frame; updates follow.
type Position ¶
type Position struct {
Net int
NetFP float64 // netPosition verbatim; fractional while partially unwound
CostC int // aggregate cost basis, cents
RealizedC int // realized P&L, cents
Expired bool
}
Position is one market's exchange-side position. Net is positive for a long holding and negative for a short one, matching the sign of the exchange's netPosition.
type PositionSnapshot ¶
PositionSnapshot is the subscription-open replay of positions by slug.
type PositionUpdate ¶
PositionUpdate is one market's position after a change.
type PrivateMessage ¶
type PrivateMessage struct {
RequestID string
Heartbeat bool
Err string
OrderSnapshot *OrderSnapshot
Execution *StreamExecution
PositionSnapshot *PositionSnapshot
PositionUpdate *PositionUpdate
Balance *Balance
}
PrivateMessage is one parsed frame off the private stream. Exactly one payload field is non-nil unless Heartbeat is set or Err is non-empty. Balance covers both the snapshot and update frames (same shape).
type PrivateStream ¶
type PrivateStream struct {
// contains filtered or unexported fields
}
PrivateStream is the account stream (path /v1/ws/private).
func (*PrivateStream) Next ¶
func (s *PrivateStream) Next(ctx context.Context) (PrivateMessage, error)
Next blocks for one frame and parses it. A parse error on one frame's content (e.g. an unparseable position) is returned but consumes only that frame — the stream remains readable.
func (*PrivateStream) SubscribeBalance ¶
func (s *PrivateStream) SubscribeBalance(ctx context.Context, requestID string) error
SubscribeBalance streams the account balance.
func (*PrivateStream) SubscribeOrders ¶
func (s *PrivateStream) SubscribeOrders(ctx context.Context, requestID string, slugs ...string) error
SubscribeOrders streams the open-order snapshot then per-execution updates, optionally filtered to slugs.
func (*PrivateStream) SubscribePositions ¶
func (s *PrivateStream) SubscribePositions(ctx context.Context, requestID string, slugs ...string) error
SubscribePositions streams the position snapshot then per-market updates, optionally filtered to slugs.
type Settlement ¶
type Settlement struct {
MarketSlug string
PriceC int
Px string // raw decimal settlement price
SettledAt string // ISO-8601
}
Settlement is a settled market's final price.
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer holds an API key id and its Ed25519 private key, and signs requests in place. Safe for concurrent use. Generate API keys at https://polymarket.us/developer.
type StreamExecution ¶
type StreamExecution struct {
Type string // EXECUTION_TYPE_*
Order OpenOrder
LastQty string // raw decimal contracts in this execution
LastPriceC int // price of this execution, cents (fills)
LastPx string // raw decimal price of this execution
TradeID string
Aggressor bool
TransactTime string // ISO-8601
RejectReason string
}
StreamExecution is one execution-report update on an order.