Current section
Files
Jump to
Current section
Files
polymarket_clob
ENDPOINT_COVERAGE.md
ENDPOINT_COVERAGE.md
# Endpoint Coverage
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, and `py-clob-client-v2` does 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 require
`Auth.L1` to 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 with `nonce`),
`Auth.L2` (HMAC), `EIP712`, `Order.Builder` (limit + market amounts, signed-order
construction), `Order.Signing` (EIP-712 domain, hashing, signature), and `Order.Rounding`
(price/size/amount decimal config) are documented in `README.md`, `AGENTS.md`,
and `PACKAGE_PLAN.md`, and are validated by parity fixtures under
`test/fixtures/parity/`. They are listed here only as cross-reference; correctness
changes for them belong in those files.
- **Higher-level SDK behavior** (`polymarket` package) — 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_clob` should 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. Adding `Enumerable`-friendly cursors is a design choice
that may belong in the higher-level `polymarket` SDK.
- 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:
1. Update the audited commit hash in this document's header.
2. Diff the new commit against the audited commit, focusing on `client.py`,
`endpoints.py`, and any new modules under `py_clob_client/`.
3. For each new public method, add a row to **Implemented**, **Not Yet Implemented**, or
**Intentionally Skipped** with a one-sentence rationale.
4. For each removed/renamed method, update both this document and the corresponding
Elixir wrapper.
5. Re-run `mix test` to verify parity fixtures still match
(`test/fixtures/parity/{clob_auth,l2_hmac,order_builder,orders}.json` are the
load-bearing ones; regenerate via `scripts/generate_parity_fixtures.py` if the Python
client's signing/auth output changed).
6. 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.