BLS API without a key: the daily cap returns HTTP 200

Checked 2026-09-08 · Statistical agencies: BLS, Census, BEA, FRED and EIA

The BLS Public Data API refuses an over-quota request with HTTP 200 and an empty Results object, so any client that branches on the status code sees success and then crashes on the missing series list. There is no Retry-After and no rate-limit header to warn you first. Unregistered callers get roughly two dozen requests a day counted against their IP address, not the 500 the registered-key documentation quotes, and v1 and v2 share one counter. The cap is not the only thing this API does quietly: several ordinary-looking requests come back 200 with part of what you asked for removed and nothing but a message string to say so, and one of them hands you ten years of data that are not the ten you asked for. Which requests, what each one drops and what to test instead of the status code are in the table.

What we saw

The over-quota response carries HTTP 200 with no Retry-After and no rate-limit headers. Source (response headers, 2026-09-08).

HTTP/2 200 
x-content-type-options: nosniff
cache-control: max-age=0, no-cache, no-store
strict-transport-security: max-age=31536000 ; includeSubDomains ; preload

Results is an empty object on refusal, not the usual {"series": [...]}, so Results['series'] raises KeyError. Source (response body, 2026-09-08).

{"status":"REQUEST_NOT_PROCESSED","responseTime":0,"message":["Request could not be serviced, as the daily threshold for total number of requests allocated to the user with registration key  has been reached."],"Results":{}}

The error text has a double space where the key would be printed, because no key was sent. Nothing in the request identifies the caller, so the counter is on the source address. Source (message[0], 2026-09-08).

allocated to the user with registration key  has been reached.

v1 and v2 draw on the same counter: once v2 is refused, v1 is refused with the identical body. Source (response body, 2026-09-08).

{"status":"REQUEST_NOT_PROCESSED","responseTime":0,"message":["Request could not be serviced, as the daily threshold for total number of requests allocated to the user with registration key  has been reached."],"Results":{}} HTTP 200

A POST to the batch endpoint is refused the same way, so switching from GET to a batched POST does not buy any headroom once the cap is hit. Source (POST body {"seriesid":["LNS14000000"]}, 2026-09-08).

{"status":"REQUEST_NOT_PROCESSED","responseTime":0,"message":["Request could not be serviced, as the daily threshold for total number of requests allocated to the user with registration key  has been reached."],"Results":{}} HTTP 200

Limits

Open question

The year-range clip returns ten years, and not the ten a caller asking for a longer range would expect. Is that deliberate, or does the clip just truncate the range it built?

Plain text