DEV Community

Dominique Church
Dominique Church

Posted on

I built an API that checks whether SEC financial data adds up

I'm a solo developer in Canada, and I built BalanceProof: an API for SEC EDGAR fundamentals on 6,000+ US public companies, where every balance sheet, income statement and cash flow is checked against its own totals before you get it.

The video above is the 33-second version. This post is the longer one: what it does, why I bothered, and how to try it for free.

The problem

If you have ever pulled fundamentals out of SEC filings yourself, you know the numbers are not as tidy as the filings look on paper. XBRL is flexible, which is great for filers and painful for anyone reading it in bulk. The same concept can be tagged several ways. Segment figures sit next to consolidated ones. Later filings recast earlier periods. A pipeline that grabs "the" total assets for a company can quietly grab the wrong one.

Most data sources hand you a number either way. You find out it was wrong when a ratio looks strange, or you never find out.

I hit every one of these while building the data load:

  • An early load dropped 37 companies because of a CIK-to-ticker mapping bug.
  • 8-K recast filings once made Stryker's Q4 and full-year figures collide. The fix was to read only periodic reports (10-K and 10-Q) and purge 81,397 rows that came from non-periodic filings.
  • JPMorgan tags "Total assets" in 23 different ways in one filing.

None of those show up as an error. The request succeeds, the numbers look plausible, and they are quietly wrong or missing. That is what pushed me to test the data against itself.

The three checks

So that became the product. Every period is tested against accounting identities that must hold if the figures are right:

  1. Balance sheet: assets = liabilities + equity
  2. Income statement: revenue - cost of revenue = gross profit
  3. Cash flow: operating + investing + financing + FX = change in cash

A filing that does not add up is not passed through silently. It is flagged, with the reason. Figures are as filed, not smoothed over.

For a concrete example, Apple's 10-Q for the quarter ended 27 Jun 2026 (filed 31 Jul 2026) reports total assets of $383.27B, liabilities of $275.75B and shareholders' equity of $107.52B. 275.75 + 107.52 = 383.27, so it passes. Microsoft's 10-K for the year ended 30 Jun 2026: assets $758.38B, liabilities $315.99B, equity $442.39B. Also passes.

(Company numbers here are examples of data checks, not investment advice.)

Python quickstart (free tier)

pip install "balanceproof[pandas]"
Enter fullscreen mode Exit fullscreen mode

Grab a free key at https://balanceproof.dev/dashboard (no card), then:

import balanceproof as bp
client = bp.Client("YOUR_KEY")  # or set BALANCEPROOF_API_KEY
sheet = client.balance_sheet("AAPL")
stmts = client.statements("AAPL", period="quarterly")
for p in stmts["periods"][:4]:
    print(p["period_end"], [(c["check"], c["status"]) for c in p["checks"]])
Enter fullscreen mode Exit fullscreen mode

balance_sheet returns the latest filed balance sheet, with assets, liabilities and equity line items and a list of any data quality issues. statements returns one entry per period with the income statement, the cash flow, the filing date, a derived list, and a checks list. Each check has a name, the rule it tested, and a status of passed, failed or not_testable (the last one names the missing figures).

If you forget the key, the client raises balanceproof.BalanceProofError with a 401 that tells you where to get a free one.

If you prefer pandas:

df = client.panel(["AAPL", "MSFT", "KO"], ["revenue", "net_income", "operating_cash_flow"],
                  period="quarterly")
clean = df[df.checks_failed.str.len() == 0]  # drop periods that failed a check
Enter fullscreen mode Exit fullscreen mode

panel gives you one row per (ticker, period), with filing_date, fiscal_period, checks_failed and derived columns next to the metrics. derived lists figures that were computed rather than filed, such as Q4 as the year minus nine months.

Use it from Claude, ChatGPT or Cursor (MCP)

There is an MCP server at:

https://balanceproof.dev/mcp
Enter fullscreen mode Exit fullscreen mode

Add it as a remote MCP server in Claude, ChatGPT or Cursor. The tools it exposes are search_companies, get_balance_sheet, check_balance_sheet, get_balance_sheet_history, get_balance_sheet_changes, get_financial_statements, get_exceptions and get_api_key. The most useful one is probably check_balance_sheet: ask "does this company's latest balance sheet add up?" and you get an answer with the reason, instead of a number the model has to trust.

What is free and what is paid

I would rather be clear about this up front.

  • Free: 1,000 API calls a month, no card. Balance sheets and statements (with their checks) are included.
  • Starter, $19/mo CAD: adds changes(), which shows what moved since the previous period and what was restated.
  • Pro, $49/mo CAD: 10,000 calls, plus as_of point-in-time queries (only what had been filed by a given date, for backtests), the exceptions() feed of failed checks and restatements across all companies (last 90 days), and verify() for 50 tickers at once.
  • Business: full exceptions history and verify() for 500 tickers.

If you call something your plan does not include, the client raises balanceproof.PlanRequired, and its .required_plan tells you which plan you would need. No silent empty results.

There is also a public restatements tracker at https://balanceproof.dev/restatements if you just want to see which companies revised figures recently.

What I would love feedback on

I would genuinely like to hear:

  • Does the check-first framing make sense, or would you rather just get the numbers?
  • Which checks are missing for your use case?
  • Anything in the Python client that feels awkward? The method list is short on purpose.
  • If you try the MCP server, did the tools do what you expected from their names?

Comments here are great. So is trying to break it: find a filing where you think the check is wrong and tell me.

It is live at https://balanceproof.dev. Thanks for reading.

Top comments (1)

Collapse
 
to21as profile image
Tobias •

We check the same kind of identity on EU e-invoices, where EN 16931's rule BR-CO-15 says the total with VAT has to equal the total without VAT plus the VAT total, and our check accepts a difference of up to two cents. Did any of your three checks need a tolerance, or do 10-K and 10-Q figures foot exactly once the non-periodic filings are out?