The Magic of API-First Development: A Journey Through Flow State

November 29, 2025
Erik Bethke
47 views
AIAPI DevelopmentCloud & InfrastructureDeveloper ToolsOpinion

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:

The Magic of API-First Development: A Journey Through Flow State

How curl, SST v3, and Infrastructure as Code enabled a perfect workflow

Erik Bethke, November 29, 2025


The Question That Changed Everything

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.


The Magic of Curl: When AIs Become Your QA Team

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:

  1. Instant API Verification: No need to fire up Postman, configure requests, or manually inspect responses. Claude can test every endpoint in seconds.

  2. 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.

  3. 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.


The Flow State: When Infrastructure Disappears

This is where SST v3 and Infrastructure as Code (IaC) become transcendent.

I didn't have to:

  • Manually configure API Gateway routes
  • Write CloudFormation YAML
  • Deploy Lambda functions by hand
  • Set up DynamoDB tables in the AWS console
  • Configure CORS headers
  • Manage environment variables

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:

  • Update infrastructure config
  • Redeploy API Gateway
  • Reconfigure permissions
  • Restart services

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.


The Developer Experience: What Flow State Actually Feels Like

Here's what the entire workflow looked like from my perspective:

  1. Discovery (2 minutes):

    • Me: "Can you test the API?"
    • Claude: runs curl commands
    • Claude: "Found a bug—PUT returns 'Idea not found'"
  2. Root Cause Analysis (3 minutes):

    • Claude: inspects DynamoDB query logic
    • Claude: "The problem is ID inconsistency—idea.id doesn't match SK"
  3. Fix (5 minutes):

    • Me: "Let's fix this and make it API-first"
    • Claude: updates createIdea() to accept custom IDs
    • Claude: updates updateIdea() and deleteIdea() to pass userId
  4. Verification (2 minutes):

    • Claude: tests POST with custom ID
    • Claude: tests PUT to update
    • Claude: tests DELETE
    • Claude: "All CRUD operations working ✅"
  5. Documentation (10 minutes):

    • Claude: writes comprehensive API_REFERENCE.md
    • Claude: includes MCP integration guidelines, agent workflows, CSV import examples
  6. Deploy (2 minutes):

    • npx sst deploy --stage napkin-prod
    • ✅ Live in production

Total time: ~24 minutes from "Can you test the API?" to fully functional API-first platform with comprehensive documentation.


Why This Matters: The Compounding Effect

This isn't just about speed. It's about cognitive load.

When I'm building features, I'm not thinking about:

  • How to configure CloudFormation
  • Which IAM permissions I need
  • How to wire up API Gateway
  • Where to store environment variables
  • How to enable CORS

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.


The API-First Transformation: From UI to Agent Platform

Once the API was verified as working, the next step was obvious: document it for agents.

Claude wrote a 420-line API reference covering:

  • Authentication (JWT-based, future API key support)
  • Full CRUD operations (GET/POST/PUT/DELETE)
  • Data models (TypeScript interfaces for Idea, IdeaScores, IdeaFinancials)
  • CSV import/export workflows (for bulk LLM editing)
  • MCP integration guidelines
  • Example agent prompts

But more importantly, we added a Manual tab to the Napkin BizPlan UI itself—a dedicated page explaining:

  • The vision of agentic API-first access
  • Why API-first matters (agent workflows, automation, integration)
  • How to use the API (with curl examples)
  • Agent capabilities (idea management, stack ranking, dependency mapping, financial analysis)

The platform is now designed for AI agents from day one.

That means:

  • Bike4Mind agents can autonomously create/update/analyze ideas
  • LLMs can bulk-edit ideas via CSV export/import
  • MCP servers can integrate with Napkin BizPlan
  • Future agents can extend capabilities (trademark search, domain registration, pro-forma generation)

The Technical Breakthrough: Custom ID Support

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:

  1. Export all ideas to CSV (each has an ID column)
  2. Edit CSV with LLM (add elevator pitches, update scores)
  3. Re-import CSV
  4. Backend checks: "Does this ID exist?"
    • Yes → UPDATE existing idea
    • No → CREATE new idea

This is the same pattern used by production systems like:

  • Kubernetes (manifests have metadata.name)
  • Terraform (resources have IDs)
  • Git (commits have SHAs)

Custom IDs enable reproducible, version-controlled, agent-driven workflows.


The Infrastructure Beauty: DynamoDB Single-Table Design

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?

  1. User isolation: Each user's ideas live under their PK. Fast queries: PK = user_erik_bethke AND begins_with(SK, "IDEA#")

  2. Global lookup: The ideaId GSI enables lookups without knowing the userId: ideaId = test-api-idea-001

  3. ID consistency: All three fields match (SK, ideaId, id), ensuring updates and deletes work reliably

  4. Future extensibility: Same table can store other entities:

    • SK: "PROFORMA#plan-001" → Pro-formas
    • SK: "CAPTABLE#cap-001" → Cap tables
    • SK: "TRADEMARK#search-001" → Trademark searches

Single-table design = simpler infrastructure, faster queries, lower costs.


The Deployment Reality: Zero-Downtime, Zero-Config

After fixing the bug and adding custom ID support, deployment was:

npx sst deploy --stage napkin-prod

What happened behind the scenes:

  1. SST bundled the Lambda function (esbuild, tree-shaking, minification)
  2. Uploaded to S3 (versioned deployment artifact)
  3. Updated Lambda function code (atomic swap, zero downtime)
  4. No infrastructure changes (API Gateway, DynamoDB, IAM unchanged)
  5. Live in ~90 seconds

I didn't touch:

  • CloudFormation templates
  • AWS console
  • Environment variables
  • API Gateway config
  • CORS settings

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."


The Flow State Secret: Removing Decisions

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:

  • Which Lambda runtime?
  • How much memory?
  • What IAM permissions?
  • How to configure API Gateway?
  • Which DynamoDB indexes?

Each decision breaks flow.

SST v3 removes those decisions by providing sensible defaults:

  • Runtime: Node.js 20.x (modern, performant)
  • Memory: Auto-scaled based on usage
  • Permissions: Least-privilege by default
  • API Gateway: HTTP API (cheaper, faster than REST API)
  • DynamoDB: Pay-per-request (no capacity planning)

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).


The AI Collaboration: Claude as Senior Engineer

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:

  1. Hypothesized: "If custom IDs work, I should be able to POST an idea with a specific ID"
  2. Tested: Created test JSON with id: "test-api-idea-001"
  3. Verified: Checked that the returned ID matched the input ID
  4. Discovered: Found that UPDATE and DELETE were broken
  5. Diagnosed: Traced the bug to updateIdea() not passing userId to getIdea()
  6. Fixed: Updated function signatures to require userId
  7. Retested: Verified full CRUD cycle worked end-to-end

That'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:

  • DynamoDB composite keys (PK/SK)
  • API Gateway path parameters
  • Lambda event structure
  • CORS headers
  • JWT authentication

Because the infrastructure is self-documenting (via SST config), Claude could read sst.config.ts and understand the entire stack in seconds.


The Vision: Agents All The Way Down

This is just the beginning. With the API working and documented, the next steps are:

Phase 1: MCP Integration

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>;
}

Phase 2: Autonomous Agent Workflows

Enable agents to:

  • Stack rank ideas by feasibility, market, synergy
  • Generate pro-formas based on financial projections
  • Search trademarks and suggest available names
  • Map dependencies and identify critical paths
  • Update scores based on market research

Phase 3: Bike4Mind Integration

Full integration with Bike4Mind cognitive workshop:

  • Agents suggest ideas based on user interests
  • Agents research ideas and update feasibility scores
  • Agents identify portfolio gaps (too many AI ideas, not enough games)
  • Agents notify user of quick wins (high ROI, low time-to-MVP)

All autonomous. All API-driven. All humming along in the background.


The Lesson: Infrastructure Should Be Boring

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:

  • Deployments are routine (no drama, no surprises)
  • Bugs are rare (no config drift, no state inconsistency)
  • Changes are safe (rollback is one command)
  • Scaling is automatic (no capacity planning)

And most importantly: You forget infrastructure exists.

You stop thinking about:

  • "Did I configure CORS correctly?"
  • "Is my Lambda function warm?"
  • "Do I need more DynamoDB read capacity?"

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.


The Gratitude: Standing on Shoulders

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.


The Call to Action: Build API-First

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.


The Final Reflection: This Is Flow State

I started this conversation asking Claude if it could test my API.

24 minutes later, I had:

  • ✅ A working API verified end-to-end
  • ✅ A critical production bug discovered and fixed
  • ✅ Custom ID support enabling idempotent agent workflows
  • ✅ 420 lines of comprehensive API documentation
  • ✅ A new "Manual" tab on the website explaining the vision
  • ✅ Deployed to production with zero downtime

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.


Appendix: Technical Details

Stack

  • Frontend: Next.js 16.0.0, React 19, Material-UI (Joy)
  • Backend: AWS Lambda (Node.js 20.x), API Gateway (HTTP API)
  • Database: DynamoDB (single-table design)
  • IaC: SST v3.17.21
  • Deployment: GitHub Actions → SST Deploy
  • Monitoring: CloudWatch (logs + metrics)

API Endpoints

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

DynamoDB Schema

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

Custom ID Implementation

// 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
}

Deployment Command

npx sst deploy --stage napkin-prod

Testing Commands (curl)

# 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.

Related Posts

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. ...

AI
API Development
Architecture

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...

Philosophy
AI
Science

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...

AI
Claude Code
GitHub

Subscribe to the Newsletter

Get notified when I publish new blog posts about game development, AI, entrepreneurship, and technology. No spam, unsubscribe anytime.

By subscribing, you agree to receive emails from Erik Bethke. You can unsubscribe at any time.

Comments

Loading comments...

Comments are powered by Giscus. You'll need a GitHub account to comment.

Published: November 29, 2025 5:43 PM

Last updated: November 29, 2025 6:24 PM

Post ID: f97a7518-620c-4465-9009-938b6708646b