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.
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 key —
Authorization: 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.comcookie 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.
| Field | Type | Meaning |
|---|---|---|
| ticker | cik | string | One of the two is required. |
| forms | string[] | 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. |
| limit | number | 1–100. Default 20. |
| since / before | string | Filing-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
}submissionBytesis 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).
| Field | Type | Meaning |
|---|---|---|
| ticker | cik | string | One of the two is required. |
| metrics | string[] | Up to 8, from the closed list below. Defaults to revenue, net_income, operating_income, assets, operating_cash_flow. |
| years | number | 1–20. Default 5. |
| Metric | Label | us-gaap tags queried |
|---|---|---|
| revenue | Revenue | RevenueFromContractWithCustomerExcludingAssessedTax, Revenues, RevenueFromContractWithCustomerIncludingAssessedTax, SalesRevenueNet |
| net_income | Net income | NetIncomeLoss, ProfitLoss |
| operating_income | Operating income | OperatingIncomeLoss |
| gross_profit | Gross profit | GrossProfit |
| assets | Total assets | Assets |
| liabilities | Total liabilities | Liabilities |
| equity | Shareholders' equity | StockholdersEquity, StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest |
| cash | Cash and equivalents | CashAndCashEquivalentsAtCarryingValue, CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents |
| operating_cash_flow | Operating cash flow | NetCashProvidedByUsedInOperatingActivities, NetCashProvidedByUsedInOperatingActivitiesContinuingOperations |
| capex | Capital expenditure | PaymentsToAcquirePropertyPlantAndEquipment, PaymentsToAcquireProductiveAssets |
| rnd_expense | Research and development | ResearchAndDevelopmentExpense |
| eps_diluted | Diluted EPS | EarningsPerShareDiluted |
| shares_diluted | Diluted shares outstanding | WeightedAverageNumberOfDilutedSharesOutstanding |
| long_term_debt | Long-term debt | LongTermDebtNoncurrent, LongTermDebt |
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.
| Field | Type | Meaning |
|---|---|---|
| ticker | cik | string | One of the two is required. |
| section | string | risk_factors (Item 1A — Risk factors) · mdna (Item 7 — Management's discussion and analysis). Default risk_factors. |
| to | string | Accession of the newer 10-K. Defaults to the latest. |
| from | string | Accession of the older 10-K. Defaults to the one before `to`. |
| summary | boolean | Write 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.985is unchanged;≥ 0.55is the same paragraph reworded; anything below has no counterpart and isaddedorremoved.- Up to 60 items come back in full;
itemsTruncatedsays 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: falseremoves 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:
| Reason | What it means |
|---|---|
| section_heading_not_found | The section is not in this filing. Smaller reporting companies may omit Item 1A; filings before 2005 have no Item 1B. |
| incorporated_by_reference | The section points at an exhibit instead of containing text. JPMorgan's Item 7 extracts to 363 characters of cross-reference. |
| section_too_short | Located, but too small to be the real section. |
| section_failed_content_check | Located, 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.
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.
| Verb | Thin means |
|---|---|
| filings | No filing matched the form or date filter. |
| facts | Every requested metric is untagged by this filer. |
| diff | The 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 resultMCP
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_…| Tool | Cost | What it does |
|---|---|---|
| list_filings | 3 cr | The filings index. |
| get_financial_facts | 5 cr | Annual XBRL figures. |
| diff_filing_section | 12 cr | Year-over-year comparison. |
| get_run | free | One past run, in full. |
| list_runs | free | Your run history. |
| get_coverage | free, public | What this desk can and cannot read. |
| get_pricing | free, public | Credit and x402 prices. |
| get_credit_balance | free | Your spendable Ounie credits. |
| whoami | free | The 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":[…]}| Verb | Price | Atomic USDC |
|---|---|---|
| filings | $0.036 | 36000 |
| facts | $0.060 | 60000 |
| diff | $0.144 | 144000 |
- 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
exactscheme compares the signed value, so a reduced settlement would be refused after the work is done. A diff whose summary could not be written returns402 partial_deliverywithout settling — retry, or ask forsummary: false.
Errors
| Status | Body | Meaning |
|---|---|---|
| 400 | invalid_ticker · invalid_metric · invalid_section … | A Gate-1 refusal. Always fatal, and it carries no price. |
| 401 | unauthorized | No session and no usable key. Never a redirect. |
| 402 | insufficient_credits | Carries required_credits, balance_credits and buy_credits_url. |
| 404 | company_not_found | No EDGAR filer matches that ticker or CIK. |
| 422 | no_filings_matched · no_metrics_tagged · section_not_extractable | Ran, produced nothing, charged nothing. |
| 429 | too_many_running | More than 5 of your runs are in flight. |
| 502 | run_failed | Something upstream broke. You were not charged. |
| 503 | upstream_unavailable | The summary upstream is refusing; refused before quoting. |
Limits
| Limit | Value |
|---|---|
| Filings per call | 100 |
| Metrics per call | 8 |
| Years per metric | 20 |
| Diff items returned in full | 60 |
| Concurrent runs per account | 5 |
| Active API keys | 5 |
| EDGAR requests per second | 10, 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
- 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.