The tutorial explains how to fetch income statements, balance sheets, and cash flow statements for public companies in Python, load them into pandas, calculate key financial metrics, including profit margins, and liquidity ratios. All examples use the FinancialData.Net API, either through the official fdnpy SDK or plain requests.
Prerequisites
- Python 3
- An API key from FinancialData.Net
- A subscription that includes financial statements: Standard for US companies and Premium for international companies, as outlined in the API documentation.
pip install fdnpy pandas requests
export FDN_API_KEY="your_api_key"
The three statement endpoints
| Statement | Endpoint | SDK method |
|---|---|---|
| Income statement | /api/v1/income-statements | get_income_statements() |
| Balance sheet | /api/v1/balance-sheet-statements | get_balance_sheet_statements() |
| Cash flow statement | /api/v1/cash-flow-statements | get_cash_flow_statements() |
All three share the same parameters:
| Parameter | Type | Description |
|---|---|---|
identifier | string | Ticker (MSFT) or SEC Central Index Key (0000789019) |
period | string, optional | year or quarter. Omit to get all periods |
offset | integer, optional | Records to skip. Max 50 records per call |
format | string, optional | json (default) or csv |
Authentication: Include your API key in the key query parameter (?key=API_KEY).
Quickstart with fdnpy
import os
from fdnpy import FinancialDataClient
client = FinancialDataClient(api_key=os.environ["FDN_API_KEY"])
income_statements = client.get_income_statements(identifier="MSFT", period="year")
print(income_statements[0])
Every SDK method returns a list[dict] parsed straight from the API JSON. The client uses requests under the hood and paginates automatically.
The latest annual record for MSFT:
{
"trading_symbol": "MSFT",
"central_index_key": "0000789019",
"registrant_name": "MICROSOFT CORP",
"fiscal_year": "2026",
"fiscal_period": "FY",
"period_end_date": "2026-06-30",
"revenue": 331839000000.0,
"cost_of_revenue": 106374000000.0,
"gross_profit": 225465000000.0,
"research_and_development_expenses": 35562000000.0,
"general_and_administrative_expenses": 7956000000.0,
"operating_expenses": 70228000000.0,
"operating_income": 155237000000.0,
"interest_expense": 3051000000.0,
"interest_income": 3301000000.0,
"net_income": 133749000000.0,
"earnings_per_share_basic": 18.0,
"earnings_per_share_diluted": 17.95,
"weighted_average_shares_outstanding_basic": 7429000000,
"weighted_average_shares_outstanding_diluted": 7453000000
}
Full field lists and sample responses: income statements, balance sheets, cash flow statements.
Load all three statements into pandas
The three endpoints return one record per reporting period. Join them on (trading_symbol, fiscal_year, fiscal_period, period_end_date) to get a single row per period.
import time
import pandas as pd
KEYS = ["trading_symbol", "fiscal_year", "fiscal_period", "period_end_date"]
def load_statements(client, ticker, period="year"):
"""Fetch income, balance sheet and cash flow statements as sorted DataFrames."""
fetchers = {
"income": client.get_income_statements,
"balance": client.get_balance_sheet_statements,
"cashflow": client.get_cash_flow_statements,
}
out = {}
for name, fetch in fetchers.items():
df = pd.DataFrame(fetch(identifier=ticker, period=period))
df["period_end_date"] = pd.to_datetime(df["period_end_date"])
out[name] = df.sort_values("period_end_date").reset_index(drop=True)
time.sleep(0.1)
return out
def build_metrics(s):
"""Join the three statements on period and derive margins, growth, liquidity."""
inc = s["income"][KEYS + ["revenue", "gross_profit", "operating_income",
"net_income", "earnings_per_share_diluted"]]
bs = s["balance"][KEYS + ["total_assets_current", "total_liabilities_current",
"total_liabilities", "total_shareholders_equity"]]
cf = s["cashflow"][KEYS + ["cash_from_operating_activities",
"acquisition_of_property_plant_and_equipment"]]
df = inc.merge(bs, on=KEYS).merge(cf, on=KEYS)
df["gross_margin"] = df["gross_profit"] / df["revenue"]
df["net_margin"] = df["net_income"] / df["revenue"]
df["revenue_yoy"] = df["revenue"].pct_change()
df["current_ratio"] = df["total_assets_current"] / df["total_liabilities_current"]
df["liabilities_to_equity"] = df["total_liabilities"] / df["total_shareholders_equity"]
return df
Run it:
df = build_metrics(load_statements(client, "MSFT", period="year")).set_index("fiscal_year")
print((df[["revenue", "gross_profit", "net_income"]] / 1e9).round(1))
print(df[["gross_margin", "net_margin", "revenue_yoy"]].mul(100).round(1))
print(df[["current_ratio", "liabilities_to_equity"]].round(2))
Output for MSFT, last three fiscal years (USD billions, margins in %):
| FY | Revenue | Gross profit | Net income |
|---|---|---|---|
| 2024 | 245.1 | 171.0 | 88.1 |
| 2025 | 281.7 | 193.9 | 101.8 |
| 2026 | 331.8 | 225.5 | 133.7 |
| FY | Gross margin | Net margin | Revenue YoY |
|---|---|---|---|
| 2024 | 69.8 | 36.0 | 15.7 |
| 2025 | 68.8 | 36.1 | 14.9 |
| 2026 | 67.9 | 40.3 | 17.8 |
| FY | Current ratio | Liabilities / equity |
|---|---|---|
| 2024 | 1.27 | 0.91 |
| 2025 | 1.35 | 0.80 |
| 2026 | 1.23 | 0.71 |
Quarterly statements
Pass period="quarter":
q = client.get_income_statements(identifier="AAPL", period="quarter")
for r in q[:3]:
print(r["fiscal_year"], r["fiscal_period"], r["period_end_date"], r["revenue"])
2026 Q3 2026-06-27 109417000000.0
2026 Q2 2026-03-28 111184000000.0
2026 Q1 2025-12-27 143756000000.0
Things to know:
fiscal_periodisQ1,Q2,Q3for quarters andFYfor annual. In the AAPL response, each fiscal year contains Q1–Q3 only, which matches 10-Q filings. Derive Q4 asFY − (Q1 + Q2 + Q3).fiscal_yearandperiod_end_datediffer. Apple's2026 Q1ends2025-12-27. Always sort and join onperiod_end_date, not on calendar year.- If you omit
period, annual and quarterly records come back mixed. Filter onfiscal_period == "FY"if you do that.
Pagination
Statement endpoints return at most 50 records per call. A call to period="quarter" can fill that page, so older history needs offset.
The SDK handles this for you. With plain requests:
import os
import requests
BASE = "https://financialdata.net/api/v1"
API_KEY = os.environ["FDN_API_KEY"]
PAGE = 50
def fetch_all(endpoint, **params):
rows, offset = [], 0
while True:
r = requests.get(
f"{BASE}/{endpoint}",
params={**params, "offset": offset, "key": API_KEY},
timeout=30,
)
r.raise_for_status()
page = r.json()
rows.extend(page)
if len(page) < PAGE:
return rows
offset += PAGE
income_statements = fetch_all("income-statements", identifier="AAPL", period="quarter")
Equivalent cURL:
curl "https://financialdata.net/api/v1/income-statements?identifier=MSFT&period=year&key=$FDN_API_KEY"
# next 50 (older) records
curl "https://financialdata.net/api/v1/income-statements?identifier=AAPL&period=quarter&offset=50&key=$FDN_API_KEY"
Screen multiple companies
tickers = ["MSFT", "AAPL", "AMZN", "NVDA"]
latest = []
for t in tickers:
metrics = build_metrics(load_statements(client, t, period="year"))
latest.append(metrics.iloc[-1]) # rows are sorted ascending, last = most recent FY
screen = pd.DataFrame(latest).set_index("trading_symbol")
print(
screen[["fiscal_year", "net_margin", "revenue_yoy", "current_ratio"]]
.sort_values("net_margin", ascending=False)
)
Fiscal year-end dates differ by company, so compare period_end_date as well as fiscal_year before ranking.
International companies
International statements live on separate endpoints (Premium plan) and cover Toronto, London, Frankfurt, Euronext Paris and Amsterdam, Tokyo, Hong Kong, Singapore, Indonesia, Malaysia, Korea, Brazil, Mexico, India, and Bombay exchanges.
GET /api/v1/international-income-statements?identifier=SHEL.L&period=year
GET /api/v1/international-balance-sheet-statements?identifier=SHEL.L&period=year
GET /api/v1/international-cash-flow-statements?identifier=SHEL.L&period=year
Differences from the US endpoints:
identifieris the exchange-suffixed ticker (SHEL.L). CIK is not accepted.- Records include
currency_code(for exampleUSDfor Shell). Do not sum or compare across companies without checking it.
Export to CSV
Add format=csv:
import io
import pandas as pd
import requests
r = requests.get(
"https://financialdata.net/api/v1/balance-sheet-statements",
params={"identifier": "MSFT", "period": "year", "format": "csv", "key": API_KEY},
timeout=30,
)
r.raise_for_status()
df = pd.read_csv(io.StringIO(r.text))
df.to_csv("msft_balance_sheet.csv", index=False)
Rate limits
From the API documentation:
| Plan | Limit |
|---|---|
| Free | 300 requests per day |
| Standard | 10 requests per second |
| Premium / Professional | 30 requests per second |
| Enterprise | 50 requests per second |
FAQ
How do I get a company's balance sheet in Python?
Call client.get_balance_sheet_statements(identifier="MSFT", period="year") from fdnpy, or GET /api/v1/balance-sheet-statements?identifier=MSFT&period=year&key=API_KEY with requests.
Can I look up companies by CIK instead of ticker?
Yes, for US statements. Pass the 10-digit Central Index Key as identifier, for example 0000789019 for Microsoft.
Is there a free way to get financial statements? The free plan (300 requests/day) covers symbol lists, stock prices, and commodity data. US financial statements require a Standard plan. See pricing.
Does it work with AI agents? Yes. FinancialData.Net publishes an MCP server exposing the same endpoints as tools.
Can I get ratios without computing them myself? Yes. There are dedicated endpoints for liquidity, solvency, efficiency, profitability, and valuation ratios computed from the statements, plus a Key Metrics endpoint with P/E, P/B, EBITDA, and free cash flow.