AlgoMaster Logo

Principles of Good API Design

High Priority10 min readUpdated September 13, 2026
Listen to this chapter
Unlock Audio

Designing an API means deciding what callers can do, what they must provide, and what they can rely on. A short endpoint name or a tidy JSON response helps, but the design also needs to handle incomplete inputs, changing data, limited permissions, and failed requests.

Good design makes those decisions understandable and dependable for the intended consumers.

This chapter develops practical principles through a fictional bookstore API, with emphasis on the reasoning behind each choice. The principles apply across API styles; the HTTP examples illustrate one possible implementation.

1. Consumer-Focused Design

Start by describing what a consumer needs to accomplish. “Reserve two copies of a book until checkout finishes” tells you more about the required interface than “expose the inventory table.”

For this task, the consumer needs to identify the book, request a quantity, and learn whether a reservation exists and when it expires. It should not need to know which warehouse table stores stock or which internal job releases expired reservations.

The diagram shows how a task can lead to an interface while leaving implementation decisions inside the service:

The consumer depends on the reservation contract. The service remains responsible for carrying it out correctly. Google's resource-oriented design guidance makes a similar distinction: an API should not simply mirror its database schema. That is guidance from Google's design framework, not a requirement that every API use Google's conventions.

Give the interface a clear, focused responsibility. A reservation operation can own the rules for holding stock without also accepting arbitrary changes to prices, delivery addresses, and payment settings. Combining unrelated capabilities into one operation makes inputs and outcomes harder to reason about.

The right boundary depends on the task. If a consumer needs the service to coordinate an entire checkout, an operation that owns that workflow may be appropriate. Simplicity means giving consumers a manageable task, not minimizing the number of endpoints at any cost.

2. Consistency and Clarity

Consistency lets consumers apply what they have learned from one operation to another. It includes naming, value types, units, and behavior.

Suppose the bookstore uses bookId to identify a book in a reservation. Using bookCode for the same identifier in an order introduces a distinction that consumers must investigate. If the two fields mean the same thing, use the same vocabulary. If they mean different things, make that difference explicit.

Meaning matters more than spelling alone. A reservation's quantity should have a documented unit, such as the number of physical copies. An expiresAt field should specify its timestamp format and time-zone interpretation. Consumers should not need to infer either from example values.

Prefer established conventions within the chosen protocol and ecosystem, then apply the API's own conventions consistently. A provider can choose camelCase for JSON fields, but that choice is a naming convention rather than a rule JSON imposes.

Consistency does not require forcing different operations into identical behavior. A book lookup returns one book; a search returns a collection. An empty search can be a successful result even though a lookup for a nonexistent identifier cannot return the requested book. The distinction follows the operation's meaning.

Avoid adding aliases for every possible preference. Supporting both bookId and bookCode in new requests creates questions about what happens when callers supply both. Each extra option becomes behavior the service must document and preserve.

3. Defaults and Side Effects

A default is the behavior an API applies when a caller omits an optional input. A side effect is a change an operation causes, such as holding stock or sending a notification. Both belong to the contract even when they are not visible in the input fields.

For a fictional catalog search, assume the bookstore chooses these rules:

Input or conditionDefined behavior
Caller omits limitReturn at most 20 books
limit between 1 and 100Return at most the requested number
limit outside that range or not an integerReject the request with a validation error
No matching booksReturn a successful empty collection
More matches remainInclude the documented way to request another page

These limits are example design choices. Their value is that the client can predict the result without experimenting with undocumented behavior.

Define missing, empty, and null values separately when they have different effects. An omitted delivery note might preserve an existing note, while an explicitly empty note might clear it. Either design requires an explanation. Silently treating every form as interchangeable can change data the caller intended to preserve.

For stock reservations, specify when the hold starts, when it expires, and whether successful reservation also creates an order. If it only holds stock, say so. The response should identify the reservation and communicate the expiry the caller needs to respect.

Make guarantees only as strong as the implementation can honor. If a stock count is an observation that can change before checkout, describe it that way. If a successful reservation guarantees a hold until a stated deadline, the service must enforce that promise under its documented conditions.

4. Errors and Recovery

A caller needs to distinguish a request it should correct from an operation it lacks permission to perform or an outcome it cannot yet determine.

For the catalog search, a client requests https://api.bookstore.example/books?limit=0. This public read operation does not require a login. The illustrative HTTP/1.1 request over HTTPS is:

Because zero violates the example's range, the server returns:

The response body is the single line above without a trailing newline. HTTP defines 400 Bad Request for a perceived client error. The example chooses it for this validation failure.

The body uses Problem Details, a standard format for HTTP error information. Its type identifies the kind of problem, while detail explains this occurrence. The example's type URL would identify a problem the bookstore defines and documents. Problem Details is an available standard, not a requirement for all HTTP APIs.

A client can use the documented problem type to choose behavior and show a useful explanation. It should not need to match the exact English sentence in detail, which may change. Correcting the limit can resolve this failure; repeating the same invalid request cannot.

The diagram shows three distinct situations a reservation client must be able to recognize:

A connection failure does not tell the caller whether the service created a reservation. For operations where duplication matters, document how clients can check an attempt’s outcome or safely retry the same attempt. The design must explain how the caller identifies that attempt before the first request; a reservation ID available only in a lost response cannot solve the problem.

Useful error information should describe the contract failure without exposing secrets or internal implementation details. Database queries and stack traces generally belong in restricted diagnostics, not in the consumer's error message.

5. Resource Limits and Efficiency

A request should not be able to demand unlimited work merely because a small dataset made that behavior convenient during development.

The catalog search's result limit gives each response a size boundary. Pagination, retrieving a collection in portions, lets a consumer continue through larger result sets. Google's pagination guidance recommends planning this behavior from the beginning because introducing it later can change what existing clients receive.

Limiting returned rows does not necessarily limit the work the service does to find or sort them. Also consider which filters, sorts, uploads, and multi-item operations the service can support within reasonable resource limits. Document relevant bounds and define what happens when a caller exceeds them.

Efficiency includes the whole consumer task. If a search screen needs a title, author name, and price for every result, returning only identifiers may force one additional call per book. A compact search response can include those common fields without including every review and edition.

There is a trade-off between useful responses and oversized ones. Use actual consumer needs and measured behavior to decide what belongs in the common response. Adding every possible related field shifts cost onto callers that do not use it.

These choices need operational support. A documented limit is meaningful only if the service enforces it. A performance expectation is credible only if you measure the implementation under relevant workloads.

6. Least Privilege

Permission is part of an operation's meaning. A customer can retrieve their own reservation without permission to inspect another customer's reservation. A warehouse service can record stock movements without receiving permission to change customer payment information.

This is least privilege: grant the access the caller needs for its responsibility. Check access to the particular operation and record, rather than treating a successful login as permission to use everything. OWASP identifies missing authorization checks on individual objects as a major API security risk.

Apply the same reasoning to fields. A response should contain the information the consumer needs for the task and has permission to read. An input should expose fields the consumer has permission to change. For example, a reservation request should not let a customer set an internal approval flag or override ownership merely because those fields exist in storage.

If a customer requests someone else's reservation, the service should deny access without returning that reservation's details. An official SDK or user interface may help users construct valid requests, but the server must enforce these rules for every client.

The principle is to define access deliberately and enforce it where the protected operation happens. Detailed identity mechanisms can vary with the API's environment.

7. Backward Compatibility

Existing clients depend on more than field names. They can depend on defaults, value meanings, supported inputs, and the conditions under which an operation succeeds.

Suppose the catalog search defaults to 20 results. Changing that default to 100 changes response size and workload even if the schema stays identical. Changing a reservation's documented default duration can also affect when a client starts checkout.

Backward compatibility means preserving the behavior the existing contract promises clients. Google's compatibility guidance explicitly considers meaning and defaults as well as data structure. Its specific release rules are Google's policy; the broader design concern is the effect a change has on consumers.

When adding a new option, consider whether callers that omit it can keep their existing behavior. The diagram illustrates that approach without prescribing a versioning mechanism:

Clients can often adopt an optional capability more easily than a replacement for existing behavior, but additions still need review. A new response field may affect a client that rejects unknown fields, and a new status value may reach code that cannot handle it. Check the contract’s rules for new fields and values, and how supported clients handle them.

Extensibility does not mean accepting arbitrary fields or inventing a configuration system for future possibilities. Keep today's contract clear, and make changes through a process that considers real consumer dependencies.

8. Documentation and Verification

Documentation should let a developer complete a task without guessing at the behavior. Explain the purpose of each operation, its required inputs, permissions, results, limits, and failure cases. Include examples that agree with the running service.

Discoverability means consumers can find the operations and information they need. A coherent reference and a short task example often help more than a long list of fields with no explanation of how they work together.

Verify the promises that matter to callers. For the bookstore, that includes an empty search, an invalid limit, a reservation expiry, and denied access to another customer's reservation. Checks that inspect only successful response shapes can miss the behavior that makes an integration dependable.

Make support possible as well. A request or operation identifier that appears in both the response and the provider's diagnostics can help teams investigate the same interaction without exchanging sensitive payloads. Its format and use are implementation choices; the goal is to connect a consumer's report with the relevant service activity.

These principles sometimes pull in different directions. A richer response may reduce calls while increasing payload size. A new option may help one consumer while making the interface harder for others to learn. Resolve the trade-off using the intended task, access requirements, workload, and compatibility commitments, then document the resulting decision.

Summary

Good API design starts with consumer tasks and expresses them through focused operations, consistent meanings, and explicit defaults. It makes failures actionable, provides recovery for uncertain outcomes, limits resource use and enforces access rules.

Preserve the behavior existing consumers depend on, and keep documentation and verification aligned with the contract. Apply these principles with judgment: the best choice is the one that makes the intended interaction clear, dependable, and practical to operate.