← Back to work
MeroShare has no API. Everything past this point was reverse engineered from a browser's network tab, one blocked request at a time.
Case Study
An unofficial autopilot for MeroShare, Nepal's IPO portal, built by reverse engineering its network traffic since it publishes no API.
IPOPilot links a MeroShare account and is supposed to notice when a new share issue opens, submit an application on your behalf, and check back later for the result. MeroShare is the only way to apply for an IPO in Nepal, and doing that by hand for more than one account, on time, every time a window opens, is exactly the kind of repetitive task a computer should be doing instead.
It has no official API. CDSC, the government-linked body that runs it, publishes nothing beyond the web app itself, so everything IPOPilot knows about MeroShare came from watching that web app's own network traffic and rebuilding it from the outside.
That approach ran into a wall CDSC built on purpose: a web application firewall that rejects anything that doesn't look like a real browser. Getting past it for some endpoints and never for others is most of this project's story.
It is also, honestly, unfinished. Two endpoints stayed blocked no matter what, a rewrite to work around them stalled partway through, and the project sat untouched for weeks before this pass came back to clean it up and tell the truth about where it stood.
Year
2025
Disciplines
Reverse engineering
Backend development
Security
API integration
Every year, Nepali brokerages open a short window to apply for shares in a new IPO through MeroShare, the web portal run by CDSC, the government-linked body that clears and settles every trade on the Nepal Stock Exchange. Applying takes a few minutes per account: log in, pick the issue, enter how many units and which bank to debit, confirm with a transaction PIN. Doing that once is nothing. Doing it for several accounts, on time, every time a window opens for weeks at a stretch, is the kind of task a computer should be doing instead of a person.
CDSC does not publish an API for any of this. There is no documentation, no developer portal, no rate limit written down anywhere, nothing to read except the MeroShare web app itself. So that's what IPOPilot is built on: opening the browser's network tab, watching what the real site sends when a real person logs in, searches for an issue, and applies, and rebuilding that exact sequence of requests from the outside.
None of the vocabulary is documented either. A BOID is the number that identifies a demat account. A DP is the depository participant, the brokerage the account sits under. A kitta is one unit of shares. None of that is written down for a developer to find. It's written down for an investor filling out a form, and the only way to learn the shape of the API underneath that form was to fill it out once by hand and watch what went over the wire.
The first version called MeroShare's endpoints straight from the server, the same way any other integration talks to any other API: from the Next.js API routes, and from the background worker that would eventually do the actual applying. No browser anywhere in the request path, because there wasn't supposed to need to be one.
CDSC's backend disagreed. A request that didn't look like it came from a real browser hitting the real site got a web application firewall response instead of data, an HTML challenge page where JSON was expected, for almost everything that mattered: logging in, reading an issue, submitting an application. It didn't fail loudly with a clear error. It just came back with the wrong shape of response, over and over, until it was obvious the requests themselves were the problem, not anything about the data being asked for.
One commit message from that stretch reads, in full: Encountered fatal API error (Nepali Government Sucks). It isn't a technical description of anything. It's what gets typed at the end of a long session of the same request failing the same way, for reasons that don't show up anywhere in the response body, and it's a reasonably honest summary of where the project stood at that point: blocked, with no error message worth debugging.
The fix was to stop pretending to be a server and start looking like the browser CDSC expected. Every call to MeroShare picked up a real Chrome user agent, an Origin and Referer pointing at meroshare.cdsc.com.np, and, once a session existed, the cookies that session had actually been issued. A Next.js API route sits in front of all of it as a proxy: the browser calls IPOPilot's own /api/proxy/meroshare/[...path], which adds those headers server side and forwards the request on to CDSC's real backend.
CDSC's login has its own quirks that had to be handled by hand. The session token doesn't come back in the response body the way it would from almost any other login endpoint. It comes back in the Authorization response header, and it's used on every later request as a raw value in that same header with no "Bearer" prefix in front of it, which isn't what any standard client library assumes. Browsers can't read most response headers across origins by default, so the proxy re-exposes CDSC's token under its own header for the browser to pick up and send back on the next call.
That was enough to get most of the product working. Logging in, linking an account, submitting an application, and later checking whether it was allotted all started returning real data instead of a challenge page. The client also retries a 429 or a 5xx with exponential backoff, but never a 401 or 403, because those mean bad credentials or a firewall block, not a server having a bad moment, and retrying either one just sends more suspicious traffic at CDSC for nothing.
Two endpoints never accepted any of it. /companyShare/active, the list of currently open issues, and /portfolio/, an account's holdings, came back as a firewall challenge page under every combination of header, cookie, and timing that got tried, even though nearby endpoints on the exact same session, using the exact same token, worked without complaint. Whatever CDSC's firewall scores to tell a script from a person, it was scoring these two differently from the rest, consistently, for weeks.
Without the issue list, IPOPilot has no way to learn a new IPO opened except being told. That defeats a large part of the point: an autopilot that can't see the runway isn't much of an autopilot. Losing both endpoints to the exact same wall at the same time made it clear this wasn't a one-off, fixable quirk. It was a line CDSC had actually drawn.
CDSC's firewall doesn't protect everything it runs. A plain public page listing open issues, meant for anyone browsing the site, sits outside whatever is guarding the MeroShare API proper. The plan was to scrape that page on a schedule instead of asking MeroShare's own endpoint, closing the gap the blocked issue list had left open.
The public page doesn't carry everything the internal API would have: the internal company share ID MeroShare uses, and the minimum and maximum unit an applicant is allowed to request. So the design became a human in the loop for exactly those three numbers. A worker job scrapes the page, creates a flagged issue with whatever it found, and notifies the user that a new issue needs three fields filled in by hand before the rest of the pipeline, applying, notifying, checking results, can pick it up automatically the same way it already did for issues entered directly.
That plan is where the project stopped. The database model, the worker job, and several new API routes for it all exist in the codebase, added and then never committed. In the same stretch, the old portfolio page and the components under it were deleted to make room for a replacement that was never finished, leaving the repository with no working portfolio view at all, old or new. File timestamps in the working copy put the last real activity in early July, and nothing had been touched since, not a commit, not an edit, until this pass came back to it weeks later. The project didn't fail. It just stopped, mid sentence, on the one part that still didn't have a clean answer.
The first thing this pass found wasn't about CDSC at all. pnpm build had been failing since the project's very first commit, on a type error in the NextAuth route that had nothing to do with MeroShare. A hand written type declaration, added before any of the CDSC work started, fully redeclared next-auth's own module instead of narrowly extending it the way next-auth's documentation asks for, and in doing so invented a return shape for its main function, an object with separate GET and POST methods, that doesn't match what the installed package actually returns: one callable request handler. Next's build time route checker had been correctly rejecting that mismatch the entire time. The fix was to delete the whole file and replace it with the two-line augmentation next-auth's own docs already describe.
A handful of smaller things had also been sitting half done. Settings had buttons for changing a password and deleting an account that did nothing, because no API route existed behind them yet; both got wired up for real. Building the application flow surfaced something the original design had missed entirely: MeroShare won't accept an application without a transaction PIN, so accounts needed a new encrypted field for it, alongside the encrypted password they already had. The old portfolio page, deleted mid pivot and never replaced, became a history view pulled live from CDSC instead of the local database, which sidesteps needing the blocked portfolio endpoint at all for that particular screen.
The project has carried three names. It started as IPOBharuwa, bharuwa being a porter, someone who carries a load for you. For most of its life it went by IPOBaje instead, baje being a colloquial word for an elder, the old man who takes care of things, while the GitHub repository itself stayed on the older name, so the two disagreed with each other for a while. This pass renamed everything, the repository, every package, the dashboard's own text, to IPOPilot, a name that doesn't require a reader to already know Nepali, and that leans on the same idea the project was always going for: you handle the one manual step, filling in three numbers by hand, and it flies the rest on autopilot. The README got rewritten at the same time, trading an aspirational feature list for a status section that says, plainly, what's finished, what's blocked, and what's still in progress, which is also most of what this page has been telling you.
Blocked
2
Broken
1
Renamed
3
Registration, linking an account, submitting an application, and checking allotment results all work end to end against the real MeroShare API. Automatic issue detection from MeroShare's own endpoint and portfolio holdings do not, and won't without driving an actual browser, which nothing in this codebase does yet. The scrape-and-complete pivot is built on the backend but not confirmed wired all the way to the dashboard. There is no test suite anywhere in the repository, and no deployment configuration for either the web app or the worker.
There's a second security pass underway right now, not yet committed. The encryption logic that used to be duplicated, byte for byte, between the web app and the worker is being pulled into one shared package. More importantly, every account is moving off a single symmetric key shared by the whole database and onto envelope encryption, a separate key per account wrapped by a master key, so one leaked key stops being able to decrypt every stored password and transaction PIN at once. Neither has landed yet. That it's what's being worked on next, instead of a nicer dashboard, is probably the most honest thing this page can say about where the project's priorities actually are.