REST Maturity Levels

Understanding the Richardson Maturity Model and how to evolve your REST APIs to higher maturity levels

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

Richardson Maturity Model

Why Maturity Levels Matter

These maturity levels serve several important purposes in API design and implementation:

  1. Common Understanding: They provide a shared language and framework for discussing API design decisions
  2. Design Guidance: They offer clear progression paths when evolving an API
  3. Standard Patterns: They encourage the use of recognizable conventions that are familiar to developers
  4. Interoperability: They promote consistency across APIs, improving tooling compatibility
  5. 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

ResourceEndpointIndividual 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

MethodDescriptionSafeIdempotentTypical Use
GETRetrieve a resource or collectionYesYesRetrieve an entity or collection from an endpoint
POSTCreate a resource or trigger a processNoNoCreate a new entity or execute a complex operation
PUTReplace a resource completelyNoYesReplace an existing entity completely
PATCHUpdate parts of a resourceNoNoPartially update an existing entity
DELETERemove a resourceNoYesDelete an existing entity
HEADRetrieve headers onlyYesYesCheck if a resource exists or has changed
OPTIONSDetermine available operationsYesYesDiscover 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:

CategoryCodesExamplesMeaning
2xx Success200, 201, 204200 OK, 201 Created, 204 No ContentThe request was successfully received, understood, and accepted
3xx Redirection301, 302, 304301 Moved Permanently, 304 Not ModifiedFurther action needs to be taken to complete the request
4xx Client Error400, 401, 403, 404, 422400 Bad Request, 404 Not FoundThe request contains incorrect syntax or cannot be fulfilled
5xx Server Error500, 503500 Internal Server Error, 503 Service UnavailableThe 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:

  1. Each response contains not just data but also links to related resources
  2. Available actions are represented as links with appropriate HTTP methods
  3. Clients follow these links to navigate the API’s state machine
  4. API evolution can happen without breaking clients
  5. Clients need minimal up-front knowledge of the API structure

Common HATEOAS Formats

Several standardized formats exist for implementing hypermedia controls:

  1. 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" }
  }
}
  1. 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"
  }
}
  1. 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:

  1. Client needs: What do your API consumers expect and require?
  2. Development resources: What level of sophistication can you reasonably implement and maintain?
  3. API longevity: How long will this API be in service, and how much might it change?
  4. 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.