Paginate available SEC filing history with JavaScript

Traverse an issuer’s available SEC history with a bounded request budget, deduplicate accessions, and detect source snapshot changes.

WHAT YOU WILL BUILD

Choose history=all explicitly, follow nextOffset, and verify that each page belongs to the same source snapshot. Preserve unfinished pagination rather than calling a bounded result complete.

Requirements: Node.js 22 or later; no extra packages. An API key from your dashboard, stored in the SEC_API_KEY environment variable.

Run the workflow

Save this as workflow.mjs and run node workflow.mjs. The production base URL is used by default. Keep credentials out of source control.

// Node.js 22+. Set SEC_API_KEY before running.
const key = process.env.SEC_API_KEY;
if (!key) throw new Error("Set SEC_API_KEY first");
const base = process.env.SEC_API_BASE_URL || "https://sec-api.dev";
async function request(path) {
  const response = await fetch(base + path, {
    headers: { Authorization: "Bearer " + key }
  });
  if (!response.ok) {
    throw new Error("HTTP " + response.status + ": " + await response.text());
  }
  return response.json();
}
const seen = new Map();
let offset = 0, snapshot = null, nextOffset = null;
for (let page = 0; page < 3; page++) {
  const result = await request("/api/v1/filings?" + new URLSearchParams({
    cik: "320193", form: "10-K", history: "all", limit: "25", offset: String(offset)
  }));
  if (result.meta.historyCoverage !== "full") throw new Error("History incomplete");
  if (snapshot && snapshot !== result.meta.snapshotId) {
    throw new Error("Source snapshot changed; restart the export");
  }
  snapshot = result.meta.snapshotId;
  for (const row of result.data) seen.set(row.accessionNumber, row);
  nextOffset = result.pagination.nextOffset;
  if (nextOffset === null) break;
  if (nextOffset <= offset) throw new Error("Pagination did not advance");
  offset = nextOffset;
}
console.log(JSON.stringify({filings: [...seen.values()], snapshot,
  hasMore: nextOffset !== null, nextOffset}, null, 2));

Request historical coverage intentionally

Recent submissions are the default because many applications need only newly filed documents. This script opts into the SEC supplemental history files for one issuer. Full coverage is conditional on validating all referenced files within the service limits. It is a statement about the available source snapshot, not proof that every document ever filed survives in that source. A warming503 should be retried according to the response guidance; do not convert it into an empty history.

Detect refreshes between pages

Offset pagination alone does not provide snapshot isolation. The script stores the first snapshotId and fails if a subsequent response differs. This is stricter than silently joining records from different retrievals. A cache refresh can change an identifier even when many rows are unchanged, so a restart is an operational choice for reproducibility, not evidence of tampering. The accession-keyed map prevents repeated rows from becoming duplicate records in the local output.

Separate a request budget from completion

Three pages bounds the work for the example. If nextOffset remains non-null, more results exist and the output reports that explicitly. Increase the budget only after considering account quotas. Form10-K is exact: amended10-K/A records need a separate selection, and a reconciliation policy should preserve both original and amended documents.

Sources and further reading

The underlying SEC dataset behavior is documented in the official SEC API reference and SEC technical specifications. Response-field names shown here belong to the sec-api.dev endpoint contract. The scripts print actual results and do not embed fabricated filing or financial data.

All tutorials ←