Convert SEC Filings to PDF With Python

The examples below use the sec-api Python package to convert any SEC filing or exhibit into a PDF. The original formatting is preserved, including images and tables, and inline XBRL tags are stripped to keep the file size down. To download a file as filed, without converting it, see the Download API Python example.

Installation

Install the package with pip:

pip install sec-api

Download a 10-K as PDF

get_pdf takes the sec.gov URL of the filing and returns the PDF as bytes, so the file is written in binary mode. The example converts Apple's annual report.

from sec_api import PdfGeneratorApi pdfGeneratorApi = PdfGeneratorApi("YOUR_API_KEY") url = "https://www.sec.gov/Archives/edgar/data/320193/000032019323000106/aapl-20230930.htm" pdf = pdfGeneratorApi.get_pdf(url) with open("apple-10-k.pdf", "wb") as f: f.write(pdf)

Download a prospectus as PDF

Prospectuses filed on Forms 424B2, 424B3, 424B4 and S-1 run to hundreds of pages, with the offering terms, financial statements and risk factors laid out in tables. The example converts a Rule 424(b)(4) prospectus of roughly four megabytes.

from sec_api import PdfGeneratorApi pdfGeneratorApi = PdfGeneratorApi("YOUR_API_KEY") # Form 424B4, a Rule 424(b)(4) prospectus url = "https://www.sec.gov/Archives/edgar/data/2109823/000121390026101599/ea0286599-08.htm" pdf = pdfGeneratorApi.get_pdf(url) with open("prospectus.pdf", "wb") as f: f.write(pdf)

Download a proxy statement as PDF

Proxy statements on Form DEF 14A are image heavy: logos, performance graphs, director photographs and the proxy voting card. Every image the filing references is rendered into the PDF, which is also why a DEF 14A takes longer to convert than a press release.

from sec_api import PdfGeneratorApi pdfGeneratorApi = PdfGeneratorApi("YOUR_API_KEY") # Form DEF 14A, Apple's 2026 proxy statement url = "https://www.sec.gov/Archives/edgar/data/320193/000130817926000008/aapl014016-def14a.htm" pdf = pdfGeneratorApi.get_pdf(url) with open("apple-def-14a.pdf", "wb") as f: f.write(pdf)

Convert an exhibit to PDF

Exhibits work the same way. The example converts the Exhibit 99.1 press release attached to Apple's Q3 2026 earnings 8-K. Exhibit URLs are listed in the documentFormatFiles array returned by the Filing Search API.

from sec_api import PdfGeneratorApi pdfGeneratorApi = PdfGeneratorApi("YOUR_API_KEY") # Exhibit 99.1, the earnings press release attached to an 8-K url = "https://www.sec.gov/Archives/edgar/data/320193/000032019326000018/a8-kex991q3202606272026.htm" pdf = pdfGeneratorApi.get_pdf(url) with open("earnings-press-release.pdf", "wb") as f: f.write(pdf)

Convert the filings of a company

The PDF Generator pairs with the Filing Search API: search returns the URLs, the generator converts them. The example saves the last five Apple 8-K filings as PDFs.

from sec_api import QueryApi, PdfGeneratorApi queryApi = QueryApi("YOUR_API_KEY") pdfGeneratorApi = PdfGeneratorApi("YOUR_API_KEY") query = { "query": 'ticker:AAPL AND formType:"8-K"', "from": "0", "size": "5", "sort": [{"filedAt": {"order": "desc"}}], } filings = queryApi.get_filings(query)["filings"] for filing in filings: url = filing["linkToFilingDetails"] name = filing["accessionNo"] + ".pdf" with open(name, "wb") as f: f.write(pdfGeneratorApi.get_pdf(url)) print("saved", name)

Handling the 202 response

A filing published moments ago may not be converted yet. The API then returns HTTP 202 rather than the PDF, meaning the conversion has been enqueued. Retry the same request after 5 seconds. Retrying 3 to 4 times usually returns the final PDF. See the FAQ for the retry ceiling and when to report a failure.

import time from sec_api import PdfGeneratorApi pdfGeneratorApi = PdfGeneratorApi("YOUR_API_KEY") def get_pdf_with_retry(url, attempts=4, delay=5): for attempt in range(attempts): pdf = pdfGeneratorApi.get_pdf(url) # a converted filing starts with the PDF magic number if pdf[:4] == b"%PDF": return pdf time.sleep(delay) raise RuntimeError("PDF not ready after " + str(attempts) + " attempts") pdf = get_pdf_with_retry( "https://www.sec.gov/Archives/edgar/data/320193/000032019323000106/aapl-20230930.htm" ) with open("apple-10-k.pdf", "wb") as f: f.write(pdf)

Good to know

  • The URL must point at the file itself under sec.gov/Archives. An inline XBRL viewer URL contains /ix?doc=, which has to be removed first.
  • Every image the filing references is rendered into the PDF, which is why proxy statements take longer to convert than press releases.
  • Forms 3, 4 and 5 are filed as XML. Passing the xslF345X02 URL renders them through their stylesheet, so the PDF matches what a reader sees on the EDGAR viewer rather than showing raw XML.
  • Each filing is converted once and then stored, so the second request for the same URL returns the PDF immediately.