Audited against Polymarket/py-clob-client-v2 commit
a2ec069fb66096e2b80ff0b7fdf628fc41d6352c.
This file tracks CLOB HTTP endpoint coverage in polymarket_clob. It intentionally does
not cover WebSockets, bot integration, the higher-level SDK, or strategy code.
Private GET requests in this package sign the base endpoint path and send query params only on the URL. This matches the audited Python client behavior for L2-authenticated GETs.
Coverage Summary
| Bucket | Count |
|---|---|
| Implemented (public market data) | 25 |
| Implemented (private account/data, L2 GET/POST/DELETE) | 13 |
| Implemented (private order management, L2 POST/DELETE) | 6 |
| Total implemented | 44 |
| Not yet implemented (private account/key management, mixed L1/L2) | 6 |
| Total not yet implemented | 6 |
| Intentionally skipped (local helpers / composed) | 3 |
| Intentionally skipped (rewards) | 6 |
| Intentionally skipped (builder surface) | 6 |
| Intentionally skipped (RFQ) | 9 |
| Total intentionally skipped | 24 |
| Total Python v2 surface enumerated | 74 |
Scope
This document covers the CLOB HTTP endpoint surface wrapped by the Elixir API modules
under PolymarketClob.API.*. Each implemented row maps a Python method on
Polymarket/py-clob-client-v2 to the Elixir wrapper that calls the same path with the same
authentication scope.
Out of Scope For This Document
- WebSockets — neither the CLOB market channel (
wss://ws-subscriptions-clob.polymarket.com/ws/market), the user channel, nor the RTDS feed (wss://ws-live-data.polymarket.com) are HTTP endpoints, andpy-clob-client-v2does not own them. WebSocket coverage will be tracked separately when those modules land. - L1 (EIP-712 ClobAuth) endpoints —
/auth/api-key(POST),/auth/derive-api-key(GET), and the readonly-API-key family are listed under "Not Yet Implemented" but requireAuth.L1to grow request-side helpers and a credential-lifecycle decision before they can be wrapped. The L1 auth primitive itself is implemented and fixture-backed; only the endpoint wrappers built on top of it are missing. - Signing and order-building primitives —
Auth.L1(ClobAuth withnonce),Auth.L2(HMAC),EIP712,Order.Builder(limit + market amounts, signed-order construction),Order.Signing(EIP-712 domain, hashing, signature), andOrder.Rounding(price/size/amount decimal config) are documented inREADME.md,AGENTS.md, andPACKAGE_PLAN.md, and are validated by parity fixtures undertest/fixtures/parity/. They are listed here only as cross-reference; correctness changes for them belong in those files. - Higher-level SDK behavior (
polymarketpackage) — Gamma, Data API, RTDS, CTF redeem/merge/split, pUSD wrap/unwrap, P&L helpers, activity normalization. The audited Python client does not own these either.
Implemented Endpoints
Public status and market data
| Python method | Elixir wrapper | Method | Path | Body/query notes |
|---|---|---|---|---|
get_ok | MarketData.ok/1 | GET | /ok | No body or query. |
get_version | MarketData.version/1 | GET | /version | No body or query. |
get_server_time | MarketData.server_time/1 | GET | /time | No body or query. |
get_markets | MarketData.get_markets/2 | GET | /markets | Query params passed through. Python uses next_cursor. |
get_simplified_markets | MarketData.get_simplified_markets/2 | GET | /simplified-markets | Query: next_cursor. |
get_sampling_markets | MarketData.get_sampling_markets/2 | GET | /sampling-markets | Query: next_cursor. |
get_sampling_simplified_markets | MarketData.get_sampling_simplified_markets/2 | GET | /sampling-simplified-markets | Query: next_cursor. |
get_market | MarketData.get_market/2 | GET | /markets/:condition_id | Path segment. |
get_clob_market_info | MarketData.get_clob_market_info/2 | GET | /clob-markets/:condition_id | Path segment. |
get_market_by_token | MarketData.get_market_by_token/2 | GET | /markets-by-token/:token_id | Path segment. |
get_market_trades_events | MarketData.get_market_trades_events/2 | GET | /markets/live-activity/:condition_id | Public path segment. |
get_order_book | MarketData.get_order_book/2 | GET | /book | Query: token_id. |
get_order_books | MarketData.get_order_books/2 | POST | /books | Body is caller-provided list. |
get_tick_size | MarketData.get_tick_size/2 | GET | /tick-size | Query: token_id. |
get_neg_risk | MarketData.get_neg_risk/2 | GET | /neg-risk | Query: token_id. |
get_fee_rate_bps | MarketData.get_fee_rate/2 | GET | /fee-rate | Query: token_id; exposed as legacy fee-rate response. |
get_midpoint | MarketData.get_midpoint/2 | GET | /midpoint | Query: token_id. |
get_midpoints | MarketData.get_midpoints/2 | POST | /midpoints | Body is caller-provided list. |
get_price | MarketData.get_price/3 | GET | /price | Query: token_id, side. |
get_prices | MarketData.get_prices/2 | POST | /prices | Body is caller-provided list. |
get_spread | MarketData.get_spread/2 | GET | /spread | Query: token_id. |
get_spreads | MarketData.get_spreads/2 | POST | /spreads | Body is caller-provided list. |
get_last_trade_price | MarketData.get_last_trade_price/2 | GET | /last-trade-price | Query: token_id. |
get_last_trades_prices | MarketData.get_last_trades_prices/2 | POST | /last-trades-prices | Body is caller-provided list. |
get_prices_history | MarketData.get_prices_history/2 | GET | /prices-history | Query names: market, startTs, endTs, fidelity, interval. |
Private account and user data
| Python method | Elixir wrapper | Method | Path | Body/query notes |
|---|---|---|---|---|
get_api_keys | Account.get_api_keys/2 | GET | /auth/api-keys | L2-authenticated; no body or query. |
get_closed_only_mode | Account.get_closed_only_mode/2 | GET | /auth/ban-status/closed-only | L2-authenticated; no body or query. |
get_order | Account.get_order/3 | GET | /data/order/:order_id | L2-authenticated path segment. |
get_open_orders | Account.get_open_orders/3 | GET | /data/orders | Query passed through; Python auto-paginates by default, Elixir is a direct page wrapper. |
get_pre_migration_orders | Account.get_pre_migration_orders/3 | GET | /data/pre-migration-orders | Query passed through; Python auto-paginates by default, Elixir is a direct page wrapper. |
get_trades | Account.get_trades/3 | GET | /data/trades | Query passed through; Python auto-paginates by default, Elixir is a direct page wrapper. |
get_trades_paginated | Account.get_trades_paginated/3 | GET | /data/trades | Same path as get_trades; exposes the Python paginated-name alias. Direct page wrapper. |
is_order_scoring | Account.is_order_scoring/3 | GET | /order-scoring | L2 GET. Query: optional order_id. |
are_orders_scoring | Account.are_orders_scoring/3 | POST | /orders-scoring | L2 POST. Body: JSON array of order IDs. |
get_notifications | Account.get_notifications/2 | GET | /notifications | Query: signature_type. |
drop_notifications | Account.drop_notifications/3 | DELETE | /notifications | Query: optional ids, comma-joined; no body. |
get_balance_allowance | Account.get_balance_allowance/3 | GET | /balance-allowance | Query: signature_type, optional asset_type, token_id. |
update_balance_allowance | Account.update_balance_allowance/3 | GET | /balance-allowance/update | Write modeled by Python as GET. Query: signature_type, optional asset_type, token_id. |
Private order management
| Python method | Elixir wrapper | Method | Path | Body/query notes |
|---|---|---|---|---|
post_order | Orders.post_order/3 | POST | /order | Elixir accepts an already-built signed order payload; it does not build/wrap the order. |
post_orders | Orders.post_orders/3 | POST | /orders | Elixir accepts already-built signed order payloads; it does not build/wrap the orders. |
cancel_order | Orders.cancel_order/3 | DELETE | /order | Body: {"orderID": id}. |
cancel_orders | Orders.cancel_orders/3 | DELETE | /orders | Body: list of order IDs/hashes. |
cancel_all | Orders.cancel_all/2 | DELETE | /cancel-all | No body or query. |
cancel_market_orders | Orders.cancel_market_orders/3 | DELETE | /cancel-market-orders | Body: optional market, asset_id; field order mirrors Python. |
Not Yet Implemented
These endpoints exist in the audited Python client and are plausible future polymarket_clob
work, but they have not been wrapped yet.
Private account/key management
| Python method | Method | Path | Body/query notes |
|---|---|---|---|
create_api_key | POST | /auth/api-key | L1-authenticated; no body. |
derive_api_key | GET | /auth/derive-api-key | L1-authenticated; no body. |
delete_api_key | DELETE | /auth/api-key | L2-authenticated; no body. |
create_readonly_api_key | POST | /auth/readonly-api-key | L2-authenticated; no body. |
get_readonly_api_keys | GET | /auth/readonly-api-keys | L2-authenticated; no body. |
delete_readonly_api_key | DELETE | /auth/readonly-api-key | Body: {"key": key}. |
Intentionally Skipped For Now
These are deliberately outside the current low-level endpoint-wrapper slices.
Local helper/composed behavior
| Python method/group | Method | Path | Body/query notes | Reason |
|---|---|---|---|---|
create_order, create_market_order | n/a | n/a | Local builder helpers. | Local order builders already exist; no HTTP endpoint. |
create_and_post_order, create_and_post_market_order | POST | /order | Composes metadata resolution, building, and posting. | Current Elixir keeps building and posting separate. |
calculate_market_price, get_order_book_hash, fee/tick/neg-risk cache helpers | n/a | n/a | Local calculation/cache behavior. | Not endpoint wrappers. |
Rewards
| Python method | Method | Path | Body/query notes | Reason |
|---|---|---|---|---|
get_current_rewards | GET | /rewards/markets/current | Query: next_cursor; Python auto-paginates. | Reward reporting may belong in the higher-level SDK. |
get_raw_rewards_for_market | GET | /rewards/markets/:condition_id | Query: next_cursor; Python auto-paginates. | Reward reporting may belong in the higher-level SDK. |
get_earnings_for_user_for_day | GET | /rewards/user | L2 GET. Query: date, signature_type, next_cursor; Python auto-paginates. | Reward reporting may belong in the higher-level SDK. |
get_total_earnings_for_user_for_day | GET | /rewards/user/total | L2 GET. Query: date, signature_type. | Reward reporting may belong in the higher-level SDK. |
get_user_earnings_and_markets_config | GET | /rewards/user/markets | L2 GET. Query: date, signature_type, next_cursor, order_by, position, no_competition; Python auto-paginates. | Reward reporting may belong in the higher-level SDK. |
get_reward_percentages | GET | /rewards/user/percentages | L2 GET. Query: signature_type. | Reward reporting may belong in the higher-level SDK. |
Builder surface
| Python method | Method | Path | Body/query notes | Reason |
|---|---|---|---|---|
post_heartbeat | POST | /v1/heartbeats | L2 POST. Body: {"heartbeat_id": heartbeat_id}. | Builder/service heartbeat behavior, not standard trading/account CLOB usage. |
create_builder_api_key | POST | /auth/builder-api-key | L2 POST; no body. | Builder credential lifecycle needs product decisions. |
get_builder_api_keys | GET | /auth/builder-api-key | L2 GET; no body or query. | Builder credential lifecycle needs product decisions. |
revoke_builder_api_key | DELETE | /auth/builder-api-key | L2 DELETE; no body. | Builder credential lifecycle needs product decisions. |
get_builder_trades | GET | /builder/trades | L2 GET. Query includes builder_code, optional filters, and next_cursor. | Builder attribution/query model needs product decisions. |
__ensure_builder_fee_rate_cached | GET | /fees/builder-fees/:builder_code | Public GET path segment used by Python cache helper. | Builder fee lookup should be designed with builder order support. |
RFQ
| Python method | Method | Path | Body/query notes | Reason |
|---|---|---|---|---|
create_rfq_request | POST | /rfq/request | L2 POST. Body includes assetIn, assetOut, amountIn, amountOut, userType; Python resolves tick size and rounds first. | RFQ is a distinct product surface. |
cancel_rfq_request | DELETE | /rfq/request | L2 DELETE. Body: {"requestId": request_id}. | RFQ is a distinct product surface. |
get_rfq_requests | GET | /rfq/data/requests | L2 GET. Query is parsed from RFQ request filters. | RFQ is a distinct product surface. |
create_rfq_quote | POST | /rfq/quote | L2 POST. Body includes requestId, assets, amounts, and userType; Python resolves tick size and rounds first. | RFQ is a distinct product surface. |
cancel_rfq_quote | DELETE | /rfq/quote | L2 DELETE. Body: {"quoteId": quote_id}. | RFQ is a distinct product surface. |
get_rfq_requester_quotes | GET | /rfq/data/requester/quotes | L2 GET. Query parsed from RFQ quote filters. | RFQ is a distinct product surface. |
get_rfq_quoter_quotes | GET | /rfq/data/quoter/quotes | L2 GET. Query parsed from RFQ quote filters. | RFQ is a distinct product surface. |
get_rfq_best_quote | GET | /rfq/data/best-quote | L2 GET. Query: optional requestId. | RFQ is a distinct product surface. |
accept_rfq_quote | POST | /rfq/request/accept | L2 POST. Python fetches quote data and builds a V1 signed order internally. | Conflicts with endpoint-wrapper-only design. |
approve_rfq_order | POST | /rfq/quote/approve | L2 POST. Python fetches quote data and builds a V1 signed order internally. | Conflicts with endpoint-wrapper-only design. |
rfq_config | GET | /rfq/config | L2 GET; no body or query. | RFQ is a distinct product surface. |
Product/Domain Decisions Needed
- API key lifecycle: decide whether
polymarket_clobshould create/delete user API keys, or only consume provided credentials. These endpoints mutate credentials and use both L1 and L2 auth. - Readonly API keys: decide whether readonly key management belongs in the low-level CLOB package or a higher-level administration surface.
- Reward reporting: decide whether rewards are low-level CLOB data or part of the higher-level Polymarket SDK.
- Builder surface: decide how builder codes, builder API keys, builder trades, fee lookup, and heartbeat should be modeled before wrapping these endpoints.
- RFQ: decide whether RFQ should be a separate module/package boundary. Python RFQ accept/approve paths build V1 orders internally, which conflicts with the current endpoint-wrapper-only design.
- Pagination shape: decide whether Elixir should add Python-style auto-pagination helpers in addition to direct page wrappers for open orders, trades, rewards, and market lists.
Pagination Policy
Wrappers that the Python client auto-paginates by default — get_open_orders,
get_trades, get_pre_migration_orders, and the cursored market-list endpoints — are
exposed in Elixir as direct page wrappers. The caller passes :next_cursor (and any
filter params) and receives the raw page body. Auto-pagination is intentionally not
provided yet because:
- The streaming consumer pattern in Elixir is typically a
Stream.resource/3, not a loop that accumulates a list. AddingEnumerable-friendly cursors is a design choice that may belong in the higher-levelpolymarketSDK. - Bot/SDK callers may want the cursor exposed for state persistence (resumable backfills), which an opaque auto-paginator would hide.
If/when auto-pagination is added, the direct page wrappers should remain so callers that need cursor visibility are not forced through a wrapper.
Maintenance
When Polymarket/py-clob-client-v2 advances:
- Update the audited commit hash in this document's header.
- Diff the new commit against the audited commit, focusing on
client.py,endpoints.py, and any new modules underpy_clob_client/. - For each new public method, add a row to Implemented, Not Yet Implemented, or Intentionally Skipped with a one-sentence rationale.
- For each removed/renamed method, update both this document and the corresponding Elixir wrapper.
- Re-run
mix testto verify parity fixtures still match (test/fixtures/parity/{clob_auth,l2_hmac,order_builder,orders}.jsonare the load-bearing ones; regenerate viascripts/generate_parity_fixtures.pyif the Python client's signing/auth output changed). - Update the Coverage Summary counts at the top of this document.
This document is the single source of truth for HTTP endpoint coverage. Anything beyond
that — auth primitives, signing, order building, WebSockets, higher-level SDK — is
documented in README.md, AGENTS.md, PACKAGE_PLAN.md, or the relevant module
moduledoc.