pub struct RateLimiter { /* private fields */ }Expand description
Holds all rate limiters for one API surface.
Created via factory methods like RateLimiter::clob_default() which
configure hardcoded limits matching Polymarket’s documented rate limits.
Implementations§
Source§impl RateLimiter
impl RateLimiter
Sourcepub fn begin_cooldown(&self, delay: Duration)
pub fn begin_cooldown(&self, delay: Duration)
Hold every request on this limiter for delay.
Extends an existing cooldown but never shortens one: several concurrent requests typically see the same 429 within a few milliseconds of each other, and taking the most recent value would let whichever response carried the smallest delay release all of them early.
Prefer HttpClient::note_rate_limited,
which derives the delay from the response. Reach for this directly only
when driving the limiter from a transport this crate does not own.
Sourcepub async fn acquire(&self, path: &str, method: Option<&Method>)
pub async fn acquire(&self, path: &str, method: Option<&Method>)
Await the appropriate limiter(s) for this endpoint.
Waits out any cooldown a previous 429 imposed, then awaits the default (general) limiter, then additionally awaits the first matching endpoint-specific limiter (burst + sustained).
Sourcepub fn clob_default() -> Self
pub fn clob_default() -> Self
CLOB API rate limits.
Transcribed from https://docs.polymarket.com/api-reference/rate-limits
as fetched on 2026-07-25, and pinned by the documented_limits tests.
Two things about the published tables need interpreting:
- The ledger group cap (900/10s across
/trades,/orders,/notificationsand/order) is genuinely shared, so those entries hold clones of one shared bucket rather than four of their own. - That group names
/orderand/orders, which also appear in the trading table at 5,000 and 2,000 per 10s. Both tables can only hold simultaneously if the group cap governs the ledger reads; a 900/10s cap on all methods would make the published trading burst unreachable. The group is therefore scoped toGET.
Ordering matters wherever one pattern is a path-segment prefix of
another: /balance-allowance/update must precede /balance-allowance,
and the specific /data/* routes must precede the /data catch-all.
Sourcepub fn gamma_default() -> Self
pub fn gamma_default() -> Self
Gamma API rate limits.
- General: 4,000/10s
- /events: 500/10s
- /markets: 300/10s
- /public-search: 350/10s
- /comments: 200/10s
- /tags: 200/10s
/status(health): 100/10s
Upstream also lists a 900/10s cap shared by /markets + /events.
It is not modelled because it can never bind: the per-endpoint caps of
300 and 500 sum to 800, which is already below it.
The published table spells the health row /ok, but that path answers
404 on gamma-api.polymarket.com — /status is the route that
answers 200, and the one Gamma::health().ping() requests. /ok is
boilerplate repeated into every surface’s table; only the CLOB host
serves it.
Sourcepub fn data_default() -> Self
pub fn data_default() -> Self
Data API rate limits.
- General: 1,000/10s
- /trades: 200/10s
- /positions and /closed-positions: 150/10s
/(health): 100/10s- Data API v2: measured per route, since upstream publishes no figures;
see
docs/specs/data-v2/OBSERVED.md
The published table spells the health row /ok, but that path answers
404 on data-api.polymarket.com — / answers 200 {"data":"OK"},
and is the route this crate requests. /ok is boilerplate repeated into
every surface’s table; only the CLOB host serves it.
Matching / is safe despite entries being prefix-matched: the
segment-boundary rule means strip_prefix("/") on /positions leaves
positions, which starts with neither / nor ?, so the entry matches
only the bare root and the root with a query string.
This limiter is shared with the two sibling hosts, so it also carries their rules:
/user-pnl: 200/10s, published as the host-wide allowance foruser-pnl-api.polymarket.com. Modelled per-path because it is the only route polyoxide calls there and matching has no host dimension.lb-api.polymarket.com(/volume,/profit) has no published limit, so those fall to the general bucket.
Sourcepub fn relay_default() -> Self
pub fn relay_default() -> Self
Relay API rate limits.
- 25 requests per 1 minute (single limiter, no endpoint-specific limits)
Sourcepub fn perps_default() -> Self
pub fn perps_default() -> Self
Perps API rate limits.
Upstream publishes no figure for the public /v1/info/* routes, only
that a per-IP token bucket exists. Each count is the highest clean
ramp stage measured by polyoxide-perps/examples/info_soak.rs on
2026-09-30 and recorded in docs/specs/perps/OBSERVED.md; quota()
reserves a tenth. The four routes throttle at different rates (a 429
on one arrived while the others kept being served), so each has its
own row. The 17 routes that were not soaked share a /v1/info
catch-all at the lowest measured rate, so an unmeasured route cannot
be driven harder than any measured one. The general bucket is a
client-wide cap measured separately: the budget is partly shared
across routes, so a mixed run capped at the most permissive route’s
50 per 10 s was throttled (11 of 487 requests over 120 s) while the
same run capped at PERPS_GENERAL was clean. See the validation runs
in OBSERVED.md.
With the general bucket at 30, bbo’s own row never binds and the klines and portfolio rows coincide with the general bucket; only the trades row and the catch-all restrict further. The rows are kept at their measured values so a later change to the general bucket does not silently loosen a route.
Row order matters: prefix matching takes the first match, so the catch-all must come last.