Overview
SpendWisely George integrates with MFapi.in to provide:
- Real-time NAV (Net Asset Value) for Indian mutual funds
- Portfolio valuation based on user holdings
- Scheme metadata including fund names and details
MFapi.in is a free, public API maintained by the community. It aggregates data from AMFI (Association of Mutual Funds in India).
API Endpoint
Get Scheme Details
GET https://api.mfapi.in/mf/{scheme_code}
Parameters:
scheme_code (required) - AMFI scheme code (e.g., 119551 for HDFC Balanced Advantage Fund)
Response:
{
"meta": {
"scheme_type": "Hybrid Scheme - Balanced Advantage",
"scheme_category": "Hybrid Scheme",
"scheme_code": 119551,
"scheme_name": "HDFC Balanced Advantage Fund - Direct Plan - Growth Option"
},
"data": [
{
"date": "15-03-2024",
"nav": "425.789"
},
{
"date": "14-03-2024",
"nav": "424.123"
}
],
"status": "SUCCESS"
}
Implementation
Portfolio Calculation
The backend fetches NAV for each holding and calculates total portfolio value:
@app.get("/api/portfolio")
def get_portfolio():
total_value = 0
portfolio = []
holdings = load_holdings() # From holdings.json
for holding in holdings:
code = holding["scheme_code"]
units = float(holding["units"])
try:
# Fetch latest NAV from MFapi.in
resp = requests.get(f"https://api.mfapi.in/mf/{code}")
data = resp.json()
if data and "data" in data and len(data["data"]) > 0:
nav = float(data["data"][0]["nav"]) # Latest NAV
fund_name = data["meta"]["scheme_name"]
value = units * nav
portfolio.append({
"scheme_code": code,
"scheme_name": fund_name,
"units": units,
"nav": nav,
"current_value": round(value, 2)
})
total_value += value
except Exception as e:
print(f"Error fetching {code}: {e}")
return {
"portfolio": portfolio,
"total_value": round(total_value, 2)
}
Implementation: server.py:201-234
Holdings Management
Users configure their mutual fund holdings via the settings panel.
Data Structure
Holdings are stored in holdings.json:
[
{
"scheme_code": "119551",
"units": 12.456
},
{
"scheme_code": "120503",
"units": 8.923
}
]
API Endpoints
Get Holdings
Response:
[
{
"scheme_code": "119551",
"units": 12.456
}
]
Implementation: server.py:136-138
Update Holdings
POST /api/holdings
Content-Type: application/json
[
{
"scheme_code": "119551",
"units": 15.789
},
{
"scheme_code": "120503",
"units": 10.234
}
]
Response:
Implementation: server.py:140-144
Finding Scheme Codes
Search by Fund Name
MFapi.in provides a search endpoint:
GET https://api.mfapi.in/mf/search?q={query}
Example:
curl "https://api.mfapi.in/mf/search?q=HDFC%20Balanced"
Response:
[
{
"schemeCode": 119551,
"schemeName": "HDFC Balanced Advantage Fund - Direct Plan - Growth"
},
{
"schemeCode": 100541,
"schemeName": "HDFC Balanced Advantage Fund - Regular Plan - Growth"
}
]
This endpoint is not implemented in the current George version but can be added for better UX.
Common Scheme Codes
Popular mutual funds:
| Fund Name | Scheme Code |
|---|
| HDFC Balanced Advantage Fund - Direct Growth | 119551 |
| SBI Bluechip Fund - Direct Growth | 120503 |
| Axis Midcap Fund - Direct Growth | 120830 |
| Parag Parikh Flexi Cap Fund - Direct Growth | 122639 |
| ICICI Prudential Bluechip Fund - Direct Growth | 120716 |
Frontend Integration
Fetch Portfolio
async function fetchPortfolio() {
try {
const res = await fetch('/api/portfolio');
const data = await res.json();
if (data.total_value) {
appData.portfolioValue = data.total_value;
renderUI(); // Update "Investments" card
}
} catch (e) {
console.error("Portfolio Fetch Error", e);
}
}
Implementation: index.html:558-571
Add Holdings
let currentHoldings = [];
function addHolding() {
const code = document.getElementById('mfCode').value;
const units = parseFloat(document.getElementById('mfUnits').value);
if (code && units) {
currentHoldings.push({ scheme_code: code, units: units });
renderHoldings();
// Clear inputs
document.getElementById('mfCode').value = '';
document.getElementById('mfUnits').value = '';
}
}
function renderHoldings() {
const list = document.getElementById('mfList');
list.innerHTML = currentHoldings.map((h, i) =>
`<div class="flex justify-between text-xs bg-white p-2 rounded border">
<span class="font-mono">${h.scheme_code}</span>
<span>${h.units} units</span>
<button onclick="removeHolding(${i})" class="text-red-500">x</button>
</div>`
).join('');
}
Implementation: index.html:641-662
Save Holdings
async function saveHoldings() {
await fetch('/api/holdings', {
method: 'POST',
body: JSON.stringify(currentHoldings),
headers: { 'Content-Type': 'application/json' }
});
alert("Holdings Saved!");
fetchPortfolio(); // Refresh portfolio value
}
Implementation: index.html:664-671
UI Components
Settings Panel
Users manage holdings in the settings drawer:
<div class="p-4 bg-green-50 rounded-2xl border border-green-100">
<h3 class="font-bold text-green-900 mb-2">Mutual Fund Holdings</h3>
<!-- Display current holdings -->
<div id="mfList" class="space-y-2 mb-2"></div>
<!-- Add new holding -->
<div class="flex gap-2">
<input type="text" id="mfCode" placeholder="Scheme Code"
class="flex-1 p-3 bg-white rounded-xl text-sm">
<input type="number" id="mfUnits" placeholder="Units"
class="w-20 p-3 bg-white rounded-xl text-sm">
</div>
<button onclick="addHolding()" class="w-full mt-2 bg-green-600 text-white p-3 rounded-xl font-bold text-sm">
Add Holding
</button>
<button onclick="saveHoldings()" class="w-full mt-2 bg-slate-900 text-white p-3 rounded-xl font-bold text-sm">
Save & Update
</button>
</div>
Implementation: index.html:249-265
Portfolio Display
Total portfolio value is shown on the main dashboard:
<div class="card p-4 bg-white">
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">
Investments
</p>
<h3 id="uiPortfolio" class="text-xl font-black text-slate-800">₹0</h3>
<p class="text-[10px] text-green-500 font-bold mt-1">MFapi.in Linked</p>
</div>
Implementation: index.html:151-156
Data Caching
MFapi.in NAV data is updated once daily (after market close). Implement caching to avoid redundant API calls.
Recommended caching strategy:
import time
from functools import lru_cache
@lru_cache(maxsize=100)
def fetch_nav_cached(scheme_code: str, cache_key: str):
"""Cache NAV for 24 hours using date as cache key"""
resp = requests.get(f"https://api.mfapi.in/mf/{scheme_code}")
return resp.json()
@app.get("/api/portfolio")
def get_portfolio():
cache_key = time.strftime("%Y-%m-%d") # Daily cache
for holding in holdings:
data = fetch_nav_cached(holding["scheme_code"], cache_key)
# Process data...
Rate Limits
MFapi.in does not enforce strict rate limits but recommends:
- Max 100 requests/minute per IP
- Avoid excessive polling - NAV updates once daily
- Implement caching - Cache responses for 24 hours
The API is community-maintained and free. Please use responsibly to keep it available for everyone.
Error Handling
Invalid Scheme Code
try:
resp = requests.get(f"https://api.mfapi.in/mf/{code}")
data = resp.json()
if not data or "data" not in data or len(data["data"]) == 0:
print(f"No data found for scheme {code}")
continue
nav = float(data["data"][0]["nav"])
except Exception as e:
print(f"Error fetching {code}: {e}")
# Skip this holding and continue
Implementation: server.py:208-229
Network Errors
import requests
from requests.exceptions import Timeout, ConnectionError
try:
resp = requests.get(
f"https://api.mfapi.in/mf/{code}",
timeout=5 # 5 second timeout
)
resp.raise_for_status() # Raise for 4xx/5xx
except Timeout:
print("MFapi.in request timeout")
except ConnectionError:
print("Network error connecting to MFapi.in")
except Exception as e:
print(f"Unexpected error: {e}")
Alternative APIs
If MFapi.in is unavailable, consider:
| API | Features | Cost |
|---|
| RapidAPI - Mutual Funds India | NAV, historical data | Freemium |
| AMFI Direct | Official AMFI data | Free (manual parsing) |
| NSE/BSE APIs | Market data for ETFs | Registration required |
Best Practices
1. Validate Scheme Codes
function addHolding() {
const code = document.getElementById('mfCode').value;
// Validate format (AMFI codes are 6 digits)
if (!/^\d{6}$/.test(code)) {
alert("Scheme code must be 6 digits");
return;
}
// Validate against API before saving
fetch(`https://api.mfapi.in/mf/${code}`)
.then(res => res.json())
.then(data => {
if (data.status === "SUCCESS") {
currentHoldings.push({ scheme_code: code, units: units });
} else {
alert("Invalid scheme code");
}
});
}
2. Handle Stale Data
from datetime import datetime
data = resp.json()
latest = data["data"][0]
date_str = latest["date"] # "15-03-2024"
date_obj = datetime.strptime(date_str, "%d-%m-%Y")
if (datetime.now() - date_obj).days > 5:
print(f"Warning: NAV data is {(datetime.now() - date_obj).days} days old")
// Show fund name, not just code
portfolio.forEach(holding => {
console.log(`${holding.scheme_name}: ₹${holding.current_value}`);
});
Troubleshooting
”Error fetching scheme” Message
- Verify Scheme Code: Check code is 6 digits and valid
- Test API Directly:
curl https://api.mfapi.in/mf/119551
- Check Network: Ensure server has internet access
Portfolio Not Updating
- Check Holdings Saved: Verify
holdings.json exists and has data
- Refresh Page: Click “REFRESH” button to trigger portfolio fetch
- Check Logs: Look for errors in server console
Incorrect Valuation
- Verify Units: Ensure decimal precision is preserved (e.g., 12.456, not 12)
- Check NAV Date: Latest NAV might be from previous trading day
- Recalculate Manually:
units * NAV = current value