Skip to main content

String Numerics

All price, quantity, balance, and monetary values in the API are returned as strings, not floats or integers.
{
  "price": "0.65",
  "quantity": "10",
  "balance": "993.50",
  "notional": "6.50"
}

Why Strings?

IEEE 754 floating-point arithmetic causes precision errors that are unacceptable in financial applications:
# Float math is broken for money
>>> 0.1 + 0.2
0.30000000000000004

>>> 1000.00 - 6.50
993.4999999999999   # Wrong!

# String → Decimal is safe
>>> from decimal import Decimal
>>> Decimal("1000.00") - Decimal("6.50")
Decimal('993.50')   # Correct!
The backend uses Python’s Decimal type internally. By returning strings, we ensure zero precision loss from server to client.

How to Handle

from decimal import Decimal

# Parse API response
price = Decimal(order["price"])     # Decimal("0.65")
qty = Decimal(order["quantity"])    # Decimal("10")
notional = price * qty              # Decimal("6.50") — exact

# Send in requests
payload = {
    "quantity": str(qty),           # "10"
    "price": str(price),            # "0.65"
}

Fields That Are Strings

Every numeric field in the API uses string encoding:
CategoryFields
Pricesbuy, sell, best_bid, best_ask, mid_price, spread, last_trade, GET /v1/priceprice
Orderquantity, notional, fee, limit_price, fill_price
Accountbalance, starting_balance, pnl, pnl_percent, unrealized_pnl
Positionavg_entry_price, current_price, market_value, unrealized_pnl
Order Booksize (bid/ask levels)
Candlesopen, high, low, close
Volumevolume
Request bodies also expect strings for quantity and price fields. Sending a raw float (e.g., 10.5 instead of "10.5") may work but is not recommended — the API will coerce it, but you lose client-side precision.

Known exception: GET /markets/updown live_price.buy / live_price.sell

The legacy /markets/updown endpoint emits live_price.buy and live_price.sell as floats, not strings — every other API surface uses strings. Parse defensively:
buy = float(market["live_price"]["buy"])   # float here, str everywhere else
This is a known drift slated for unification; treat the float values as informational quotes (UI display, signal detection), and convert to Decimal(str(buy)) before any order math:
from decimal import Decimal
buy_dec = Decimal(str(market["live_price"]["buy"]))
The /v1/markets, /v1/markets/{id}, /v1/prices/batch, and WS /v1/ws/prices paths all emit string prices today and aren’t affected.

Polymarket-parity exceptions

A small number of endpoints emit numeric (JSON number) values instead of strings, because the corresponding Polymarket endpoint has always done so and bot SDKs ported from Polymarket type-narrow on typeof === "number". The list is intentionally short:
EndpointFieldTypeReason
GET /v1/tick-size/{token_id}minimum_tick_sizenumber (double)Live Polymarket emits {"minimum_tick_size": 0.01} as a JSON number, and py-clob-client compares it against numeric tick constants. Note: the tick_size field inside GET /v1/book is a string — only the standalone /tick-size endpoint emits a number.
GET /v1/markets/updownlive_price.buy/sellnumberPM-parity for crypto-spike feeds.
For all OTHER price-bearing endpoints (GET /v1/price, /v1/midpoint, /v1/spread, /v1/last-trade-price, /v1/book, /v1/account/positions, etc.) the string convention continues to hold.
GET /v1/price returns a string. Live Polymarket emits GET /price as {"price": "0.28"} — a JSON string, matching the wire most trading bots actually parse. PolySimulator briefly emitted a JSON number here (2026-05-12) but reverted to the string form on 2026-05-19 because PM’s real wire is the source of truth and a number broke any client doing isinstance(price, str) / JSON-Schema validation against PM’s published shape. Wrap the value with Decimal(resp["price"]) or float(resp["price"]) to be robust either way. The quote_at (ISO string) and age_ms (int) freshness fields accompany the price and are unchanged.

Next Steps