How to Download Files from SEC EDGAR Programmatically Using an API

By · Oct 12, 2026 · 7 min read

How to Download Files from SEC EDGAR Programmatically Using an API

Downloading a company’s annual report manually is straightforward. Repeating that process across companies and reporting periods requires a more reliable workflow.

If you are learning how to download files from SEC EDGAR, start with three actions: identify the company, find the filing’s metadata, and request the actual document from its archive location.

This guide connects those actions in a Python example that finds Apple’s most recent 10-K in the SEC’s recent filing history, downloads its primary HTML document, and saves identifying information alongside it. You can adapt the same workflow to download 10-K and 10-Q filings.

How SEC EDGAR Filing Downloads Work

EDGAR is the SEC’s Electronic Data Gathering, Analysis, and Retrieval system. Its public data resources support filing discovery and document retrieval, but those are separate operations.

Filing metadata tells you where a document is; downloading that metadata does not download the filing itself.

Four identifiers connect the workflow:

  • Ticker: A market symbol, such as AAPL, useful for finding a company.
  • CIK: The Central Index Key identifying an EDGAR filer. Apple’s CIK is 320193.
  • Accession number: The identifier for an accepted submission. Preserve its leading zeros.
  • Primary document filename: The filename of the main report within that submission.

Use the company’s CIK for its archive directory. Do not infer that directory from the accession number’s first ten digits, which can identify a filing agent. The SEC explains these relationships in its EDGAR archive documentation.

A submission can contain the primary report, exhibits, images, and supporting files. Downloading the primary document alone does not collect every attachment.

What You Need Before You Start

Use Python 3 and the requests library. Install the dependency:

python -m pip install requests

This installs the HTTP client used by the script below.

The public SEC submissions API requires no API key. However, automated requests should declare an organization or application and a real contact email in the User-Agent header.

Set that identification before running the script. Replace the example with your own details.

For macOS or Linux:

export SEC_USER_AGENT=”Your Organization [email protected]”

For PowerShell on Windows:

$env:SEC_USER_AGENT = “Your Organization [email protected]”

The script reads this environment variable and stops if it is missing.

The SEC currently limits each user to no more than ten requests per second across machines. The example runs sequentially and pauses before each request. Multiple workers need a shared rate limiter. See the SEC’s Developer Resources for its fair-access guidance.

How to Download Files from SEC EDGAR: Step by Step

Resolve the ticker to a CIK

The script requests the SEC’s company ticker mapping at:
https://www.sec.gov/files/company_tickers.json
It finds the entry whose ticker matches AAPL and reads its cik_str value.
Ticker mappings are a starting point for identification. Confirm the company name before using the downloaded report in research; the SEC does not guarantee the mapping’s accuracy or completeness. Its Webmaster FAQ describes the mapping files.

Find a matching filing

Next, the script requests:

https://data.sec.gov/submissions/CIK0000320193.json

The submissions endpoint uses a ten-digit, zero-padded CIK. Its filings.recent object contains parallel arrays: values at the same position describe the same filing.

The script uses form, filingDate, accessionNumber, and primaryDocument to select a 10-K and locate its report. It chooses the most recently filed matching entry, rather than relying on array order.

This searches recent history, not every historical filing. For older records, inspect filings.files, which lists additional submissions JSON files. The endpoint and history structure are documented in the SEC’s EDGAR API guide.

Build the source-document URL

For modern filings, the document location follows this pattern:

https://www.sec.gov/Archives/edgar/data/{CIK}/{ACCESSION_WITHOUT_DASHES}/{PRIMARY_DOCUMENT}

The archive CIK is unpadded. Remove dashes from the accession number only for the directory component, and retain the primary filename returned by the metadata.

For example, Apple’s 2025 annual report is available as an HTML source document. The script discovers its own matching filing, so its selected report can change as new filings become available.

Download, save, and verify the document

Save the following complete example as download_filing.py. It includes request timeouts, status checking, bounded retries, safe local paths, and basic HTML validation.

import json
import os
import re
import time
from pathlib import Path
from urllib.parse import quote

import requests

TICKER = "AAPL"
FORM = "10-K"

user_agent = os.environ.get("SEC_USER_AGENT", "").strip()
if not user_agent:
    raise SystemExit(
        "Set SEC_USER_AGENT to your organization and real contact email."
    )

session = requests.Session()
session.headers.update({
    "User-Agent": user_agent,
    "Accept-Encoding": "gzip, deflate",
})


def get(url):
    """Make a paced GET request, with at most four attempts."""
    for attempt in range(4):
        time.sleep(0.5)

        try:
            response = session.get(url, timeout=(10, 60))
        except (requests.Timeout, requests.ConnectionError):
            if attempt == 3:
                raise
            time.sleep(2 ** attempt)
            continue

        retryable = response.status_code in (429, 500, 502, 503, 504)

        if retryable and attempt < 3:
            retry_after = response.headers.get("Retry-After", "")
            response.close()

            if retry_after.isdigit():
                delay = int(retry_after)
                if delay > 60:
                    raise RuntimeError(
                        "Retry-After exceeds 60 seconds; stop and reschedule."
                    )
            elif retry_after:
                raise RuntimeError(
                    "Server requested a dated retry; stop and reschedule."
                )
            else:
                delay = 2 ** attempt

            time.sleep(delay)
            continue

        response.raise_for_status()
        return response

    raise RuntimeError("Request attempts exhausted.")


# Resolve the ticker.
companies = get(
    "https://www.sec.gov/files/company_tickers.json"
).json()

company = next(
    (
        item for item in companies.values()
        if item["ticker"].upper() == TICKER
    ),
    None,
)
if company is None:
    raise RuntimeError(f"Ticker not found: {TICKER}")

cik = int(company["cik_str"])

# Retrieve filing metadata.
submissions = get(
    f"https://data.sec.gov/submissions/CIK{cik:010d}.json"
).json()

recent = submissions["filings"]["recent"]
matches = [
    index for index, form in enumerate(recent["form"])
    if form == FORM
]
if not matches:
    raise RuntimeError(
        "No matching recent filing; inspect filings.files for older history."
    )

index = max(
    matches,
    key=lambda n: (
        recent["filingDate"][n],
        recent["accessionNumber"][n],
    ),
)

accession = recent["accessionNumber"][index]
document = recent["primaryDocument"][index]

# Validate metadata before using it in URLs and paths.
if not re.fullmatch(r"\d{10}-\d{2}-\d{6}", accession):
    raise RuntimeError("Unexpected accession number.")

if (
    not document
    or document != document.split("/")[-1]
    or "\\" in document
):
    raise RuntimeError("Unexpected primary-document filename.")

if Path(document).suffix.lower() not in (".htm", ".html"):
    raise RuntimeError("This example expects an HTML primary document.")

url = (
    f"https://www.sec.gov/Archives/edgar/data/{cik}/"
    f"{accession.replace('-', '')}/"
    f"{quote(document, safe='')}"
)

# Download and perform basic response checks.
response = get(url)
content = response.content

if not content or b"<html" not in content[:8192].lower():
    raise RuntimeError(
        "Response does not look like the expected HTML document."
    )

if (
    b"undeclared automated tool" in content.lower()
    or b"request rate threshold exceeded" in content.lower()
):
    raise RuntimeError(
        "SEC access restriction returned instead of a filing."
    )

# Save through a temporary file.
folder = Path("filings") / f"{cik:010d}" / accession
folder.mkdir(parents=True, exist_ok=True)

safe_name = re.sub(r"[^A-Za-z0-9._-]", "_", document)
path = folder / safe_name
temporary = path.with_suffix(path.suffix + ".part")

temporary.write_bytes(content)
temporary.replace(path)

if path.stat().st_size != len(content):
    raise RuntimeError(
        "Saved file size differs from downloaded content."
    )

# Retain identifying metadata beside the report.
metadata = {
    "ticker": TICKER,
    "company": submissions["name"],
    "cik": f"{cik:010d}",
    "form": FORM,
    "filingDate": recent["filingDate"][index],
    "accessionNumber": accession,
    "primaryDocument": document,
    "sourceUrl": url,
    "bytes": len(content),
}
(folder / "metadata.json").write_text(
    json.dumps(metadata, indent=2),
    encoding="utf-8",
)

print(f"Company: {metadata['company']} (CIK {cik:010d})")
print(
    f"Filing: {FORM}, filed {metadata['filingDate']}, "
    f"accession {accession}"
)
print(f"Source: {url}")
print(f"Saved: {path.resolve()} ({path.stat().st_size:,} bytes)")

Run the saved script:

python download_filing.py

On success, it prints the company, filing date, accession number, source URL, saved location, and byte count. Those values come from the live response; they are not fixed sample output.

The report is saved under filings/{CIK}/{accession}/, with metadata.json beside it. Open the HTML file and check its company name, form, and reporting period. The automated checks confirm a nonempty HTML response and matching saved byte count, but they do not fully validate the report’s contents.

To retrieve a quarterly report, change FORM to “10-Q”. The exact comparison excludes amendments such as 10-Q/A; include those separately when your workflow requires them.

Using SEC Filing Data’s API

Direct SEC access fits workflows that can manage company lookup, filing selection, document retrieval, and access policies themselves. A provider may be useful when you want filing search and document processing within one integration.

The SEC Filings API service page introduces SEC Filing Data’s offering.

For discovery, its SEC Query API documents POST /v1/filings/query, an API token supplied directly in the Authorization header, and JSON requests using query_type and query_value. Documented pagination controls include from and size, with a maximum size of 100.

Its SEC Filing Download and PDF Generator API documentation describes original-format retrieval and PDF generation. However, the published page lists conflicting HTTP methods, while its Python example demonstrates a PDF-generation request. Confirm the applicable download contract before implementing a product-specific source-file downloader.

The main example therefore uses direct SEC requests. SEC Filing Data is a third-party provider; its credentials, endpoints, and service limits are separate from the SEC’s public resources.

Choosing the Right Filing Document and Format

Choose the file that answers your question before selecting a parser or converting its format.

Resource What it contains How to use it
Submissions JSON Filing identifiers, dates, forms, and filenames Locate reports; it is not the report itself
Primary HTML document The main report; it may contain inline XBRL Read or parse the filing downloaded by this example
Exhibits and supporting files Separate contracts, attachments, images, or data files Inspect the filing’s document list and retrieve the required files
Complete submission .txt Disseminated submission content with headers and document sections Use a submission-aware parser rather than treating it as clean prose
XBRL financial-data JSON Extracted financial facts Analyze structured values alongside the source report

The SEC describes complete submission files and component-document directories in its archive access guidance. Its API documentation explains extracted XBRL data.

Saving an HTML report does not automatically save its linked images or exhibits. A fully self-contained archive requires collecting those dependencies separately.

Do not assume every filing has a native PDF version. This example preserves the HTML source. A generated PDF is a conversion output, and renaming an .htm file to .pdf does not convert it.

Common Download Errors and How to Handle Them

Access denied or undeclared automated tool

Check that SEC_USER_AGENT contains accurate identification, stop repeated requests, and review your overall traffic. The script raises an error for HTTP 403 rather than repeatedly retrying it.

If access remains blocked, follow the SEC’s access-denied guidance. Do not rotate addresses or identities to evade restrictions.

Rate limiting or temporary server failures

The script retries HTTP 429 and selected server errors with increasing delays, up to four attempts. It honors short numeric Retry-After values and stops when the server requests a longer or dated retry.

Persistent failures should trigger a later scheduled run. Adding parallel requests can make the problem worse.

Missing filing or HTTP 404

Check the company CIK, accession directory, and exact primary filename. Preserve leading zeros in accession numbers.

If the desired report is absent from recent history, inspect the additional files listed under filings.files. The SEC history endpoint does not use the provider’s from and size pagination controls.

Browser requests fail although Python requests work

The SEC states that data.sec.gov does not support CORS, the browser mechanism controlling cross-origin requests. Use a backend workflow such as this Python script for retrieval. See the SEC API documentation.

Making Downloads Repeatable

A repeatable download process needs a stable identity for each saved document. Use the company CIK, accession number, and original document filename together; a ticker alone is insufficient.

For recurring jobs:

  • Cache company mappings instead of fetching them for every document.
  • Skip verified files already recorded in your download manifest.
  • Retain source URLs, retrieval timestamps, byte counts, and checksums.
  • Log failures and reschedule them within your access policy.
  • Track amendments explicitly when maintaining a reporting history.

The example supplies the folder structure and metadata sidecar. Duplicate prevention and checksums are useful next steps for a production workflow.

Conclusion

You can now identify a company, select a filing, construct its archive URL, and save the primary document with supporting metadata. Keep the original format and verify the saved report before building analysis around it.

When evaluating a provider-based workflow, the SEC Filing Data API site is the next place to review available services and their documentation.

Learn More

Learn about our SEC Filing API and Investor Relations Website Services.

In this article

Featured

Tags

10-K annual report (1) 10-K Filing (2) 10-K Filings (1) 10-K Report (2) 10-Q Filings (2) 10-Q quarterly report (1) 10-Q Report (1) 10-Q vs 10-K (1) 8-K filing (1) 8-K filing requirements (1) analyzing company earnings reports (1) Annual Report (2) Annual Reports (1) API for Investors (1) Assets (1) Blue sky laws (1) Business Model (1) Company Analysis (1) company earnings dates (1) Company Filings (1) Company Reports (1) Company Research (1) corporate earnings schedule (1) corporate transactions (1) Corporate Website Posting Requirements (1) deal structure (1) Download SEC Filings (1) earnings announcement dates (1) earnings calendar (1) earnings release dates (1) earnings report guidance and outlook (1) EDGAR (1) EDGAR Database (1) EDGAR Filings (1) EDGAR Search (1) Exempt securities (1) Filing Automation (1) Filing Insights (2) Financial Analyst (1) Financial Data API (1) Financial Filings (1) Financial Performance (1) financial reporting (1) Financial Reports (1) financial results dates (1) Financial Statements (5) Fiscal Year (1) Floating Stock (1) Form 1-A (1) Form 10-K (2) Form 10-Q (3) Form 8-K (3) Form D (2) Form DEF 14A (1) Form N-1A (1) Form N-CEN (1) Form N-CSR (1) Form N-Port (1) Form N-PX (1) Form S-1 (3) Going Public (1) Going Public Tips (1) how investors should read earnings reports (1) how to read 10-Q filings (1) how to tell if earnings are good or bad (1) Impact of SEC Filings on Stock Prices (1) income statement vs balance sheet in earnings (1) Initial Public Offering (1) Insider Buying and Selling (1) Insider Trading (1) Insider Trading API (1) Insider Trading Trends 2025 (1) investment due diligence (1) Investment Signals from Insiders (1) Investor Relations Page (1) Investor Relations Strategy (1) Investor Relations Websites (1) Investor Research (1) IPO Advisors (1) IPO Filings (1) IPO Process (1) IPO Process Steps (1) IR Website Disclosure Requirements (1) key metrics in earnings reports (1) Late Filings (1) Legal Proceedings (2) Liabilities (1) M&A financial analysis (1) Market Behavior (1) Market Sentiment Indicators (1) mergers and acquisitions (1) Opportunistic Insider Purchases (1) Pricing and Allocation (1) Private Company Filings (1) Private offerings (1) Proxy Statements (1) public company disclosure (1) Public Company Filings (1) Public Trading (1) quarterly earnings dates (1) Quarterly Report (1) quarterly report dates (1) reading SEC filings (10-Q) (1) red flags in earnings reports (1) Red Flags in SEC Filings (1) Registration Statement (2) Regulation D (1) Risk Factors (2) Rule 506 (1) S-1 Registration Statement (1) Search Filings (1) SEC Compliance (1) SEC EDGAR (1) SEC EDGAR API (2) SEC EDGAR Forms (1) SEC Filing (4) SEC Filing Data (4) SEC Filing Deadlines (2) SEC Filing Guide (1) SEC Filings (6) SEC Filings API (1) SEC Filings Search (1) SEC Form 10-Q (1) SEC Form 4 Insights (1) SEC Form 8-K (1) SEC Forms (1) sec proxy statement form (1) SEC Regulations (1) SEC Reporting (1) Securities Marketing (1) Share Issuance (1) Shareholder Equity (1) shareholder information (1) signs of trouble in quarterly earnings reports (1) stock earnings calendar (1) stock market earnings calendar (1) Stock Market Insider Activity (1) Stock Market Listing (1) Stock Prices (1) Stockholders Market (1) understanding 10-Q reports (1) understanding quarterly earnings reports (1) Waiting Period (1) what to look for in earnings reports (1) what to look for in quarterly earnings (1) when do companies report earnings (1) When is a proxy statement filed (1)
Get Started
Get free alerts on your SEC filings with data insights
Start for Free
Get Started
Get free alerts on your SEC filings with data insights
Start for Free
Share via
Copy link