13 Designing an efficient API
Designing an efficient API is about helping consumers achieve their goals with the fewest calls and the least data, while keeping provider infrastructure lean and affordable. Inefficiency surfaces as long, sequential call chains, oversized payloads, battery and data-plan drain, and overloaded backends—issues that harm user satisfaction and business outcomes. Efficiency is a design concern (second only to security), but optimization should come after you’ve produced a clear, user-friendly, interoperable API and verified runtime basics: enable HTTP/2 (or compression and keep-alive with HTTP/1.1), set fair rate limits, expose informative rate-limit headers, and confirm the true root cause isn’t in infrastructure, databases, or code.
Making APIs efficient hinges on rethinking data and flows to fit real needs. Replace multi-step, client-assembled workflows with purpose-built operations that encapsulate business logic; enrich list models and right-size page limits to trim sequential calls; and only move data that matters. Use caching and conditional requests so the best call is the one that isn’t made: set explicit cache policies based on data volatility and context, return cache directives, and let consumers validate freshness with entity tags and 304-not-modified responses. Reduce payloads by letting consumers select models (via content negotiation or preferences) and fields, centralize repetitive code/label dictionaries, and prefer partial updates with JSON Merge Patch over full replacements. Finally, tune pagination: choose sensible defaults and maxima, and favor cursor-based pagination for volatile or large datasets unless random page jumps are a hard requirement.
Optimize call flows further with bulk operations that process many items at once. Design clear requests (IDs in bodies for updates, ID lists or filters for deletes), allow same-input bulk updates via filters, and choose an error policy that fits the domain—mixed (with per-item results and 207 Multi-Status) or all-or-nothing—documenting statuses, headers, and bodies per element when needed. Let clients control response volume in bulk outcomes (e.g., IDs only vs. full models) and restrict bulk features via dedicated scopes or endpoints. When deep, consumer-specific optimizations would contort a versatile business API, introduce an experience layer (such as a backend-for-frontend) that composes and tailors responses on top of reusable business APIs, keeping performance high without sacrificing clarity, reuse, or maintainability.
An API can negatively impact consumers with slow response times and high data volumes, as well as providers with costly overloaded infrastructure and massive total outbound data.
Creating a versatile API that does the job and is user-friendly and interoperable puts us on the right track. Still, we can improve data, operation, and flow design to be totally efficient.
The home screen information flow is slow due to numerous sequential calls, which stresses the infrastructure with up to 100 parallel calls. Also, three months of transactions mean a lot of data.
Due to relevant data additions and larger transaction list pages, the flow is faster and infrastructure-stress-free. However, the process may slow down with more owners and accounts, and the data volume remains substantial.
The real issue is delegating business logic to the consumer. It’s more efficient to create an operation that fulfills actual needs. It is fast and returns only necessary ready-to-use data.
The application must call the API to show data when it isn’t cached. When data is cached for 10 minutes, the application gets data from the cache, which may be inaccurate. Before using it, the application can call the API to ensure its cache is up to date.
The Cache-Control response header contains directives indicating if and how the returned data can be cached.
The If-None-Match header allows us to return data only if it has changed compared to the provided Etag value.
We can leverage a custom media type or preference to get the complete or summarized model on any read, list, or search operation.
If field selection is enabled, consumers can, for example, get only the account balance instead of all its data.
Updating the home address city with JSON Merge Patch requires sending the whole addresses array. With JSON Patch, we only describe the modification of the city property of the first element without the need to send the addresses array.
The bulk requests body contains the data of multiple resources (if any). If each element needs to be identified, the final path parameter is moved at each resource data level in the body (PATCH or PUT) or in a list query parameter (DELETE).
Each element of the bulk response contains the same data a unitary call would return. Response elements are ordered as in the request.
Possible design for all-or-nothing operations if we don’t care about element-specific HTTP statuses or headers. The errors list contains all errors of all errored elements.
The Design of Web APIs, Second Edition ebook for free