A deep dive into how curl, SST v3, and Infrastructure as Code enable perfect developer flow state. When Claude discovered a production bug through API testing, we went from discovery to deployment in 24 minutes—without ever leaving the terminal. This is the story of transforming Napkin BizPlan into an API-first platform designed for autonomous AI agents.
Share this post:
Export:
How curl, SST v3, and Infrastructure as Code enabled a perfect workflow
Erik Bethke, November 29, 2025
It started with a simple question from me to Claude: "Can you login and export and import ideas?"
I'd been building Napkin BizPlan as a UI-first business planning tool. But my real vision was bigger: I wanted AI agents to manage my idea pipeline. Not as a gimmick, but as a genuine workflow—autonomous agents from Bike4Mind helping me stack rank projects, generate pro-formas, search trademarks, and identify dependencies.
The problem? I'd built a web UI. Claude can't use web UIs. But Claude can use APIs.
So I asked: "Can you test the API with curl?"
The answer changed everything.
Here's what blew my mind: Claude can test APIs directly via curl commands.
Most developers think of curl as a debugging tool—something you use manually to spot-check endpoints. But when you give an AI like Claude access to curl, something magical happens:
Instant API Verification: No need to fire up Postman, configure requests, or manually inspect responses. Claude can test every endpoint in seconds.
Discovery Through Testing: Claude doesn't just run the tests I ask for—it discovers bugs I didn't know existed. It tried a PUT request and got "Idea not found." That led to discovering a critical ID mismatch bug that would've broken every update operation.
End-to-End Workflows: Claude tested the full CRUD cycle—POST to create, PUT to update, DELETE to remove—all in a single conversation. It even caught that I needed to write JSON to temp files because inline JSON breaks curl syntax.
Here's what that looked like:
# Claude tested login
curl -X POST https://0e0qra818e.execute-api.us-east-1.amazonaws.com/auth/login \
-H "Content-Type: application/json" \
-d '{"password":"YOUR_PASSWORD"}'
# Discovered the API worked for GET
curl https://0e0qra818e.execute-api.us-east-1.amazonaws.com/ideas
# Found a bug with PUT
curl -X PUT https://0e0qra818e.execute-api.us-east-1.amazonaws.com/ideas/test-id \
-H "Content-Type: application/json" \
-d '{"elevatorPitch":"Updated"}'
# Result: {"error":"Internal server error","details":"Idea not found"}
# Root cause: ID mismatch in DynamoDB
# idea.id = "csv-import-1764432524946-3"
# SK = "IDEA#0772b061-63c3-4a4e-b99f-906bf2a48e9c" // Different UUID!
That last discovery was the critical moment. Claude found a production bug through API testing that I never would've caught through UI testing.
Why? Because the UI only showed the idea.id field. But the database was using a different UUID in the sort key (SK). Updates and deletes were querying by the wrong ID, failing silently.
This is where SST v3 and Infrastructure as Code (IaC) become transcendent.
I didn't have to:
All of that was already done. It just worked.
Here's the entire infrastructure setup for the Ideas API:
// sst.config.ts
const ideas = new sst.aws.Function("Ideas", {
handler: "src/functions/ideas.handler",
environment: {
IDEAS_TABLE: table.name,
},
});
api.route("GET /ideas", ideas.arn);
api.route("POST /ideas", ideas.arn);
api.route("PUT /ideas/{ideaId}", ideas.arn);
api.route("DELETE /ideas/{ideaId}", ideas.arn);
That's it. Four lines to define four endpoints.
When Claude found the bug, I didn't have to:
I just fixed the Lambda function code:
// Before
async function createIdea(ideaData: Omit<Idea, 'id' | 'createdAt' | 'updatedAt'>): Promise<Idea> {
const ideaId = randomUUID(); // ❌ Always generated new ID
// After
async function createIdea(ideaData: Omit<Idea, 'createdAt' | 'updatedAt'> & { id?: string }): Promise<Idea> {
const ideaId = ideaData.id || randomUUID(); // ✅ Use provided ID or generate new
Deployed with a single command:
npx sst deploy --stage napkin-prod
Under 2 minutes from bug discovery to production fix. No infrastructure changes. No config updates. No downtime.
Here's what the entire workflow looked like from my perspective:
Discovery (2 minutes):
Root Cause Analysis (3 minutes):
idea.id doesn't match SK"Fix (5 minutes):
Verification (2 minutes):
Documentation (10 minutes):
Deploy (2 minutes):
npx sst deploy --stage napkin-prodTotal time: ~24 minutes from "Can you test the API?" to fully functional API-first platform with comprehensive documentation.
This isn't just about speed. It's about cognitive load.
When I'm building features, I'm not thinking about:
I'm thinking about business logic.
The infrastructure is invisible. SST handles it. AWS handles it. It just works.
That means I stay in flow state—the mental zone where you're 100% focused on solving the actual problem (making a great business planning tool) instead of fighting incidental complexity (infrastructure configuration).
And when bugs surface, Claude can test them instantly via curl. No context switching to Postman. No manual clicking through UIs. Just:
curl -X PUT https://api.napkinbizplan.com/ideas/test-id -d @data.json
Instant feedback. Instant iteration.
Once the API was verified as working, the next step was obvious: document it for agents.
Claude wrote a 420-line API reference covering:
Idea, IdeaScores, IdeaFinancials)But more importantly, we added a Manual tab to the Napkin BizPlan UI itself—a dedicated page explaining:
The platform is now designed for AI agents from day one.
That means:
The key technical insight was enabling custom IDs.
Most CRUD APIs auto-generate UUIDs for new records. That works for UI-driven workflows, but breaks agent workflows:
Problem: If an agent exports ideas to CSV, edits them with an LLM, and re-imports, how do you match edited rows to existing database records?
Naive Solution: Match by title. But titles can change. And what if two ideas have the same title?
Correct Solution: Custom IDs. When creating an idea, optionally provide an id field:
{
"id": "custom-idea-001", // ✅ Explicitly provided
"title": "AI-First Accounting",
"elevatorPitch": "QuickBooks alternative",
// ...
}
Now the agent workflow is idempotent:
This is the same pattern used by production systems like:
metadata.name)Custom IDs enable reproducible, version-controlled, agent-driven workflows.
Here's the DynamoDB schema that makes all this possible:
{
PK: "user_erik_bethke", // Partition Key (user ID)
SK: "IDEA#test-api-idea-001", // Sort Key (IDEA# prefix + idea ID)
ideaId: "test-api-idea-001", // Indexed for lookups without userId
id: "test-api-idea-001", // Returned to API clients
title: "API Test Idea",
elevatorPitch: "Testing custom ID creation",
// ... rest of idea fields
}
Why this design?
User isolation: Each user's ideas live under their PK. Fast queries: PK = user_erik_bethke AND begins_with(SK, "IDEA#")
Global lookup: The ideaId GSI enables lookups without knowing the userId: ideaId = test-api-idea-001
ID consistency: All three fields match (SK, ideaId, id), ensuring updates and deletes work reliably
Future extensibility: Same table can store other entities:
SK: "PROFORMA#plan-001" → Pro-formasSK: "CAPTABLE#cap-001" → Cap tablesSK: "TRADEMARK#search-001" → Trademark searchesSingle-table design = simpler infrastructure, faster queries, lower costs.
After fixing the bug and adding custom ID support, deployment was:
npx sst deploy --stage napkin-prod
What happened behind the scenes:
I didn't touch:
Everything just worked.
This is the promise of Infrastructure as Code. Not "infrastructure is complex but at least it's versioned." But "infrastructure is invisible because it's correct by default."
Here's the deeper insight: Flow state isn't about working faster. It's about removing decisions.
Every time you have to think about infrastructure, you're making decisions:
Each decision breaks flow.
SST v3 removes those decisions by providing sensible defaults:
You only make decisions when the default is wrong. 95% of the time, the default is right.
That means 95% of your mental energy goes to solving the problem (building a great business planning tool), not fighting the platform (configuring infrastructure).
Here's what makes this workflow truly magical: Claude isn't just executing commands. It's thinking.
When testing the API, Claude didn't just blindly run curl commands. It:
id: "test-api-idea-001"updateIdea() not passing userId to getIdea()userIdThat's the behavior of a senior engineer. Not a junior following instructions, but an experienced developer reasoning about the system.
And the magic is: I didn't have to break flow state to explain the infrastructure. Claude already understood:
Because the infrastructure is self-documenting (via SST config), Claude could read sst.config.ts and understand the entire stack in seconds.
This is just the beginning. With the API working and documented, the next steps are:
Create a Model Context Protocol server for Napkin BizPlan:
interface NapkinMCPServer {
listIdeas(filters?: IdeaFilters): Promise<Idea[]>;
createIdea(data: CreateIdeaInput): Promise<Idea>;
updateIdea(id: string, updates: UpdateIdeaInput): Promise<Idea>;
rankIdeas(criteria: RankingCriteria): Promise<RankedIdea[]>;
analyzeDependencies(): Promise<DependencyGraph>;
}
Enable agents to:
Full integration with Bike4Mind cognitive workshop:
All autonomous. All API-driven. All humming along in the background.
Here's the controversial take: Good infrastructure is boring.
Not "boring" as in uninteresting. "Boring" as in predictable, reliable, invisible.
When your infrastructure is boring:
And most importantly: You forget infrastructure exists.
You stop thinking about:
You just build features. And when bugs appear, you test with curl, fix the code, and deploy in 90 seconds.
That's flow state. That's the magic.
None of this would be possible without:
SST v3: Infrastructure as Code that actually respects your time. No boilerplate. No config bloat. Just new sst.aws.Function() and it works.
AWS Lambda: Serverless compute that scales from zero to millions without you thinking about it. No servers to patch. No containers to orchestrate.
DynamoDB: Single-table design that handles everything from user auth to pro-formas without you writing schema migrations.
Claude (Sonnet 4.5): An AI that can test APIs, diagnose bugs, write production code, and generate comprehensive documentation—all while explaining its reasoning.
curl: The 27-year-old command-line tool that's still the best way to test HTTP APIs. Simple. Composable. Universal.
If you're building a SaaS product in 2025 and you're not API-first, you're missing the future.
UI-first is for humans. And humans are slow.
API-first is for agents. And agents are 24/7.
Your competitive advantage isn't your UI. It's your API. It's how fast AI agents can integrate with your platform. It's how autonomously your users can automate their workflows.
So build the API first. Document it well. Make it agent-friendly.
Then build the UI as a convenience layer on top.
Because the future isn't users clicking buttons. It's agents orchestrating workflows. And if your platform can't integrate with agents, you'll be left behind.
I started this conversation asking Claude if it could test my API.
24 minutes later, I had:
I never left my terminal. I never opened the AWS console. I never debugged infrastructure.
I just described what I wanted, Claude tested it, we fixed the bugs, and SST deployed it.
That's the magic. That's the flow state. That's the future of software development.
Infrastructure that disappears. APIs that self-document. AI that thinks. And humans that stay focused on what matters: building great products.
Want to try the API? Visit napkinbizplan.com and check out the Manual tab.
Built with SST v3, AWS Lambda, DynamoDB, and Claude Code.
Because infrastructure should be invisible, APIs should be first-class, and AI should be your pair programmer.
Welcome to the future.
POST /auth/login → JWT authentication
GET /ideas → List all ideas
POST /ideas → Create new idea (optional custom ID)
PUT /ideas/:ideaId → Update idea (partial updates)
DELETE /ideas/:ideaId → Delete idea
Table: napkin-prod-IdeasTable
PK (String): user_{userId} // Partition key
SK (String): IDEA#{ideaId} // Sort key
ideaId (String): {ideaId} // GSI for global lookup
GSI: IdeaIndex
ideaId (String): {ideaId} // Partition key
// Accept optional ID in createIdea()
async function createIdea(
ideaData: Omit<Idea, 'createdAt' | 'updatedAt'> & { id?: string }
): Promise<Idea> {
const ideaId = ideaData.id || randomUUID(); // Use provided or generate
// ... create item with ideaId
}
npx sst deploy --stage napkin-prod
# Login
curl -X POST https://0e0qra818e.execute-api.us-east-1.amazonaws.com/auth/login \
-H "Content-Type: application/json" \
-d '{"password":"YOUR_PASSWORD"}'
# Create idea with custom ID
curl -X POST https://0e0qra818e.execute-api.us-east-1.amazonaws.com/ideas \
-H "Content-Type: application/json" \
-d @idea.json
# Update idea
curl -X PUT https://0e0qra818e.execute-api.us-east-1.amazonaws.com/ideas/test-id \
-H "Content-Type: application/json" \
-d '{"elevatorPitch":"Updated pitch"}'
# Delete idea
curl -X DELETE https://0e0qra818e.execute-api.us-east-1.amazonaws.com/ideas/test-id
End.
The Control Plane: Maximizing the Human-Machine Interface
The paradigm shift as fundamental as mobile or cloud: humans commanding autonomous agent fleets. Not chatbots—control planes. Not UI-first—API-first. ...
Leylines: On Discovery, Creation, and Navigating the Hyperdimensional Universe
Everything that can exist, does exist—somewhere in the vast hyperdimensional universe. The question isn't whether to discover or create, but how effic...
Claude Code as My GitHub Project Manager: 35 Issues Triaged in Minutes
How Claude Code helped me triage 35 GitHub issues, close 9 completed features, create app labels, and build a ghetto newsletter system - all while shi...
Get notified when I publish new blog posts about game development, AI, entrepreneurship, and technology. No spam, unsubscribe anytime.
Published: November 29, 2025 5:43 PM
Last updated: November 29, 2025 6:24 PM
Post ID: f97a7518-620c-4465-9009-938b6708646bLoading comments...