SEC EDGAR's public data (no key): the ticker-to-CIK files, submissions (company profile plus filing history), XBRL company facts, company concepts and frames, filing indexes and document URLs, EDGAR full-text search, and Form D notices of exempt offerings (one filing's primary_doc.xml, a date-window listing, and SEC's quarterly data sets).

User-Agent required. SEC rejects requests without a declared User-Agent naming the company and a contact email, e.g. "Acme Corp ops@acme.com". Set sec.edgar.user-agent or pass {user-agent: ...}; calls fail before any request when it is missing.

Fair access: at most 10 requests per second per client across all of SEC's hosts. Exceeding it gets the client blocked for a while. Nothing here retries; pace bulk work with a schedule or tasks.

Hosts: www.sec.gov (ticker files, Archives), data.sec.gov (submissions, XBRL APIs), efts.sec.gov (full-text search).

Context Vars

Optional
  • sec.edgar.user-agent

Values

DATA

DATA: Str "https://data.sec.gov"

EFTS

EFTS: Str "https://efts.sec.gov/LATEST"

FORM_D_TABLES

FORM_D_TABLES: Map {
  submissions: "FORMDSUBMISSION",
  issuers: "ISSUERS",
  offerings: "OFFERING",
  related-persons: "RELATEDPERSONS",
  recipients: "RECIPIENTS",
  signatures: "SIGNATURES"
}

SEARCH_WINDOW

SEARCH_WINDOW: Int 10000

WWW

WWW: Str "https://www.sec.gov"

Functions

accession-plain

fn (accession: Str): Str

An accession number without dashes, as used in Archives paths: "0000320193-24-000123" → "000032019324000123".

address

fn (node: Any): Map

amount

fn (v: Str?): Any

A Form D amount as an Int, or as filed when it isn't digits ("Indefinite"), or null when empty (pure).

cik-digits

fn (cik: Any): Str

The CIK's digits without leading zeros, as a Str ("0000320193", 320193 and "CIK320193" all give "320193"); an Err for anything else.

cik-for-ticker

fn (ticker: Str): Str?
fn (ticker: Str, opts: Map): Str?

The zero-padded CIK for a ticker, or null. Downloads the ticker file; when looking up many tickers, call company-tickers once and use find-ticker.

collect-form-d-filings

fn (start-date: Str, end-date: Str, limit: Int, opts: Map): Vec<Map>

Follow form-d-filings pages until limit rows or the last page, one request per 100 rows, and at most the 10,000 rows EDGAR serves for one search. An Err on any page is returned in place of the rows.

Example

rows ::sec::edgar/collect-form-d-filings("2026-10-30", "2026-10-30", 500, {})

collect-form-d-pages

fn (start-date: Str, end-date: Str, limit: Int, opts: Map, acc: Vec): Vec

company-concept

fn (cik: Any, taxonomy: Str, tag: Str): Map
fn (cik: Any, taxonomy: Str, tag: Str, opts: Map): Map

One concept's facts for a company, e.g. taxonomy "us-gaap", tag "AccountsPayableCurrent": units.{unit}[] with end, val, accn, fy, fp, form, filed.

company-concept-url

fn (cik: Any, taxonomy: Str, tag: Str): Str

company-facts

fn (cik: Any): Map
fn (cik: Any, opts: Map): Map

Every XBRL fact a company has reported (facts.{taxonomy}.{tag}.units.{unit}[]). Large filers' files run to several MB.

company-facts-url

fn (cik: Any): Str

company-tickers

fn (): Vec<Map>
fn (opts: Map): Vec<Map>

https://www.sec.gov/files/company_tickers.json as a Vec of {cik, ticker, title} (about 10,000 listed companies, about 800 KB). Fetch it once per run and reuse it; it changes daily.

element-texts

fn (nodes: Vec): Vec<Str>

elements

fn (node: Any): Vec

field

fn (node: Any, path: Vec<Str>): Str?

filing-document

fn (cik: Any, accession: Str, document: Str): Map
fn (cik: Any, accession: Str, document: Str, opts: Map): Map

Download one filing document as {content-type, bytes}. Large exhibits can exceed http.max-response-bytes.

filing-document-url

fn (cik: Any, accession: Str, document: Str): Str

The URL of one document in a filing, e.g. its primaryDocument from recent-filings.

Example

::sec::edgar/filing-document-url(320193, "0000320193-24-000123", "aapl-20240928.htm")
// "https://www.sec.gov/Archives/edgar/data/320193/000032019324000123/aapl-20240928.htm"

filing-folder-url

fn (cik: Any, accession: Str): Str

The Archives folder of a filing: https://www.sec.gov/Archives/edgar/data/{cik}/{accession without dashes}.

filing-index

fn (cik: Any, accession: Str): Map
fn (cik: Any, accession: Str, opts: Map): Map

A filing folder's index.json listing (directory.item[] with name, type, size, last-modified).

filing-index-url

fn (cik: Any, accession: Str): Str

The human-readable filing index page: .../{accession}-index.htm.

find-ticker

fn (tickers: Vec<Map>, ticker: Str): Map?

The {cik, ticker, title} for a ticker (case-insensitive) in a ticker list, or null (pure).

flag

fn (node: Any, path: Vec<Str>): Bool?

form-d

fn (cik: Any, accession: Str): Map?
fn (cik: Any, accession: Str, opts: Map): Map?

Read one Form D filing: parse-form-d of its primary_doc.xml, plus accession and url. Null when the filing has no such document (the 404 for a paper filing or a wrong accession number). An Err for a CIK that isn't one.

Any CIK on the filing works, so rows from recent-filings and form-d-filings both lead here.

Example

subs ::sec::edgar/submissions(2162876)
row first(::sec::edgar/recent-filings(subs, 1, {forms: ["D", "D/A"]}))
d ::sec::edgar/form-d(subs.cik, row.accessionNumber)
d.issuer.name
d.total-offering-amount

form-d-dataset

fn (year: Int, quarter: Int): Map?
fn (year: Int, quarter: Int, opts: Map): Map?

One quarter of SEC's Form D data sets: every Form D filed in the quarter as flat tables. Two requests, the data sets page and the zip (about 4 MB). Returns {year, quarter, url, ...tables}, or null when SEC hasn't posted that quarter. opts.tables is as in parse-form-d-dataset.

SEC updates the data sets quarterly, and says the values are as filed and that it can't guarantee their accuracy.

Example

d ::sec::edgar/form-d-dataset(2026, 3, {tables: ["offerings"]})
length(d.offerings)

form-d-dataset-url

fn (html: Str, year: Int, quarter: Int): Str?

A quarter's zip URL from the HTML of SEC's Form D data sets page, or null when the page doesn't list it (pure). The URLs can't be built: the folder has changed over the years and some names end _d_0.zip.

form-d-datasets-page-url

fn (): Str

form-d-fields

fn (root: Any): Map

form-d-filing

fn (hit: Map): Map

One full-text search hit as a row (pure): {accession, form, filed, ciks, names, file-numbers, exemptions, url}. url is form-d-url for the first CIK, and exemptions are upper-cased by the index ("06B", where the filing says "06b").

form-d-filings

fn (start-date: Str, end-date: Str): Map
fn (start-date: Str, end-date: Str, opts: Map): Map

One page (100 rows, newest first) of the Form D filings in a date window, through full-text-search. Returns {total, total-is-exact, from, filings, next-from}; pass next-from as opts.from for the next page. It is null on the last page.

forms defaults to ["D"], and that lists amendments too. EDGAR matches forms=D on the root form, so it returns both D and D/A, while forms=D,D/A returns only the D/A filings. opts.forms: ["D/A"] narrows to amendments. Keep originals alone by filtering on form.

EDGAR serves the first 10,000 hits of a search and no further; past that total stops at 10,000 and total-is-exact is false. A quarter has more than that, so use narrower windows, or form-d-dataset.

opts also takes ciks, and anything else full-text-search does.

Example

page ::sec::edgar/form-d-filings("2026-10-20", "2026-10-31")
page.total
first(page.filings).url

form-d-issuer

fn (node: Any): Map

form-d-page

fn (body: Map, from: Int): Map

A full-text search body as {total, total-is-exact, from, filings, next-from} (pure). from is the offset it was asked for.

form-d-person

fn (node: Any): Map

form-d-recipient

fn (node: Any): Map

form-d-signature

fn (node: Any): Map

form-d-table

fn (archive: Bytes, names: Vec<Str>, table: Str): Vec<Map>

form-d-url

fn (cik: Any, accession: Str): Str

The URL of a Form D filing's XML, primary_doc.xml in the filing folder. Use this rather than the url from recent-filings: the primaryDocument of a Form D is xslFormDX01/primary_doc.xml, the same filing rendered as HTML.

Example

::sec::edgar/form-d-url(2162876, "0002162876-26-000002")
// "https://www.sec.gov/Archives/edgar/data/2162876/000216287626000002/primary_doc.xml"

frames

fn (taxonomy: Str, tag: Str, unit: Str, period: Str): Map
fn (taxonomy: Str, tag: Str, unit: Str, period: Str, opts: Map): Map

One fact across all companies for a period: the latest value each filer reported that best fits the calendar period.

Example

f ::sec::edgar/frames("us-gaap", "AccountsPayableCurrent", "USD", "CY2019Q1I")
length(f.data)

frames-url

fn (taxonomy: Str, tag: Str, unit: Str, period: Str): Str

/api/xbrl/frames/{taxonomy}/{tag}/{unit}/{period}.json, e.g. period "CY2019Q1I" (instant) or "CY2023" (annual). Units with a slash such as USD/shares are written USD-per-shares.

full-text-params

fn (q: Str, opts: Map): Map

Query parameters for EDGAR full-text search (pure). opts: forms (Vec or comma list), start-date / end-date (YYYY-MM-DD, sets dateRange=custom), ciks, from (offset; pages are 100 hits). Unset options are omitted.

fn (q: Str): Map
fn (q: Str, opts: Map): Map

EDGAR full-text search (filings since 2001) via efts.sec.gov/LATEST/search-index, the endpoint behind the EDGAR full-text search web page. SEC does not document it as a public API, so its shape can change. Returns the raw Elasticsearch-style body: hits.total.value and hits.hits[]._source with ciks, display_names, form, file_date, adsh (accession) and more. Wrap phrases in double quotes.

Example

res ::sec::edgar/full-text-search("\"climate risk\"", {forms: ["10-K"], start-date: "2025-01-01", end-date: "2025-03-31"})
res.hits.total.value

headers

fn (opts: Map): Map

The SEC request headers (User-Agent from settings), or an Err when no user-agent is set.

kid

fn (node: Any, tag: Str): Any

kids

fn (node: Any, tag: Str): Vec

pad-cik

fn (cik: Any): Str

A CIK zero-padded to the ten digits EDGAR's JSON APIs use.

Example

::sec::edgar/pad-cik(320193)        // "0000320193"
::sec::edgar/pad-cik("CIK0000320193") // "0000320193"

parse-form-d

fn (text: Str): Map

A Form D primary_doc.xml as a Map (pure). The main fields:

  • form ("D" or "D/A"), is-amendment, previous-accession
  • issuer, the primary issuer, and issuers, all of them with the primary first: {cik, name, address, phone, jurisdiction, entity-type, entity-type-other, year-of-inc, year-of-inc-choice, previous-names}
  • related-persons: [{name, first-name, middle-name, last-name, address, relationships, clarification}]
  • exemptions, as filed: ["06b", "3C", "3C.1"]
  • first-sale-date (YYYY-MM-DD), null when first-sale-yet-to-occur is true
  • total-offering-amount, total-amount-sold, total-remaining: an Int, or "Indefinite"
  • minimum-investment, sales-commissions, finders-fees, gross-proceeds-used, total-investors, has-non-accredited-investors, non-accredited-investors
  • industry-group, investment-fund-type, is-40-act, revenue-range, net-asset-value-range, security-types, more-than-one-year, business-combination
  • sales-compensation: [{name, crd, broker-dealer, broker-dealer-crd, address, states, foreign-solicitation}]
  • signatures: [{issuer-name, signature, signer, title, date}]

An element the filing leaves out is null. An Err for malformed XML, and for XML that isn't a Form D.

parse-form-d-dataset

fn (archive: Bytes): Map
fn (archive: Bytes, opts: Map): Map

Read tables from a quarterly data set zip as Bytes (pure). Returns a Map of table name to rows, keyed by SEC's column names (see the FormD_readme.html in each zip), with every value a Str.

opts.tables picks from submissions, issuers, offerings, related-persons, recipients and signatures. It defaults to submissions, issuers and offerings, leaving out the three tables of people, which are the bulk of the data.

Rows of one filing share ACCESSIONNUMBER across tables.

parse-tickers

fn (body: Any): Vec<Map>

{"0": {cik_str, ticker, title}, ...} → [{cik, ticker, title}] with cik zero-padded (pure).

previous-names

fn (node: Any, tag: Str): Vec<Str>

read-form-d-tables

fn (archive: Bytes, names: Vec<Str>, tables: Vec<Str>, acc: Map): Map

recent-filings

fn (subs: Map, limit: Int, opts: Map): Vec<Map>

filings.recent of a submissions body as rows (pure): [{accessionNumber, filingDate, form, primaryDocument, ...}], newest first, at most limit. Each row also gets url, the primary document's URL. opts.forms keeps only those form types (e.g. ["10-K", "10-Q"]).

search-tickers

fn (tickers: Vec<Map>, name: Str, limit: Int): Vec<Map>

Ticker-list rows whose company name contains name (case-insensitive), at most limit (pure).

sec-get

fn (url: Str, opts: Map): Any

security-types

fn (offering: Any): Vec<Str>

submissions

fn (cik: Any): Map
fn (cik: Any, opts: Map): Map

data.sec.gov/submissions/CIK##########.json: the company profile (name, tickers, exchanges, SIC, state of incorporation, fiscal year end, addresses, former names) and its most recent filings (at least a year, up to 1,000) in filings.recent, with older pages listed in filings.files.

Example

sub ::sec::edgar/submissions(320193)
sub.name  // "Apple Inc."

submissions-page

fn (file-name: Str): Map
fn (file-name: Str, opts: Map): Map

One older submissions page named in filings.files (e.g. "CIK0000320193-submissions-001.json"); its columns are the same as filings.recent.

submissions-url

fn (cik: Any): Str