Issue Nº 38 — Sep 20, 2026

This contest API's rate limit shows up as a Cloudflare page, not its own error

Issue thirty-eight covers Codeforces, the keyless API behind the competitive-programming site's contests, ratings, and problem archive. This session queried it live. A missing user returns a normal JSON error, but an unknown method returns an HTML page instead. And sending ten requests at once does not trigger the documented rate-limit message at all: it trips Cloudflare first.

A missing user gets a clean JSON error, but a bad path does not

GET https://codeforces.com/api/user.info?handles=tourist, queried live this session, returned 200 with real rating data: {"status":"OK","result":[{"handle":"tourist","rating":3307,...}]}. The response also carried access-control-allow-origin: *.

GET the same endpoint with a handle that does not exist returned 400, but the body stayed a JSON envelope: {"status":"FAILED","comment":"handles: User with handle ... not found"}. A caller who checks the status field, not just the HTTP code, catches this cleanly.

GET https://codeforces.com/api/no.such.method, an unrecognized method path queried live this session, returned 404 with an HTML error page titled "Codeforces", not a JSON body.

So the tidy {"status":"FAILED"} shape only covers method-level errors, like a bad parameter. A wrong path skips the API layer and falls onto Codeforces' regular web server.

The documented rate limit and the one this session actually hit are different things

Codeforces' own docs state a 1-request-per-2-seconds limit, and say that exceeding it returns a "FAILED" status with a "Call limit exceeded" comment. The docs never say which HTTP code carries that body. Five sequential requests, spaced by normal request latency, all returned 200 with no such message.

Ten requests fired at once, queried live this session, told a different story: seven came back 200, and three came back 503 with an HTML body reading "503 Service Temporarily Unavailable".

Those three 503 responses carried server: cloudflare, cf-cache-status: DYNAMIC, and a cf-ray ID. The edge network in front of Codeforces, not the API's own throttling code, cut the requests off.

A caller who only handles the documented {"status":"FAILED","comment":"Call limit exceeded"} shape and ignores non-JSON responses will crash on a burst of traffic, because the first thing to trip is Cloudflare's edge, not the API's own rate limiter.

The problem archive is one call, but it is a big one

GET https://codeforces.com/api/problemset.problems, queried live this session with no parameters, returned 200 with every problem Codeforces has ever set: 11,401 entries in the problems array and a matching 11,401 in problemStatistics, in a single 2.26 MB response.

There is no pagination on this endpoint. A client that wants the full archive gets it in one request, but that also means a client that only wants a handful of recent problems still downloads the entire history.

Paired with the rate limit from the previous section, a client that calls this endpoint from inside a loop of other Codeforces calls should treat it as a request on its own, not something to run alongside a burst of smaller calls.

For most integrations, the practical move is to cache this response locally and refresh it occasionally, rather than calling it as part of a normal request cycle.

Codeforces, by the numbers

Rendered live from the atlas entry
AuthenticationNone required
HTTPSSupported
CORSEnabled
PricingFree
FormatsJSON
Rate limit

Documented at 1 request every 2 seconds (codeforces.com/apiHelp: "API may be requested at most 1 time per two seconds"); exceeding it returns HTTP 200 with {"status":"FAILED","comment":"...Call limit exceeded"} rather than an HTTP error. Confirmed via a recent archive.org snapshot of the docs page since the live docs page itself returns a Cloudflare interactive challenge to this sandbox (the API endpoints are never challenged).

competitive-programmingcontestsratingsproblems

Sources

Facts checked Sep 2026