Build a DCF from Live API Data with Claude Code (Financial Modeling Prep)
Pull financial statements from the Financial Modeling Prep API, let Claude Code build a DCF you can audit line by line, and cross-check it against FMP's own valuation. Includes the working DCF code and the free-tier call budget.
This post contains affiliate links. If you sign up through one, we may earn a commission at no extra cost to you. How we handle this.
Most DCF models start with someone retyping numbers from a 10-K into a spreadsheet. Those numbers go stale the day the next filing lands, and a transposed digit in free cash flow flows straight into the valuation. An API fixes the data problem: every figure comes from a source you can name, and re-running the model next quarter takes a command instead of an afternoon.
This walkthrough connects Claude Code to the Financial Modeling Prep (FMP) API, pulls three financial statements and a company profile, and builds a five-year DCF in Python. You keep the assumptions. Claude does the fetching, the cleaning, and the arithmetic, and it hands back a workbook your team can open. We'll also cross-check the result against FMP's own DCF endpoint, because the gap between the two numbers is the most useful output of the exercise.
One scope note: this is a tutorial on the workflow, not a recommendation to buy or sell any security. The numbers are only as good as the assumptions you feed in.
Why Build It Instead of Calling the DCF Endpoint
FMP has a discounted-cash-flow endpoint. You pass a ticker and get back a DCF value next to the stock price. It's a fast sanity check, but you can't see or change what's inside it. FMP also documents an advanced DCF endpoint that takes your own assumptions, but a model you compute locally has an advantage the endpoints can't match: every growth rate, every discount rate and every cell is yours to audit, and an MD can ask "why 9%?" and get an answer.
So the plan is to use the API for data and the cheap DCF endpoint as a second opinion, and to keep the valuation logic in code you control.
New Claude workflows for finance professionals, every week
One email a week with the newest playbooks. Free, like everything here.
No spam. Unsubscribe anytime.
What a DCF Needs and Where It Comes From
FMP serves its current data under a /stable/ base URL, with separate endpoints for each statement. Map every input your model needs to one source before you write a prompt:
| DCF input | Where it comes from |
|---|---|
| Revenue, operating income, interest expense, tax | income-statement |
| Operating cash flow, capex, free cash flow | cash-flow-statement |
| Debt, cash, net debt | balance-sheet-statement |
| Price, beta, market cap | Company profile endpoint |
| Risk-free rate, equity risk premium | Your assumption. Write it down as an explicit input |
| Cross-check value | discounted-cash-flow |
The statement endpoints take a symbol, a period (annual or quarterly) and a limit for how many periods to return. The cash flow response includes freeCashFlow, capitalExpenditure and operating cash flow as separate fields.
Step 1: Get a Free Key and Keep It Out of the Repo
FMP's free Basic plan gives you 250 API calls a day, and signing up doesn't require a credit card. That's enough to build and test this entire workflow. Create an account at financialmodelingprep.com, copy your key, and put it in an environment variable rather than a file:
# macOS / Linux export FMP_API_KEY="your-key-here" # Windows PowerShell $env:FMP_API_KEY = "your-key-here"
There's a wrinkle worth knowing before you hand this to an agent: FMP authenticates with an apikey query parameter, so the key sits inside the URL of every request. Any tool that logs full URLs will log your key too. Tell Claude never to print request URLs or write the key into a file, and add .env and the response cache to .gitignore.
Step 2: Set Up the Project and a Caching Fetch Helper
dcf-run/ ├── CLAUDE.md ← DCF Model Builder template + your assumptions ├── fmp.py ← one fetch helper, everything goes through it ├── dcf.py ← the valuation math ├── cache/ ← raw API responses (gitignored) └── output/ ← workbook and valuation summary
Download the DCF Model Builder playbook as the base of your CLAUDE.md. It covers the WACC build-up, terminal value methods and sensitivity tables, so you don't have to re-explain valuation conventions on every run. Then ask Claude to write fmp.py, or start from this version:
import json, os, pathlib, requests
BASE = "https://financialmodelingprep.com/stable"
CACHE = pathlib.Path("cache")
def fmp(endpoint, **params):
"""GET one FMP endpoint. Responses are cached so reruns cost zero API calls."""
tag = "_".join(f"{k}-{v}" for k, v in sorted(params.items()))
path = CACHE / f"{endpoint}_{tag}.json"
if path.exists():
return json.loads(path.read_text())
r = requests.get(
f"{BASE}/{endpoint}",
params={**params, "apikey": os.environ["FMP_API_KEY"]},
timeout=30,
)
r.raise_for_status()
data = r.json()
if not isinstance(data, list) or not data:
raise RuntimeError(f"No usable data for {endpoint} {params}")
CACHE.mkdir(exist_ok=True)
path.write_text(json.dumps(data))
return dataThe cache matters more than it looks. On the free plan, the call budget is the constraint. A single ticker costs five calls: three statements, the profile, and the cross-check endpoint. At 250 calls a day that's 50 tickers, and every rerun after the first fetch is free because the helper reads from disk. The helper also fails loudly on an empty or malformed response instead of passing it on. Claude shouldn't build a model on a response it can't read.
Cached filenames never contain the key, and the key never gets written anywhere. Keep it that way.
Step 3: The Prompt
Start with a prompt that tells Claude to discover field names instead of assuming them. API schemas drift, and a model that silently reads a missing field as zero produces confident wrong answers.
Build a DCF for MSFT using the FMP API. Use the fmp() helper in fmp.py and the FMP_API_KEY environment variable; never print request URLs or write the key anywhere. Pull 5 years of annual income statement, balance sheet and cash flow statement, plus the company profile. Before writing any model code, print the field names from one real response of each endpoint and map them to the inputs in CLAUDE.md. If a field is missing, mark it missing and stop. Don't substitute a default. Then project 5 years of unlevered free cash flow, propose growth and WACC assumptions with your reasoning from the historical numbers, and wait for my changes before computing the valuation.
Two instructions in that prompt do most of the work. "Print the field names first" makes Claude look at the real response before it writes code against it. "Stop on missing" stops a gap in the data from turning into a quietly wrong number. If you want a reference for the second habit, the Financial Data Collector playbook is built around it: every field carries its source or is marked missing, and nothing gets a fallback value. It pulls from yfinance rather than FMP, which also makes it a useful free fallback for a ticker your FMP plan doesn't cover.
The Adjustment Most API DCF Tutorials Skip
FMP reports freeCashFlow as operating cash flow minus capital expenditure. Operating cash flow is measured after interest has been paid. If you discount that figure at WACC and then subtract net debt, you charge for the company's debt twice: once in the cash flow and once in the bridge to equity value.
The clean fix is to add back after-tax interest before discounting, which turns reported free cash flow into an unlevered figure. Here's the valuation core, with the guard that WACC must exceed terminal growth:
def unlevered_fcf(fcf, interest_expense, tax_rate):
"""Reported FCF is after interest. Add back after-tax interest
so it can be discounted at WACC."""
return fcf + interest_expense * (1 - tax_rate)
def dcf_per_share(base_fcf, growth, wacc, terminal_g, net_debt, shares):
if wacc <= terminal_g:
raise ValueError("WACC must exceed terminal growth")
fcf, pv = base_fcf, 0.0
for year, g in enumerate(growth, start=1):
fcf *= 1 + g
pv += fcf / (1 + wacc) ** year
terminal = fcf * (1 + terminal_g) / (wacc - terminal_g)
pv += terminal / (1 + wacc) ** len(growth)
return (pv - net_debt) / shares
def sensitivity(base_fcf, growth, net_debt, shares, waccs, terminal_gs):
return {
(w, g): dcf_per_share(base_fcf, growth, w, g, net_debt, shares)
for w in waccs
for g in terminal_gs
}The tax rate is the effective rate, income tax expense divided by pre-tax income, taken from the income statement and kept within a sensible range. Have Claude print it so you can see what it used. Note that the cash flow statement adds stock-based compensation back as a non-cash item, which flatters free cash flow at companies that pay heavily in stock. If that applies, tell Claude to deduct it in the base year and say so in the summary.
Step 4: Set the Assumptions and Run the Sensitivity
Claude will propose growth rates, a discount rate and a terminal growth rate. Treat that as a first draft, because the assumptions are the model. Put the ones that matter in CLAUDE.md as plain, numeric rules:
## Valuation assumptions - Projection: 5 years, growth fading toward terminal rate - Risk-free rate: [your number], equity risk premium: [your number] - Beta: from the FMP profile, flag if it looks unusual - Cost of debt: interest expense / total debt, tax-effected - WACC weights: market cap and total debt - Terminal growth: 2-3%, never above the risk-free rate - Always output a WACC x terminal growth sensitivity grid
The sensitivity grid earns its place because it shows how much of the value rests on two numbers. In a typical DCF the terminal value accounts for well over half of the total, so a one-point swing in WACC can move the answer more than any operating assumption. Present the grid, not a single price.
Step 5: Cross-Check Against FMP's Own DCF
Now call the discounted-cash-flow endpoint for the same ticker, which costs one call, and ask Claude to compare it with your result:
Fetch FMP's discounted-cash-flow value for MSFT and compare it with our base case and the sensitivity grid. If the two differ by more than 25%, list the assumptions most likely to explain the gap, such as growth rate, discount rate, how free cash flow was defined, and share count. Don't adjust our model to match FMP.
That last sentence is the important one. The endpoint is a second opinion with inputs you can't see, so your model shouldn't chase it. A large gap is a prompt to review your assumptions, and sometimes the answer is that yours were the aggressive ones.
Step 6: Excel for the Reviewer, and When to Go Further
Python is the right place to fetch, validate and calculate. The version your MD or client opens is a workbook. Once the numbers are settled, have Claude write them out through the DCF Model Builder format, with live formulas for the projections, WACC build and sensitivity tables, so a reviewer can change an assumption and watch the value move. For a written valuation summary to go with it, the DCF Valuation Model Builder playbook produces the report layer.
A cash-flow-driven DCF is the right size for a quick valuation. When you need projected balance sheet and debt schedules behind the cash flows, for a leveraged company or a deal model, step up to the 3-Statement Model Builder. Pull the same FMP statements as the historical base, build the integrated projections, and let the DCF read free cash flow from the model instead of from a growth assumption.
Where This Workflow Breaks
- Negative or erratic free cash flow. Growth rates applied to a negative base give nonsense. Normalize with a multi-year average or switch to a margin-based projection, and say which you chose.
- Banks and insurers. Free cash flow isn't a meaningful measure for financial companies, because debt is part of operations. Use a dividend or excess-return model instead.
- Currency mismatches on ADRs. Statements can be reported in one currency while the stock trades in another. Have Claude check both before it divides by the price.
- Share count. Use diluted shares and note which period they come from. Basic shares overstate value per share.
- Plan coverage. What your plan returns, such as how many years, which exchanges and annual versus quarterly data, depends on the tier. On the first run, have Claude print how many periods came back per endpoint. If you get five years of history when the model needs ten, you want to find out before you trust the output.
- Rate limits. Paid plans cap calls per minute, and the free plan caps them per day. If Claude loops over a watchlist, tell it to stop on a 429 response rather than retry in a tight loop.
Free or Paid?
Build everything on the free plan first. It's the cheapest way to find out whether the endpoints and history you need are included for the tickers you cover. Then decide whether the limits get in your way: the 250-call daily cap, the depth of history, or the lack of coverage for non-US names.
FMP's paid plans were listed at Starter $22, Premium $59 and Ultimate $149 per month when billed annually, with per-minute call limits of 300, 750 and 3,000. Plans change, so check the pricing page before you commit, and confirm that any endpoint your model depends on is included in the tier you pick.
If the Modeling Theory Is the Gap
Automating a DCF is only useful if you can tell when the output is wrong. If you're newer to valuation and the WACC and terminal value steps feel like black boxes, learn them by building a model by hand first. Corporate Finance Institute teaches DCF, LBO and 3-statement modeling, and it's a sensible way to build the judgment that checks Claude's work.
The Short Version
- Get a free FMP key and keep it in an environment variable, since it travels in the URL.
- Route every call through one caching helper so the 250-call budget goes further.
- Make Claude print real field names and stop on missing data.
- Convert reported free cash flow to unlevered before discounting at WACC.
- Present the sensitivity grid, and treat FMP's own DCF as a second opinion only.