6 min read
On this page

What Makes a Good API

A good API is one developers can use without reading the source code. They guess the endpoint name, the request shape, and the error format — and they guess right. That is the bar. Everything else — documentation, SDKs, developer portals — is support structure. The API itself must be self-evident.

The companies that understand this treat their API as a product. Stripe is the canonical example. Their API is consistent, predictable, and generous with error messages. Developers routinely cite Stripe as the reason they chose to build on the platform, not the payment processing itself. The API is the product.

The Five Qualities

Consistency

Every endpoint should feel like it was designed by the same person. If you use created_at on one resource, do not use createdAt on another. If list endpoints return { data: [], has_more: true }, every list endpoint returns that shape.

Stripe enforces this rigorously:

{
  "object": "list",
  "url": "/v1/customers",
  "has_more": false,
  "data": [
    {
      "id": "cus_abc123",
      "object": "customer",
      "created": 1677000000,
      "email": "jane@example.com",
      "name": "Jane Doe"
    }
  ]
}

Every Stripe resource has an id field, an object field that tells you the type, and a created timestamp. List endpoints always wrap results in data with a has_more boolean. Once you learn one endpoint, you know them all.

Predictability

Developers build mental models. If GET /users/123 returns a user, they expect GET /orders/456 to return an order in the same shape. If POST /users creates a user and returns 201, they expect POST /orders to do the same.

GitHub's API demonstrates this well. Every resource follows the same URL structure:

GET    /repos/{owner}/{repo}/issues          # list issues
GET    /repos/{owner}/{repo}/issues/{number}  # get one issue
POST   /repos/{owner}/{repo}/issues           # create an issue
PATCH  /repos/{owner}/{repo}/issues/{number}  # update an issue

The pattern is the same for pull requests, comments, labels, and milestones. You learn the pattern once.

Good Error Messages

Bad error messages create support tickets. Good error messages create self-sufficient developers. Compare these two responses to an invalid request:

{
  "error": "Bad Request"
}

versus Stripe's approach:

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "param": "currency",
    "message": "Missing required param: currency. You must specify a currency when creating a charge.",
    "doc_url": "https://stripe.com/docs/error-codes/parameter-missing"
  }
}

The second response tells the developer exactly what went wrong, which parameter is the problem, and where to find documentation. The developer fixes the issue in 30 seconds instead of opening a support ticket.

Discoverability

A discoverable API helps developers find what they need. This means sensible resource names, logical URL hierarchies, and responses that include links to related resources.

GitHub's API responses include URLs to related resources:

{
  "id": 1,
  "number": 1347,
  "title": "Found a bug",
  "user": {
    "login": "octocat",
    "url": "https://api.github.com/users/octocat"
  },
  "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments",
  "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}"
}

Developers can explore the API by following links in responses, without consulting documentation for every URL.

Stability

An API that breaks is worse than an API that never existed. Developers build products on your API. When you change a field name or remove an endpoint, their production systems break at 2 AM.

Stripe versions their API by date (2023-10-16) and maintains backward compatibility within each version. Twilio keeps deprecated endpoints running for years with clear migration guides. This is not optional generosity — it is the cost of being a platform.

The Cost of a Bad API

Bad APIs are expensive in ways that do not show up in sprint planning:

  • Support tickets — every unclear error message generates a ticket. At scale, this requires a dedicated developer relations team just to handle integration questions.
  • Integration bugs — inconsistent field names and behaviors cause bugs in client applications. These bugs surface in production, not in development.
  • Slow adoption — developers evaluate APIs during hackathons, proof-of-concepts, and "let me try this for an hour" sessions. A confusing API loses in the first 15 minutes.
  • Churn — developers who build on a bad API eventually rebuild on a competitor's API. The switching cost is real, but so is the frustration threshold.
  • Internal cost — bad internal APIs slow down your own teams. If your microservices have inconsistent APIs, every team wastes time reading other teams' source code.

Stripe as the Gold Standard

What Stripe gets right that others do not:

  • Consistent object model — every resource has id, object, created, livemode, and metadata
  • Expandable relationships — pass expand[]=customer to inline related objects instead of making a second request
  • Idempotency keys — every POST endpoint accepts an Idempotency-Key header for safe retries
  • Test mode — every API key has a test mode counterpart; no separate sandbox environment
  • Error specificity — errors include the parameter name, a human-readable message, a machine-readable code, and a documentation link
  • Versioning by date — API versions are pinned to your account, so upgrades are deliberate, not accidental

These are not innovations. They are engineering discipline applied consistently over a decade. Any team can do this. Few teams do.

The API Is a Product

Treating the API as a product means:

  • User research — talk to the developers who will integrate with your API before designing it
  • Design reviews — review API designs with the same rigor you review UI designs
  • Prototyping — mock the API and let consumers try it before you build it
  • Iteration — measure time-to-first-successful-call and optimize for it
  • Documentation as feature — API docs are not an afterthought; they ship with the endpoint
  • Changelog — every change is documented, every deprecation has a migration path

The best APIs feel inevitable. Developers do not marvel at them — they just work. That feeling of "of course it works this way" is the result of deliberate, disciplined design.

Common Pitfalls

  • Designing for the database — exposing your database schema as your API. Internal column names, join table IDs, and nullable fields that exist for migration reasons should never leak into the public contract.
  • Inconsistent naming — mixing camelCase and snake_case, using created_at on one resource and creation_date on another, abbreviating some fields (qty) but not others (quantity).
  • Vague errors — returning 400 or 500 with no explanation. Every error should answer: what went wrong, which field caused it, and how to fix it.
  • Breaking changes without versioning — renaming a field, changing a type from string to integer, or removing an endpoint without a deprecation period.
  • Designing in isolation — building the API without input from the developers who will consume it. The producer's mental model is not the consumer's mental model.
  • Over-engineering — adding HATEOAS, hypermedia controls, and content negotiation to an API that serves three internal clients. Start simple. Add complexity when you have evidence it is needed.

Key Takeaways

  • A good API is consistent, predictable, and generous with error messages. Developers should be able to guess correctly.
  • The API is a product. Apply the same rigor to API design that you apply to UI design: user research, prototyping, iteration, and documentation.
  • Stripe is the gold standard because of engineering discipline, not innovation. Consistent object models, clear errors, safe retries, and stable versioning are achievable by any team.
  • Bad APIs are expensive. They generate support tickets, cause integration bugs, slow adoption, and drive churn. The cost is real but often invisible until it compounds.
  • Start with consistency. If you get nothing else right, make every endpoint feel like it belongs to the same API.