1
0 Comments

What I wish I knew before building an API-first product

I spent six months building what I thought was a clean, well-designed API. Good REST conventions, consistent naming, sensible status codes. By the time I had real users, I'd already broken their integrations twice, undercharged everyone, and written auth code I'm still embarrassed about.

This is the stuff I wish someone had told me upfront.

  1. Versioning isn't optional, and "v1" in the URL isn't enough

Every tutorial shows you /api/v1/ and calls it a day. That's not a versioning strategy, it's a placeholder.

The real question is: what counts as a breaking change? Adding a required field? Renaming a key? Changing a status code? I had no clear answer to any of those when I shipped, and I paid for it when I silently broke an integration by renaming a response field that "nobody was using."

Stripe's engineering post on API versioning is worth reading carefully. They pin each API key to the version it was created under, so existing users never see breaking changes unless they explicitly upgrade. You don't have to do exactly that, but you need some answer before you have users, not after.

At minimum: document what constitutes a breaking change, give users a deprecation window (90 days is standard), and send emails before you break anything.

  1. Auth is the first thing users touch and the last thing you want to rush

I shipped with API keys stored as plaintext in the database. I know.
The correct baseline: hash your API keys at rest (treat them like passwords), only show the full key once at creation time, and prefix them so users can identify your keys in their own codebases (e.g. sk_live_...). Stripe, GitHub, and Twilio all do this for good reason.

Also think about scopes before you think you need them. "Read-only keys" and "write keys" sounds like over-engineering on day one. It becomes a very reasonable request from enterprise users on day 60.

  1. Rate limiting is a feature, not an insult

I didn't add rate limiting early because I didn't want to seem restrictive. Within two weeks I had a user accidentally hammering my endpoint in a loop and taking down the service for everyone else.

Rate limiting protects your other users. It also protects that user. They were horrified when I told them what had happened. A clear 429 Too Many Requests with a Retry-After header is friendlier than a mysterious 503. Treat it as a guard rail, not a penalty.

  1. Pricing per-call is harder than it looks

If you charge based on API usage, you need to answer these questions before launch:

Do you charge for failed requests? (Most don't, but you should decide.)
What happens when a user hits their limit: hard cutoff or overage?
How do you handle usage spikes for good customers?
What's your billing cycle vs. your usage reset cycle?

I charged for failed requests for the first two weeks because I hadn't thought about it. Users noticed. It's a trust issue more than a money issue.

  1. Your error messages are your support queue

Every vague 500 Internal Server Error you return is a support ticket waiting to happen. Every cryptic "error": "invalid input" is a developer spending 30 minutes debugging something you could have explained in one sentence.
This is more consequential than most people assume. A qualitative study of REST API design practices from Tufts University found that error message design is one of the most consistently underspecified parts of API development, even among experienced teams.

Good API errors have three things: a machine-readable error code (invalid_api_key, not just 401), a human-readable message explaining what went wrong, and ideally a link to relevant docs. This breakdown of what makes error messages actually useful has concrete before/after examples worth bookmarking.

  1. Documentation is part of the product

I launched with a README and a Postman collection. That was fine for week one. By week four, users were asking questions I'd already answered in Slack three times, and I had no canonical place to send them.
The minimum viable docs at launch: an authentication guide, a quick-start with working code examples, a full endpoint reference, and a changelog. The ROI is disproportionate. Every hour you spend writing docs saves you several hours of support.

  1. Idempotency is a kindness to your users

Networks fail. Clients retry. If your POST /orders endpoint isn't idempotent, a user with a flaky connection might create the same order five times and not know it until they check their account.

The fix is straightforward: accept an Idempotency-Key header, store it with a short TTL, and return the same response for duplicate requests. It takes maybe half a day to implement and it will save at least one user from a genuinely bad experience.

The meta-lesson

All of these mistakes share the same root cause: I designed the API for the happy path and for users who would use it exactly as intended. Real developers are in a hurry, their networks drop, their loops have bugs, and they'll hit your endpoints in ways you never imagined.

Build for that developer, not the one in your head.

What's the thing you got wrong first when building an API? Curious whether others hit the same walls or found completely different problems.

Resources: https://www.cs.tufts.edu/~jfoster/papers/vlhcc23.pdf

posted to Icon for group Developers
Developers
on May 13, 2026
Trending on Indie Hackers
I've been building for months and made $0. Here's the honest psychological reason — and it's not what I expected. User Avatar 177 comments 7 years in agency, 200+ B2B campaigns, now building Outbound Glow User Avatar 65 comments This system tells you what’s working in your startup — every week User Avatar 53 comments 11 Weeks Ago I Had 0 Users. Now VIDI Has Reviewed $10M+ in Contracts - and I’m Opening a Small SAFE Round User Avatar 46 comments The "Book a Demo" Button Was Killing My Pipeline. Here's What I Replaced It With. User Avatar 29 comments My AI bill was bleeding me dry, so I built a "Smart Meter" for LLMs User Avatar 18 comments