Skip to content
← Back

On API Design: Making the Right Thing Easy

6 min read

There's a principle I keep coming back to when designing APIs: make the right thing easy and the wrong thing hard.

It sounds obvious. Most good principles do. But in practice, I see APIs every day where the "correct" usage requires reading three pages of documentation, while the "incorrect" usage is the first thing you'd try.

The pit of success

Rico Mariani coined the term "pit of success" to describe systems where users fall into doing the right thing by default. The metaphor is physical: success shouldn't require climbing. You should just... land there.

Consider two approaches to a database query API:

// Version A: The wrong thing is easy
const result = db.query("SELECT * FROM users WHERE id = " + userId);
 
// Version B: The right thing is easy
const result = db.query("SELECT * FROM users WHERE id = $1", userId);

Version A compiles. It works. It's the first thing you'd try. It's also a SQL injection vulnerability. Version B is parameterized by default. The API makes it harder to write insecure code than secure code.

In short, respect the person who will use your API at 4 PM on a Friday when they just want to ship a feature and go home.

Errors as guidance

A good API doesn't prevents mistakes and helps you fix them.

// Bad: What am I supposed to do with this?
throw new Error("Invalid input");
 
// Better: Now I know what went wrong and what to do
throw new Error(
  `Expected "currency" to be an ISO 4217 code (e.g., "usd"), ` +
  `but received "${currency}". See https://docs.example.com/currency`
);

The second error message does three things: it tells you what it expected, what it got, and where to learn more. That's not extra work. That's the work.

Defaults matter more than options

Every option you add to an API is a decision you're pushing onto your users. Sometimes that's necessary. Often, you're just avoiding the harder work of choosing a good default.

I've worked on APIs where the configuration object had thirty fields. Thirty decisions the user had to research before they could make their first request. That's not flexibility. That's a failure to have opinions.

Good defaults are opinionated. They represent a judgment about what most people need most of the time. You can always offer escape hatches for advanced users, but the common case should require zero configuration.

Consistency is a feature

If your API uses createdAt in one endpoint and created_at in another, you've failed. Neither convention is wrong, but the inconsistency forces users to remember arbitrary differences instead of building intuition.

Naming conventions, parameter ordering, error formats, pagination styles... pick one approach and use it everywhere. Boring consistency beats exciting variety.

Ship less

The best APIs I've worked on got better by removing things. Every endpoint, every parameter, every option is a commitment. It's something you'll maintain, document, and support for years.

Before adding a feature to an API, I ask: can I explain why this needs to exist in one sentence? If the explanation requires "well, there's this edge case where..." then it probably doesn't need to exist yet.

You can always add later. You can almost never remove.