NPV and IRR are two of the most useful financial metrics in business analysis. Python's numpy-financial library can compute them — but it's a heavy dependency, and the API approach is cleaner for web apps that need server-side calculation.
This tutorial shows how to calculate NPV and IRR via REST API in Python, with a realistic investment analysis example.
The Problem
You're building an investment analysis tool. Users input cash flows and a discount rate, and your app needs to tell them:
- NPV — Is this investment worth more than it costs? (Positive NPV = yes)
- IRR — What's the effective annual return? Compare this to their hurdle rate.
Setup
pip install httpx # async-friendly; or use requests
Get a free API key at RapidAPI.
import os
import httpx
RAPIDAPI_KEY = os.environ["RAPIDAPI_KEY"]
BASE_URL = "https://fincalcapi.p.rapidapi.com"
HEADERS = {
"X-RapidAPI-Key": RAPIDAPI_KEY,
"X-RapidAPI-Host": "fincalcapi.p.rapidapi.com",
}
Calculating NPV
Net Present Value: the present value of future cash flows, discounted at your required rate of return, minus the initial investment.
def calculate_npv(discount_rate: float, cash_flows: list[float]) -> dict:
"""
discount_rate: annual discount rate as a percentage (e.g., 10.0 for 10%)
cash_flows: list starting with initial investment (negative), then returns
"""
with httpx.Client() as client:
response = client.get(
f"{BASE_URL}/npv",
params={
"discount_rate": discount_rate,
"cash_flows": ",".join(str(cf) for cf in cash_flows),
},
headers=HEADERS,
)
response.raise_for_status()
return response.json()
# Example: $100k investment, 10% discount rate
# Returns $30k, $40k, $50k, $60k over 4 years
result = calculate_npv(
discount_rate=10.0,
cash_flows=[-100000, 30000, 40000, 50000, 60000],
)
print(f"NPV: ${result['npv']:,.2f}")
print(f"Decision: {'✅ Accept' if result['npv'] > 0 else '❌ Reject'}")
# NPV: $42,084.37
# Decision: ✅ Accept
Calculating IRR
Internal Rate of Return: the discount rate at which NPV equals zero. Compare this to your cost of capital.
def calculate_irr(cash_flows: list[float]) -> dict:
"""
Returns the rate at which NPV of cash_flows equals 0.
Uses Newton-Raphson iteration — no numpy required.
"""
with httpx.Client() as client:
response = client.get(
f"{BASE_URL}/irr",
params={
"cash_flows": ",".join(str(cf) for cf in cash_flows),
},
headers=HEADERS,
)
response.raise_for_status()
return response.json()
result = calculate_irr([-100000, 30000, 40000, 50000, 60000])
print(f"IRR: {result['irr_percent']:.2f}%")
# IRR: 24.89%
# (This beats a 10% hurdle rate, so the investment is worth making)
Building a Full Investment Analyser
Here's a practical class that combines both metrics:
from dataclasses import dataclass
from typing import Optional
import httpx
@dataclass
class InvestmentAnalysis:
npv: float
irr_percent: float
hurdle_rate: float
recommendation: str
payback_period_years: Optional[float]
@property
def is_viable(self) -> bool:
return self.npv > 0 and self.irr_percent > self.hurdle_rate
class InvestmentAnalyser:
def __init__(self, api_key: str, hurdle_rate: float = 10.0):
self.headers = {
"X-RapidAPI-Key": api_key,
"X-RapidAPI-Host": "fincalcapi.p.rapidapi.com",
}
self.hurdle_rate = hurdle_rate
self.base_url = "https://fincalcapi.p.rapidapi.com"
def analyse(self, cash_flows: list[float]) -> InvestmentAnalysis:
with httpx.Client() as client:
# Fetch NPV and IRR in parallel
npv_resp = client.get(
f"{self.base_url}/npv",
params={
"discount_rate": self.hurdle_rate,
"cash_flows": ",".join(str(cf) for cf in cash_flows),
},
headers=self.headers,
)
irr_resp = client.get(
f"{self.base_url}/irr",
params={"cash_flows": ",".join(str(cf) for cf in cash_flows)},
headers=self.headers,
)
npv_data = npv_resp.json()
irr_data = irr_resp.json()
npv = npv_data["npv"]
irr = irr_data["irr_percent"]
# Simple payback: cumulative sum crosses zero
cumulative = 0.0
payback = None
for i, cf in enumerate(cash_flows):
cumulative += cf
if cumulative >= 0:
payback = float(i)
break
if npv > 0 and irr > self.hurdle_rate:
recommendation = f"ACCEPT — IRR {irr:.1f}% exceeds hurdle rate {self.hurdle_rate}%"
elif npv > 0:
recommendation = "MARGINAL — Positive NPV but IRR below hurdle rate"
else:
recommendation = "REJECT — Negative NPV"
return InvestmentAnalysis(
npv=npv,
irr_percent=irr,
hurdle_rate=self.hurdle_rate,
recommendation=recommendation,
payback_period_years=payback,
)
# Usage
analyser = InvestmentAnalyser(api_key=RAPIDAPI_KEY, hurdle_rate=12.0)
project_a = analyser.analyse([-500_000, 100_000, 150_000, 200_000, 250_000, 300_000])
print(f"Project A: {project_a.recommendation}")
print(f" NPV: ${project_a.npv:,.0f}")
print(f" IRR: {project_a.irr_percent:.1f}%")
print(f" Viable: {project_a.is_viable}")
Running in FastAPI / Flask
If you're building a web API on top of this:
# FastAPI example
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class InvestmentRequest(BaseModel):
cash_flows: list[float]
hurdle_rate: float = 10.0
@app.post("/analyse")
async def analyse_investment(req: InvestmentRequest):
analyser = InvestmentAnalyser(
api_key=os.environ["RAPIDAPI_KEY"],
hurdle_rate=req.hurdle_rate,
)
try:
result = analyser.analyse(req.cash_flows)
return result
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
Why Not numpy-financial?
| numpy-financial | FinCalc API | |
|---|---|---|
| Bundle size | ~15MB (numpy) | 0 bytes |
| Server dependency | pip install | HTTP call |
| Precision handling | Your problem | Handled |
| Other calculations | You build them | 7 more endpoints |
| Cost | Free | Free tier available |
For quick scripts, numpy-financial is fine. For production web apps, an API keeps your containers lean and your code simple.
What's Next
With the same key you can also hit:
-
/amortize— loan payment schedules -
/mortgage— housing cost breakdown -
/compound-interest— savings growth projections -
/roi— investment return analysis -
/break-even— unit economics for SaaS/products -
/depreciation— asset value schedules
👉 Free API key on RapidAPI — 50 calls/day free.
Top comments (0)