What Building sIndeX Taught Me About Handing Over Code
sIndeX started as a script to automate my own repetitive SEO analysis, and the acquisition taught me that undocumented knowledge is a liability with a price attached.
sIndeX was not a product idea. It was a script that grew.
I was doing search-performance analysis by hand, on a schedule, and the work was identical every time. Pull the data. Compare it against the previous period. Find the pages that moved. Find the queries where impressions were rising and clicks were not. Write it up. Repeat.
So I automated the pulling. Then the comparison. Then the part where I decided what counted as an opportunity worth flagging. At some point the thing that had been a folder of scripts had a scheduler, a database, and a name, and it was collecting search-performance data through Google APIs and producing the analysis I used to spend afternoons producing manually.
In February 2024 it was acquired by Michael Paul Salinas, a U.S. Marine veteran and entrepreneur. What follows is what the process taught me, most of which was uncomfortable.
Automating your own work is a better starting point than having an idea
I did not do user research. I did not validate a market. I had a problem I understood completely, because I was the one experiencing it every week, and the feedback loop was immediate: either the tool saved me the afternoon or it did not.
That gave me something most side projects lack, which is a real specification. I knew which edge cases mattered because I had hit them by hand. I knew which parts of the analysis were judgment calls and which were mechanical, because I had been making those judgment calls myself. When the automated version produced something wrong, I noticed instantly, because I knew what the right answer looked like.
The limitation is equally real. A tool built to fit one person’s workflow encodes one person’s assumptions. Some of what I built was general. Some of it was the shape of my own habits, made durable in code, and separating those two categories later was work.
Rate limits are architecture, not an edge case
The most important design constraint in the entire system was quota.
Google’s APIs meter you. There is a ceiling on requests, and the data you want for a meaningful analysis does not fit under the ceiling if you ask for it naively. This is not something you handle with a retry wrapper bolted on at the end. It determines the shape of the whole system.
The collector had to be resumable, because a run that dies halfway cannot start over without burning the quota twice. State had to live in the database rather than the process, so a job could pick up where it stopped. Backfill and incremental update became genuinely different code paths with different pacing. A single 429 was normal operating condition rather than an error, and the retry logic had to distinguish “slow down” from “this request will never work.”
# Every collector job records its position before doing anything else.
# Crash recovery is then a query, not a replay.
cursor = db.get_cursor(site_id, date)
for window in remaining_windows(cursor):
rows = api.fetch(site_id, window) # may raise QuotaExceeded
db.write(rows)
db.advance_cursor(site_id, window.end)
I did not design it this way from the start. I designed it this way after the third time a long collection failed at hour two and I had nothing to show for the quota it had spent. The rewrite was small, and it should have been the first version.
If an external service imposes a hard limit on your throughput, that limit is a first-class input to your architecture. Treating it as an exception to handle later means rebuilding around it later.
What due diligence actually asked about
I had expected questions about the code. Structure, quality, test coverage, technical debt. Some of that came up. It was not the center of it.
What the buyer actually wanted to know was operational.
What does this cost to run every month, and what makes that number move? Not the current bill, the function. If usage doubles, does the cost double, or does it hit a quota wall and stop working?
Which credentials does it hold, where do they live, and what is the procedure for rotating them? I had an answer, but the answer was “I would do it manually, and I know which four places to look.” That is not a procedure. That is a person.
What happens when it breaks at three in the morning? How would someone know? What is the first thing they would check?
And the question that mattered most: how much of this system exists only in your head?
I gave an honest answer and it was not a good one.
Undocumented knowledge is a liability with a price attached
This is the part I actually want to write down, because it changed how I work.
I knew a great deal about sIndeX that was written nowhere. Why one API call used a date offset that looked arbitrary. Which failure in the logs was benign and which meant real data loss. Why a particular threshold in the opportunity-detection logic was set where it was, which was because I had tuned it by eye against data I no longer had. Why one module had a structure that made no sense unless you knew what it had originally been.
None of that transfers in a handover call. I tried. You can walk someone through a system for hours and what they retain is the map, not the terrain. The moment they hit something surprising, they do not have the context that would tell them whether it is a bug or a deliberate choice, and they have no way to find out except asking me.
Tribal knowledge feels like an asset while you hold it. In a handover it is the opposite. Every undocumented decision is a question the new owner has to answer expensively, and that cost sits either in the valuation or in your calendar for the following months. It was in my calendar.
The uncomfortable version of this: I had unconsciously treated being the only person who understood the system as a form of security. It is not security. It is a constraint on what the thing can become, and it caps its value.
What I would do differently
Write the runbook first. Before the feature work, not after. What the system does, how to start and stop it, what the alerts mean, what to do when each one fires. If you cannot write the runbook, you do not yet understand the operational shape of what you are building. Writing it early surfaces that while the design is still cheap to change.
Make the boring parts legible. Credential rotation, backup and restore, the deploy procedure, the manual steps you perform occasionally and remember only because you invented them. These are the parts nobody documents because they are not interesting, and they are precisely the parts a new owner needs on day one.
Write a README that explains why. Most READMEs describe how to run the thing. Installation, environment variables, the start command. Useful, and it is the least of what a reader needs. The valuable document explains why the system is shaped this way. Why the collector is resumable. Why that threshold is set there. What was tried and abandoned, and what broke when it was tried. A future maintainer can read code to learn how. They cannot read code to learn why, and without why they will either preserve a workaround that is no longer needed or remove a safeguard that still is.
I would add one habit to that list: record decisions when you make them, in a plain file in the repository, dated. Not a formal architecture document. Two or three sentences saying what you chose and what you rejected. It takes a minute at the time and is close to unrecoverable a year later.
Honest scale
This was a small acquisition. It did not change my life or my finances in any meaningful way, and I want to be clear about that rather than let the word “acquired” do work it has not earned.
The value was in what it forced me to learn. Building something you use yourself teaches you to build for a real specification. Running it against a metered external API teaches you that constraints belong in the design. And handing it to someone else teaches you, more directly than any code review, exactly how much of your system was never actually written down.
I write documentation differently now. Not because someone told me to, but because I have felt the specific cost of not having it.