filing.firehose

← All posts · Published 2026-07-21

SEC EDGAR API Rate Limits: What You Need to Know

The SEC's EDGAR API enforces a 10 req/s/IP rate limit. Learn how to detect 429 errors, implement exponential backoff, and scale across multiple IPs without hitting compliance walls.

The EDGAR API Rate Limit: 10 Requests Per Second, Per IP

The SEC's Electronic Data Gathering, Organization, and Retrieval (EDGAR) system is the canonical source for public company filings. If you're building a quant pipeline, compliance dashboard, or filing ingestion service, you'll hit EDGAR's API. And you'll hit its rate limits fast.

The rule is simple: 10 requests per second per IP address. That's the published ceiling. Exceed it, and you get a 429 Too Many Requests response. For data teams scaling analysis across thousands of tickers, this constraint forces architectural choices early.

This post covers what the limit actually means, how to detect it in the wild, and which backoff strategies work in practice.

Understanding the 10 req/s Limit

The SEC doesn't hide this. Their public documentation states the limit clearly. A single IP can make 10 HTTP requests to sec.gov/cgi-bin/browse-edgar in one second. The 11th request arrives within that window, you get throttled.

What that means practically:

  • Sequential requests at 10 Hz can maintain throughput indefinitely (if paced correctly).
  • Burst traffic (20 requests in 100 ms) will trigger 429s even if you average under 10 req/s over a longer window.
  • The limit is per IP, not per user agent, hostname, or API key (EDGAR doesn't require authentication for bulk access).
  • The clock resets on each second boundary, not on a rolling window. This is important for implementation.

If you're running multiple scripts or machines from the same corporate proxy IP, you're all competing against the same bucket. This is why understanding the limit matters: casual requests from 50 developers might look like a botnet.

Detecting 429 Responses

A 429 tells you the server is rate-limiting your IP. When you hit one, your connection isn't refused. Instead, you receive an HTTP 429 status code with a Retry-After header (if the SEC includes one) or just a bare response.

Here's a minimal Python example:

import requests import time def fetch_edgar(url, max_retries=3): for attempt in range(max_retries): response = requests.get(url, timeout=10) if response.status_code == 429: print(f"Rate limited. Attempt {attempt + 1}/{max_retries}") # Handle backoff here return None if response.status_code == 200: return response.text # 400, 404, 5xx etc. response.raise_for_status() return None

The status code is reliable. Watch for it in your logs. If you see 429s appearing regularly, your request cadence needs adjustment.

Exponential Backoff: The Right Approach

When a 429 arrives, don't retry immediately. Backoff strategies reduce contention and let the server catch up.

Exponential backoff with jitter is standard in distributed systems. Here's the idea: wait 2^n seconds plus a small random jitter, then retry. If it fails again, increment n and repeat.

import random import time def fetch_with_backoff(url, max_retries=5, base_delay=1): for attempt in range(max_retries): response = requests.get(url, timeout=10) if response.status_code == 200: return response.text if response.status_code == 429: if attempt < max_retries - 1: delay = (2 ** attempt) * base_delay + random.uniform(0, 1) print(f"429 received. Backing off {delay:.2f}s") time.sleep(delay) else: return None else: response.raise_for_status() return None

This approach starts with a 1-2 second wait, then doubles: 2-3s, 4-5s, 8-9s, 16-17s. By attempt 5, you're waiting up to 17 seconds. The randomness (jitter) prevents the thundering herd problem where multiple clients retry in lockstep.

Don't hardcode this. Use a library. Python's urllib3 has built-in retry logic. The requests-retry library is clean. For production: tenacity or backoff packages handle complex scenarios well.

Request Pacing and Concurrency Control

Backoff is reactive. The smarter move is proactive: never hit the limit in the first place.

If you know you need 1000 filings and you're bottlenecked at 10 req/s, that's 100 seconds minimum. Queue your requests and execute at ~9 req/s to leave headroom:

from queue import Queue import threading import time def worker(queue, delay=0.11): # ~9 req/s while True: url = queue.get() if url is None: # Poison pill to stop break time.sleep(delay) response = requests.get(url, timeout=10) process(response) queue.task_done() # Set up 1 worker thread (single IP = single queue) queue = Queue() thread = threading.Thread(target=worker, args=(queue,), daemon=True) thread.start() # Enqueue all URLs for ticker in tickers: url = f"https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK={ticker}&type=10-K&dateb=&owner=exclude&count=100" queue.put(url) queue.join()

This single-threaded approach with a fixed delay is boring but reliable. You'll never see a 429. If you need faster throughput, the next step is legitimate: multiple IPs.

Scaling Across Multiple IPs

The 10 req/s limit is per IP. If you have 10 proxies or residential IPs, you can theoretically hit 100 req/s total. But this requires discipline.

Key rules:

  • Rotate IPs randomly, not in sequence. Sequential rotation is detectable and feels like abuse.
  • Identify your IPs correctly. If your proxy fails over or your cloud provider reassigns IPs, you'll misattribute requests.
  • Monitor per-IP request counts. If one IP is getting hammered, it's a configuration bug, not a feature.
  • Respect User-Agent. Set it to something descriptive: "MyQuant/1.0 (quant research; contact: research@example.com)". This isn't required, but it's ethical and reduces SEC friction.

Many SEC regulars use rotating residential proxies for high-volume scraping. This is legal if you're not impersonating anyone and you're respecting the rate limit per IP. But it walks a line: the SEC monitors for abuse patterns. If you're pulling 10 million filings a day across 50 IPs, expect scrutiny.

A cleaner approach: ask yourself if you really need 10M filings. Most quant use cases work fine with quarterly refreshes of the Russell 3000 + small-cap universe. That's roughly 4000 tickers, 4 filings/year each, ~16k requests. At 10 req/s, that's under 30 minutes total. No proxy rotation needed.

Practical Patterns in Production

Real pipelines combine several tactics:

  • Queue all URLs upfront (don't make decisions on what to fetch in the hot path).
  • Pace requests at 8 req/s, leaving 2 req/s headroom for retries or latency jitter.
  • Log every request: URL, timestamp, status code, response size. Anomalies show up fast.
  • Set Read-Timeout to 30s and Connection-Timeout to 10s. EDGAR's servers are old. They stall sometimes.
  • Cache aggressively. If you've downloaded a 10-K once, don't download it again tomorrow. Use ETags if available.
  • Monitor your own metrics. Plot requests/second in real-time. A dashboard that shows 429s per hour catches problems before they escalate.

Teams running large-scale filing analysis often build a lightweight ingestion layer that queries EDGAR once, caches responses locally (to S3 or a database), and does analysis on the cached copy. This decouples your analysis from EDGAR's uptime and rate limits. The 429 becomes a rare exception, not your bottleneck.

What Happens If You Ignore the Limit

The SEC doesn't ban IPs outright, but they do throttle aggressively. Sustained 429s from an IP can trigger IP-level rate limiting (slower recovery, longer cooldowns). In extreme cases, the SEC has blocked IP ranges for weeks.

You won't get a formal notice. You'll just notice your backoff strategy isn't working anymore: even with 60-second waits between retries, you keep getting 429s. At that point, you need a different IP or you need to wait it out.

The reputational cost is subtle but real. If you're a financial firm and your IP gets blacklisted, the SEC team analyzing your disclosures notices. It signals poor engineering discipline. For institutional quants, that's a bad look.

Tooling and Automation

If you're building a filing pipeline from scratch, don't reinvent the wheel. Libraries like edgar (Python) and sec-api-io (REST wrapper) handle rate limiting for you. They abstract away the pacing logic and give you a simple interface.

For custom pipelines, tools like FilingFirehose (a filing indexing service) let you query pre-parsed, deduplicated EDGAR data without hitting the raw API at all. No 429s, no rate limits, just JSON. If your use case is "get all 8-Ks mentioning bankruptcy" or "pull all XBRL financials for the last 3 years," that's simpler than building your own scraper.

But if you own the scraper, the patterns here are proven. Exponential backoff, paced requests, minimal IP rotation, and aggressive caching will keep you under the radar and in production for years.

Conclusion

The 10 req/s limit isn't a trap. It's a bandwidth-sharing mechanism. Respect it, and EDGAR becomes a reliable data source. Ignore it, and you'll spend hours debugging 429s when you could've been shipping analysis.

The tactical wins: pacing at 8-9 req/s, exponential backoff with jitter, and per-IP monitoring. The strategic win: cache, don't re-query. Most quant problems don't require real-time EDGAR access. They require clean, historical data. Get it once, analyze forever.


Start a free FilingFirehose trial →

Try it on your stack

Get an API key in 2 minutes. Self-serve via Stripe, cancel anytime.

View pricing → Read API docs