Skill: kanaksinghal/mfapi (version 0.1.0)

--- name: mfapi description: Query Indian mutual fund NAV data, scheme info, and history via the free MFapi.in REST API. homepage: https://www.mfapi.in metadata: { "openclaw": { "emoji": "₹", "requires": { "bins": ["jq", "curl"] } }, } --- # MFapi Skill Query Indian mutual fund data — NAV history, scheme info, search — using the free [MFapi.in](https://www.mfapi.in) API. ## Setup No authentication or API keys required. The API is completely free and open. Ensure `curl` and `jq` are installed: ```bash # Debian/Ubuntu sudo apt install -y curl jq # macOS brew install curl jq ``` ## What is an ISIN Code? ISIN (International Securities Identification Number) is a 12-character alphanumeric code that uniquely identifies a security globally (e.g. `INF200K01UT4`). Indian mutual fund schemes have up to two ISINs: - **isinGrowth** — identifies the Growth option of the scheme - **isinDivReinvestment** — identifies the IDCW (Dividend) Reinvestment option (may be `null`) ISIN codes are printed on CAS (Consolidated Account Statements), broker/demat platforms, and AMFI's website. They are stable identifiers — unlike scheme names, they don't change when a fund house rebrands. ## Base URL ``` https://api.mfapi.in ``` Data is updated 6× daily (10:05 AM, 2:05 PM, 6:05 PM, 9:05 PM, 3:09 AM, 5:05 AM IST). ## Usage ### Search schemes by name ```bash curl -s "https://api.mfapi.in/mf/search?q=HDFC" | jq '.[] | {schemeCode, schemeName}' ``` ### List all schemes (paginated) ```bash curl -s "https://api.mfapi.in/mf?limit=100&offset=0" | jq '.[] | {schemeCode, schemeName}' ``` ### Get latest NAV for a scheme ```bash curl -s "https://api.mfapi.in/mf/125497/latest" | jq '{scheme: .meta.scheme_name, nav: .data[0].nav, date: .data[0].date}' ``` ### Get NAV history for a scheme ```bash curl -s "https://api.mfapi.in/mf/125497" | jq '{scheme: .meta.scheme_name, records: (.data | length)}' ``` ### Get NAV history with date range ```bash curl -s "https://api.mfapi.in/mf/125497?startDate=2023-01-01&endDate=2023-12-31" | jq '.data' ``` ### Get latest NAV for all schemes ```bash curl -s "https://api.mfapi.in/mf/latest" | jq '.[:5]' ``` ## Response Formats ### Latest NAV (`/mf/{scheme_code}/latest`) ```json { "meta": { "fund_house": "HDFC Mutual Fund", "scheme_type": "Open Ended Schemes", "scheme_category": "Equity Scheme - Large Cap Fund", "scheme_code": 125497, "scheme_name": "HDFC Top 100 Fund - Direct Plan - Growth", "isin_growth": "INF179K01BB2", "isin_div_reinvestment": null }, "data": [ { "date": "26-10-2024", "nav": "892.45600" } ], "status": "SUCCESS" } ``` ### Search (`/mf/search?q=...`) ```json [ { "schemeCode": 125497, "schemeName": "HDFC Top 100 Fund - Direct Plan - Growth" } ] ``` ### Scheme list (`/mf`) ```json [ { "schemeCode": 125497, "schemeName": "HDFC Top 100 Fund - Direct Plan - Growth", "isinGrowth": "INF179K01BB2", "isinDivReinvestment": "INF179K01BC0" } ] ``` ## Endpoints Reference | Method | Endpoint | Description | Parameters | |--------|----------|-------------|------------| | GET | `/mf/search` | Search schemes by name | `q` (required) | | GET | `/mf` | List all schemes (paginated) | `limit` (1–1000), `offset` (default 0) | | GET | `/mf/{scheme_code}` | NAV history for a scheme | `startDate`, `endDate` (ISO 8601) | | GET | `/mf/{scheme_code}/latest` | Latest NAV for a scheme | — | | GET | `/mf/latest` | Latest NAV for all schemes | — | ## Get Latest NAV by ISIN (Python) The API doesn't support querying by ISIN directly. The `scripts/get_nav.py` script resolves ISIN → scheme code using a locally cached scheme list, then fetches the latest NAV. ### How it works 1. **Cache** — Downloads the full scheme list (`/mf`, ~37k schemes) to `/tmp/mfapi-schemes.json`. Refreshes automatically if the cache is missing or older than 24 hours. 2. **Lookup** — Searches `isinGrowth` and `isinDivReinvestment` fields in the cache. If no match, refreshes the cache and retries. 3. **Fetch** — Uses the resolved scheme code to call `/mf/{scheme_code}/latest`. ### Usage ```bash # Single ISIN python3 scripts/get_nav.py INF200K01UT4 # Multiple ISINs python3 scripts/get_nav.py INF200K01UT4 INF846K01DP8 ``` ### Example output ```json { "isin": "INF200K01UT4", "scheme_code": 119800, "scheme_name": "SBI Liquid Fund - DIRECT PLAN -Growth", "fund_house": "SBI Mutual Fund", "category": "Debt Scheme - Liquid Fund", "nav": "4277.67540", "date": "18-02-2026" } ``` When multiple ISINs are passed, the output is a JSON array. ## Notes - Dates in responses use `DD-MM-YYYY` format; query params use ISO 8601 (`YYYY-MM-DD`) - NAV values are strings with 5-decimal precision - Scheme codes can be found via the search or list endpoints, or from AMFI's website - The `/mf` endpoint returns all ~37,000 schemes; the Python script caches it locally at `/tmp/mfapi-schemes.json` to avoid repeated large fetches - Cache auto-refreshes on ISIN lookup miss or after 24 hours - No rate-limit headers are published, but the API asks for fair usage ## Examples ```bash # Find all SBI mutual fund schemes curl -s "https://api.mfapi.in/mf/search?q=SBI" | jq '.[].schemeName' # Get today's NAV for a known scheme curl -s "https://api.mfapi.in/mf/119551/latest" | jq '.data[0]' # Compare NAVs across a year curl -s "https://api.mfapi.in/mf/125497?startDate=2025-01-01&endDate=2025-12-31" \ | jq '[.data[0], .data[-1]] | {latest: .[0], oldest: .[1]}' # Get fund house and category for a scheme curl -s "https://api.mfapi.in/mf/125497/latest" | jq '.meta | {fund_house, scheme_category}' # List first 10 Direct Plan Growth schemes matching "Axis" curl -s "https://api.mfapi.in/mf/search?q=Axis" \ | jq '[.[] | select(.schemeName | test("Direct.*Growth"))] | .[:10]' # Get latest NAV by ISIN python3 scripts/get_nav.py INF200K01UT4 ```

path: scripts/get_nav.py
content: #!/usr/bin/env python3
"""Get latest mutual fund NAV by ISIN code using MFapi.in.

Usage:
    python3 get_nav.py <ISIN> [ISIN ...]

Examples:
    python3 get_nav.py INF200K01UT4
    python3 get_nav.py INF200K01UT4 INF846K01DP8
"""

import json
import sys
import time
import urllib.request
from pathlib import Path
from typing import Any, Dict, List, Optional

BASE_URL = "https://api.mfapi.in"
CACHE_PATH = Path("/tmp/mfapi-schemes.json")
CACHE_MAX_AGE = 86400  # 24 hours in seconds


def load_cache() -> List[Dict[str, Any]]:
    """Load scheme list from cache, refreshing if stale or missing."""
    if CACHE_PATH.exists() and (time.time() - CACHE_PATH.stat().st_mtime) < CACHE_MAX_AGE:
        with open(CACHE_PATH) as f:
            return json.load(f)
    return refresh_cache()


def refresh_cache() -> List[Dict[str, Any]]:
    """Download full scheme list and write to cache."""
    data = api_get("/mf")
    CACHE_PATH.write_text(json.dumps(data))
    return data


def api_get(path: str):
    """GET request to MFapi.in and return parsed JSON."""
    url = f"{BASE_URL}{path}"
    req = urllib.request.Request(url, headers={"Accept": "application/json"})
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read())


def find_scheme_code(isin: str, schemes: List[Dict[str, Any]]) -> Optional[int]:
    """Find scheme code by matching ISIN against isinGrowth or isinDivReinvestment."""
    for s in schemes:
        if s.get("isinGrowth") == isin or s.get("isinDivReinvestment") == isin:
            return s["schemeCode"]
    return None


def get_nav_by_isin(isin: str) -> Dict[str, Any]:
    """Resolve ISIN to scheme code, fetch latest NAV, return result dict."""
    schemes = load_cache()
    scheme_code = find_scheme_code(isin, schemes)

    # Cache miss — refresh and retry
    if scheme_code is None:
        schemes = refresh_cache()
        scheme_code = find_scheme_code(isin, schemes)

    if scheme_code is None:
        return {"isin": isin, "error": f"No scheme found for ISIN {isin}"}

    resp = api_get(f"/mf/{scheme_code}/latest")
    meta = resp.get("meta", {})
    nav_entry = resp.get("data", [{}])[0]

    return {
        "isin": isin,
        "scheme_code": meta.get("scheme_code", scheme_code),
        "scheme_name": meta.get("scheme_name"),
        "fund_house": meta.get("fund_house"),
        "category": meta.get("scheme_category"),
        "nav": nav_entry.get("nav"),
        "date": nav_entry.get("date"),
    }


def main():
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <ISIN> [ISIN ...]", file=sys.stderr)
        sys.exit(1)

    results = [get_nav_by_isin(isin) for isin in sys.argv[1:]]
    output = results[0] if len(results) == 1 else results
    print(json.dumps(output, indent=2))


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)