Search code examples
resthttp-status-codes

RESTAPI: What is a correct response format and status code when a resource exists however is expired?


Let's assume we have resource Employee. Employee looks like that (from entity and DB perspective):

{
    id: 123,
    name: "John Doe",
    // more props here
    workPermitExpireDateUTC: "1999-06-30 00:00:00"
}

when we query the all employees we want to display additional flag isExpired calculated on a fly:

GET: /api/employees:

// HttpStatuCode: 200
[
    {
        id: 123,
        name: "John Doe",
        // more props here
        workPermitExpireDateUTC: "1999-06-30 00:00:00",
        workPermitExpired: true // calculated on a fly, based on system clock
    },
    {
        id: 456,
        name: "George Smith",
        // more props here
        workPermitExpireDateUTC: "2025-06-30 00:00:00",
        workPermitExpired: false // calculated on a fly, based on system clock
    }
]

When I query single non-expired employee

GET /api/employess/456 I expect to get 200 Response with the employee data - that's obvious.

However, if we consider expired employee, I do not want to send all data, as client is not needed to know it. So my thought is to return like this:

GET: /api/employees/123:

// HttpStatuCode: 200
{
    id: 123,
    workPermitExpired: true,
    errorMessage: "Employee work permit expired"
}

but it doesn't feel right, since 200 response should not have different structure depending on resource id.

How about such url GET: /api/employees/123/non-expired and return 200 in case non expired, and 410 in case of expired?


Solution

  • What is a correct response format and status code when a resource exists however is expired?

    An important thing to understand in REST, is that our API is a facade, deliberately designed to make our domain look like every other dumb document store on the web, so that all of the general purpose web tools "just work".

    Another way of saying the same thing: HTTP is an application in the domain of transporting documents over a network. What we are doing when we build a REST API, is that we are adapting our domain protocols to a language of exchanging documents with the client.

    Status codes and headers belong to the transport-documents-over-a-network domain.

    GET: /api/employees/123
    

    The correct response to this largely depends on whether we have a current representation of this resource (which is a concern of the transport documents domain). The semantics of that representation in our domain really don't enter into it.

    If we have a current representation of the resource, then this is fine

    200 OK
    Content-Type: application/json
    
    {
        "id": 123,
        "workPermitExpired": true,
        "errorMessage": "Employee work permit expired"
    }
    

    When we cannot satisfy the request to provide a current resource, then we will normally use a status code that indicates to general purpose components that the response body describes an error condition, rather than a representation of the resource.

    404 Not Found
    Content-Type: application/json
    
    { "error": "Who is 123?" }
    

    it doesn't feel right, since 200 response should not have different structure depending on resource id.

    Right. The way that this usually translates is to define your schema more carefully. If you are expecting to turn some fields on and off in the representation, then your schema definition needs to describe those fields as optional. For example, we might describe an "active permit" schema and an "expired permit" schema (under a different key) with the constraint that the client should expect one or the other, but not both.

    Another possibility is to use different content types for different representations. RFC 6838 is where you look for details, but the rough outline is that we would define two different schema, and then assign to each schema a distinct content type

    • application/prs.maciej-active-permit+json
    • application/prs.maciej-expired-permit+json

    application/+json tells general purpose consumers that the representations are "just" json docments, but consumers that are familiar with your specific media types will have more specific understanding to work with.

    RFC 7807 is a standardized demonstration of this approach, where specific semantics for field names are encoded into the json document, and the media type application/problem+json tells general-purpose components what is really going on.

    You see a similar idea in application/merge-patch+json and application/json-patch+json.