Skip to main content
Tutorial

SEC EDGAR API in Python: Filings, Full-Text Search, Agents

Pull SEC filings and full-text search results in Python or from an agent, with no User-Agent plumbing and the licence and source on every response.

fastcrw
By RecepNovember 7, 20267 min read

By the fastCRW team · Catalogue figures checked 2026-11-05 against GET /v1/data · Verify independently before relying on any figure.

To use the SEC EDGAR API from Python, you request a filer's submissions from data.sec.gov or search the full text of filings, and you follow the SEC's fair-access rules yourself. Pergamon exposes the same EDGAR records through one fastCRW key, and every response carries the source, the licence and the attribution. This guide shows the calls in Python and from an agent. Every call was run on 2026-11-05.

Nothing here is investment advice. The filings are public records, and what you conclude from them is your call.

The gotchas of raw EDGAR

The SEC publishes its own rules for automated access. It states "Current max request rate: 10 requests/second" and asks clients to "declare your user agent in request headers" (sec.gov). Third-party guides report a 403 response when the User-Agent header is missing (edgrapi.com). That detail comes from those guides, not from the SEC, so treat it as their claim.

Two more things trip people up:

  • The CIK must be zero-padded to 10 digits. The data paths use it, for example CIK0000320193.json. Pergamon needs the same form. With cik=320193 the upstream answers 404, and with cik=0000320193 it answers 200.
  • The User-Agent is handled for you. The licence row for the full-text search source says: "SEC asks that automated access declare a contact in the User-Agent, which fastCRW does." The SEC's fair-access limits still apply upstream, so keep your own request volume modest.

Ticker or name to CIK

If you start from a company name or ticker, use the entity suggest capability. It is small and returns a handful of candidates:

curl "https://api.fastcrw.com/v1/data/secfts/entity-suggest?keysTyped=apple" \
  -H "Authorization: Bearer $CRW_API_KEY"

Trimmed response, as returned on 2026-11-05:

{
  "credits_used": 1,
  "data": {
    "took": 660,
    "hits": {
      "total": { "value": 230, "relation": "eq" },
      "hits": [
        {
          "_id": "320193",
          "_source": { "entity": "Apple Inc. (AAPL)", "tickers": "AAPL", "...": "..." }
        }
      ]
    }
  }
}

The _id is the CIK without padding. Pad it yourself before you call the other SEC capabilities. The response is a list of hits, not a single answer, so check the entity name and tickers before you use the id.

The full company list, sec/company-tickers, is also available, but it is 797 KB over REST. Use it only when you need the whole table.

Filing history with sec/submissions

With a padded CIK, sec/submissions returns the issuer profile and the filing history. The call costs 1 credit.

from crw import CrwClient

client = CrwClient()  # reads CRW_API_KEY
result = client.get_data("sec", "submissions", {"cik": "0000320193"})
print(result["provenance"]["attributionText"])
company = result["data"]
print(company["name"], company["tickers"])
print(list(company["filings"].keys()))

On 2026-11-05 the SDK call returned Apple Inc. with the ticker AAPL, and the filings object held the keys files and recent. The profile around it carries fields such as cik, exchanges, sic, sicDescription, fiscalYearEnd, stateOfIncorporation and formerNames. The REST response for the same CIK was 164 KB.

filings.recent is a set of parallel arrays, one array per field, with one entry per filing. For Apple it held 1,002 entries, with fields such as accessionNumber, filingDate, form, primaryDocument and reportDate. Older filings are listed in separate files, which the files block names. For Apple the first of those files is named CIK0000320193-submissions-001.json and covers 1,271 filings from 1994 to 2015. This post does not walk those files.

Zip the arrays together to get one row per filing. This function returns the most recent 10-K filings for a CIK:

from crw import CrwClient

client = CrwClient()  # reads CRW_API_KEY


def recent_filings(cik: str, form: str = "10-K", limit: int = 5) -> list[dict]:
    company = client.get_data("sec", "submissions", {"cik": cik})["data"]
    recent = company["filings"]["recent"]
    rows = zip(
        recent["form"],
        recent["filingDate"],
        recent["accessionNumber"],
        recent["primaryDocument"],
    )
    matches = [
        {"form": f, "filed": d, "accession": a, "document": p}
        for f, d, a, p in rows
        if f == form
    ]
    return matches[:limit]


for row in recent_filings("0000320193"):
    print(row)

The filter is an exact match on the form string as the SEC files it. A filter of 10-K does not return a 10-K/A amendment, and the recent block also holds forms such as 8-K and 4. Pass the form string exactly as it appears in the array.

Output on 2026-11-05, first three rows of the five printed:

{'form': '10-K', 'filed': '2026-11-03', 'accession': '0000320193-26-000026', 'document': 'aapl-20260926.htm'}
{'form': '10-K', 'filed': '2025-10-31', 'accession': '0000320193-25-000079', 'document': 'aapl-20250927.htm'}
{'form': '10-K', 'filed': '2024-11-01', 'accession': '0000320193-24-000123', 'document': 'aapl-20240928.htm'}

Which capability to use

Four of the capabilities cover most research work. Pick by the question you are answering:

CapabilityUse it forSize seen on 2026-11-05
secfts/entity-suggestA name or ticker to candidate CIKsSmall, one call
sec/submissionsOne company's profile and filing history164 KB for Apple
secfts/searchA phrase across the text of filingsAbout 60 KB for the query below
sec/company-tickersThe full list of tickers and CIKs, for bulk mapping797 KB

Every row costs 1 credit per call. Watch the response size as well: the full ticker list is cheap in credits but large in memory.

Full-text search across filings

The secfts/search capability searches the text of EDGAR filings and their exhibits. It takes a required q and optional filters such as forms, startdt and enddt. Put a phrase in inner double quotes so the search matches it as one unit. Pass the dates as YYYY-MM-DD, and use forms to keep results to one filing type, such as 10-K, so the list you read is short enough to review.

from crw import CrwClient

client = CrwClient()  # reads CRW_API_KEY
hits = client.get_data("secfts", "search", {
    "q": "\"artificial intelligence\"",
    "forms": "10-K",
    "startdt": "2026-01-01",
    "enddt": "2026-06-30",
})
first = hits["data"]["hits"]["hits"][0]["_source"]
print(first["form"], first["file_date"], first["adsh"])

The same query returned about 60 KB on 2026-11-05. Each hit gives you an identity: the accession number (adsh), the entity names with tickers and CIKs, the form, the file date and the file type. It does not give you the sentence that matched. The catalogue description says the response carries the matching document's identity and facet counts, not a matched-text snippet. To quote the text, open the filing at its accession number.

The find output lists more optional filters: ciks, entityName, locationCodes, locationType, from and sort. Use ciks to limit a phrase search to one company after you have resolved its CIK.

From an agent

The MCP tools expose the same capabilities. An agent calls crw_find_data once to discover a capability, then crw_get_data with the options. The get call for the entity suggest looks like this:

{"name": "crw_get_data", "arguments": {"provider": "secfts", "capability": "entity-suggest", "options": {"keysTyped": "apple"}}}

The agent then passes the padded CIK to sec/submissions with the same tool. Over the hosted endpoint on 2026-11-05 the submissions result for Apple was about 164 KB of text, under the 256 KiB MCP cap, and it returned creditsUsed: 1.

The MCP tools return camelCase keys, such as creditsUsed and attributionText. The REST API returns credits_used. Use whichever surface your code calls, and do not mix the two in one parser.

Putting it together

A research agent that answers "what has this company said about AI in its 10-K filings?" can run the same three steps every time:

  1. Resolve the name with secfts/entity-suggest, then pad the CIK to 10 digits.
  2. Read the recent 10-K filings with sec/submissions and keep the accession numbers.
  3. Run secfts/search with the phrase, the form filter and the ciks filter, then open the matching filings to read the passages.

The first two steps return data you can check. The third returns identities that the agent must follow to the filing. Keep that split visible in the agent's output, so a reader can tell which sentences came from a filing the agent opened and which came from the search hit alone.

Errors you will see

  • upstream_error with "SEC EDGAR returned 404 for this request". Check the CIK padding first. This is the error a short CIK produces.
  • upstream_timeout. The upstream did not answer in time, and the call is not billed. Retry after a pause, with the same options.
  • invalid_options. A required option is missing, for example q on the search. The error names the option, so fix the call rather than retrying it.
  • result_too_large. Only over MCP, see the limits below.

Limits

  • EDGAR's own fair-access rules still apply upstream. Keep request rates within the SEC's published limit, and do not loop over thousands of CIKs without a pause.
  • The MCP cap is 256 KiB. A larger result is refused with result_too_large and, for a server source, refunded. The full company list is 797 KB, so over MCP use secfts/entity-suggest for a lookup instead.
  • REST and the Python SDK return the whole body. They have no 256 KiB cap, so a large result comes back in full, and your code has to hold it in memory.
  • Search returns identities, not passages. Plan for a second step that opens the filing.
  • The older filing files are not walked here. The recent block covers the latest filings. Older history needs the files that the response names.

Sources

Related: Public data API for AI agents · Build a finance research agent · Financial research agents for investment work · SEC EDGAR on Pergamon · SEC full-text search on Pergamon

FAQ

Frequently asked questions

Why does my SEC call return 404 with a short CIK?
The SEC data paths expect a 10-digit, zero-padded CIK. Pad it with leading zeros, so 320193 becomes 0000320193. With the padded form the same call returns 200.
Do I need to set a User-Agent header?
Not when you call through fastCRW. The licence row for the full-text search source states that fastCRW declares the contact in the User-Agent. The SEC's rate limits still apply upstream, so keep your request volume modest.
Can I get the matching sentence from a full-text search hit?
No. The search returns the identity of the matching document: accession number, entity, form and file date. Open the filing at that accession number to read the passage.

Get Started

Try fastCRW free

Run a live request in the playground — no signup required. Or grab a free API key with 500 credits, earn up to 500 more, no credit card.

Continue exploring

More tutorial posts

View category archive