API Design Best Practices: Build APIs that Developers Love


Welcome to Zero Downtime, the official engineering blog by SquareUp. This platform is our dedicated space for sharing in-depth technical insights, best practices, architectural decisions, and lessons learned from real-world software development.
Through Zero Downtime, we aim to foster a culture of continuous learning and open knowledge sharing, whether you're an experienced engineer, a product visionary, or someone just getting started in tech. Our goal is simple: to provide thoughtful, well-crafted content that helps you build better software, avoid costly mistakes, and stay ahead of emerging trends.
APIs are the contracts of modern software. A poorly designed API will frustrate developers, slow down teams, and necessitate frequent hotfixes. We’ve learned this the hard way, and here’s what we now follow religiously.
1. Overview
In 2000, Roy Fielding introduced the REST architectural style, a revolutionary approach to designing web services.
Since then, REST has become the industry standard for building modern web applications and APIs, widely adopted for its simplicity, scalability, and ease of integration. Today, RESTful services power much of the web.
It is used by organizations of all sizes and across various industries. Therefore, understanding how to design a REST API properly is a crucial skill for software developers.
There are different stages of maturity in designing a REST API as defined by Richardson’s Maturity Model.
Level-3 HyperMedia/HATEOAS:- The API includes hypermedia links within responses, guiding clients dynamically through available actions, making the system more self-descriptive and discoverable.
Level-2 HTTP Verbs:- The API makes proper use of HTTP methods like GET
, POST
, PUT
, and DELETE
to perform actions on resources.
Level-1 Resource-Based Address/URI:- Introduces multiple URIs to represent individual resources. Each resource is exposed through its unique URL. Better structure, but still uses a single HTTP verb (usually POST) for all actions.
Level-0 Swamp of Pox:- The API relies on a single endpoint and uses HTTP as a transport protocol only, typically with POST
XML or JSON payloads. No resource modeling, HTTP verbs, or URI structure.
Level 3 represents the highest stage of REST maturity, aligning closely with Roy Fielding's original vision of a truly RESTful API. However, in practice, most public APIs stop at Level 1 or Level 2.
While reaching Level 2, which involves proper use of HTTP verbs, requires thoughtful design and discipline, it significantly improves API quality, reliability, and scalability. For most applications, this level is both practical and powerful.
Level 3, which introduces Hypermedia (HATEOAS), is rarely implemented in the real world. In the upcoming sections, we’ll explore why achieving Level 3 is so challenging and whether it’s always necessary.
Stateless API 🧊
A REST API should be Restful and Stateless, not Restless and Stateful.
What does Stateless mean?
In a distributed environment, statelessness means that no client is tied to a specific server. Servers do not maintain any session information about the client, allowing requests to be routed to any available server in a load-balanced manner. This design promotes scalability and flexibility, as it removes dependencies on individual server instances.
In a single-server environment, statelessness means that the server can handle any request without relying on previous interactions or state information. Each request contains all the necessary information for processing, regardless of any previous requests.
🔧 Why Stateless APIs Matter
✅ Scalability: Since no session state is stored on the server, any instance can handle any request. This enables horizontal scaling and load distribution.
✅ High Availability: If one server fails, another can take over without affecting client interactions, improving fault tolerance and uptime.
✅ Better Performance: Stateless APIs are easier to cache. Identical requests yield identical responses without server-side memory overhead.
✅ Simplified Testing & Maintenance: With no complex session management, APIs become more modular, predictable, and easier to test in isolation.
Statelessness is a core principle of REST and one of the key reasons REST APIs scale so well in both cloud-native and traditional environments.
Organize the API design around resources
A good API design is organized around resources, for example, customers or orders, and not actions or verbs. For instance, endpoints like “create-order” should be avoided.
https://api.amazon.com/v1/orders // Good
https://api.amazon.com/v1/generate-order // Avoid
https://api.amazon.com/v1/users // Good
https://api.amazon.com/v1/create-user // Avoid
Why is this poor design?
Because we have the HTTP protocol that brings the action. We have the HTTP methods or verbs: GET, POST, PUT, PATCH, and DELETE to handle the actions.
This way, we provide consistency
between different endpoints.
And when we have consistency, clients can make assumptions about the behavior of the API based on their prior knowledge of the HTTP protocol.
Moreover, at a high level, HTTP verbs map to CRUD operations of a database:
HTTP | Databse |
GET | Read |
POST | Create |
PUT | Update |
DELETE | Delete |
Real World Example
While the previous examples are straightforward, real-world scenarios can be more complex.
For example, consider how to model an endpoint that returns customer orders with the ability to sort by an attribute and paginate the results.
We have a 1 to many relationship here, and we can represent it with path parameters.
GET /customer/orders
However, we can improve this API.
Tip 1: Entities are grouped into collections.
Usually, entities are grouped into collections, such as orders and customers. We organize resources hierarchically, which makes the API more intuitive
.
In general, it's helpful to use plural nouns for URIs that reference collections. This provides consistent naming convention
When we need to get all customers or only a particular one.
GET /customers/customer/orders
Tip 2: Use parametrized URIs for identity. To identify a specific user, we use parameterized URIs. Generally, path parameters are recommended when you need to specify the identity or key of a specific resource being accessed or modified, and not query parameters.
GET /customers/{customer_id}/orders
Tip 3: Avoid resource URIS more complex than /collection/item/collection. Avoid resource URIs that are more complex than 2 levels
. For example, instead of having customers/orders/products, which have 3 levels, we could have 2 simpler URIs that serve the same purpose.
/customers/1/orders/99/products // Avoid
/customers/1/orders // Good
/orders/99/products // Good
This simplifies maintenance and allows for greater flexibility in the future.
Tip 4: Use query params for additional options or metadata.
To sort the collection, we can use query parameters to provide additional options or metadata. For example, we can sort by price.
GET /customers/{customer_id}/orders?sort=price&limit=10
Query parameters are generally recommended for filtering, sorting, and pagination or when additional properties or options need to be passed to an operation.
In this case, the sort query parameter sorts the orders of a customer, and the limit parameter specifies that only the first 10 matching results should be returned.
To summarize, we used customer as a resource, orders as a subresource, and query parameters to get further options on those resources.
Do not return plain text 📃
Returning plain text for a REST endpoint is not a good idea. Why?
When a client application receives plain text instead of a structured media type, it needs to perform additional parsing and processing to extract the necessary data. This can introduce errors and inefficiencies that are undesirable.
It is ideal to use JSON, XML, or YAML to represent and transmit data. These media types provide a structured way of representing data, making it easy for the client application to parse and understand the data being returned.
For REST APIs, JSON is the preferred option if possible. It is widely supported in modern programming languages and frameworks, more so than XML or YAML. XML is also well-supported, but it has unnecessary verbosity. Using XML can result in larger file sizes, slower parsing, and increased bandwidth usage.
YAML is less verbose and more expressive than JSON. However, it does not have the same level of compatibility across programming languages and systems as JSON.
Versioning a RESTful web API
What happens when we change the API? 🧨
Changing a REST API after it's been adopted by clients is one of the worst things to do.
Clients suddenly find out the hard way that the API they've been using is not working anymore.
This leads to broken code, failing applications, and angry users.
Clients trust your API to remain stable and predictable, and changing it betrays that trust.
This forces clients to update documentation, modify code, and provide support to their clients.
This should be done ASAP to ensure that clients continue to work as before.
In short, changing a REST API is a recipe for disaster, unless you have a very good reason for doing so and a solid plan for managing the transition.
How to handle API updates?
The common way to update a web API is by versioning.
We can specify the version of the API in the URI, by appending query parameters, by adding HTTP headers, or by specific Media Types.
Which one should we choose❓
Let’s see the benefits and trade-offs.
Considering performance implications, the URI versioning and Query String versioning schemes are cache-friendly.
These 2 approaches are pretty common; however, from a RESTful perspective, the URL should not be different depending on the version when fetching the same data.
So, for REST purists, we have the options to specify the version, using a Custom or an Accept header. These are less intrusive, since they don’t change the URL.
Development team preferences should dictate the choice of API versioning, with URI versioning being the simplest and Media Type considered the purest.
API Versioning | Example |
URI | domain.com/v2/customers |
Query Params | domain.com/customers?version=2 |
Headers | domain.com/customers |
Custom-Header: api-version=2 | |
Media Type | domain.com/customers |
Accept: application/e-commerce.v1+json |
Handling Exceptions
When developing a REST API, it's essential to have robust exception handling to prevent uncaught exceptions that could propagate to the client.
For instance, if a user requests user details, the API might require a user ID as a number.
Instead of displaying a generic error message and a 500 status code, we should catch the exception and wrap it with a descriptive message and an appropriate HTTP status code.
Using the right status code
, is crucial, and you can refer to the Status code definitions page published by the standards organization IETF for guidance. https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes
Distinguish between client-side errors
, that require client changes and server-side errors
, that the application must address. Monitoring 5xx errors helps identify server problems.
A global error handling strategy
Improves the user experience by providing clear and consistent error messages, and makes the API scalable while reducing the amount of duplicate code needed to handle errors.
Worth HATEOAS?
The last level of the REST API maturity is to use hypermedia or HATEOAS.
This is achieved through links in the representation of an order that identify the available operations on that order.
{
"orderID": 3,
"productID": 2,
"quantity": 4,
"orderValue": 16.60,
"links": [
{
"rel": "customer",
"href": "https://adventure-works.com/customers/3",
"action": "GET",
"types": [
"text/xml",
"application/json"
]
},
{
"rel": "self",
"href": "https://adventure-works.com/orders/3",
"action": "DELETE",
"types": []
}
]
}
This provides a discoverable and self-descriptive API, which allows the server to change URIs without breaking clients. However, this principle has some major disadvantages.
Performance Concerns:
- Including links in API responses can impact performance, especially for APIs with many requests for the same resource. In such cases, including links is a waste of resources.
Lack of Standardization:
- There is no widely accepted standard for implementing HATEOAS in REST APIs.
Low Adoption:
- Due to reasons such as limited client support and complexity, HATEOAS remains more of a theoretical principle and is not commonly put into practice.
Looking Ahead
This blog, Zero Downtime, is our way of sharing insights, best practices, and technical knowledge with the engineering community. From backend development patterns and architectural decisions to performance optimization and real-world implementation stories, we aim to contribute thoughtfully and consistently.
As we continue to build for the future, we invite you to follow along on our journey, one post at a time.
Follow us on
LinkedIn:- https://www.linkedin.com/company/atsquareup/
Instagram:- https://www.instagram.com/atsquareup/
YouTube:- https://www.youtube.com/@atsquareup
Visit Website:- https://www.squareupdigital.com
Subscribe to my newsletter
Read articles from SquareUp directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

SquareUp
SquareUp
At SquareUp, we are more than just a software development company. We are a team of thinkers, creators, and problem solvers committed to building meaningful digital experiences that truly make a difference. We offer a complete range of software services that cover every stage of the digital journey. From thoughtful strategy and user-centric design to powerful web and mobile application development, we provide tailored solutions that align with your vision and goals. Our focus is not just on building software but on creating experiences that are impactful, engaging, and built to last. Our team is made up of skilled designers, developers, and project managers who work collaboratively with each client. We take the time to understand your unique needs, challenges, and ambitions. This close collaboration ensures that the solutions we deliver are not only technically sound but also aligned with your business objectives. At SquareUp, creativity and technology go hand in hand. We believe that the best digital solutions come from a deep understanding of both people and technology. Every project we take on is approached with care, precision, and a strong commitment to quality. We work with businesses of all sizes, from innovative startups to established enterprises. No matter the scale, we bring the same level of passion, attention to detail, and dedication to excellence. Whether you are looking to launch a new product, improve an existing one, or bring a new idea to life, SquareUp is here to help you succeed in the digital world. Together, let us shape the future of your business through thoughtful design and reliable technology.