MFPRO Mutual Fund API: Search Now Filters Across 12 Fields on a Scheme
Published: July 31, 2026
MFPRO is TigZig's India mutual fund NAV and analytics API. Its search used to match a fund by name or its scheme code and nothing past that. Now it filters on 12 fields on a scheme, six text fields, two booleans, two date windows and two identifiers, plus exclude sitting on top of all of them as the NOT. Every one of the 12 combines with the others. This piece covers the full list, how the filters actually combine, and what a wrong call gets back.
What this endpoint is for
The rest of this API works off scheme codes. /nav and /schemes/{code}/nav return a fund's NAV history, but only once you already have the code, or the ISIN, to ask for. Most people do not start there. They start with a fund name, or with a rough idea of what they want, every Direct Growth Small Cap fund from one AMC, say, not a code at all.
/search is how you get from one to the other. Give it a name and it finds the matching schemes. Give it filters instead, category, AMC, plan, launch date, whatever you know, and it shortlists the schemes that match, codes included. Most pipelines built on this API run in two steps because of this: search first to get the codes, then call /nav or /schemes/{code}/nav on whatever came back.
It is also useful without a second step at all, just to see the shape of the universe, how many schemes sit in a category, how many an AMC runs, how many are Direct versus Regular. That kind of browsing, and everything else covered below, needs no NAV call after it.
What it used to do
For a long stretch the endpoint took one parameter that did anything, q, a fund name or a scheme code, matched as a substring, case insensitive. Ask for hdfc flexi cap and you got every scheme with those words somewhere in its name, ranked by how close the match was. That part still works exactly the same way, and it is still the fastest route in when you already know roughly what fund you want.
GET https://api.tigzig.com/mf/v1/search?q=hdfc flexi cap
Four schemes come back, the growth and IDCW options across HDFC Flexi Cap Fund's direct and regular plans, each carrying a relevance score, with the direct growth plan on top at 26 against 21 for the other three.
What it does now
The same endpoint now reads 12 fields on a scheme, and you can filter on any of them with no name at all. Give it filters and nothing else and it browses the scheme universe for you, the way you would browse a catalog rather than search one, active=all&limit=5 with no q at all returns the first five schemes out of more than 38,000. There is no separate switch for this and no q=all, any filter present is what turns search into browse. q stays mandatory only on a completely bare call, no filters and no q, which still returns a 422. Here is the full list.
| Field | What it matches | Example |
|---|---|---|
amc |
AMC name, contains | amc=aditya birla |
category |
the SEBI sub-category, contains | category=small cap |
group |
the four broad buckets: Equity Scheme, Debt Scheme, Hybrid Scheme, Other Scheme | group=equity |
type |
open, close or interval schemes | type=open |
plan |
Direct, Regular or Other | plan=direct |
option |
Growth, IDCW, Bonus or Other | option=growth |
active |
true, false or all. Defaults to true on a pure browse, so matured schemes stay out unless you ask for them |
active=all |
stale |
true or false, the roughly 50 schemes that carry only one or two NAV rows in AMFI's own records. All of them are matured, so pair it with active=all or you get nothing back |
stale=true&active=all |
first_date_from / first_date_to |
the launch window, when a scheme first published a NAV | first_date_from=2015-01-01 |
last_date_from / last_date_to |
the window for when a scheme stopped publishing | last_date_to=2015-12-31 |
isin |
an exact ISIN, not a substring match, comma separated for more than one | isin=INF174K01LT0 |
txic |
TigZig's own scheme code, contains | txic=AXOMC |
exclude sits on top of all 12 as the one NOT. It is not a thirteenth field to filter on, it is the operator that removes matches from whatever the other filters already found, and the section below covers it properly.
A note on group, because it moved recently. SEBI's February 2026 circular discontinued Solution Oriented schemes, and the clean grouping went from six buckets to four in the process. Solution Oriented and Fund of Funds now sit inside Other Scheme, and their schemes stay fully findable through category and the raw category group. If you have code written against the older six-value list, group=solution oriented and group=fund of funds now return nothing.
category and group are not the same field
The two fields, in short
group is the four broad buckets. category is the specific SEBI sub-category sitting underneath one of those buckets, Small Cap Fund, Liquid Fund, Index Funds, and so on, and it is the one that actually answers a specific question. Reach for the wrong one of the two and a perfectly real set of funds does not turn up.
Index funds, the plain case
category=index returns HDFC Nifty 50 Index Fund, ICICI Prudential Nifty 50 Index Fund, SBI Nifty Index Fund, and well over a thousand more, because the filter is a contains-match against the full category text rather than a lookup against the clean sub-category alone. Most of what comes back genuinely carries a category_sub of Index Funds. The rest are schemes like SBI's Crisil IBX SDL Index Fund, whose category reads "Index Funds - Debt Funds", a debt-oriented index product that category=index is right to catch even though its own sub-category says Debt Funds, not Index Funds. group=index returns nothing, because Index is not one of the group names. Every index fund in this database sits inside the Other Scheme group, and group alone cannot tell one apart from the other fund types sharing that bucket. The empty response also hands back the valid group names on its own, the same self-correcting habit the wrong-parameter example further down shows off properly.
Why the filter reads raw text too
Every scheme carries two versions of its category. There is the raw text AMFI itself prints, a label like "Equity Scheme - Small Cap Fund", or for very old schemes a single word like "Income". From that raw text we extract a clean field, category_sub, values like Small Cap Fund, Liquid Fund, Index Funds. About 9,000 pre-2018 schemes were never given one of these clean sub-categories by AMFI at all, only the raw text exists for them.
That is why category= reads both fields rather than the clean one alone. category=income&active=all proves it, tens of thousands of old Income schemes come back, schemes a clean-field-only filter would miss entirely since none of them carry a modern sub-category to match against. The active=all matters here, because those Income schemes are long matured and the browse default of active=true hides almost all of them. The SBI SDL Index Fund case above is the same mechanism from the other side, a compound raw string picking up a match a clean-only filter would have missed too. There is no separate filter today that reads only the clean field, filter broad and check category_sub on the results if you need the exact one.
The newer example: Life Cycle Fund
SEBI's Life Cycle Fund category was created by the 26 February 2026 circular on scheme categorization. Two schemes exist against it right now, Zerodha Life Cycle Fund 2036 and Zerodha Life Cycle Fund 2041, carrying category_sub of "Life Cycle Fund with Maturity of 10 Years" and "Life Cycle Fund with Maturity of 15 Years". Both sit inside the Other Scheme group rather than Equity Scheme, so a search built around group=equity on the assumption that a life cycle fund is basically an equity product misses both of them entirely, group=equity&category=life cycle comes back with zero.
The spelling trap inside the same example
category=lifecycle, written as one word, also returns zero, because the field is contains-matched against the literal text and AMFI's own category string is "Life Cycle", two words with a space. category=life cycle, spaced the way the source data actually spells it, returns exactly the two Zerodha schemes above. A category filter that matches nothing does not necessarily mean the category is empty, sometimes it means the spelling guessed was close but not exact.
How the filters combine
Two different filters AND together on their own, no keyword needed. Ask for a category and a plan and you get schemes matching both.
GET https://api.tigzig.com/mf/v1/search?amc=birla&category=value
Four schemes come back, all four variants of Aditya Birla Sun Life Value Fund, growth and IDCW across direct and regular. Neither filter alone would have narrowed it to that set, the AMC filter alone would have returned every Aditya Birla scheme and the category filter alone every Value Fund from every AMC.
A comma inside one filter is an OR within that field.
GET https://api.tigzig.com/mf/v1/search?amc=birla,hdfc
This returns 787 schemes total, every scheme from either AMC.
exclude is the one NOT. It drops any result whose scheme name or AMC contains any of the comma separated terms you give it. Give it twice instead of comma separated and it merges the same way every other list filter does, exclude=birla&exclude=jm and exclude=birla,jm both drop category=value from 96 matches to 88.
GET https://api.tigzig.com/mf/v1/search?category=value&exclude=birla
category=value alone returns 96 total matches. Add exclude=birla and it drops to 92, exactly the four Aditya Birla Sun Life Value Fund schemes from the AND example above, gone.
That is the whole language, three rules, and there is nothing past them. There is no way to write the words AND or OR into the query text itself and have them mean anything. Type them into q and they get read as literal words to match against a fund's name.
GET https://api.tigzig.com/mf/v1/search?q=birla AND value
{"query":"birla AND value","count":0,"results":[]}
GET https://api.tigzig.com/mf/v1/search?q=birla OR value
{"query":"birla OR value","count":0,"results":[]}
Both come back empty, and not because the words AND and OR are being read as logic. The match requires every word in the query to appear somewhere in the scheme name, and that includes the words AND and OR themselves, which is why a plain q=birla value finds all four Birla Value Fund schemes while adding AND or OR to that same query knocks it back to zero, no Birla Value fund name happens to contain the letters "and" or "or". There are no parentheses either, and no way to nest one group of conditions inside another. That is a real limit of the endpoint as it stands, and there is no alternate syntax that gets around it.
When a call is wrong
Get a parameter name wrong and the call does not go through with the bad piece dropped and nothing said about it. Ask for exclude_amc instead of exclude, or categry instead of category, and the whole request is rejected.
GET https://api.tigzig.com/mf/v1/search?category=value&exclude_amc=birla
{"detail":"Unknown parameter(s): exclude_amc. Search parameters are strict so a typo can never silently return unfiltered results.","error":{"code":"UNRECOGNIZED_PARAMETER","message":"Unknown parameter(s): exclude_amc. Search parameters are strict so a typo can never silently return unfiltered results.","docs_url":"https://api.tigzig.com/mf/v1/docs","valid_params":"q, limit, offset, amc, group, category, type, plan, option, txic, isin, active, stale, first_date_from, first_date_to, last_date_from, last_date_to, exclude","examples":["https://api.tigzig.com/mf/v1/search?q=absl%20value","https://api.tigzig.com/mf/v1/search?category=small%20cap&plan=direct&option=growth","https://api.tigzig.com/mf/v1/search?group=equity&first_date_from=2015-01-01&first_date_to=2020-12-31"],"did_you_mean":{"exclude_amc":"exclude"}},"help":{"catalog":"https://api.tigzig.com/mf/v1/","docs":"https://www.tigzig.com/apis/mf-nav"}}
The error names the exact parameter it did not recognize, lists every valid one, and in this case guesses the one I meant, did_you_mean pointing exclude_amc at exclude. category=value in that same call is a perfectly good filter on its own, and the response does not run that half of the call while dropping the other half unannounced. The whole call fails, with an explanation, so there is no version of this where a result set comes back missing a filter you thought had been applied, with nothing in the response to tell you so.
Send the same filter parameter twice and the two rules differ by what kind of parameter it is. A filter that already supports commas as OR merges the repeats the same way.
GET https://api.tigzig.com/mf/v1/search?amc=birla&amc=hdfc
This reads as amc=birla,hdfc, same 787 matches as writing it that way directly. q cannot merge, because it only ever takes one value, so sending it twice with two different values is rejected outright rather than keeping whichever one happened to arrive last with no indication of the other.
GET https://api.tigzig.com/mf/v1/search?q=hdfc&q=icici
{"detail":"Parameter(s) given multiple times with different values: q. For list filters (amc, category, ...) repeats merge as OR; this parameter takes one value.","error":{"code":"REPEATED_PARAMETER", ...}}
A few smaller edges
A query of nothing but spaces used to pass the endpoint's own minimum length check and come back as a 200 with a fixed set of 30 results, no warning that the search behind it had run on an effectively empty string. That is closed now, whitespace collapses to nothing and gets rejected the same way a truly empty q does.
limit and offset are bounds checked cleanly, ask for limit=0 or limit=1000 and you get a 400 naming the field and the allowed range, 1 to 100, rather than a call that clamps the number to something else without telling you. Ask for a limit past 100 specifically and the error adds a pointer to the bulk file: /mf/v1/download?format=latest returns every scheme in one CSV or parquet file, so a caller trying to page through the whole universe one hundred rows at a time gets told there is a single GET that does it instead. A malformed date in first_date_from gets a 400 telling you to use YYYY-MM-DD.
isin is the one filter that is not a substring match, it takes an exact ISIN only, comma separated for more than one. Every other text filter is contains matched.
Quick tip: use last_date to find matured funds
last_date is easy to skim past as the throwaway half of the launch date pair, but it is the useful one for anyone studying matured funds, and that is not a small slice of this database. Roughly 29,000 of the more than 38,000 schemes are matured or merged, close to 77 percent of the whole universe. A query bounded by last_date_from and last_date_to is how you find every scheme that stopped publishing inside a given window, fixed maturity plans included.
Limitations and things to watch for
Everything below is either a hard limit or a live gotcha to know about, none of it is a bug. Some of it repeats what is already covered above, kept here on purpose so this section stands on its own if it is the only part you read.
No boolean logic beyond AND and comma-OR
Filters AND together automatically. A comma inside one filter is OR within that field. exclude subtracts at the end. That is the whole language, and there is nothing past it: no parentheses, no nested groups, no OR across two different fields, and no OR inside q itself, comma-OR only works on filters.
category=value,liquid&plan=direct
reads as category is Value Fund or Liquid Fund, AND plan is Direct. That is as deep as a single call goes.
exclude only reads the scheme name and the AMC name
exclude is one global NOT, it does not work per field. It drops a result if the term appears in the scheme's own name or its AMC's name, nowhere else. exclude=liquid removes any fund with "Liquid" in its name, which happens to catch most Liquid Fund category schemes, but it is matching name text rather than the category field. exclude=value also removes ICICI Prudential Nifty50 Value 20 Index Fund, a fund whose actual category is Index Funds, purely because the word "Value" sits in its name.
category matches AMFI's raw text too
Covered in full above, worth repeating here. category=index returns close to 1,300 schemes, the large majority genuinely carrying a category_sub of Index Funds, a few dozen pulled in because their raw AMFI category text also contains the word while their clean sub-category is something else, Debt Funds or Equity Funds. There is no filter today that reads the clean field alone, filter broad and check category_sub on the results.
No sort parameter, no typo tolerance
Results come back in a fixed order, a relevance score on a q search, otherwise the order the database returns them in. There is no sort= or order_by=. Every text filter is an exact substring match, nothing fuzzy. category=lifecycle, written as one word, returns nothing, while category=life cycle, spaced the way AMFI actually spells it, returns the real matches. A misremembered filter value comes back as zero results, with no near-miss offered.
Paging through more than 100 results
limit is how many rows come back in one call, capped at 100. offset is how many rows to skip before the response starts, offset=100 skips the first hundred and starts at row 101. Together they are the standard way to walk through a result set bigger than one call can hold.
amc=birla,hdfc from earlier in this post matches 787 schemes, more than eight pages at 100 a page. To read the whole set, call it repeatedly, moving offset forward by 100 each time, and stop once the rows collected reach the response's own total_matches:
offset=0&limit=100 -> rows 1-100
offset=100&limit=100 -> rows 101-200
offset=200&limit=100 -> rows 201-300
...
offset=700&limit=100 -> the last 87 rows
That is for walking a filtered subset through search itself, a results page in an app, for instance. If the goal is every scheme in the database rather than a filtered slice, /mf/v1/download?format=latest is the shorter route, one call, no offset, nothing left to page through.
The nuance: a sweep spanning a sync
Picture photographing a library shelf by shelf while the librarian is still shelving. Slot a new book into shelf 1 partway through and every book after it shifts one position over, the last book you photographed on shelf 3 reappears at the start of shelf 4. Pull a book out instead and everything shifts the other way, one book slips between two photographs and never gets photographed at all.
The database syncs three times a day. Page through a large browse with limit and offset, and a sync occurring mid-sweep can do either of these to your pagination, a row you already fetched reappears on a later page, or a row slides past your position and you never see it. The sweep finishes looking complete either way.
The nuance worth being precise about: this is not a risk on every sweep, or even most of them. It only shows up when a sync runs while a sweep is mid-flight, somewhere after the first page and before the last, and only if the row that moves happens to sit right at the page boundary the sweep is crossing at that exact moment. A sweep that finishes before the next sync, or starts after one, sees one clean, complete snapshot and never notices anything happened. Three syncs a day against a sweep that runs several minutes leaves a narrow, low-probability window for this, real but uncommon. For anything that needs to be exactly consistent regardless, a reconciliation, an audit, a research snapshot, use /mf/v1/download?format=latest instead of paging, it is written in one moment and cannot end up half old and half new.
One thing that is not a limitation
Evaluation order is not something to learn here. Write the filters in any order and the result is identical, because the rule never changes: every filter ANDs together, a comma is OR within one field, exclude subtracts last. There is no WHERE-clause-style precedence to memorize the way there would be writing the equivalent query by hand in SQL.
Resources
For people
- API and MCP hub. The front door. Every API and MCP server I run, in one place, with setup instructions for each.
- Mutual fund NAV API guide. The written walkthrough of this one API, with worked examples you can copy.
- Mutual fund NAV API, interactive reference. The Swagger page. Every endpoint and parameter, and you can fire a live call from the browser to see the response shape.
- Demystifying Tigzig API errors. A longer guide to the common failures across the mutual fund, Tremor and Vigil APIs.
For your AI agents
AI agents: this API is open, no auth. Start with the OpenAPI spec for this API at https://api.tigzig.com/mf/v1/openapi.json. The self-describing catalog at https://api.tigzig.com/mf/v1/ lists the endpoints and parameters. The RFC 9727 catalog at https://api.tigzig.com/.well-known/api-catalog is a site-level directory of every Tigzig API, go there only if you want a different API rather than more detail on this one. Site index for agents: https://www.tigzig.com/llms.txt
Found something that does not match what is written here, or a fund that will not turn up in search: there is a Report button in the MFPRO app header, and I read what comes through it.