Most “get me a company’s fundamentals” projects don’t end up on one API, they end up on three. One product covers the raw financial reports. A second, often from a different vendor entirely, covers valuation ratios. A third handles the earnings calendar and whether the last print beat or missed. Same underlying data, three separate products, and three docs pages, pricing tiers, and response schemas to reconcile before you can build anything.
That fragmentation isn’t an accident, it reflects three genuinely different questions:
- Financial statement API: what did the company actually report? (income statement, balance sheet, cash flow)
- Fundamental data API: how is the market pricing that? (P/E, P/B, ROE, market cap)
- Earnings date API: when does the next number come out, and did the last one beat or miss?
Most teams end up wiring together a financial-statements provider for the reports, a separate fundamentals/screener source for ratios, and a third call (or a scraped earnings calendar) just to know when the next print lands.
This guide covers all three with one API — Infoway — with working Python for each, plus the specific gotchas (string-typed TTM fields, inconsistent symbol formats, mixed timestamp precision) that aren’t obvious from the docs alone.
Setup
Register at infoway.io for an API key, the free trial tier doesn’t require a credit card. Every REST call uses the same single header:
apiKey: YOUR_API_KEYBase URL: https://data.infoway.io. All examples below use plain requests; a Python SDK (pip install infoway-sdk) is also available if you’d rather not hand-roll the HTTP layer, though it doesn’t wrap every financial endpoint shown here.
Symbols carry a market suffix — AAPL.US, 00700.HK, 000001.SZ, 7203.JP, RELIANCE.IN — and a type parameter (STOCK_US, STOCK_HK, STOCK_CN, STOCK_JP, STOCK_IN) is required alongside symbol on every financial-data call.
Financial Statement API: Income Statement, Balance Sheet & Cash Flow
This is the “financial statement api” bucket — raw reported numbers, not ratios. Three statements share one response schema, which is the main thing that makes this pleasant to work with instead of painful: you write one parser, not three.
| Field | Meaning |
|---|---|
periodType | fq (quarter), fy (annual), fh (half-year) |
periodDate | Report date, e.g. 2025-12-31 |
itemId | Line item ID, e.g. net_income, total_assets |
itemName | Human-readable label |
itemGroup | Statement section, e.g. Income Statement |
itemValue | Value for that period |
ttm | Trailing-twelve-month rollup (null for point-in-time items like balance sheet totals) |
parentItemId | Set only on sub-line-items |
import requests
API_KEY = "YOUR_API_KEY"
HEADERS = {"apiKey": API_KEY}
BASE = "https://data.infoway.io/common/basic/financial"
def get_items(symbol, type_, endpoint, period_type="fq"):
r = requests.get(f"{BASE}/{endpoint}", headers=HEADERS,
params={"symbol": symbol, "type": type_, "period_type": period_type})
return r.json()["data"]
# Income statement — /financial/income_statement
income = get_items("AAPL.US", "STOCK_US", "income_statement")
# Balance sheet — /financial/balance_sheet
balance = get_items("AAPL.US", "STOCK_US", "balance_sheet", period_type="fy")
# Cash flow — /financial/cash_flow
cash_flow = get_items("AAPL.US", "STOCK_US", "cash_flow")A quick, genuinely useful cross-check: compare TTM free cash flow to TTM net income. Reported earnings are the easiest line to manage; cash flow is harder to fake.
def fcf_to_earnings_ratio(symbol, type_):
cash_flow = get_items(symbol, type_, "cash_flow")
income = get_items(symbol, type_, "income_statement")
fcf = next(i for i in cash_flow if i["itemId"] == "cf_free_cash_flow" and i["ttm"])
ni = next(i for i in income if i["itemId"] == "net_income" and i["ttm"])
ratio = fcf["ttm"] / ni["ttm"]
print(f"{symbol}: FCF/Net Income (TTM) = {ratio:.2f}")
if ratio < 0.8:
print(" → earnings aren't fully converting to cash — worth digging into receivables/inventory")
fcf_to_earnings_ratio("AAPL.US", "STOCK_US")There’s also a /financial/revenue endpoint for revenue broken out by business segment or geography (itemGroup will read Revenue by business or Revenue by region) — the same schema, mostly populated at the annual (fy) level. Useful if you’re building something like a segment-mix chart without a second API to learn.
Fundamental Data API: P/E, P/B, ROE, and Market Cap
This is the “fundamental data api” / “stock fundamental data api” bucket, derived ratios (not raw statements). Two endpoints cover it, and they’re easy to mix up:
| Endpoint | Returns | Use it for |
|---|---|---|
/financial/statistics | A snapshot per period across five groups: Key stats (market cap, shares outstanding), Valuation ratios (P/E, P/B, P/S), Profitability ratios (margins, ROE), Liquidity ratios, Solvency ratios | Batch screening, single-symbol lookups |
/v2/basic/stock/valuation/{symbol} | Daily P/E and P/B history as a time series | Charting valuation over time, percentile ranking |
statistics responses add two fields beyond itemValue: currentValue (live) and ttmStr — a string, not a number.
When there’s no TTM figure it comes back as the literal string "-", and float("-") will throw. Always guard it:
def safe_float(v):
try:
return float(v)
except (TypeError, ValueError):
return NoneUnlike the financial/* (v1) endpoints, the valuation endpoint lives under /v2/basic/stock/, Hong Kong symbols there drop the leading zero: 700.HK, not 00700.HK. Get this wrong and you won’t get an error, you’ll get an empty data array, which is a much more annoying thing to debug at 2am.
Building a Fundamentals-Based Stock Screener
The most common thing people build with a financial data api for stock screening is a batch filter, P/E under some threshold, decent margins, low leverage. Here’s a minimal one:
def get_statistics(symbol, type_):
r = requests.get("https://data.infoway.io/common/basic/financial/statistics",
headers=HEADERS, params={"symbol": symbol, "type": type_})
return r.json()["data"]
def screen(symbol, type_, pe_max=15, net_margin_min=10):
items = get_statistics(symbol, type_)
latest = {}
for i in items:
if i["itemId"] not in latest or i["periodDate"] > latest[i["itemId"]]["periodDate"]:
latest[i["itemId"]] = i
pe = safe_float(latest.get("pe_ratio", {}).get("currentValue"))
net_margin = safe_float(latest.get("net_margin", {}).get("currentValue"))
if pe is None or pe > pe_max:
return None
if net_margin is not None and net_margin < net_margin_min:
return None
return {"symbol": symbol, "pe": pe, "net_margin": net_margin}
watchlist = [("AAPL.US", "STOCK_US"), ("00700.HK", "STOCK_HK"), ("000001.SZ", "STOCK_CN")]
results = [r for r in (screen(s, t) for s, t in watchlist) if r]
print(results)itemId naming can vary slightly by market and instrument type, so before hardcoding field names in production, pull one full response and check itemName rather than guessing.
Earnings Date API: Next Earnings Date, History, and Beat/Miss
This is the bucket most vendors handle worst: a “stock earnings date api” that only tells you when, with no way to find out how it went, or an earnings-history endpoint that tells you the past but nothing about what’s coming. Infoway splits this cleanly into two endpoints that are meant to be used together.
Next / historical earnings dates — /v2/basic/stock/events/{symbol}:
import time
def get_earnings_events(symbol, limit=50):
r = requests.get(f"https://data.infoway.io/common/v2/basic/stock/events/{symbol}",
headers=HEADERS, params={"limit": limit})
items = r.json()["data"]["items"]
return [i for i in items if i["event_type"] == "earnings"]
def next_earnings_date(symbol):
events = get_earnings_events(symbol)
now = time.time()
upcoming = sorted([e for e in events if int(e["event_date"]) > now],
key=lambda e: int(e["event_date"]))
return upcoming[0] if upcoming else None
event = next_earnings_date("AAPL.US")
if event:
days_out = (int(event["event_date"]) - time.time()) / 86400
print(f"Next earnings in {days_out:.0f} days: {event['description']}")Same endpoint answers “historical earnings dates api” queries too, just filter for event_date in the past instead of the future. Note event_date is a Unix seconds timestamp, returned as a string.
Actual vs. estimate (Beat/Miss) — /financial/earnings:
def latest_earnings_surprise(symbol, type_):
r = requests.get("https://data.infoway.io/common/basic/financial/earnings",
headers=HEADERS, params={"symbol": symbol, "type": type_, "period_type": "fq"})
data = r.json()["data"]
latest = sorted(data, key=lambda x: x["periodKey"])[-1]
print(f"{symbol} {latest['periodKey']}: EPS {latest['epsPercentage']:+.1f}% vs. estimate, "
f"Revenue {latest['revenuePercentage']:+.1f}% vs. estimate")
return latest
latest_earnings_surprise("AAPL.US", "STOCK_US")epsPercentage and revenuePercentage are pre-computed surprise percentages — no need to derive (actual - estimate) / estimate yourself. Leave period_type off entirely and you get the full historical earnings series, which covers “historical earnings data api” use cases without a second call.
Combine both and you have a complete earnings-day pipeline: poll events daily for the countdown, poll earnings right after the print for the surprise, and fire an alert when abs(epsPercentage) clears whatever threshold matters to your strategy.
Dividend Data API: Yield, Payout Ratio, and Ex-Dividend Dates
Two endpoints, and they answer different questions:
/financial/dividend— dividend yield and payout ratio as a period snapshot, samecurrentValue/ttmStrshape asstatistics./financial/dividend_payout— every individual payout event: ex-date, record date, payment date, amount. This is the one that answers “ex dividend date api” and “next dividend date api” queries — it’s also the only financial endpoint with noperiod_typeparam; it just returns the full history.
def dividend_history(symbol, type_):
r = requests.get("https://data.infoway.io/common/basic/financial/dividend_payout",
headers=HEADERS, params={"symbol": symbol, "type": type_})
for p in r.json()["data"]:
print(p["exDate"], p["amount"], p["type"]) # type: quarterly / interim / special
dividend_history("AAPL.US", "STOCK_US")One timestamp trap worth flagging explicitly: dividend_payout dates (exDate, recordDate, paymentDate) are Unix milliseconds (13 digits), while the events endpoint’s event_date is Unix seconds (10 digits). Both are “a timestamp” — parse them with the wrong unit and your dates land a few decades off.
Gotchas Summary
| Issue | Detail |
|---|---|
ttmStr is a string | Returns "-" when there’s no TTM value — guard before casting to float |
| v1 vs v2 symbol format | financial/* wants 00700.HK; v2/basic/stock/* wants 700.HK. Wrong format returns empty data, not an error |
| Mixed timestamp precision | events.event_date = seconds; dividend_payout date fields = milliseconds |
Omitting period_type | Returns the entire history for that symbol — fine for backfills, wasteful for a single lookup |
revenue endpoint | Populated mostly at fy (annual); quarterly segment data isn’t guaranteed for every symbol |
itemId naming | Not perfectly uniform across markets — pull a full response and check itemName before hardcoding |
FAQ
Is there a free financial statement API?
Infoway’s trial tier includes financial statement, fundamental, and earnings-date endpoints — no credit card required to start. Rate limits scale with plan tier.
What’s the difference between a financial statement API and a fundamental data API?
A financial statement API returns what a company reported (income statement, balance sheet, cash flow — raw itemValues). A fundamental data API returns ratios computed from those statements plus market price (P/E, P/B, ROE, market cap). Infoway splits them the same way: financial/income_statement et al. for statements, financial/statistics for ratios.
How do I get a stock’s next earnings date via API?
Call /v2/basic/stock/events/{symbol}, filter for event_type == "earnings", and take the earliest event_date that’s still in the future. The financial/earnings endpoint only covers already-reported quarters — it has no concept of an upcoming date.
Does the earnings endpoint give EPS estimates before the report, or only after?
Only after. epsEstimate and epsActual are both populated together once results are in — this is a Beat/Miss endpoint, not a pre-earnings consensus feed.
How far back does historical fundamental data go?
Coverage varies by symbol and market; leaving period_type unset on any financial/* endpoint returns everything available for that symbol rather than defaulting to a fixed lookback window.
Can I get market cap from this API?
Yes, it’s under the Key stats group in /financial/statistics, alongside shares outstanding, rather than a separate market-cap-specific endpoint.