A financial ratios API exists because the alternative, parsing XBRL yourself, is one of the least rewarding detours in fintech. Every US public company reports its income statement, balance sheet, and cash flow statement in XBRL, a machine-readable format that is structured in theory and hostile in practice. Concepts come from four taxonomies (us-gaap, dei, ifrs-full, srt). Companies bolt on custom extension tags for anything unusual. Balance sheet values exist at points in time while income values cover durations, and the two can't be mixed without care. A parser that works for one filer often breaks on the next.
If what you actually want is a profit margin, a return on equity, or a debt-to-equity figure for a screener, a dashboard, or a research notebook, you can skip all of that. This post covers three levels of the same data: precomputed ratios in one call, individual XBRL concepts when a canned ratio isn't enough, and a cross-sectional screener for the whole market. All three come from the companies' own SEC filings, through the same API key that serves prices and historical bars.
Calling the financial ratios API#
The ratios endpoint returns standard fundamental ratios for a ticker, derived from its XBRL filings: margins, return on equity, debt to equity, and related measures.
curl --compressed -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/fnd/stocks/AAPL/ratios"
Two things are worth noticing. First, the figures are computed from filed financial statements, so they update when a 10-K or 10-Q is filed and they don't drift with the share price the way market-derived multiples do. Second, the endpoint is ticker-addressed and tickers are case-insensitive, so aapl and AAPL resolve to the same company.
For a dashboard or a watchlist, this endpoint is usually the whole integration. One request per symbol, a JSON body of named ratios, no XBRL anywhere in your code.
Dropping down to raw XBRL concepts#
Precomputed ratios cover the common cases, and research questions rarely stay common. Maybe you want equity averaged across the year instead of taken at year end, or a margin built on a revenue line that excludes one segment. For that, pull the underlying concepts directly.
GET /v1/fnd/stocks/{ticker}/financials/{concept} returns one XBRL concept across every reported period. Concept names come from the standard us-gaap taxonomy, so Revenues, NetIncomeLoss, StockholdersEquity, and Assets work as written.
import os
import requests
BASE = "https://api.sifting.io/v1"
HEADERS = {"X-API-Key": os.environ["SIFTING_KEY"]}
def concept(ticker, name):
r = requests.get(f"{BASE}/fnd/stocks/{ticker}/financials/{name}",
headers=HEADERS)
r.raise_for_status()
return r.json()
income = concept("MSFT", "NetIncomeLoss")
equity = concept("MSFT", "StockholdersEquity")
Python's requests library sends Accept-Encoding: gzip on every call and decompresses transparently, which matters here for reasons covered in the pitfalls section.
Every observation is tagged with a period code and returned as a value with an explicit unit. Duration codes look like CY2024 (a calendar-aligned year) or CY2024Q1 (one quarter). Point-in-time codes carry an I suffix, like CY2024Q1I. The distinction is structural: income statement concepts exist only over durations, and balance sheet concepts exist only at instants. A hand-rolled return on equity therefore divides a CY2024 net income observation by an equity observation at an I-suffixed instant; the exact response shape is documented in /docs.
Check the unit before doing arithmetic. Monetary observations are tagged USD, share counts shares, per-share figures USD/shares, and dimensionless values pure. A division that ignores units can be off by a factor of millions and still look plausible on a chart.
Screening every filer at once#
The third level is cross-sectional. GET /v1/fnd/stocks/screener/{concept}/{period} returns one concept for all filers in a single snapshot: every reported NetIncomeLoss for CY2024, for example. That one request replaces the loop-over-thousands-of-tickers ETL job that fundamental screeners usually start life as. Rank the output, join it against a positions table, or feed it into a notebook; the shape is the same value-and-unit observations as the per-ticker endpoint, keyed by filer.
Responses on this endpoint are large, which is why compression stops being optional here.
Common pitfalls#
The 406 that looks random. The heavy fundamentals endpoints, /financials, /financials/{concept}, and the screener, refuse to send uncompressed responses. Without Accept-Encoding: gzip they return HTTP 406 with the body {"error":"gzip_required"}. Python requests and browser fetch advertise gzip by default; plain curl does not, which is why the first example uses --compressed. The trap is that a headerless curl works fine against lighter endpoints like /profile, so the failure looks endpoint-specific and flaky until you read the error code in the body.
Duration codes where instant codes belong. Requesting StockholdersEquity for CY2024 finds no duration observation, because equity is never reported over a span of time. The value you want lives at an instant, under an I-suffixed code at the period boundary. When a concept query comes back thinner than expected, check the suffix before assuming the data is missing.
Fiscal years that don't line up. AAPL closes its fiscal year in late September; MSFT closes in June. Compare the two companies' self-labeled "fiscal 2024" annual figures and you're comparing periods that end nine months apart, which skews every growth rate computed downstream. The CY-prefixed period codes are calendar-anchored for exactly this reason: CY2024 covers the same twelve months for every filer, so cross-company comparisons should anchor there.
Start with the ratios endpoint and see whether it already answers your question; most dashboards never need more. Drop to individual concepts when the definition of a ratio needs adjusting, and reach for the screener when the question is about the whole market rather than one company. The free tier includes 10,000 REST calls a month with no credit card, which is enough to prototype all three levels end to end. Start building free



