Finance & Market Data
FMP Financial Statements API: Examples & Validation
Use the FMP financial statements API for income, balance sheet and cash flow data with code examples, period alignment, validation and error controls.
Published: April 13, 2026 · By the QVeris Team

On this page
The shortest correct three-statement workflow
Keep the FMP key on your server. Normalize symbol + fiscalYear + period, retain statement and filing dates, preserve nulls, verify currency and units, and run reconciliation checks before calculating ratios or giving the data to an agent.
Developers building fundamental research, screeners, valuation models, portfolio analytics or financial-research agents.
An FMP API key, a trusted server runtime, a defined annual/quarter/TTM policy, and explicit null, currency and revision handling.
Choose standardized, TTM or as-reported data
FMP exposes several statement families because comparison, latest-performance analysis and filing-level audit are different jobs. Start with the question your application must answer.
| Need | Stable endpoint | Use it when | Main caution |
|---|---|---|---|
| Profitability | /income-statement | Revenue, margins, operating income, net income and EPS. | Fiscal period and diluted/basic share basis. |
| Financial position | /balance-sheet-statement | Cash, working capital, debt, assets, liabilities and equity. | It is a point-in-time statement. |
| Cash generation | /cash-flow-statement | Operating, investing and financing cash flow; capex and free cash flow. | Capex sign convention and FX effects. |
| Rolling latest year | /income-statement-ttm/balance-sheet-statement-ttm/cash-flow-statement-ttm | Latest-year trend or valuation inputs without waiting for fiscal year-end. | Do not add TTM to annual or quarterly rows. |
| Comparable metrics | /key-metrics · /ratios/key-metrics-ttm · /ratios-ttm | Margins, returns, liquidity, leverage and valuation screens. | Know the formula and denominator period. |
| Filing-level audit | /financial-statement-full-as-reported | You need original filing taxonomy or your own mapping layer. | Fields vary by filer and taxonomy. |
Do not describe one as universally “more accurate.” They answer different questions. Keep the selected representation in dataset metadata.
Annual, quarterly and TTM are different contracts
Best for audited multi-year trends and full fiscal-year models. Companies can have non-calendar fiscal years.
Best for recent operating changes. A fiscal Q1 can fall in a different calendar year than expected.
A rolling latest-twelve-month view. Useful for current ratios, but not an additional period to sum into a history.
Preserves issuer taxonomy and filing detail. Mapping consistency becomes your responsibility.
Start with symbol + fiscalYear + period. Confirm date, calendarYear, filingDate and acceptedDate when present.
One endpoint can contain a missing, duplicate or revised period. Build maps by key and report unmatched rows.
Revenue and cash flow cover a period; cash, debt and equity are measured at a date. Use the balance-sheet date that closes the same fiscal period.
Fetch the three FMP statements safely
FMP supports API-key authorization in a header or query parameter. A server-side header keeps the key out of URLs, browser history and common access logs.
curl --fail --silent --show-error \
--get "https://financialmodelingprep.com/stable/income-statement" \
--data-urlencode "symbol=AAPL" \
--data-urlencode "period=annual" \
--data-urlencode "limit=5" \
--header "apikey: $FMP_API_KEY"import os
from concurrent.futures import ThreadPoolExecutor
import requests
BASE = "https://financialmodelingprep.com/stable"
ENDPOINTS = {
"income": "income-statement",
"balance": "balance-sheet-statement",
"cashflow": "cash-flow-statement",
}
def fetch(name: str, symbol: str, period="annual", limit=5):
response = requests.get(
f"{BASE}/{ENDPOINTS[name]}",
params={"symbol": symbol.upper(), "period": period, "limit": limit},
headers={"apikey": os.environ["FMP_API_KEY"]},
timeout=10,
)
response.raise_for_status()
rows = response.json()
if not isinstance(rows, list) or not rows:
raise ValueError(f"No {name} rows for {symbol}")
return rows
def fiscal_key(row: dict):
year = row.get("fiscalYear") or row.get("calendarYear")
return row.get("symbol"), str(year), row.get("period")
def get_three_statements(symbol: str):
with ThreadPoolExecutor(max_workers=3) as pool:
futures = {name: pool.submit(fetch, name, symbol) for name in ENDPOINTS}
data = {name: future.result() for name, future in futures.items()}
indexed = {
name: {fiscal_key(row): row for row in rows}
for name, rows in data.items()
}
common = set.intersection(*(set(rows) for rows in indexed.values()))
if not common:
raise ValueError("The three statements share no fiscal periods")
return [
{"key": key, **{name: indexed[name][key] for name in ENDPOINTS}}
for key in sorted(common, reverse=True)
]const paths = {
income: "income-statement",
balance: "balance-sheet-statement",
cashflow: "cash-flow-statement",
} as const;
async function getStatements(symbol: string, period = "annual") {
const key = process.env.FMP_API_KEY;
if (!key) throw new Error("FMP_API_KEY is missing");
const entries = await Promise.all(
Object.entries(paths).map(async ([name, path]) => {
const url = new URL(\`https://financialmodelingprep.com/stable/\${path}\`);
url.searchParams.set("symbol", symbol.toUpperCase());
url.searchParams.set("period", period);
url.searchParams.set("limit", "5");
const response = await fetch(url, {
headers: { apikey: key },
signal: AbortSignal.timeout(10_000),
});
if (!response.ok) throw new Error(\`\${name}: HTTP \${response.status}\`);
const rows: unknown = await response.json();
if (!Array.isArray(rows) || rows.length === 0) {
throw new Error(\`\${name}: empty statement response\`);
}
return [name, rows] as const;
}),
);
return Object.fromEntries(entries);
}Normalize identity before financial values
Provider payloads can evolve. Keep a stable internal envelope around the fields your product actually uses, and retain the raw payload or a content hash where your policy allows.
| Field group | Recommended fields | Rule |
|---|---|---|
| Instrument | symbol, cik | Resolve symbol changes and exchange variants outside the statement row. |
| Fiscal identity | fiscalYear, period, date | Use as the join identity; never rely on array order. |
| Filing provenance | filingDate, acceptedDate, finalLink | Retain to explain when and where the value entered the dataset. |
| Measurement | reportedCurrency, unit, scale | Normalize currency and magnitude before comparison. |
| Representation | standardized or as_reported | Do not blend the two without a documented mapping. |
| Revision | retrievedAt, payload hash, version | Support restatement detection and reproducible research. |
A missing field can mean not applicable, not disclosed, not mapped or temporarily unavailable. Preserve null and attach a quality reason before ratios or rankings.
Turn accounting relationships into tests
Validation should produce flags and tolerances, not silently rewrite source data. Some differences are legitimate because of rounding, foreign-exchange effects, taxonomy mapping or issuer presentation.
| Check | Test | Failure meaning |
|---|---|---|
| Balance sheet equation | assets ≈ liabilities + equity | Mapping, units, rounding or incomplete statement. |
| Period consistency | Same symbol, fiscal year, period and compatible dates. | Mixed quarter/year, missing statement or restatement. |
| Currency and scale | Same reported currency and documented unit. | Cross-listing, conversion or million/unit mismatch. |
| Cash movement | Opening to closing cash agrees with net change, allowing explicit FX effects. | Sign convention, FX, acquisition classification or missing row. |
| Net income bridge | Cash-flow starting income is compatible with the income statement. | Continuing-operations, minority interest or mapping difference. |
| Duplicate identity | At most one active record per representation and fiscal key. | Restatement or ingestion duplication; select by documented rule. |
def close_enough(left, right, rel_tol=0.001, abs_tol=1.0):
if left is None or right is None:
return None # unknown, not pass
return abs(left - right) <= max(abs_tol, rel_tol * max(abs(left), abs(right)))
def validate_balance_sheet(row):
assets = row.get("totalAssets")
liabilities = row.get("totalLiabilities")
equity = row.get("totalStockholdersEquity")
return {
"balanceEquation": close_enough(assets, liabilities + equity)
if None not in (assets, liabilities, equity) else None,
"currencyPresent": bool(row.get("reportedCurrency")),
"periodPresent": bool(row.get("period")),
}- Test one known annual period and one known quarter before loading full history.
- Confirm the provider's capex sign before deriving free cash flow.
- Compare a material value with the linked filing during acceptance testing.
- Keep null, zero and not-applicable as distinct states.
- Store retrieval time and a payload fingerprint for revision detection.
- Block downstream ratios when identity, currency or period checks fail.
Calculate only after the source periods pass
Use income-statement numerator and denominator from the same period and representation.
Document whether debt is total debt, net debt or interest-bearing debt, and whether EBITDA is annual or TTM.
Prefer a documented provider field or normalize the capex sign before calculating operating cash flow less capex.
A flow numerator usually needs an average opening/closing balance denominator, not only the closing balance.
Production controls for statement pipelines
Statements change far less often than prices. Cache aggressively, then refresh around expected filings and provider updates.
Retry 429 and 5xx with exponential backoff and jitter. Do not retry invalid symbols, bad keys or unsupported plan access.
Keep provider, endpoint, parameters, retrieval time and payload fingerprint so a research result can be reproduced.
Track empty results, missing-period rate, reconciliation failures, revision rate and time from filing to usable data.
Common financial statement failure modes
| Symptom | Likely cause | What to do |
|---|---|---|
| HTTP 200, empty array | Unsupported symbol, period or coverage. | Treat as not found; verify symbol and statement coverage. |
| 403 | Missing/invalid key or plan access. | Stop retrying; inspect secret injection and entitlement. |
| 429 | Rate limit exceeded. | Back off, reduce concurrency and cache repeated history. |
| Three arrays have different lengths | Missing, duplicate or revised fiscal period. | Join by fiscal key and surface unmatched records. |
| Ratio jumps by 1,000× | Unit, currency or magnitude mismatch. | Inspect reported currency and scaling before recalculation. |
| Quarterly totals do not match annual | Restatement, fiscal calendar, rounding or discrete-quarter/YTD semantics. | Inspect period semantics and filing versions; never force equality. |
| Previously stored value changed | Restatement or provider normalization update. | Create a new version, diff it and retain prior provenance. |
Use a capability layer when the workflow must choose
FMP remains the data provider in this pattern. QVeris is useful when an agent needs to discover a statement capability, inspect the current schema, invoke it consistently, preserve evidence or route to another provider. A fixed FMP integration can remain direct.
Search by statement type, market coverage, period and required provenance.
Read the live input/output contract before calling; confirm supported identifiers and periods.
Invoke with validated arguments, then apply the same fiscal alignment and reconciliation gates.
FMP financial statements API questions
Which endpoints return the three financial statements?
Use income-statement, balance-sheet-statement and cash-flow-statement under the stable base URL. Use the same symbol, period and limit across the three requests.
Should I use annual, quarterly or TTM data?
Use annual for audited multi-year comparisons, quarterly for recent change, and TTM for a rolling latest-year view. Do not mix period types without explicit conversion.
What is standardized versus as reported?
Standardized statements improve common-field comparability. As-reported endpoints preserve original filing taxonomy for audit and custom mapping.
How should I join the three statements?
Join on symbol, fiscal year and fiscal period, then confirm statement date and filing metadata. Filing date alone and array position are unsafe keys.
How do I validate statement data?
Check period and currency alignment, the balance-sheet equation, cash movement, duplicated periods, missing values, filing timestamps and revision behavior.
Can missing values be replaced with zero?
Not automatically. Null can mean not reported, not applicable, unavailable or not mapped. Preserve it and attach a quality reason before deriving ratios.
Can I expose the FMP key in frontend code?
No. Store it in a server-side environment variable or secret manager and request FMP from a trusted backend.
When should I use QVeris with FMP?
Use QVeris for capability discovery, live schema inspection, consistent invocation or provider fallback. A fixed single-provider integration can call FMP directly.
Verify endpoints and fields against live docs
Make every financial conclusion traceable
Start with correctly aligned FMP statements. Add QVeris when the workflow needs capability discovery, schema inspection or provider-aware routing.
A verified profile link does not guarantee entitlement, field availability, price or current execution success; inspect the current contract before calling.
