HTTP

HTTP for APIs

Remarks#

HTTP APIs use a wide spectrum of HTTP verbs and typically return JSON or XML responses.

Create a resource

Not everyone agrees on what the most semantically correct method for resource creation is. Thus, your API could accept POST or PUT requests, or either.

The server should respond with 201 Created if the resource was successfully created. Pick the most appropriate error code if it was not.

For example, if you provide an API to create employee records, the request/response might look like this:

POST /employees HTTP/1.1
Host: example.com
Content-Type: application/json

{
    "name": "Charlie Smith",
    "age": 38,
    "job_title": "Software Developer",
    "salary": 54895.00
}
HTTP/1.1 201 Created
Location: /employees/1/charlie-smith
Content-Type: application/json

{
    "employee": {
        "name": "Charlie Smith",
        "age": 38,
        "job_title": "Software Developer",
        "salary": 54895.00
        "links": [
            {
                "uri": "/employees/1/charlie-smith",
                "rel": "self",
                "method": "GET"
            },
            {
                "uri": "/employees/1/charlie-smith",
                "rel": "delete",
                "method": "DELETE"
            },
            {
                "uri": "/employees/1/charlie-smith",
                "rel": "edit",
                "method": "PATCH"
            }
        ]
    },
    "links": [
        {
            "uri": "/employees",
            "rel": "create",
            "method": "POST"
        }
    ]
}

Including the links JSON fields in the response enables the client to access resource related to the new resource and to the application as a whole, without having to know their URIs or methods beforehand.

Edit a resource

Editing or updating a resource is a common purpose for APIs. Edits can be achieved by sending either POST, PUT or PATCH requests to the respective resource. Although POST is allowed to append data to a resource’s existing representation it is recommended to use either PUT or PATCH as they convey a more explicit semantic.

Your server should respond with 200 OK if the update was performed, or 202 Accepted if it has yet to be applied. Pick the most appropriate error code if it cannot be completed.

Full updates

PUT has the semantics of replacing the current representation with the payload included in the request. If the payload is not of the same representation type as the current representation of the resource to update, the server can decide which approach to take. RFC7231 defines that the server can either

  • Reconfigure the target resource to reflect the new media type
  • Transform the PUT representation to a format consistent with that of the resouce before saving it as the new resource state
  • Reject the request with a 415 Unsupported Media Type response indicating that the target resource is limited to a specific (set) of media types.

A base resource containing a JSON HAL representation like …

{
    "name": "Charlie Smith",
    "age": 39,
    "job_title": "Software Developer",
    "_links": {
        "self": { "href": "/users/1234" },
        "employee": { "href": "https://www.acmee.com" },
        "curies": [{ "name": "ea", "href": "https://www.acmee.com/docs/rels/{rel}", templated": true}],
        "ea:admin": [
            "href": "/admin/2",
            "title": "Admin"
        ]
    }
}

… may receive an update request like this

PUT /users/1234 HTTP/1.1
Host: https://www.acmee.com
Content-Type: "application/json; charset=utf-8"
Content-Length: 85
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)

{
    "name": "Charlie Gold-Smith",
    "age": 40,
    "job_title": "Senior Software Developer"
}

The server may now replace the state of the resource with the given request body and also change the content-type from application/hal+json to application/json or convert the JSON payload to a JSON HAL representation and then replace the content of the resource with the transformed one or reject the update request due to an inaplicable media type with a 415 Unsupported Media Type response.

There is a difference between replacing the content directly or first transforming the representation to the defined representation model and then replacing the existing content with the transformed one. A subsequent GET request will return the following response on a direct replacement:

GET /users/1234 HTTP/1.1
Host: https://www.acmee.com
Accept-Encoding: gzip, deflate
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
ETag: "e0023aa4e"

{
    "name": "Charlie Gold-Smith",
    "age": 40,
    "job_title": "Senior Software Developer"
}

while the transformation and then replace approach will return the following representation:

GET /users/1234 HTTP/1.1
Host: https://www.acmee.com
Accept-Encoding: gzip, deflate
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
ETag: e0023aa4e

{
    "name": "Charlie Gold-Smith",
    "age": 40,
    "job_title": "Senior Software Developer",
    "_links": {
        "self": { "href": "/users/1234" },
        "employee": { "href": "https://www.acmee.com" },
        "curies": [{ "name": "ea", "href": "https://www.acmee.com/docs/rels/{rel}", templated": true}],
        "ea:admin": [
            "href": "/admin/2",
            "title": "Admin"
        ]
    }
}

Side-Effects

Note that PUT is allowed to have side-effects although it is defined as idempotent operation! This is documented in RFC7231 as

A PUT request applied to the target resource can have side effects on other resources. For example, an article might have a URI for identifying “the current version” (a resource) that is separate from the URIs identifying each particular version (different resources that at one point shared the same state as the current version resource). A successful PUT request on “the current version” URI might therefore create a new version resource in addition to changing the state of the target resource, and might also cause links to be added between the related resources.

Producing additional log entries is not considered as side effect usually as this is certainly no state of a resource in general.

Partial updates

RFC7231 mentions this regarding partial updates:

Partial content updates are possible by targeting a separately identified resource with state that overlaps a portion of the larger resource, or by using a different method that has been specifically defined for partial updates (for example, the PATCH method defined in RFC5789).

Partial updates can therefore be performed in two flavors:

  • Have a resource embed multiple smaller sub-resources and update only a respective sub-resource instead of the full resource via PUT
  • Using PATCH and instruct the server what to update

Partial update with overlapping state

If a user representation needs to be partially updated due to a move of a user to an other location, instead of updating the user directly, the related resource should be updated directly which reflects to a partial update of the user representation.

Before the move a user had the following representation

GET /users/1234 HTTP/1.1
Host: https://www.acmee.com
Accept: application/hal+json; charset=utf-8
Accept-Encoding: gzip, deflate
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
ETag: "e0023aa4e"

{
    "name": "Charlie Gold-Smith",
    "age": 40,
    "job_title": "Senior Software Developer",
    "_links": {
        "self": { "href": "/users/1234" },
        "employee": { "href": "https://www.acmee.com" },
        "curies": [{ "name": "ea", "href": "https://www.acmee.com/docs/rels/{rel}", templated": true}],
        "ea:admin": [
            "href": "/admin/2",
            "title": "Admin"
        ]
    },
    "_embedded": {
        "ea:address": {
            "street": "Terrace Drive, Central Park",
            "zip": "NY 10024"
            "city": "New York",
            "country": "United States of America",
            "_links": {
                "self": { "href": "/address/abc" },
                "google_maps": { "href": "https://maps.google.com/?ll=40.7739166,-73.970176" }
            }
        }
    }
}

As the user is moving to a new location she updates her location information like this:

PUT /address/abc HTTP/1.1
Host: https://www.acmee.com
Content-Type: "application/json; charset=utf-8"
Content-Length: 109
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)

{
    "street": "Standford Ave",
    "zip": "CA 94306",
    "city": "Pablo Alto",
    "country": "United States of America"
}

With the transformation-before-replace semantic for the mismatched media-type between the existing address resource and the one in the request, as described above, the address resource is now updated which has the effect that on a subsequent GET request on the user resource the new address for the user is returned.

GET /users/1234 HTTP/1.1
Host: https://www.acmee.com
Accept: application/hal+json; charset=utf-8
Accept-Encoding: gzip, deflate
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
ETag: "e0023aa4e"

{
    "name": "Charlie Gold-Smith",
    "age": 40,
    "job_title": "Senior Software Developer",
    "_links": {
        "self": { "href": "/users/1234" },
        "employee": { "href": "https://www.acmee.com" },
        "curies": [{ "name": "ea", "href": "https://www.acmee.com/docs/rels/{rel}", templated": true}],
        "ea:admin": [
            "href": "/admin/2",
            "title": "Admin"
        ]
    },
    "_embedded": {
        "ea:address": {
            "street": "Standford Ave",
            "zip": "CA 94306",
            "city": "Pablo Alto",
            "country": "United States of America"
            "_links": {
                "self": { "href": "/address/abc" },
                "google_maps": { "href": "https://maps.google.com/?ll=37.4241311,-122.1524475" }
            }
        }
    }
}

Patching partial data

PATCH is defined in RFC5789 and is not directly part of the HTTP spec per se. A common misbelief is, that sending only the fields that should be partially updated is enough within a PATCH request. The specification therefore states

The PATCH method requests that a set of changes described in the request entity be applied to the resource identified by the Request-URI. The set of changes is represented in a format called a “patch document” identified by a media type.

This means that a client should calculate the necessary steps needed to transform the resource from state A to state B and send these instructions to the server.

A popular JSON based media-type for patching is JSON Patch.

If the age and the job-title of our sample user changes and an additional field representing the income of the user should be added a partial update using PATCH using JSON Patch may look like this:

PATCH /users/1234 HTTP/1.1
Host: https://www.acmee.com
Content-Type: application/json-patch+json; charset=utf-8
Content-Length: 188
Accept: application/json
If-Match: "e0023aa4e"

[
    { "op": "replace", "path": "/age", "value": 40 },
    { "op": "replace", "path": "/job_title", "value": "Senior Software Developer" },
    { "op": "add", "path": "/salery", "value": 63985.00 }
]

PATCH may update multiple resources at once and requires to apply the changes atomically which means either all changes have to be applied or none at all which puts transactional burden on the API implementor.

A successful update may return something like this

HTTP/1.1 200 OK
Location: /users/1234
Content-Type: application/json
ETag: "df00eb258"

{
    "name": "Charlie Smith",
    "age": 40,
    "job_title": "Senior Software Developer",
    "salary": 63985.00
}

though is not restricted to 200 OK response codes only.

To prevent in-between updates (changes done in-between the previous fetch of the representation state and the update) ETag, If-Match or If-Unmodified-Since header should be used.

Error Handling

The spec on PATCH recommends the following error handling:

Type Error Code
Malformed patch document 400 Bad Request
Unsupported patch document 415 Unsupported Media Type
Unprocessable request, i.e. if the resoure would become invalid by applying the patch 422 Unprocessable Entity
Resource not found 404 Not Found
Conflicting state, i.e. a rename (move) of a field which does not exist 409 Conflict
Conflicting modification, i.e. if the client uses a If-Match or If-Unmodified-Since header which validation failed. If no precondition was available, the latter error code should be returned 412 Precondition Failed or 409 Conflict
Concurrent modification, i.e. if the request needs to be applied before acception further PATCH requests 409 Conflict

Delete a resource

Another common use of HTTP APIs is to delete an existing resource. This should usually be done using DELETE requests.

If the deletion was successful, the server should return 200 OK; an appropriate error code if it was not.

If our employee Charlie Smith has left the company and we now want to delete his records, that might look like this:

DELETE /employees/1/charlie-smith HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Content-Type: application/json

{
    'links': [
        {
            'uri': '/employees',
            'rel': 'create',
            'method': 'POST'
        }
    ]
}

List resources

The last common use of HTTP APIs is to obtain a list of existing resources on the server. Lists like this should be obtained using GET requests, since they only retrieve data.

The server should return 200 OK if it can supply the list, or an appropriate error code if not.

Listing our employees, then, might look like this:

GET /employees HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Content-Type: application/json

{
    'employees': [
        {
            'name': 'Charlie Smith',
            'age': 39,
            'job_title': 'Software Developer',
            'salary': 63985.00
            'links': [
                {
                    'uri': '/employees/1/charlie-smith',
                    'rel': 'self',
                    'method': 'GET'
                },
                {
                    'uri': '/employees/1/charlie-smith',
                    'rel': 'delete',
                    'method': 'DELETE'
                },
                {
                    'uri': '/employees/1/charlie-smith',
                    'rel': 'edit',
                    'method': 'PATCH'
                }
            ]
        },
        {
            'name': 'Donna Prima',
            'age': 30,
            'job_title': 'QA Tester',
            'salary': 77095.00
            'links': [
                {
                    'uri': '/employees/2/donna-prima',
                    'rel': 'self',
                    'method': 'GET'
                },
                {
                    'uri': '/employees/2/donna-prima',
                    'rel': 'delete',
                    'method': 'DELETE'
                },
                {
                    'uri': '/employees/2/donna-prima',
                    'rel': 'edit',
                    'method': 'PATCH'
                }
            ]
        }
    ],
    'links': [
        {
            'uri': '/employees/new',
            'rel': 'create',
            'method': 'PUT'
        }
    ]
}

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow