Blog · 2026-08-27

SEC EDGAR API: clean JSON isn't correct JSON (2026)

Clean JSON is well-formatted; correct JSON is right. Four parsing traps make SEC data wrong even when the JSON looks perfect
Pretty JSON can still be wrong. Formatting was never the hard part.

Every SEC data API returns clean JSON now. Yours could too, in an afternoon.

The formatting was never the hard part. Whether the number is right is.

SEC EDGAR hands you the raw facts for free. Turning them into flat, well-typed JSON is easy. Turning them into JSON that is actually correct comes down to four specific judgment calls, and if you miss any of them your data looks perfect and reads wrong.

Key takeaway: Clean SEC data and correct SEC data are not the same thing. Clean is well-formatted; correct is right. Four traps make EDGAR data wrong even when the JSON is valid: the same metric is tagged under different XBRL concepts (so revenue comes back null), 13F value changed units in 2023 (so totals are off by 1,000x), one fund reports a holding under several sub-managers (so positions double-count), and a 13F put is a bearish bet, not ownership. Edgrapi's /v1/fundamentals and /v1/holdings make all four calls, so the JSON is correct, which clean formatting alone never guarantees.

Clean data and correct data are not the same thing

Clean data is well-formatted: flat fields, consistent types, valid JSON you can parse without a fight. Correct data is right: revenue that is not null because the filer used a different tag, a 13F total that is not off by a factor of a thousand, a position that is not counted twice. The SEC gives you the raw facts. Clean formatting is an afternoon. Correctness is four decisions.

Here is the uncomfortable part.

Your parser cannot tell the difference. A wrong revenue figure and a right one are both just numbers in a field. The JSON validates either way. That is exactly why these traps ship to production: nothing looks broken.

Now that hosted, free SEC APIs are everywhere, clean JSON is table stakes. The four calls below are what separate data you can build on from data that quietly lies to you.

Why is your SEC revenue null for half the companies you check?

Because the same line item is filed under different XBRL tags across companies and years. Apple reports revenue as RevenueFromContractWithCustomerExcludingAssessedTax. Another company files it as Revenues, a third as SalesRevenueNet. Read one tag and you get a number for Apple and null for everyone else. The us-gaap taxonomy has thousands of overlapping concepts, and a typical 10-K carries six to ten thousand facts, per the StockFit XBRL notes.

This is the trap under every "just read companyfacts" tutorial.

It works on Apple, so it looks done. Then you run it across a watchlist and half the revenue fields are empty, because those filers tagged the same line differently. Tesla does not even use a us-gaap tag for it; it files custom concepts like tsla:AutomotiveSalesRevenue and tsla:ServicesAndOtherRevenue.

One metric, revenue, filed under several different XBRL tags by different companies; a candidate list resolves them all to one field
One metric, many tags. You need a candidate list per field, not a single tag.

The fix is a per-field list of candidate tags, tried in order, first one with data wins. Edgrapi keeps that list: revenue resolves across five tags, net income across two, and so on, so a single /v1/fundamentals call returns a number for a company whether it tags revenue the modern way or the old way. We go deeper on this in why pulling clean fundamentals is harder than it looks.

Why is a fund's 13F 1,000 times too big or too small?

Because the value column changed units. For years, 13F value was reported in thousands of dollars. The SEC's 2022 amendment to Form 13F switched it to whole dollars for periods filed after January 3, 2023. So a 2021 filing that reads 1,743,219 means about 1.74 billion dollars; a 2024 filing that reads the exact same number means about 1.7 million. Read both the same way and one is wrong by three orders of magnitude.

The same 13F value 1,743,219 means 1.74 billion on a pre-2023 filing reported in thousands and 1.7 million on a post-2023 filing reported in whole dollars
Same number, two eras, 1,000x apart. The unit depends on the filing date, not the value.

Nothing in the number tells you which era it is from.

The unit depends on the filing date, not the value, so a parser that treats every filing identically silently mixes billions and millions in the same table. Assume the wrong era and a fund looks a thousand times bigger or smaller than it is.

The fix is to detect pre-2023 filings and multiply their value by a thousand, so everything you return is in whole dollars and comparable across quarters. Edgrapi does this before it hands back a single position, which the 13F holdings guide covers in full.

Why does a 13F position look double-counted?

Because one manager can report the same holding several times, once per sub-adviser. A large firm splits its book across internal managers, each files its slice, and the raw information table lists the same CUSIP on several rows. Sum the rows and the position, the total, and the holder count are all inflated. The fix is to aggregate by CUSIP, combining shares and value into one row per security before you rank anything.

This is the single most common bug in homemade 13F pipelines.

You see a fund holding what looks like 400 million shares of one name, and half of it is the same block reported twice under two sub-managers. The cover page lists them as other included managers, per the SEC's Form 13F FAQ, but the information table just repeats the CUSIP. Aggregate first, then read.

Edgrapi collapses those rows to one position per CUSIP as it parses, which is why its Apple line matches the fund's actual stake instead of a sum of duplicate slices.

Is a 13F put a long position?

No. The 13F information table has a put-or-call column, and a put is a bearish bet, not ownership of the shares. Fold puts into the share totals and a fund's "position" in a stock can actually be a short against it. The fix is to read the put-or-call field and keep options separate from long shares, never summing a put into a holding.

Most raw parsers ignore that column entirely.

So a fund shows a large, confident-looking "holding" in a stock that is really a put wagering the stock falls. If your screen is looking for conviction, you just found the opposite of it and labeled it a buy.

Edgrapi keys every row by CUSIP and by put or call, so a put is tracked as its own line and never merges into the long position. A put reads as a put.

How do you get correct JSON out of EDGAR?

Make these four judgment calls every time, or use an endpoint that already does. Edgrapi's /v1/fundamentals resolves each metric across its candidate XBRL tags, so revenue is never null over a tag mismatch. Its /v1/holdings aggregates a 13F by CUSIP, applies the right units by filing date, and flags puts. Same clean JSON any API returns, with the four decisions already made.

The trapWhat breaksThe fix
XBRL tag driftRevenue null for filers who tag it differentlyCandidate-tag list per field, first with data wins
13F units switch (2023)Values off by 1,000x depending on filing dateDetect pre-2023, multiply by 1,000, report whole dollars
CUSIP double-countPositions and totals inflated by sub-manager rowsAggregate one row per CUSIP
Put counted as a holdingA bearish bet read as ownershipKeep put/call separate from long shares

These four are not the whole list. XBRL also mixes year-to-date and quarterly facts, hides the fourth quarter behind a full-year-minus-nine-months subtraction, and carries restated numbers for the same period. But the shape of the problem is always the same: correctness is a series of decisions, not a file format.

Start: check one tag

Pick a company that is not Apple, pull its revenue straight from companyfacts under a single tag, and see whether your code returns a number or a null. If it is null, you have met the first trap, and the other three are waiting in the 13F.

Then point /v1/fundamentals or /v1/holdings at the same company and compare. The free tier is 100 credits, no card. Same clean JSON, four fewer ways to be wrong. Point it at https://api.edgrapi.com and start with the company whose revenue just came back null.

Frequently asked questions

What is the difference between clean and correct SEC data?

Clean data is well-formatted: flat fields, consistent types, valid JSON. Correct data has the right values. SEC filings can be perfectly clean and still wrong, because the hard part is the judgment calls: resolving XBRL tags that differ by company, handling the 13F units change, aggregating duplicate positions, and telling a put from a holding. Clean is formatting; correct is those decisions.

Why is my SEC revenue null for some companies?

Because the same line item is filed under different XBRL tags. Apple uses RevenueFromContractWithCustomerExcludingAssessedTax; others use Revenues or SalesRevenueNet, and Tesla uses custom tags. Read a single tag and you get a number for one company and null for the rest. The fix is a candidate list per field, tried in order, so revenue resolves whichever tag the filer chose.

Why is a fund's 13F value off by 1,000x?

Because 13F value changed units. It was reported in thousands of dollars until the SEC's 2022 amendment, then whole dollars for periods filed after January 3, 2023. The same number means about 1.74 billion on a 2021 filing and 1.7 million on a 2024 one. The unit depends on the filing date, so a parser that treats every filing the same is off by three orders of magnitude.

Why does a 13F position look double-counted?

Because one manager can report the same holding under several sub-advisers, so the raw information table lists the same CUSIP on multiple rows. Sum them and the position, total, and holder count are all inflated. The fix is to aggregate by CUSIP into one row per security before you rank or total anything.

Is a 13F put a long position?

No. The 13F information table has a put-or-call column, and a put is a bearish bet, not ownership of the shares. Fold puts into share totals and a fund's apparent holding can actually be a short. Read the put-or-call field and keep options separate from long shares, so a put reads as a put.

Does the SEC EDGAR API normalize XBRL tags for you?

The SEC's own companyfacts endpoint does not; it returns raw XBRL where revenue sits under whatever tag the filer used, so normalizing is on you. Edgrapi's /v1/fundamentals resolves each metric across a candidate-tag list, so revenue is never null over a tag mismatch, and /v1/holdings handles the 13F units, CUSIP aggregation, and puts.

Get a free API key