REST Maturity Levels
10 minute read
Introduction to API Maturity Levels
Not every API that claims to be “RESTful” truly adheres to all REST principles. The journey from a basic web API to a fully compliant REST API involves progressive levels of sophistication and adherence to REST architectural constraints.
Martin Fowler popularized this concept as the “Glory of REST” or “Steps towards REST,” but the formal structure comes from Leonard Richardson’s Maturity Model, which divides REST implementation into four distinct levels (Level 0 to Level 3).
It’s important to understand that very few APIs in the wild fully implement all four levels. The highest level—Hypermedia Controls (HATEOAS)—is rarely seen in production APIs despite being theoretically “pure REST.” In practice, Level 2 has become the de facto standard for what most developers consider “RESTful” today.
The Richardson Maturity Model: A Visual Overview
Why Maturity Levels Matter
These maturity levels serve several important purposes in API design and implementation:
- Common Understanding: They provide a shared language and framework for discussing API design decisions
- Design Guidance: They offer clear progression paths when evolving an API
- Standard Patterns: They encourage the use of recognizable conventions that are familiar to developers
- Interoperability: They promote consistency across APIs, improving tooling compatibility
- Educational Value: They help teams understand the full vision of REST architecture, even if not fully implemented
Following these levels, even if you don’t reach Level 3, creates a more intuitive and maintainable API that developers will find easier to work with.
Level 0 - The Swamp of POX (Plain Old XML/JSON)
Level 0 represents the most basic form of HTTP-based integration. At this level:
- HTTP is used merely as a transport protocol
- A single endpoint (URL) handles multiple types of requests
- The operation type is embedded in the request body or URL
- Method semantics are ignored (typically only POST is used)
Characteristics of Level 0 APIs
- RPC-Style: Designed around actions rather than resources
- Single Endpoint: Often a single URL with operation details in the request body
- Tunneling: All operations tunneled through POST requests
- Custom Request/Response Format: No standard conventions for interactions
Level 1 - Urls and Resources
Level 1 introduces the fundamental REST concept of resources. At this level:
- Different resources are addressed with unique URIs
- The API is organized around resources (nouns), not actions (verbs)
- Resource identification is built into the URL structure
- Operations still typically use a single method (usually POST)
Resources (or entities) become the basic building blocks of the API. Each resource type gets its own endpoint, and individual resources are identified by their URI.
Resource Addressing in Level 1
Resource | Endpoint | Individual Resource |
---|---|---|
Users | /users | /users/{id} |
Tasks | /tasks | /tasks/{id} |
Products | /products | /products/{id} |
Level 1 also addresses hierarchical relationships through nested URL structures. For example, accessing a task’s assignee might use a URL like /tasks/123/assignee
.
Example of Level 1 API Calls
# Still using POST for all operations, but with resource-oriented URLs
POST /users HTTP/1.1
Content-Type: application/json
{
"action": "create",
"name": "John Doe",
"email": "[email protected]"
}
POST /users/123 HTTP/1.1
Content-Type: application/json
{
"action": "update",
"name": "John Smith"
}
POST /tasks/123/assignee HTTP/1.1
Content-Type: application/json
{
"action": "assign",
"userId": 456
}
Sample Response from a Level 1 API
{
"id": 123,
"title": "This is the task title",
"assignee": {
"id": 315,
"name": "Batman"
}
}
Benefits of Level 1 APIs
- Improved organization: Resources are logically grouped
- Better discoverability: URLs follow intuitive patterns
- Clearer structure: Resource hierarchies are represented in URLs
- Resource focus: Emphasis on resources rather than operations
However, Level 1 APIs still don’t leverage HTTP methods semantically, limiting their RESTfulness. They’re a step up from Level 0 but still not truly RESTful.
Level 2 - HTTP Verbs
Level 2 represents what most developers consider “REST” today. At this level:
- HTTP verbs are used to express the operation semantics
- HTTP status codes are used to convey outcomes
- Resources are manipulated through standard HTTP methods
- HTTP headers are leveraged appropriately
This level fully embraces HTTP as an application protocol rather than just a transport mechanism, using its built-in verbs to express the CRUD (Create, Read, Update, Delete) operations.
HTTP Methods in RESTful APIs
Method | Description | Safe | Idempotent | Typical Use |
---|---|---|---|---|
GET | Retrieve a resource or collection | Yes | Yes | Retrieve an entity or collection from an endpoint |
POST | Create a resource or trigger a process | No | No | Create a new entity or execute a complex operation |
PUT | Replace a resource completely | No | Yes | Replace an existing entity completely |
PATCH | Update parts of a resource | No | No | Partially update an existing entity |
DELETE | Remove a resource | No | Yes | Delete an existing entity |
HEAD | Retrieve headers only | Yes | Yes | Check if a resource exists or has changed |
OPTIONS | Determine available operations | Yes | Yes | Discover what operations are allowed on a resource |
Example of Level 2 API Calls
# Using proper HTTP verbs for different operations
GET /users HTTP/1.1
Accept: application/json
GET /users/123 HTTP/1.1
Accept: application/json
POST /users HTTP/1.1
Content-Type: application/json
{
"name": "John Doe",
"email": "[email protected]"
}
PUT /users/123 HTTP/1.1
Content-Type: application/json
{
"id": 123,
"name": "John Smith",
"email": "[email protected]"
}
PATCH /users/123 HTTP/1.1
Content-Type: application/json
{
"name": "John Smith"
}
DELETE /users/123 HTTP/1.1
HTTP Status Codes in RESTful APIs
Level 2 APIs also make appropriate use of HTTP status codes:
Category | Codes | Examples | Meaning |
---|---|---|---|
2xx Success | 200, 201, 204 | 200 OK, 201 Created, 204 No Content | The request was successfully received, understood, and accepted |
3xx Redirection | 301, 302, 304 | 301 Moved Permanently, 304 Not Modified | Further action needs to be taken to complete the request |
4xx Client Error | 400, 401, 403, 404, 422 | 400 Bad Request, 404 Not Found | The request contains incorrect syntax or cannot be fulfilled |
5xx Server Error | 500, 503 | 500 Internal Server Error, 503 Service Unavailable | The server failed to fulfill a valid request |
Benefits of Level 2 APIs
- Standard operations: Clear mapping between HTTP methods and CRUD operations
- Semantic responses: HTTP status codes provide meaningful response information
- Cacheability: Proper use of GET enables HTTP caching
- Idempotency: PUT, DELETE, and GET operations can be safely retried
- Better tooling support: Standard HTTP semantics work with existing tools and frameworks
Level 2 provides a good balance between RESTful purity and practical implementation, which is why it’s the most common level of REST API implementation in production today.
Level 3 - Hypermedia Controls (HATEOAS)
Level 3 represents the pinnacle of REST API design, commonly referred to as HATEOAS (Hypermedia as the Engine of Application State). At this level, the API not only provides data but also includes hypermedia controls that guide the client on what actions can be performed next.
When an API implements Level 3, responses include links to related resources and available operations. This allows clients to navigate the API dynamically without prior knowledge of all endpoints, much like how a user navigates a website by following links rather than memorizing URLs.
How HATEOAS Works
In a HATEOAS API:
- Each response contains not just data but also links to related resources
- Available actions are represented as links with appropriate HTTP methods
- Clients follow these links to navigate the API’s state machine
- API evolution can happen without breaking clients
- Clients need minimal up-front knowledge of the API structure
Common HATEOAS Formats
Several standardized formats exist for implementing hypermedia controls:
- HAL (Hypertext Application Language)
{
"id": 123,
"title": "This is the task title",
"status": "in-progress",
"_links": {
"self": { "href": "/tasks/123" },
"assignee": { "href": "/tasks/123/assignee" },
"complete": { "href": "/tasks/123/complete" }
}
}
- JSON:API
{
"data": {
"type": "tasks",
"id": "123",
"attributes": {
"title": "This is the task title",
"status": "in-progress"
},
"relationships": {
"assignee": {
"links": {
"self": "/tasks/123/relationships/assignee",
"related": "/tasks/123/assignee"
}
}
},
"links": {
"self": "/tasks/123"
}
},
"links": {
"complete": "/tasks/123/complete"
}
}
- Siren
{
"class": [ "task" ],
"properties": {
"id": 123,
"title": "This is the task title",
"status": "in-progress"
},
"links": [
{ "rel": [ "self" ], "href": "/tasks/123" }
],
"entities": [
{
"class": [ "user" ],
"rel": [ "assignee" ],
"href": "/tasks/123/assignee"
}
],
"actions": [
{
"name": "complete-task",
"title": "Complete Task",
"method": "PUT",
"href": "/tasks/123/complete"
},
{
"name": "cancel-task",
"title": "Cancel Task",
"method": "DELETE",
"href": "/tasks/123/cancel"
}
]
}
Implementation Example in ASP.NET Core
[ApiController]
[Route("api/tasks")]
public class TasksController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult<TaskResource> GetTask(int id)
{
// Fetch task from database
Task task = _repository.GetTask(id);
if (task == null)
return NotFound();
// Create resource with hypermedia controls
TaskResource resource = new TaskResource
{
Id = task.Id,
Title = task.Title,
Status = task.Status,
Links = new Dictionary<string, Link>
{
["self"] = new Link
{
Href = Url.ActionLink("GetTask", "Tasks", new { id = task.Id })
},
["assignee"] = new Link
{
Href = Url.ActionLink("GetAssignee", "Tasks", new { id = task.Id })
}
}
};
// Add conditional links based on state
if (task.Status == "in-progress")
{
resource.Links["complete"] = new Link
{
Href = Url.ActionLink("CompleteTask", "Tasks", new { id = task.Id }),
Method = "PUT"
};
resource.Links["cancel"] = new Link
{
Href = Url.ActionLink("CancelTask", "Tasks", new { id = task.Id }),
Method = "DELETE"
};
}
return Ok(resource);
}
// Other actions...
}
public class TaskResource
{
public int Id { get; set; }
public string Title { get; set; }
public string Status { get; set; }
public Dictionary<string, Link> Links { get; set; } = new Dictionary<string, Link>();
}
public class Link
{
public string Href { get; set; }
public string Method { get; set; } = "GET";
}
Benefits of Level 3 APIs
- Self-discovery: Clients can discover available operations at runtime
- Loose coupling: Clients are less dependent on hardcoded endpoints
- API evolution: The server can introduce new capabilities without breaking clients
- Better documentation: The API essentially documents itself through hypermedia controls
- State transitions: Clear representation of possible state transitions for resources
Challenges of Level 3 APIs
Despite these advantages, very few APIs reach this level because:
- Implementation complexity: Requires significant additional design and implementation effort
- Client support: Many client frameworks aren’t built to leverage hypermedia controls effectively
- Payload size: The extra payload size can impact performance
- Cognitive overhead: Introduces complexity that may not be justified for simpler use cases
- Developer familiarity: Most developers aren’t familiar with hypermedia-driven development
When to Use Level 3
Consider implementing HATEOAS when:
- You anticipate frequent API evolution
- API discovery is important for your clients
- You want to minimize versioning issues
- Your API has complex state transitions
- Client and server development teams are separate
- Long-term API maintenance is a concern
While Level 3 represents the “pure” REST as defined by Roy Fielding, Level 2 has become the pragmatic compromise for most real-world REST APIs.
Conclusion: Finding Your REST Maturity Sweet Spot
The Richardson Maturity Model provides a useful framework for understanding REST API design, but it’s important to remember that higher levels aren’t always better for every use case. The key is to choose the maturity level that best fits your specific needs.
Most modern APIs settle at Level 2, which provides a good balance between RESTful principles and practical considerations. Level 3 offers additional benefits but comes with increased complexity that may not be justified for all scenarios.
When designing your API, consider:
- Client needs: What do your API consumers expect and require?
- Development resources: What level of sophistication can you reasonably implement and maintain?
- API longevity: How long will this API be in service, and how much might it change?
- Team expertise: What’s the team’s familiarity with REST principles?
Remember that even Roy Fielding, who defined REST, acknowledged that not all constraints need to be satisfied in every scenario. The goal is to create an API that is intuitive, maintainable, and meets the needs of its consumers—regardless of which exact maturity level it achieves.