API reference

Filings Desk API

Three verbs over SEC EDGAR: the filings index, typed XBRL facts, and a paragraph-level comparison of two annual filings. One synchronous call each, JSON in and out. Three rails share one meter — 3, 5 and 12 Ounie credits respectively.

Filings Desk reports what a company filed and how the language changed. It is not investment advice and it is not a recommendation. Every response carries that sentence as a field, so an agent passing a result along carries it too.

Quickstart

Mint a key at /dashboard/api-keys, then post a ticker.

curl -X POST https://filings.ounie.com/api/diff \
  -H "Authorization: Bearer flg_live_…" \
  -H "Content-Type: application/json" \
  -d '{"ticker":"AAPL","section":"risk_factors"}'

Every verb accepts ticker or cik. A CIK may be passed with or without leading zeros; it is padded to ten digits internally, which is what EDGAR requires.

Authentication

  • Your app keyAuthorization: Bearer flg_live_…, minted at /dashboard/api-keys. Up to five active at a time; the raw token is shown once.
  • The Ounie master key — your ounie.com developer key (ounie_live_…) works here too, once you enable Use across Ounie apps in your Ounie settings. One key, the whole fleet.
  • A browser session — the shared .ounie.com cookie authenticates the same endpoints for the dashboard.

Clients that cannot set a header may pass ?api_key=flg_live_… on any endpoint. A bad key returns 401 — never a redirect, which an agent cannot follow.

POST /api/filings — 3 credits

Every filing EDGAR holds for one company, newest first.

FieldTypeMeaning
ticker | cikstringOne of the two is required.
formsstring[]Form types to keep. Any EDGAR form type is accepted; common ones are 10-K, 10-Q, 8-K, 20-F, 40-F, S-1, DEF 14A, 4.
limitnumber1–100. Default 20.
since / beforestringFiling-date window, YYYY-MM-DD.
{
  "company": { "cik": "0000320193", "name": "Apple Inc.", "tickers": ["AAPL"], … },
  "filings": [
    { "accession": "0000320193-25-000079", "form": "10-K",
      "filedAt": "2025-10-31", "reportDate": "2025-09-27",
      "documentUrl": "https://www.sec.gov/Archives/edgar/data/320193/…/aapl-20250927.htm",
      "filingIndexUrl": "…", "items": [], "isInlineXbrl": true,
      "submissionBytes": 9392337 }
  ],
  "total": 41,
  "moreAvailable": true
}
  • submissionBytes is EDGAR's own size field and it counts the whole submission, exhibits included — it overstates the primary document by five to twelve times. It is named for what it is and nothing is sized off it.
  • moreAvailableis true when older filings exist beyond EDGAR's ~1,000-entry recent block and were not fetched. It is there so that “no filings before 2015” is never mistaken for a fact about the company.

POST /api/facts — 5 credits

Annual figures read from the company's own XBRL, via EDGAR's company-concept endpoint (~18 KB per concept, rather than the 4–8 MB full fact set).

FieldTypeMeaning
ticker | cikstringOne of the two is required.
metricsstring[]Up to 8, from the closed list below. Defaults to revenue, net_income, operating_income, assets, operating_cash_flow.
yearsnumber1–20. Default 5.
MetricLabelus-gaap tags queried
revenueRevenueRevenueFromContractWithCustomerExcludingAssessedTax, Revenues, RevenueFromContractWithCustomerIncludingAssessedTax, SalesRevenueNet
net_incomeNet incomeNetIncomeLoss, ProfitLoss
operating_incomeOperating incomeOperatingIncomeLoss
gross_profitGross profitGrossProfit
assetsTotal assetsAssets
liabilitiesTotal liabilitiesLiabilities
equityShareholders' equityStockholdersEquity, StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest
cashCash and equivalentsCashAndCashEquivalentsAtCarryingValue, CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents
operating_cash_flowOperating cash flowNetCashProvidedByUsedInOperatingActivities, NetCashProvidedByUsedInOperatingActivitiesContinuingOperations
capexCapital expenditurePaymentsToAcquirePropertyPlantAndEquipment, PaymentsToAcquireProductiveAssets
rnd_expenseResearch and developmentResearchAndDevelopmentExpense
eps_dilutedDiluted EPSEarningsPerShareDiluted
shares_dilutedDiluted shares outstandingWeightedAverageNumberOfDilutedSharesOutstanding
long_term_debtLong-term debtLongTermDebtNoncurrent, LongTermDebt
There is no universal tag. JPMorgan does not carry RevenueFromContractWithCustomerExcludingAssessedTax at all. Apple does — and also carries Revenues, which stops at FY2018. A priority list that takes the first tag returning data reports Apple's 2018 revenue as current, a wrong answer indistinguishable from a right one. So every candidate is queried and the one with the most recent annual coverage wins.
{
  "series": [
    { "metric": "revenue", "label": "Revenue",
      "tag": "RevenueFromContractWithCustomerExcludingAssessedTax",
      "triedTags": ["RevenueFromContract…", "Revenues", …],
      "unit": "USD", "tagged": true,
      "points": [ { "fiscalYear": 2025, "periodEnd": "2025-09-27",
                    "value": 416161000000, "form": "10-K",
                    "filed": "2025-10-31", "accession": "0000320193-25-000079" } ] }
  ],
  "notTagged": ["gross_profit"]
}

A metric this filer does not tag appears in notTagged, by name. It is never returned as a zero and never silently dropped. If none of the requested metrics are tagged, the call is thin and refunds in full.

POST /api/diff — 12 credits

One section of an annual filing compared against the previous annual filing, paragraph by paragraph.

FieldTypeMeaning
ticker | cikstringOne of the two is required.
sectionstringrisk_factors (Item 1A — Risk factors) · mdna (Item 7 — Management's discussion and analysis). Default risk_factors.
tostringAccession of the newer 10-K. Defaults to the latest.
fromstringAccession of the older 10-K. Defaults to the one before `to`.
summarybooleanWrite the prose summary. Default true. False skips the model entirely and is never gated by upstream availability.
{
  "section": "risk_factors",
  "sectionLabel": "Item 1A — Risk factors",
  "from": { "accession": "0000320193-24-000123", "filedAt": "2024-11-01",
            "url": "https://www.sec.gov/Archives/…", "words": 11284 },
  "to":   { "accession": "0000320193-25-000079", "filedAt": "2025-10-31",
            "url": "https://www.sec.gov/Archives/…", "words": 11190 },
  "counts": { "added": 3, "removed": 1, "changed": 9,
              "unchanged": 42, "softened": 4, "hardened": 2 },
  "identical": false,
  "items": [
    { "kind": "added", "heading": "The Company's business…", "text": "…", "words": 214 },
    { "kind": "changed", "similarity": 0.78, "toneShift": "softened",
      "previous": "…", "text": "…",
      "toneEvidence": { "hedgeDensityBefore": 4.1, "hedgeDensityAfter": 5.4,
                        "severityDensityBefore": 3.9, "severityDensityAfter": 3.2 } }
  ],
  "itemsTruncated": false,
  "summary": { "headline": "…", "paragraphs": ["…"], "model": "openai/gpt-5-nano" }
}
  • Paragraphs are matched by similarity, not position. Filings are re-paginated every year; positional comparison would report one insertion at the top as a rewrite of everything below it.
  • 0.985 is unchanged; 0.55 is the same paragraph reworded; anything below has no counterpart and is added or removed.
  • Up to 60 items come back in full; itemsTruncated says when there were more. The counts always describe everything, not just what was returned.
  • The model never performs the comparison.It is handed the comparison's output and asked to describe it, so it cannot introduce a change that is not there. summary: false removes it entirely.
  • Only 10-Kfilings are compared. A 10-Q carries an abbreviated “material changes” item that is not comparable to an annual one.

Section extraction

A modern 10-K is 220,000 to 1,400,000 characters of text. Two of them will not fit in a prompt, so the section is cut out first — and the cut has to be right, because a comparison of the wrong section is fluent, confident and false.

The widest-gap rule.For every “Item 1B. Unresolved Staff Comments” heading, take the last “Item 1A. Risk Factors” before it, and keep the pair furthest apart. The first match in a filing is almost always the table of contents, and taking the last fails outright on filings whose final “Item 1A” falls after their final Item 1B. Non-breaking and zero-width characters are stripped before matching, since they sit inside the heading in most filings.

The extraction is then checked — long enough to be a section, and reading like the section it claims to be. When the check fails you get a reason and no charge:

ReasonWhat it means
section_heading_not_foundThe section is not in this filing. Smaller reporting companies may omit Item 1A; filings before 2005 have no Item 1B.
incorporated_by_referenceThe section points at an exhibit instead of containing text. JPMorgan's Item 7 extracts to 363 characters of cross-reference.
section_too_shortLocated, but too small to be the real section.
section_failed_content_checkLocated, but the text does not read like that section, so it was rejected rather than compared.

The tone signal

On a reworded paragraph, toneShift is one of softened, hardened or neutral. It means exactly one thing: hedging density moved relative to severity density.

  • Hedges — may, might, could, generally, typically, believe, expect, approximately, certain…
  • Severity — material, significant, substantial, adverse, severe, harm, loss, failure, cannot…
  • Both are counted per hundred words and returned in toneEvidence, so you can re-derive the label yourself.
This is a measurement of the wording, never a judgement about what the company meant. Whether a risk actually got less serious is not something a word count can tell you, and this one does not pretend to.

Thin results & refunds

A call that could not produce its artifact is never billed. Credits are reserved at submit and returned pool by pool, exactly as they were drawn. On REST this is a 422 carrying charged: false.

VerbThin means
filingsNo filing matched the form or date filter.
factsEvery requested metric is untagged by this filer.
diffThe section could not be extracted from one or both filings.

A diff that finds zero changesis not thin. “The risk language did not move this year” is a real finding and is billed in full.

Availability

EDGAR is free and public, so the index and facts verbs have no external dependency that can refuse them. The one metered upstream in this app is the model that writes a diff's summary paragraph.

A scheduled probe checks that upstream with a real request — never a read, because a provider whose account is unpaid keeps answering reads perfectly while refusing every write. While it is refusing, a diff that asked for a summary is refused up front with a 503, before any price is quoted and before any credits are reserved. Nothing is charged, and no reserve/refund pair appears in your ledger.

The other two verbs keep working throughout, and so does {"summary": false}.

Reading past runs

Reading work you already paid for is free, forever.

GET /api/filings?limit=20&verb=diff     # your run history
GET /api/runs/<run_id>                  # one run, with its full result

MCP

Streamable HTTP at /api/mcp, legacy SSE at /api/sse. Works with Claude, Cursor, ChatGPT, the AI SDK and the Ounie AI Team — which cannot set static headers on a manual HTTP entry, so the key rides the URL.

Endpoint  https://filings.ounie.com/api/mcp   (legacy SSE: /api/sse)
Auth      Authorization: Bearer flg_live_…
          …or https://filings.ounie.com/api/mcp?api_key=flg_live_…
ToolCostWhat it does
list_filings3 crThe filings index.
get_financial_facts5 crAnnual XBRL figures.
diff_filing_section12 crYear-over-year comparison.
get_runfreeOne past run, in full.
list_runsfreeYour run history.
get_coveragefree, publicWhat this desk can and cannot read.
get_pricingfree, publicCredit and x402 prices.
get_credit_balancefreeYour spendable Ounie credits.
whoamifreeThe key's owner and id.

A tool call that cannot be covered fails with insufficient_credits and a link to top up. An agent can never overdraw.

x402 — keyless, per call

Agents with no Ounie account pay in USDC on Base. One endpoint takes all three verbs, and the price is computed from your payload rather than baked into the route — so the keyless rail and the credit rail can never disagree about what a call costs.

POST https://filings.ounie.com/api/x402/filings
{"verb":"facts","ticker":"TSLA","metrics":["revenue","net_income"]}

→ 402  {"x402Version":1,"accepts":[{"scheme":"exact","network":"base",
         "maxAmountRequired":"60000", …}]}
→ retry with  X-Payment: <signed authorization>
→ 200  {"payment":{"tx_hash":"0x…"}, "series":[…]}
VerbPriceAtomic USDC
filings$0.03636000
facts$0.06060000
diff$0.144144000
  • Two gates. Anything knowable from the request alone — an unknown verb, a malformed ticker, a metric outside the enum — returns 4xx with no price in the body. You are never asked to sign for work that cannot succeed.
  • Verify, run, then settle. An on-chain settlement is final and there is no refund path, so a call that produced nothing is refused before the money moves.
  • All or nothing. The exact scheme compares the signed value, so a reduced settlement would be refused after the work is done. A diff whose summary could not be written returns 402 partial_delivery without settling — retry, or ask for summary: false.

Errors

StatusBodyMeaning
400invalid_ticker · invalid_metric · invalid_section …A Gate-1 refusal. Always fatal, and it carries no price.
401unauthorizedNo session and no usable key. Never a redirect.
402insufficient_creditsCarries required_credits, balance_credits and buy_credits_url.
404company_not_foundNo EDGAR filer matches that ticker or CIK.
422no_filings_matched · no_metrics_tagged · section_not_extractableRan, produced nothing, charged nothing.
429too_many_runningMore than 5 of your runs are in flight.
502run_failedSomething upstream broke. You were not charged.
503upstream_unavailableThe summary upstream is refusing; refused before quoting.

Limits

LimitValue
Filings per call100
Metrics per call8
Years per metric20
Diff items returned in full60
Concurrent runs per account5
Active API keys5
EDGAR requests per second10, self-imposed

That last one is deliberate. EDGAR returns no 429 and publishes no throttle header — its documented failure mode is a silent block of the user agent, which is unrecoverable. The absence of an error is not headroom.

What this is not

Filings Desk reports what a company filed and how the language changed. It is not investment advice and it is not a recommendation.
  • No score, rating or outlook. There is no number in this product that summarises whether a company is doing well.
  • No price or market data. EDGAR holds filings, not quotes.
  • No full-text search across filers.EDGAR's full-text index effectively starts in 2001, filters on the root form so exhibits come back alongside the document you asked for, and saturates its total at 10,000. It is not shipped rather than shipped with caveats nobody reads.
  • No 10-Q comparison.A quarterly “material changes” item is not the annual set.

Credits are managed on your Ounie account — top up here. Verb names and labels: filings = Filings index · facts = XBRL facts · diff = Risk diff.