Introduction#

A hypermedia API provides an entry point to the API, which contains hyperlinks the clients can follow. Just like a person who visits a regular website, if they know the initial URL, then they can follow hyperlinks to navigate through the site. This has the advantage that the client only needs to understand how to detect and follow links. The URLs (apart from the initial entry point) and other details of the API can change without breaking the client.

The entry point to the Plone RESTful API is the portal root. The client can send an HTTP request for a REST API response by setting the "Accept" HTTP header to "application/json":

http

GET /plone HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X GET http://nohost/plone -H "Accept: application/json" --user admin:secret

httpie

http http://nohost/plone Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))

This uses content negotiation.

The server will then respond with the portal root in the JSON format:

HTTP/1.1 200 OK
Content-Type: application/json

{
    "@components": {
        "actions": {
            "@id": "http://localhost:55001/plone/@actions"
        },
        "aliases": {
            "@id": "http://localhost:55001/plone/@aliases"
        },
        "breadcrumbs": {
            "@id": "http://localhost:55001/plone/@breadcrumbs"
        },
        "contextnavigation": {
            "@id": "http://localhost:55001/plone/@contextnavigation"
        },
        "navigation": {
            "@id": "http://localhost:55001/plone/@navigation"
        },
        "navroot": {
            "@id": "http://localhost:55001/plone/@navroot"
        },
        "types": {
            "@id": "http://localhost:55001/plone/@types"
        },
        "workflow": {
            "@id": "http://localhost:55001/plone/@workflow"
        }
    },
    "@id": "http://localhost:55001/plone",
    "@type": "Plone Site",
    "UID": "55c25ebc220d400393574f37d648727c",
    "allow_discussion": null,
    "contributors": [],
    "creators": [
        "admin"
    ],
    "description": "",
    "effective": null,
    "exclude_from_nav": false,
    "expires": null,
    "id": "plone",
    "is_folderish": true,
    "items": [
        {
            "@id": "http://localhost:55001/plone/front-page",
            "@type": "Document",
            "description": "Congratulations! You have successfully installed Plone.",
            "review_state": "private",
            "title": "Welcome to Plone",
            "type_title": "Page"
        }
    ],
    "items_total": 1,
    "language": {
        "title": "English",
        "token": "en"
    },
    "lock": {
        "locked": false,
        "stealable": true
    },
    "parent": {},
    "relatedItems": [],
    "review_state": null,
    "rights": "",
    "subjects": [],
    "table_of_contents": null,
    "text": null,
    "title": "Plone site",
    "type_title": "Plone Site"
}
@id

A unique identifier for resources (IRIs). The @id property can be used to navigate through the web API by following the links.

@type

Sets the data type of a node or typed value.

items

A list that contains all objects within that resource.

A client application can "follow" the links (by calling the @id property) to other resources. This lets a developer build a loosely coupled client that does not break if some URLs change. Only the entry point of the entire API (in our case the portal root) needs to be known in advance.

Here is another example, this time showing a request and response for a document. Click http, curl, httpie, or python-requests to show the syntax of the request for that client:

http

GET /plone/front-page HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X GET http://nohost/plone/front-page -H "Accept: application/json" --user admin:secret

httpie

http http://nohost/plone/front-page Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/front-page', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))
HTTP/1.1 200 OK
Content-Type: application/json

{
    "@components": {
        "actions": {
            "@id": "http://localhost:55001/plone/front-page/@actions"
        },
        "aliases": {
            "@id": "http://localhost:55001/plone/front-page/@aliases"
        },
        "breadcrumbs": {
            "@id": "http://localhost:55001/plone/front-page/@breadcrumbs"
        },
        "contextnavigation": {
            "@id": "http://localhost:55001/plone/front-page/@contextnavigation"
        },
        "navigation": {
            "@id": "http://localhost:55001/plone/front-page/@navigation"
        },
        "navroot": {
            "@id": "http://localhost:55001/plone/front-page/@navroot"
        },
        "types": {
            "@id": "http://localhost:55001/plone/front-page/@types"
        },
        "workflow": {
            "@id": "http://localhost:55001/plone/front-page/@workflow"
        }
    },
    "@id": "http://localhost:55001/plone/front-page",
    "@type": "Document",
    "UID": "SomeUUID000000000000000000000001",
    "allow_discussion": false,
    "changeNote": "",
    "contributors": [],
    "created": "1995-07-31T13:45:00+00:00",
    "creators": [
        "test_user_1_"
    ],
    "description": "Congratulations! You have successfully installed Plone.",
    "effective": null,
    "exclude_from_nav": false,
    "expires": null,
    "id": "front-page",
    "is_folderish": false,
    "language": "",
    "layout": "document_view",
    "lock": {
        "locked": false,
        "stealable": true
    },
    "modified": "1995-07-31T17:30:00+00:00",
    "next_item": {},
    "parent": {
        "@id": "http://localhost:55001/plone",
        "@type": "Plone Site",
        "description": "",
        "title": "Plone site",
        "type_title": "Plone Site"
    },
    "previous_item": {},
    "relatedItems": [],
    "review_state": "private",
    "rights": "",
    "subjects": [],
    "table_of_contents": null,
    "text": {
        "content-type": "text/plain",
        "data": "<p>If you&#x27;re seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone mailing lists about this.</p>",
        "encoding": "utf-8"
    },
    "title": "Welcome to Plone",
    "type_title": "Page",
    "version": "current",
    "versioning_enabled": true,
    "working_copy": null,
    "working_copy_of": null
}

Content Negotiation#

Content negotiation is a mechanism defined in the HTTP specification that makes it possible to serve different versions of a document (or more generally, a resource representation) at the same URI, so that user agents can specify which version fit their capabilities the best.

The user agent (or the REST consumer) can ask for a specific representation by providing an Accept HTTP header that lists acceptable media types, such as JSON:

GET /
Accept: application/json

The server is then able to supply the version of the resource that best fits the user agent's needs. This is reflected in the Content-Type header:

HTTP 200 OK
Content-Type: application/json

{
  'data': ...
}