Lasha's blog

Designing Humane and Predictable APIs

Intro: Why I Still Care About API Design

I’ve built enough APIs to know this: it’s not just code, it’s the little things that make people hate—or love—you when they use it. My early APIs were slapdash. I threw stuff together and hoped for the best. I didn’t think about names, responses, or how messy nesting hell could become. Now I’m more intentional. I design APIs like I’m writing a note to a future version of myself—one that's clean, simple, and never yells, “Why the hell did I do that?”


1. Picking the Right Architecture

I don’t pick REST just because everyone else does. I start with the use case. Want simple CRUD? REST is still my go-to—it’s mature and everyone’s comfortable with it. But for real-time updates, REST drags. I’ve had situations where switching to gRPC made everything smoother—thanks to its speed, streaming support, and tight schema following. One time I even sketched out a little “decision tree”: real time or streaming → gRPC; simple data flows → REST. Saved me from a bunch of headaches later.


2. Making URLs Feel Human

URLs should be like street signs, not confusing mazes. Once, I had routes like /users/123/orders/456/items/789—and yeah, it looked neat until I needed to separate orders or items. I flattened it: /orders/456/items. Clean, straightforward, and still meaningful. I stick to plural names for lists (/products) and singular with IDs (/products/123). It helps with routing, documentation, and sanity.


3. Where to Put Parameters (Don’t Mess It Up)

Putting everything in query params is like over-salting soup—it breaks the flavor. Now I follow a simple rule:

Also, I used to cram ?include=comments to load nested data. Felt clever—until I noticed responses getting slow and inconsistent. Now I stick to separate endpoints or filter logic for predictability.


4. Using HTTP Methods Right (They Mean Something)

HTTP methods are not for decoration. Misusing them breaks caches, tools, and expectations. Now I stick with:

I once used PATCH with a “merge patch” style for partial updates, and trust me—that stopped a whole slew of bugs later.


5. Request Body: Keep It Predictable

Nothing surprises me more than a POST that returns something totally different than what I sent. Keeps developers (and future me) guessing. So I mirror request and response structures—same fields, same shape. And I learned that attaching a body to DELETE is just bad luck. Some tools choke on it. Now I do a POST /posts/123/cancel when I need metadata or explanation. Cleaner.


6. Pagination That Scales

Imagine an API that dumps thousands of items in one go. Slow, bloated, and begging for trouble. Offset pagination (?page=2&limit=50) is simple—but breaks when data shifts. Now I prefer cursor pagination, with something like ?cursor=abc123&limit=20. It’s stable even if data changes mid-scroll. Bonus: opaque tokens let me tweak logic later without breaking clients. I also use RFC 8299 links in headers to keep JSON tight.


7. Status Codes That Actually Communicate

Status codes are like traffic signals. I use them right:

Once I saw errors masked behind a 200. Lesson learned: stick to the code.


8. Response Body: Be Helpful, Not Cryptic

Responses aren’t just data—they’re communication. A good response gives the answer or tells you how to recover when it fails. Now I include minimal nesting, simple IDs, timestamps, relationships, and actionable links or available actions. Headers inform caching or rate-limit hints so I don't bloat the body.


9. Returning Errors (Don’t Be Vague)

Errors should be clearer than a ‘404’ or ‘500’. I learned to give structured JSON with:

I use consistent titles too—helps troubleshoot faster.


10. Caching: Don’t Be a Slowpoke

Caching can make or break API performance. Whenever possible, I make responses cacheable with Cache-Control, ETag, Expires. Saves server load and speeds things up. HTTP has good tools—use 'em. And sometimes I add selective in-memory caches for really hot endpoints.


11. Versioning: Handle Change Gracefully

APIs evolve. I learned that slapping v1 in the path (e.g. /v1/users) is easier than silently breaking clients. Headers (Accept-Version) work too for cleaner paths. I always deprecate things before removing them—with notes in docs or code. It avoids the “Everything broke overnight” panic.


12. Exposing APIs Publicly: Checklist Before the World Sees It

Going public with your API isn’t a switch you flip—it’s a checklist you tick off:

A dedicated API subdomain (api.yoursite.com) is cleaner, lets you configure security and caching independently. Only go path-based (/api) if you have to keep it with the frontend.


13. Developer Experience: Make It Feel Nice

If your API is unpleasant, developers will bounce. I now always aim for:

Switched-on docs can make or break adoption.


14. Documentation: Write It Like You’d Read It

Docs aren’t just auto-generated—but the backbone can be. I use OpenAPI to generate reference, then wrap it with:

For public APIs, I go hybrid: combine automated references with hand-crafted guides and examples, searchable and clean.


15. Enforcing Consistency: Kill the Wild West

In a big team or across multiple APIs, inconsistency is your enemy. I build linting, standards, and gating into CI so endpoints feel like they came from the same family—no quirks, no surprises.


16. Security: Don’t Be Lazy

Every open API is a potential time bomb. I design with security first—not as an afterthought:

Those are simple steps that save you from embarrassment—or worse, real breaches.


17. Testing: Build Confidence

APIs without tests are accidents waiting to happen. I generate contract tests from OpenAPI, test error cases, retry flows, and edge behavior. If it breaks, I catch it before anyone notices.


18. Collections: Think Content, Not Just Rows

Resources and collections deserve different treatment. I send lists (GET /invoices) with minimal data (e.g. ID, name) and let clients fetch details separately. I also envelope them:

{
  "data": [...],
  "meta": { "total": 123, "limit": 20 },
  "links": { "next": "/...?cursor=abc" }
}

Keeps everything tidy, predictable, and efficient.


Wrapping Up: Write APIs That Still Make Sense in 2025

API design isn’t a checklist—it’s a mindset. Be predictable, honest, simple. Make your API feel like a tool, not a puzzle.

I still screw up sometimes, but these habits keep disasters at bay. If you’re designing APIs—think about your users, even if that’s future you. They’ll thank you for it.