API Documentation

API Design Principals

The API is developed using the REST style. It exposes the data model for reading/editing at a low level.

URLs are mapped to resources in three different ways:

  • A URL that represents a collection of objects, a GET on this URL generally returns a paged result of links to the contained objects:

    >>> GET("https://api.metropublisher.com/123/sections")
    {'items': [['https://api.metropublisher.com/123/sections/c1c189f2-d24a-11e1-b431-001b63a90f63'],
               ['https://api.metropublisher.com/123/sections/c1c1a7f2-d24a-11e1-8bea-001b63a90f63'],
               ['https://api.metropublisher.com/123/sections/c1c1b3dc-d24a-11e1-b1d7-001b63a90f63']]}
    
  • A URL that represents a single object. This is normally a dictionary (or hash-map) representing the object:

    >>> GET("https://api.metropublisher.com/123/sections/c1c1a7f2-d24a-11e1-8bea-001b63a90f63")
    {'auto_featured_stories': False,
     'auto_featured_stories_num': 5,
     'externalurl': None,
     'feature_image_url': None,
     'hide_in_nav': False,
     'lead_story_url': None,
     'meta_description': None,
     'meta_keywords': None,
     'meta_title': None,
     'ord': 1,
     'parent_uuid': 'c1c1b3dc-d24a-11e1-b1d7-001b63a90f63',
     'show_prev_next': False,
     'title': 'Sample Subsection',
     'urlname': 'sample-subsection',
     'uuid': 'c1c1a7f2-d24a-11e1-8bea-001b63a90f63'}
    
  • Higher level methods which perform complex operations. (e.g. GET http://api.metropublisher.com/123/tag_cloud)

Using different HTTP methods on the same URL will cause different actions to be performed:

Resource Type GET PUT POST DELETE PATCH
Single Object Retrieve all information about an object Replace or Create an object   Delete the object Replace some attributes of the object
Collection Retrieve some information on a lot of objects. Similar to a SQL query. Replace all objects in the collection Create a new object in the collection Delete the collection  
Higher Level Retrieve information   Perform an action    

Errors

All API errors return a 4xx or 5xx HTTP status code and may have a dictionary as the body. The HTTP status codes have meanings as defined in the HTTP Specification.

The returned dictionary, if present, will have an 'errors' key. For example:

>>> GET("https://api.metropublisher.com/123/sections?fields=unknown-xyz")
{'error': 'bad_parameters',
 'error_description': 'One or more of your incoming parameters failed validation, see info for details',
 'error_info': {'fields': '"unknown" is not one of hide_in_nav, ord, parentid, title, url, urlname, uuid'}}

The possible keys in the dictionary are:

error:a string code identifying the error to computers
error_description:(optional) a plaintext description of the error not meant to be displayed to end users (only developers)
error_uri:(optional) A link to a human readable web page explaining the error to end users
error_info:(optional) An object with more detailed information on the error. This object is normally a dictionary and it's form varies according to the value in error.

Authentication

The MP API authentication is based on the OAuth 2 specification using bearer tokens. To use the API, you must first get a token using an OAuth and then use that token to access the API.

Getting Tokens

Currently the MP API only allows the "Client Credentials" Oauth 2 grant type. For this grant type you will need to get an API User/Secret from the Service Dashboard.

Once you have the API User/Secret you can get the token via an HTTP POST request to https://go.metropublisher.com/oauth/token. For example:

>>> POST('https://go.metropublisher.com/oauth/token',
...      'grant_type=client_credentials&api_key=sd68naoxU&api_secret=567',
...      content_type='application/x-www-form-urlencoded') 
{'access_token': 'vF9dft4qmT',
 'expires_in': 86400,
 'items': [{'id': 3,
            'roles': ['mp.super'],
            'url': 'https://api.metropublisher.com/3/'}],
 'token_type': 'bearer'}

This returns a bearer token valid for 86400 seconds with a value of vF9dft4qmT. Once the token expires, a new one must be fetched.

Trying to access a resource without a token or with an invalid or expired token will result in a HTTP 401 response.

Using Tokens

To use an access token to gain access to a resource the token must be included in the in the Authorization HTTP Request header. For example, a bearer token is included like this:

Authorization: Bearer vF9dft4qmT

Authorization

Access tokens have roles (e.g. public_user) which control access to the API. Each URL/HTTP Method requires a certain level of access to be able to use it. For example, users with only the public_user role will not be able to call PUT on most URLs.

Additionally, some functionality of URLs may require certain roles. For example a public_user calling GET on a URL will only receive data that they are allowed to see.

The Service Dashboard Website is where roles are set for API User/Secret combinations (and hence the tokens generated for them).

Caching

There is a HTTP cache in front of the API to improve performance. Most GET requests to the API will have a Cache-Control header detailing for how long the result may be cached.

If an application has a specific need to get uncached data, it should use the Cache-Control: no-cache header.

Applications should NOT use no-cache unless absolutely necessary as it will cause the applications to slow down. The resource cache times will be kept as small as possible while guaranteeing good performance.

Data Format

The format of the returned data is determined by the HTTP Accept header. If this header is missing, JSON is returned. If no available format matches any option in the Accept header an error is returned with the 406 status code.

Currently JSON is the only available format. More space efficient binary formats such as binary plist are planned in near future.

Collection Resources

Controlling what is returned

Collection resources can be passed a fields parameter to control what attributes of the contained resources are returned. Each returned item is a list of attribute values in the order passed to fields.

In a query string, fields is a comma separated list. So, for example, to get both the id and URL to all sections you would do this:

>>> GET("https://api.metropublisher.com/123/sections?fields=uuid-title-url")
{'items': [['c1c189f2-d24a-11e1-b431-001b63a90f63',
            'Sample Section 2',
            'https://api.metropublisher.com/123/sections/c1c189f2-d24a-11e1-b431-001b63a90f63'],
           ['c1c1a7f2-d24a-11e1-8bea-001b63a90f63',
            'Sample Subsection',
            'https://api.metropublisher.com/123/sections/c1c1a7f2-d24a-11e1-8bea-001b63a90f63'],
           ['c1c1b3dc-d24a-11e1-b1d7-001b63a90f63',
            'Sample Section',
            'https://api.metropublisher.com/123/sections/c1c1b3dc-d24a-11e1-b1d7-001b63a90f63']]}

Paging

All collection results are paged, a typical result would be a hash map/dictionary:

{'next': 'https://api.metropublisher.com/123/foo?fields=url-id&page=9&foofilter=bar',
 'prev': 'https://api.metropublisher.com/123/foo?fields=url-id&page=11&foofilter=bar',
 'items': [['https://api.metropublisher.com/123/foo/101', 101],
           ['https://api.metropublisher.com/123/foo/223', 223],
           ...
           ['https://api.metropublisher.com/123/foo/254', 254]]}

Where the keys are defined as follows

next:A link to the next page of results maintaining the current search parameters. If it is not present, there is no next page.
prev:A link to the previous page of results maintaining the current search parameters. If it is not present, there is no previous page.
items:A list of items as lists. The item attributes are the same number of and in the order of the fields query parameter. The fields query parameter normally defaults to url, giving a list of urls to the individual items.

Ordering

By default no order is applied to the returned results. Each collection resource will provide a list of possible ordering options and modifiers.

Ordering options often map to fields and modifiers to the way that field should be sorted. The modifier, which always has a default value, is separated from the ordering option by a period.

For example, it is the default for sections sorted by urlname that ascending order is used, so these are equivalent:

>>> GET("https://api.metropublisher.com/123/sections?fields=urlname-uuid&order=urlname")
{'items': [['sample-section', 'c1c1b3dc-d24a-11e1-b1d7-001b63a90f63'],
           ['sample-section-2', 'c1c189f2-d24a-11e1-b431-001b63a90f63'],
           ['sample-subsection', 'c1c1a7f2-d24a-11e1-8bea-001b63a90f63']]}
>>> GET("https://api.metropublisher.com/123/sections?fields=urlname-uuid&order=urlname.asc")
{'items': [['sample-section', 'c1c1b3dc-d24a-11e1-b1d7-001b63a90f63'],
           ['sample-section-2', 'c1c189f2-d24a-11e1-b431-001b63a90f63'],
           ['sample-subsection', 'c1c1a7f2-d24a-11e1-8bea-001b63a90f63']]}

To sorting in descending order we change the modifier:

>>> GET("https://api.metropublisher.com/123/sections?fields=urlname-uuid&order=urlname.desc")
{'items': [['sample-subsection', 'c1c1a7f2-d24a-11e1-8bea-001b63a90f63'],
           ['sample-section-2', 'c1c189f2-d24a-11e1-b431-001b63a90f63'],
           ['sample-section', 'c1c1b3dc-d24a-11e1-b1d7-001b63a90f63']]}

Single Object Resources

A resource that represents a single object nearly always returns a hash map (or dictionary). If the PUT HTTP method is defined for a resource, then it uses the same input as GET returns. So PUTing data from a previous GET should have no effect.

Warning

Using PUT for editing an object may be dangerous if the API is extended and new keys are introduced. As PUT replaces the target object existing information in these new keys may be deleted.

Where possible the PATCH method should be used for editing.

The PATCH method allows editing individual keys of an object rather than replacing it. Only the keys present in the request will trigger changes.

Data Types

Dates and Times

Dates and times are passed as strings in the ISO 8601 format, for example:

2012-04-24
2012-04-24T09:51:00.838444

Almost all time zones handled by the API will be naive, i.e. they will not contain any timezone information. These naive timezones are in the timezone of the MP instance, which can be discovered by checking the instance settings:

>>> GET("https://api.metropublisher.com/123/settings/timezone")
'Europe/Berlin'