Living Documentation: Why I'm Not Writing API Contracts Yet
🚨 Long Post Alert.
This is longer than my usual fare. Grab a coffee, settle in, and let’s dive deep.
After spending considerable time defining functional requirements, I found myself at a crossroads: should I now draft API contracts for every endpoint in my PermaTechHub MVP, or was there a smarter way forward? The temptation was real—lay out every path, every request body, every response schema in advance. Get everything perfectly aligned before writing a single line of code. But something kept nagging at me: what if this turned into wasted effort?
This post is about that tension—between planning and flexibility, between documentation and discovery—and why I ultimately decided that living documentation and vertical slices are the path forward, not exhaustive up-front contracts.

The API Contract Question
With all my functional requirements documented—user stories refined, acceptance criteria clarified, constraints and use cases spelled out—it felt natural to take the next step: define the API contracts. After all, I’d already committed to RESTful APIs with URI versioning, standardized error handling, and OpenAPI/Swagger documentation in my architectural decisions. Why not draft the contracts now, before implementation?
I even started setting up the structure: a new api-contracts folder, mirroring the organization of my functional requirements. One file per endpoint, each following a consistent template: path, method, authentication requirements, request and response schemas, error formats, notes. It was clean, organized, and felt like the “right” thing to do. But then doubt crept in.
The problem wasn’t the idea of contracts—it was the timing. My functional requirements had already evolved multiple times before I even started writing them down. Features I thought were essential got cut. New ones—like simulated payments—emerged as I clarified what “realistic MVP” actually meant. And I hadn’t written a single line of implementation code yet. If the requirements were still this fluid, how could I confidently lock down API paths, request payloads, and response schemas?
The Risk of Premature Specification
Here’s the thing about API contracts: they’re more concrete than functional requirements. FRs describe what the system should do; contracts describe exactly how clients will interact with it—down to field names, data types, and HTTP status codes. That level of precision is valuable when you’re ready to implement, but it’s also fragile when the domain is still being understood.
Every contract you write is a bet that you’ve understood the problem correctly. And if you’re wrong—if a feature gets reprioritized, if edge cases emerge, if you realize a better data model during implementation—you have to go back and update the contracts. More files to maintain. More documentation to keep in sync. More places where outdated information can mislead future work.
I’d already learned this lesson with my functional requirements. Despite careful planning, I’d had to add features (like simulated payments), remove others (overly ambitious moderation workflows), and restructure entire sections (splitting User Accounts, Dashboard, and Authentication into separate features). And that was just at the requirements level—before any technical design decisions had been tested against real code.
If I wrote exhaustive API contracts now, I’d essentially be doubling down on assumptions that hadn’t yet been validated by implementation. And for a solo developer working on nights and weekends, that felt like signing up for unnecessary rework.
The Common Wisdom: API-First, But Not API-Everything
Now, to be fair, API-first design is a well-established best practice. The idea is simple: define your API contracts before you start coding, so that frontend and backend teams can work in parallel, and so that the interface is driven by client needs rather than implementation convenience. I’m not arguing against that philosophy—it’s sound, and it works well in mature teams with stable requirements.
But API-first doesn’t mean API-exhaustive. You don’t need to specify every endpoint before you start building. In fact, many teams draft contracts for just the next sprint or iteration, treating them as living documents that evolve alongside the code. The contract serves as a communication tool and a design aid, not as a rigid spec that must be followed to the letter.
The key insight is that contracts and code co-evolve. You draft a contract, implement it, discover edge cases or better models, update the contract, and move on. The documentation stays close to reality because it’s being continuously validated and refined by actual implementation experience.
This is especially true in projects where the domain is still being explored. You don’t know what you don’t know yet. And trying to specify everything up front is a recipe for either wasted effort (if things change) or premature commitment (if you force yourself to stick with suboptimal designs just because they’re already documented).
Learning from Functional Requirements
My experience with functional requirements reinforced this lesson. Even though I’d carefully broken down user stories, identified acceptance criteria, and documented constraints, the FRs themselves had already shifted multiple times—and I expect them to shift more as I start implementing and testing real workflows.
That’s not a failure of planning; it’s a sign of healthy iteration. Requirements evolve as you learn more about the problem space. The trick is to embrace that fluidity without letting it paralyze you. You need just enough structure to guide development, but not so much that change becomes expensive.
The same logic applies to API contracts. If I write contracts for every endpoint now, I’m creating a large surface area of documentation that will need to be updated as requirements evolve. But if I focus on just the next vertical slice—one feature, from data model to API to implementation—I keep the documentation tight, relevant, and easy to maintain.
The Vertical Slice Approach
This is where the idea of vertical slices comes in. Instead of trying to document everything horizontally (all requirements, then all contracts, then all code), you work vertically: pick one feature, define its requirements, draft its contracts, implement it, test it, and deploy it. Then move to the next feature.
This approach has several advantages. First, it limits the scope of documentation at any given time—you’re only maintaining contracts for what you’re actively building. Second, it creates a tight feedback loop between design and implementation—if your contract doesn’t fit the reality of the code, you find out immediately, not months later. Third, it builds momentum and confidence—you have working, tested features to show for your effort, not just piles of speculative documentation.
For a solo developer working on a learning project, this felt like the right balance. I’m not trying to coordinate multiple teams or manage parallel workstreams. I’m trying to build something real, learn from it, and avoid getting stuck in planning paralysis. Vertical slices keep me moving forward without over-committing to designs that might not survive contact with reality.
Treating Documentation as a Living Artifact
So here’s what I decided: instead of writing API contracts for every endpoint now, I’ll draft contracts just-in-time as I work on each feature. When I’m ready to implement user authentication, I’ll define the contracts for login, logout, and token refresh. When I move to listings, I’ll draft contracts for browse, search, detail, and CRUD operations. And so on.
This keeps the documentation close to the code, ensures it reflects real implementation decisions, and avoids the trap of over-specification. It also means the contracts can serve their intended purpose: as communication tools and design aids, not as rigid blueprints that must be followed regardless of what I learn along the way.
And crucially, this approach acknowledges that both functional requirements and API contracts are living documents. They’re not set in stone; they evolve as the project evolves. The goal isn’t to get everything perfect up front—it’s to maintain just enough structure to guide development, while staying flexible enough to adapt when new insights emerge.
What This Means for the Project
Adopting a vertical slice approach and treating documentation as living artifacts has some immediate implications. First, I’ll be focusing on one feature at a time—probably starting with user authentication, since it’s foundational and relatively well-understood. I’ll define the data model, draft the API contracts, implement the endpoints, write tests, and deploy it to my home lab cluster. Only then will I move to the next feature.
Second, I’ll be embracing iteration rather than fearing it. If I realize partway through implementing listings that my data model needs adjustment, I’ll update the requirements, contracts, and code—without feeling like I’ve “failed” at planning. Iteration is the point; that’s how you learn and improve.
Third, I’m acknowledging that AI-assisted development plays a role here. With AI, I expect to move faster on implementation, which means I can afford to spend time on infrastructure, observability, and DevOps without getting bogged down. But that speed only makes sense if the documentation I’m maintaining is tight and relevant—not sprawling and speculative.
The Bigger Picture: Sustainability and Learning
This decision ties back to the broader themes of my sabbatical and this project. I’m not trying to build the perfect system on the first try. I’m trying to learn, iterate, and build something sustainable—both in terms of the technology (permacomputing principles, reusing old hardware) and the process (avoiding burnout, maintaining focus, staying adaptable).
Over-documenting too soon works against that goal. It creates friction, invites rework, and risks turning the project into a chore. But working in vertical slices—picking one feature, going deep, learning from it, and moving on—feels aligned with the kind of disciplined, thoughtful engineering I want to practice.
And honestly? It feels liberating. I’m not locked into decisions I made weeks ago based on incomplete understanding. I’m free to learn, adapt, and improve as I go. That’s the kind of process that leads to better software—and better engineers.
What Comes Next
So where does this leave the project? With a clear path forward—but first, a pause. After so much time spent in the world of documentation, I need to step back and give my brain a break from specs and templates. For the next days, I’ll shift my focus to setting up my infrastructure: getting my home lab cluster humming, configuring Docker and K3s, and making sure my development environment is ready for the real work ahead.
Once the foundation is in place, I’ll return to the product itself, tackling one feature at a time—starting with authentication. I’ll define the data model (users, roles, tokens), draft the API contracts (register, login, logout, refresh), implement the endpoints in Spring Boot, write tests, and deploy to my K3s cluster. Along the way, I’ll document what I learn, update the requirements and contracts as needed, and build confidence in both the architecture and the process.
After authentication is solid, I’ll move to marketplace listings—the core of the platform. Then transactions, then moderation, then observability. Each feature will be a vertical slice, fully implemented and tested before moving on. And each one will teach me something new about the domain, the tools, and myself as an engineer.
This isn’t the fastest path to a feature-complete MVP. But it’s the path that balances ambition with pragmatism, planning with flexibility, and learning with shipping. And that’s exactly what this sabbatical is for: not just building something, but building it the right way—one thoughtful, deliberate step at a time.