# MF NAV API - Open Mutual Fund Data

Daily NAV history for 38,000+ Indian mutual fund schemes from AMFI India - every NAV in AMFI's public records: all scheme types (open-ended, closed-ended, interval), April 2006 to present, INCLUDING matured and merged schemes. 37M+ records, survivorship-bias-free: the dead schemes (matured FMPs, wound-up series, funds from defunct AMCs) stay queryable, so historical analysis does not see only the survivors. Updated 3x daily. No auth required.

**How the data is built:** synced from AMFI's official daily NAVAll.txt plus AMFI's historical NAV archives (merged July 2026 in gated batches, each reconciled cell-by-cell before the next ran). On overlap the existing value always wins; source-filed anomalies are published as filed, never silently cleaned. Full story in the docs linked from https://www.tigzig.com/apis/mf-nav

**Quick start:**
- `GET /mf/v1/nav?scheme=119775` - full NAV history by AMFI code
- `GET /mf/v1/nav?scheme=INF174K01LT0&latest=true` - latest NAV by ISIN
- `GET /mf/v1/nav?scheme=120468&since=2024-01-01&to=2024-12-31` - date-bounded
- `GET /mf/v1/search?q=absl value` - name/code search (AMC-alias aware, relevance-ranked)
- Bulk: `?schemes=` comma-separated, up to 50 per call. Full corpus: use `/mf/v1/download` (csv/tsv/parquet/sqlite, regenerated 3x daily) instead of looping /nav.

**Gotchas:** dates are YYYY-MM-DD. `scheme` accepts an AMFI numeric code (4-7 digits) or a 12-char ISIN. A valid scheme with zero NAV rows in your date window returns 200 with `data: []`, not a 404. Matured schemes return their full history. Errors carry `error.examples` with working calls.

**Navigation (machine-readable):** catalog https://api.tigzig.com/mf/v1/ | downloads manifest https://api.tigzig.com/mf/v1/downloads/manifest | data dictionary (field semantics + caveats, read before trusting filtered browses) https://www.tigzig.com/md/mfpro/data-dictionary.md | human docs https://www.tigzig.com/apis/mf-nav

Author: Amar Harolikar - https://www.linkedin.com/in/amarharolikar

Rate limit: 300 requests/min per IP. Downloads: 10/min.

Part of Tigzig: free interactive tools, open-source repos, APIs and MCP servers for analytics and live data across global and Indian markets, macro indicators and filings. Catalog: https://api.tigzig.com/.well-known/api-catalog | Guide: https://www.tigzig.com/llms.txt

## API catalog: every endpoint, the data schema, and worked examples

`GET /mf/v1/`

The starting point. One call returns every endpoint, the data schema and
worked examples, so nothing below has to be guessed.

### What this API serves

Daily NAV history for **38,000+ Indian mutual fund schemes**, published by AMFI
India. April 2006 onward, **including matured and closed schemes** - a fund that
shut in 2014 still returns its full history.

### Start here

- `GET /mf/v1/search?q=<name>` - find a scheme and its code
- `GET /mf/v1/nav?scheme=<code or ISIN>` - pull its NAV history
- `GET /mf/v1/download?format=latest` - the whole scheme master in one file

### What the response contains

- `endpoints` - every path with its parameters and an example URL
- `schema` - field-by-field meaning, including `latest_snapshot` column semantics
- `rate_limits` - the per-IP limits actually enforced

No authentication. No key. No sign-up.

## Search and browse schemes: name/code search + metadata filters (category, AMC, plan, dates)

`GET /mf/v1/search`

Find a scheme by name or code, or browse the whole universe by metadata.
Works with a search term, with filters, or with both.

### Searching by name or code

- **Numeric `q`** matches `scheme_code`, exact or prefix.
- **Text `q`** is multi-word **AND** on the scheme name: every word must appear,
  in any order, case-insensitive. `q=hdfc cap` matches `HDFC Mid Cap`.
- `AND` and `OR` are **not operators** here - they are matched as literal words.
  For OR across fund houses use a comma: `amc=hdfc,icici`.
- AMC nicknames expand automatically, so `absl` finds `Aditya Birla Sun Life`.

### Browsing without a search term

Every filter works **with or without** `q`. With no `q` at all this becomes a
browse over the full universe.

- Values are contains-matched and case-insensitive
- A comma inside one filter means **OR**: `amc=hdfc,icici`
- Different filters **AND** together: `category=small&plan=direct`
- `exclude=` removes matches globally

```
/mf/v1/search?q=absl value
/mf/v1/search?category=small cap&plan=direct&option=growth
/mf/v1/search?group=equity&first_date_from=2015-01-01&first_date_to=2020-12-31
/mf/v1/search?isin=INF174K01LT0&active=all
```

### Two response shapes

- A **`q`-only** call returns the compact shape: `scheme_code`, `scheme_name`,
  `isin`, `isin2`, `category_sub`, `is_active`, `relevance`.
- **Any filter present** returns the enriched shape: every `scheme_meta` field
  per row, plus `total_matches` and `matched_categories`.

### Two things that surprise callers

**Read `isin2` as well as `isin`.** IDCW-reinvestment unit classes carry their
identifier in `isin2` and leave `isin` empty, so about 1,000 schemes look
identifier-less if you read `isin` alone.

**Filter-only calls hide matured schemes by default**, so `all ICICI equity funds`
is not buried under 29,000 closed ones. Add `active=all` to include them, or
`active=false` for matured only. Calls with `q=` are unaffected. A filter-only
call that matches nothing live will tell you how many matured schemes it found.

### Strict by design

An unknown parameter name fails with `400 UNRECOGNIZED_PARAMETER` and a
did-you-mean, rather than silently returning unfiltered results. Repeated
list-filters merge as OR; repeated scalars with different values fail loudly.

**Limits:** `limit` 1-100, default 20. For the entire universe in one shot use
`/mf/v1/download?format=latest` instead of paginating - the universe refreshes
three times a day, and a long pagination sweep crossing a sync can skip or
duplicate a row.

## NAV data by AMFI code or ISIN (single or bulk)

`GET /mf/v1/nav`

NAV history for one or more schemes, by AMFI code or ISIN.

### Basic calls

```
/mf/v1/nav?scheme=119775                                   one scheme, full history
/mf/v1/nav?scheme=INF174K01LT0                             same, by ISIN
/mf/v1/nav?schemes=119775,120468,INF174K01LT0              up to 50 per call
/mf/v1/nav?scheme=119775&since=2024-01-01&to=2024-12-31    a bounded window
/mf/v1/nav?scheme=119775&latest=true                       newest row only
```

`latest=true` composes with `since` / `to`.

### The input is forgiving on purpose

You should not have to remember exact spellings.

- `isin=` and `isins=` are synonyms of `scheme=` and `schemes=`
- Parameter **names** are case-insensitive, and so are identifier **values**
  (`inf174k01lt0` works)
- A comma list in the singular parameter (`?scheme=A,B`) is treated as bulk
- Repeated parameters merge: `?schemes=A&schemes=B` is the same as `?schemes=A,B`
- `/mf/v1/navs` (plural) is accepted too

### Two response shapes, one rule

A singular parameter carrying **exactly one** identifier returns the flat shape.
Everything else returns the wrapped bulk shape.

- Single: `{scheme_code, scheme_name, isin, isin2, count, data:[{date, nav}]}`
- Bulk: `{count, schemes:[...], not_found:[...]}`

**A bad entry never fails the batch.** Invalid or unknown identifiers are served back
in `not_found` while every valid scheme returns normally. Only an entirely invalid
list errors.

### An empty result explains itself

Every scheme carries `first_available_date` and `latest_available_date`. If
`latest_available_date` is older than your `since`, nothing has been published for
your window yet - the usual cause is a poller running ahead of AMFI's daily publish.
`Last-Modified` is set even on an empty window.

### Identifiers worth knowing about

- Read **`isin2`** as well as `isin`: IDCW-reinvestment unit classes carry their
  identifier there and leave `isin` empty.
- One ISIN can belong to **more than one scheme code**. When a fund changes manager,
  AMFI issues a new code and the ISIN survives, so a single code returns only one leg
  of the history. To find the other legs, search the ISIN with `active=all`.
- A NAV of `0.00` on a matured scheme is AMFI's terminal marker, published as filed.

**Headers:** `X-RateLimit-*` on success, and `Last-Modified` (weak - derived from the
latest NAV date in the response).

**Rate limit:** 300 requests per minute per IP.

## NAV data for a single scheme (REST path form)

`GET /mf/v1/schemes/{code}/nav`

REST-canonical form of the single-scheme NAV lookup.

- AMFI code path: `/mf/v1/schemes/119775/nav`
- ISIN path: `/mf/v1/schemes/INF174K01LT0/nav`
- Bounded window: add `?since=YYYY-MM-DD&to=YYYY-MM-DD`

Same response shape as `GET /mf/v1/nav?scheme=...`.

## Download full NAV dataset

`GET /mf/v1/download`

Download the complete AMFI NAV dataset as a pre-generated static file.

Formats (zip for browsers, gz for programmatic use):
- **csv.zip** / **csv.gz** - CSV, universal
- **tsv.zip** / **tsv.gz** - TSV, ideal for LLM context
- **parquet** - Columnar compressed, ideal for pandas/DuckDB
- **sqlite.zip** / **sqlite** - SQLite database, extract then query locally
- **latest** / **latest.csv.gz** / **latest.csv.zip** / **latest.parquet** - THE FULL
  SCHEME MASTER + latest NAV + latest quarterly AAUM: one row per scheme (all
  ~38,000, matured included with final frozen NAVs), 22 columns - identifiers
  (incl. both ISINs), names, AMC, full category taxonomy (raw + clean group),
  plan/option, launch + last-NAV dates,
  is_active/is_stale, TXIC, aaum_cr_quarterly_avg + quarter label + ISO twin. ~1-9 MB by format.
  THE efficient daily sync AND the local-analysis table: one GET replaces
  thousands of per-scheme calls; column semantics in the catalog's
  `schema.latest_snapshot` block. `latest` = plain CSV (opens in Excel).

Files are regenerated 3x daily after sync. Sizes shown in the catalog endpoint.

Rate limits are their own budgets, separate from the 300/min the other MF
endpoints share, and they are SPLIT BY REQUEST KIND because the two cost
wildly different amounts of Worker CPU:

- Range reads (HTTP 206) - 3,000/min and 25,000/day per IP. This is what a
  remote DuckDB or pyarrow query issues; one unfiltered scan of the full
  parquet is ~300 requests, so the day allows ~83 full scans.
- Whole-file (GET/HEAD returning 200/304) - 60/min and 500/day per IP.

The X-RateLimit-* headers report the budget for the kind of request you just
made, and carry a -Day pair alongside the per-minute one. Read them rather
than hardcoding these numbers, and pace against BOTH: a job watching only the
per-minute figure can be refused hours later by the daily ceiling.

IMPORTANT for future edits: this handler is FRONTED by the CF R2 Worker
at cloudflare/downloads-worker/worker.js. For all /mf/v1/download* paths
that match a service in downloads-index.json (the common case), the
Worker resolves the file and validates format at the edge - THIS
ORIGIN CODE NEVER RUNS. Any user-visible behavior change (error shape,
error codes, format-validation rules, headers, Cache-Control, ETag,
rate-limit-info headers) needs a matching edit in worker.js AND a
Worker redeploy. See downloads-worker/README.md ("GOTCHA - READ BEFORE
EDITING") for the checklist.

## Download file sizes and metadata

`GET /mf/v1/downloads/manifest`

Returns pre-generated file sizes and row counts.

Supports ETag / If-None-Match for cheap re-polling. If the manifest hasn't
changed since the caller's last fetch, returns `304 Not Modified` with an
empty body (saves bandwidth for daily polls).
