A Go client library for the Polymarket APIs. Provides access to market data, order books, pricing, events, trades, positions, and authenticated endpoints (orders, balances, cancellations) across all three Polymarket API surfaces.
Zero external dependencies — built entirely on the Go standard library.
go get github.com/caxqueiroz/polymarket-goRequires Go 1.25 or later.
Order signing and submission use the official CLOB V2 protocol. The API base URL remains https://clob.polymarket.com.
- The Polygon exchange addresses and order-signing domain use V2. L1 credential authentication still uses its original domain.
- Signed orders include a millisecond
Timestamp,Metadata, andBuilder. Empty metadata/builder values become zero bytes32; custom values must be 32-byte hex strings. Expiration remains in the HTTP payload but is not part of the V2 signature. Taker,Nonce, andFeeRateBPSare deprecated. Nonzero values are rejected instead of silently ignored; do not populate them from the fee-rate endpoint.PostOrderWithOptionssupportsPostOnlyandDeferExec. Post-only orders require GTC or GTD. UseOrderFAKfor fill-and-kill; the legacyOrderIOCconstant is translated to FAK on submission.SignatureTypePoly1271supports deposit-wallet signatures. SetMakerto the deposit-wallet address and authenticate HTTP requests with the owner EOA's credentials/address. Wallet deployment, collateral wrapping, and approvals are separate operations and are not performed by this library.- Builder attribution is a public bytes32 code in
CreateOrderParams.Builder, included before signing.WithBuilderCredentialsis a deprecated no-op and no longer emits legacy builder HMAC headers. Query the public paginated feed withGetBuilderTradesByCode;WithBuilderCodesupplies the code for the deprecatedGetBuilderTradeswrapper, not for order signing. - Trade responses retain taker-order and maker-fill attribution, and balance responses expose the per-contract
Allowancesmap. Cancellation methods require server acknowledgement; partial batch cancellation returns both successful IDs and a*CancelErrordescribing unconfirmed IDs.
signed, err := signer.CreateOrder(polymarket.CreateOrderParams{
TokenID: tokenID,
Price: 0.50,
Size: 10,
Side: polymarket.Buy,
Builder: os.Getenv("POLY_BUILDER_CODE"), // optional public bytes32 code
})
if err != nil {
return err
}
resp, err := client.Clob.PostOrderWithOptions(ctx, signed, polymarket.OrderGTC,
polymarket.PostOrderOptions{PostOnly: true})go run ./examples/postorder -token TOKEN_ID previews an order without network calls. It requires a local POLY_PRIVATE_KEY; only an explicit -send submits an order. Check the token's tick size, minimum order size, market type, collateral, approvals, and account eligibility before submitting.
Offline regression coverage includes independent viem-generated V2 signing vectors (with source commit recorded in testdata/v2-signing.json), all four signature types, negative-risk domains, literal HTTP payloads, pagination, and cancellation failures. Run go test -race ./... and go vet ./.... These checks do not establish live trading readiness.
package main
import (
"context"
"fmt"
"log"
polymarket "github.com/caxqueiroz/polymarket-go"
)
func main() {
client := polymarket.NewClient()
ctx := context.Background()
// Search for markets
results, err := client.Gamma.Search(ctx, "bitcoin")
if err != nil {
log.Fatal(err)
}
for _, e := range results.Events {
fmt.Println(e.Title)
}
// Get order book depth
book, err := client.Clob.GetBook(ctx, "TOKEN_ID")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Best bid: %s, Best ask: %s\n", book.Bids[0].Price, book.Asks[0].Price)
}client := polymarket.NewClient(
polymarket.WithCredentials(&polymarket.Credentials{
Key: os.Getenv("POLY_API_KEY"),
Secret: os.Getenv("POLY_API_SECRET"),
Passphrase: os.Getenv("POLY_PASSPHRASE"),
Address: os.Getenv("POLY_ADDRESS"),
}),
)
orders, err := client.Clob.GetOrders(ctx, nil)
balance, err := client.Clob.GetBalanceAllowance(ctx, nil)
trades, err := client.Clob.GetTrades(ctx, nil)
_, err = client.Clob.CancelAll(ctx)The top-level Client provides access to three sub-clients, each targeting a different Polymarket API:
client := polymarket.NewClient()
client.Clob // Order book, pricing, market parameters
client.Gamma // Market/event metadata, search, tags
client.Data // Trades, positions, activity, open interest| Sub-client | Base URL | Focus |
|---|---|---|
ClobClient |
https://clob.polymarket.com |
Order book, prices, spreads, tick sizes |
GammaClient |
https://gamma-api.polymarket.com |
Market/event metadata, search, tags |
DataClient |
https://data-api.polymarket.com |
Trades, positions, activity, holders |
Public endpoints work without credentials. Authenticated CLOB endpoints (orders, trades, balances) require API key credentials.
Use functional options to customize the client:
client := polymarket.NewClient(
polymarket.WithHTTPClient(myHTTPClient),
polymarket.WithClobBaseURL("https://custom-clob-url.com"),
polymarket.WithGammaBaseURL("https://custom-gamma-url.com"),
polymarket.WithDataBaseURL("https://custom-data-url.com"),
)| Option | Description |
|---|---|
WithHTTPClient(c) |
Use a custom *http.Client (timeouts, transport, retries) |
WithClobBaseURL(u) |
Override CLOB API base URL |
WithGammaBaseURL(u) |
Override Gamma API base URL |
WithDataBaseURL(u) |
Override Data API base URL |
WithCredentials(c) |
Set API key credentials for authenticated CLOB requests |
Several CLOB endpoints require API key authentication. To get your API key, secret, and passphrase, follow the Polymarket CLOB Authentication guide to derive credentials from your wallet.
Pass your credentials via WithCredentials:
client := polymarket.NewClient(
polymarket.WithCredentials(&polymarket.Credentials{
Key: "your-api-key", // API key (UUID)
Secret: "your-base64-secret", // Base64url-encoded HMAC secret
Passphrase: "your-passphrase", // Passphrase
Address: "0xYourWallet", // Wallet address (hex)
}),
)
// Authenticated endpoints now work automatically
orders, err := client.Clob.GetOrders(ctx, nil)
balance, err := client.Clob.GetBalanceAllowance(ctx, nil)
err = client.Clob.CancelOrder(ctx, "order-id")When credentials are set, the library automatically signs every CLOB request with the required POLY_* headers (HMAC-SHA256). Public endpoints continue to work normally — the auth headers are simply included alongside your requests.
For production use, load credentials from environment variables:
client := polymarket.NewClient(
polymarket.WithCredentials(&polymarket.Credentials{
Key: os.Getenv("POLY_API_KEY"),
Secret: os.Getenv("POLY_API_SECRET"),
Passphrase: os.Getenv("POLY_PASSPHRASE"),
Address: os.Getenv("POLY_ADDRESS"),
}),
)Placing orders on the CLOB requires EIP-712 typed data signatures. The library includes a complete, zero-dependency implementation of Keccak-256 and secp256k1 ECDSA signing.
// 1. Create an order signer with your private key
signer, err := polymarket.NewOrderSigner(
os.Getenv("POLY_PRIVATE_KEY"), // hex-encoded secp256k1 private key
polymarket.Polygon, // chain config (Polygon mainnet)
)
// 2. Build and sign an order in one step
signed, err := signer.CreateOrder(polymarket.CreateOrderParams{
TokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
Price: 0.50, // price per share (0..1)
Size: 10.0, // number of shares
Side: polymarket.Buy,
})
// 3. Post the signed order via the CLOB client (requires API credentials)
resp, err := client.Clob.PostOrder(ctx, signed, polymarket.OrderGTC)
fmt.Printf("Order ID: %s\n", resp.OrderID)// Build the order (unsigned)
order, err := signer.BuildOrder(polymarket.CreateOrderParams{
TokenID: tokenID,
Price: 0.75,
Size: 100.0,
Side: polymarket.Sell,
Expiration: time.Now().Add(time.Hour).Unix(), // Unix seconds; HTTP-only
NegRisk: true, // for neg-risk markets
SignatureType: polymarket.SignatureTypePoly, // proxy wallet
Maker: proxyWalletAddr, // override maker address
})
// Sign it
signed, err := signer.SignOrder(order)| Constant | Description |
|---|---|
OrderGTC |
Good Till Cancelled |
OrderGTD |
Good Till Date |
OrderFOK |
Fill Or Kill |
OrderFAK |
Fill And Kill |
OrderIOC |
Legacy spelling; submitted as FAK |
| Constant | Description |
|---|---|
SignatureTypeEOA |
Direct wallet (externally owned account) |
SignatureTypePoly |
Polymarket proxy wallet |
SignatureTypeGnosisSafe |
Gnosis Safe multisig |
SignatureTypePoly1271 |
Deposit wallet (POLY_1271), with its address as Maker |
// Health & info
client.Clob.Health(ctx) // error
client.Clob.ServerTime(ctx) // (string, error)
// Markets
client.Clob.GetMarket(ctx, conditionID) // (*ClobMarket, error)
client.Clob.ListMarkets(ctx, nextCursor) // (*CursorPage[ClobMarket], error)
client.Clob.ListSimplifiedMarkets(ctx, nextCursor) // (*CursorPage[SimplifiedMarket], error)
client.Clob.AllMarkets(ctx) // *CursorIterator[ClobMarket]
// Single-token pricing
client.Clob.GetPrice(ctx, tokenID, side) // (string, error)
client.Clob.GetMidpoint(ctx, tokenID) // (string, error)
client.Clob.GetSpread(ctx, tokenID) // (string, error)
client.Clob.GetLastTradePrice(ctx, tokenID) // (*LastTradePrice, error)
// Bulk pricing (POST)
client.Clob.GetPrices(ctx, []BookParams{...}) // (map[string]map[string]string, error)
client.Clob.GetMidpoints(ctx, []BookParams{...}) // (map[string]string, error)
client.Clob.GetSpreads(ctx, []BookParams{...}) // (map[string]string, error)
client.Clob.GetLastTradesPrices(ctx, []BookParams{...}) // ([]LastTradesPriceEntry, error)
// Order book
client.Clob.GetBook(ctx, tokenID) // (*OrderBook, error)
client.Clob.GetBooks(ctx, []BookParams{...}) // ([]OrderBook, error)
// Market info
client.Clob.GetTickSize(ctx, tokenID) // (TickSize, error)
client.Clob.GetNegRisk(ctx, tokenID) // (bool, error)
client.Clob.GetFeeRateBPS(ctx, tokenID) // (float64, error)
client.Clob.GetPriceHistory(ctx, PriceHistoryParams{...}) // (*PriceHistoryResponse, error)
client.Clob.GetMarketTradeEvents(ctx, conditionID) // ([]MarketTradeEvent, error)
// Authenticated — Orders (requires WithCredentials)
client.Clob.PostOrder(ctx, signedOrder, orderType) // (*PostOrderResponse, error)
client.Clob.GetOrder(ctx, orderID) // (*Order, error)
client.Clob.GetOrders(ctx, &OrdersParams{...}) // ([]OpenOrder, error)
client.Clob.CancelOrder(ctx, orderID) // error
client.Clob.CancelOrders(ctx, []string{...}) // ([]string, error)
client.Clob.CancelAll(ctx) // ([]string, error)
// Authenticated — Trades, Balance, Notifications
client.Clob.GetTrades(ctx, &ClobTradesParams{...}) // ([]UserTrade, error)
client.Clob.GetBalanceAllowance(ctx, &BalanceParams{...}) // (*BalanceAllowance, error)
client.Clob.GetNotifications(ctx) // ([]Notification, error)// Markets
client.Gamma.ListMarkets(ctx, &GammaMarketParams{...}) // ([]GammaMarket, error)
client.Gamma.GetMarketByID(ctx, id) // (*GammaMarket, error)
client.Gamma.GetMarketBySlug(ctx, slug) // (*GammaMarket, error)
// Events
client.Gamma.ListEvents(ctx, &GammaEventParams{...}) // ([]GammaEvent, error)
client.Gamma.GetEventByID(ctx, id) // (*GammaEvent, error)
client.Gamma.GetEventBySlug(ctx, slug) // (*GammaEvent, error)
// Search & tags
client.Gamma.Search(ctx, query) // (SearchResponse, error)
client.Gamma.ListTags(ctx) // ([]GammaTag, error)
client.Gamma.GetTagByID(ctx, id) // (*GammaTag, error)
client.Gamma.GetTagBySlug(ctx, slug) // (*GammaTag, error)client.Data.ListTrades(ctx, &TradesParams{...}) // ([]Trade, error)
client.Data.ListPositions(ctx, &PositionsParams{...}) // ([]Position, error)
client.Data.ListActivity(ctx, &ActivityParams{...}) // ([]Activity, error)
client.Data.ListHolders(ctx, &HoldersParams{...}) // ([]TokenHolders, error)
client.Data.GetOpenInterest(ctx, market) // ([]OpenInterest, error)CLOB list endpoints use cursor-based pagination. You can page manually or use the iterator:
page, err := client.Clob.ListMarkets(ctx, "") // first page
for _, m := range page.Data {
fmt.Println(m.Question)
}
if page.NextCursor != "" {
page, err = client.Clob.ListMarkets(ctx, page.NextCursor)
}iter := client.Clob.AllMarkets(ctx)
for iter.Next(ctx) {
market := iter.Item()
fmt.Println(market.Question)
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}Gamma and Data endpoints use limit/offset pagination managed via their respective param structs:
limit, offset := 10, 0
markets, err := client.Gamma.ListMarkets(ctx, &polymarket.GammaMarketParams{
Limit: &limit,
Offset: &offset,
})All API errors are returned as *polymarket.APIError with the HTTP status code, status text, and response body:
book, err := client.Clob.GetBook(ctx, "invalid-token")
if err != nil {
var apiErr *polymarket.APIError
if errors.As(err, &apiErr) {
fmt.Printf("Status: %d, Body: %s\n", apiErr.StatusCode, apiErr.Body)
}
}Convenience helpers for common error checks:
if polymarket.IsNotFound(err) {
// 404 — resource doesn't exist
}
if polymarket.IsRateLimited(err) {
// 429 — back off and retry
}The library intentionally does not embed timeout, retry, or circuit breaker logic. Instead, use the standard Go approach — pass a configured *http.Client:
// Simple timeout
client := polymarket.NewClient(
polymarket.WithHTTPClient(&http.Client{
Timeout: 10 * time.Second,
}),
)
// Per-request timeout via context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
book, err := client.Clob.GetBook(ctx, tokenID)For retries, circuit breakers, or rate limiting, wrap the HTTP transport:
// Example with hashicorp/go-retryablehttp
retryClient := retryablehttp.NewClient()
retryClient.RetryMax = 3
client := polymarket.NewClient(
polymarket.WithHTTPClient(retryClient.StandardClient()),
)See the examples/ directory for runnable programs:
| Example | Description |
|---|---|
examples/markets |
List active markets with volume and pricing |
examples/prices |
Fetch CLOB prices, midpoints, and spreads |
examples/orderbook |
Display order book depth for a market |
examples/orders |
Authenticated: list orders, balance, trades |
examples/search |
Search markets and events by keyword |
Run an example:
go run ./examples/markets- String prices in CLOB — The CLOB API returns prices as strings to avoid floating-point precision loss. This library preserves that.
Numbertype for Gamma floats — The Gamma API inconsistently returns numeric fields as either JSON numbers (123.45) or JSON strings ("123.45"). TheNumbertype (underlyingfloat64) handles both. Usefloat64(m.Liquidity)orm.Liquidity.Float64()when you need a plain float64.- Separate market types —
ClobMarketandGammaMarketare distinct types because the two APIs return different field sets with different JSON naming conventions. StringSliceunmarshaler — The Gamma API inconsistently serializes array fields (likeoutcomes) as either a JSON array["Yes","No"]or a JSON string"[\"Yes\",\"No\"]". The customStringSlicetype handles both transparently.- Pointer fields for optional params — Query parameter structs use
*int,*bool, etc. to distinguish "not set" from zero values. - Generic cursor pagination —
CursorPage[T]andCursorIterator[T]eliminate boilerplate across CLOB list endpoints. - Pure-Go EIP-712 signing — Includes Keccak-256 (pre-NIST) and secp256k1 ECDSA implementations with zero external dependencies. The
OrderSignerhandles the full EIP-712 typed data hashing and signing flow for CLOB order placement.
polymarket-go/
client.go # Client, NewClient, functional options, shared HTTP
auth.go # Credentials, HMAC signing, request signing
crypto.go # Keccak-256, secp256k1 curve, ECDSA signing
signer.go # EIP-712 order signing, OrderSigner, ChainConfig
errors.go # APIError, IsNotFound, IsRateLimited
models.go # Shared types: Side, Interval, TickSize, BookParams
pagination.go # CursorPage[T], CursorIterator[T]
clob.go # ClobClient (~29 methods, public + authenticated)
clob_models.go # CLOB request/response types
gamma.go # GammaClient (12 methods)
gamma_models.go # Gamma types, Number, StringSlice unmarshalers
data.go # DataClient (5 methods)
data_models.go # Data types
*_test.go # Tests
examples/ # Runnable examples
MIT