stoney core: REST API

From stoney cloud
Revision as of 11:24, 14 November 2013 by Chrigu (Talk | contribs)


Jump to: navigation, search

REST API

  • The REST API will be implemented as a first-class citizen
    • It provides all the available functions and data to its clients
    • Serves as a data and business logic abstraction layer
  • The REST API will be implemented using HTTPS and REST principles
    • Clients are required to validate the certificate (at least via CA)
  • The REST API uses JSON as the primary data interchange format (serialization of data structures should be abstracted), other formats should be possible in the future.
  • Authentication via Basic HTTP-Auth
  • Multiple authentication methods can be added in the future (possibly Web-Server assisted):
    • X509 Certificate based authentication
    • Kerberos
    • API key with shared secret
    • Access tokens
    • OAuth
  • versioned API:
    • starting with one version number in the URI, for example: https://api.selfcare.com/v1/customer , corresponding to the major version in SemVer
    • minor version will be added via Request-Header-Field in future (as-needed)
  • All API calls need to be fully nonblocking. If an expensive call has to be made to a backend system, the client needs to be provided with a status URI which can be checked for the current status or preferably be notified via WebSockets.
  • Input validation must be performed for all data (validation of data happens twice: in the API and the client)
  • Meaningful error message will be presented to the client
  • All API functions are to be documented using an accepted documentation standard (doxygen (preferred), phpDocumentor or Sami)
  • The API will be based on existing, proven and tested open source modules and components, coming either from a framework are as stand alone implementations,


Why a REST API?

  • Separation and abstraction of presentation and business logic
    • Faster development/test cycles for business logic
    • Smaller development packages
  • Support for multiple clients with the same code base
    • HTML/JS/CSS for selfcare Web GUI
    • Command line interface for easy scripting
    • Integration into third party provisioning systems for resellers
  • Automatic testing of functionality
  • Base for responsive resp. Mobile First Web-Applications/-Design


Yii related API modules

Service implementation details

Base URI

The RESTful web service has to be accessible via a secure HTTP (HTTPS) base URI, for instance https://api.example.com/v1/customers. The definition of the base URI is up to the provider of the service. The only requirements are the use of HTTPS and the presence of the service's version information, so that further changes are possible without breaking existing clients.

Client authentication and authorization

The service needs to authenticate each client via HTTP basic authentication by a user name and a corresponding password.

Furthermore the service must retrieve the authenticated users role and object ownership and respect their respective value when returning collections and elements and acting on HTTP methods.

Data interchange format

The service needs to accept and send all data in the JSON data interchange format via HTTP, encoded as UTF-8. Thus a client needs to accept and use the application/json media type. Further media types might be supported in the future.

This results in the following required request and respons headers:

Request header Response header
Accept: application/json Content-Type: application/json; charset=UTF-8
  • If the client sends an Accept header with an unsupported value (at the moment only application/json is supported), the service will respond with a 406 (Not Acceptable) error code.
  • If no Accept header is sent, the server will use JSON, possibly pretty-printed and annotated.
  • If the client sends a Content-Type other than application/json on a POST, PUT or PATCH request, the services response with a 415 (Unsupported Media Type) error code.

Future extension: Client may supply application/vnd.org.stoney-cloud.api+json as Content-Type to declare the requested schema/format of the data. This can then also be used to introduce additional versioning.

Error codes and responses

The service needs to return appropriate HTTP status codes for every request, the following table lists the commonly used codes:

HTTP status code Text Description
200 OK Success.
201 Created A new resource was successfully created.
400 Bad Request The request was invalid. A descriptive error message will be sent within the response body.
404 Not Found The requested resource could not be found but may be available again in the future.
406 Not Acceptable The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request.
401 Unauthorized The client has failed or not yet tried to authenticate.
403 Forbidden The client is not allowed to access the requested resource.
415 Unsupported Media Type The request entity has a media type which the server or resource does not support. For example, sending XML instead of JSON in a POST, PUT or PATCH method.
500 Internal Server Error An internal error succoured. A descriptive error message will be sent within the response body.
503 Service Unavailable The service is temporary unavailable, because it is overloaded or down for maintenance

Additionally the service returns a descriptive error object in case a HTTP error was returned (4xx) within the message body of the response. An error object consists of an error code and a human readable error message, with further detailed error messages if applicable.

{ 
  "error": { 
    "code": 123,
    "message": "Validation failed"
    "details" : [
      {
        "code" : 5432,
        "field" : "firstName",
        "message" : "First name cannot be longer than 35 characters"
      },
      {
        "code" : 5123,
        "field" : "password",
        "message" : "Password cannot be blank"
      }
    ]
  }
}

Mandatory headers

Besides the above mentioned headers, the following headers are mandatory.

  • every answer to a GET reqest should always include ETag and Last-Modified header. This allows a proxy to cache requests and a client to revalidate already fetched data.
  • the service must recognize ETag, Last-Modified and Cache-Control: none provided by the client and act accordingly.
  • every answer to a GET request must include proper Cache-Control headers

Resources and HTTP methods

Resources are always nouns, and specified in plural (such as resellers, customers, users etc.), this prevents one from dealing with irregular pluralizations such as person/people.

The manipulation of resources happens via the HTTP request methods such as GET, POST, PUT, DELETE and PATCH. The following example illustrates the concept with a fictive user resource:

HTTP request Description
GET /users Retrieves a list of users
GET /users/12345678 Retrieves a specific user with user ID 12345678
POST /users Creates a new user
PUT /users/12345678 Updates the user with user ID 12345678
PATCH /users/12345678 Partly updates the user with user ID 12345678
DELETE /users/12345678 Deletes the user with user ID 12345678
DELETE /users Deletes all users

POST

On successful creation of an object, the service must return an URI string to the newly created element.

Example:

Request:

POST /v1/users/ HTTP 1.1
HOST: api.example.com
Accept: application/json
Content-Type: application/json
{
  "usersName": "Mueller",
  "usersType": "reseller",
}

Answer:

HTTP/1.1 201 Created
Content-Type: application/json; charset=UTF-8 
Location: https://api.example.com/v1/users/67890
{
  "id": 67890,
  "location": "https://api.example.com/v1/users/67890",
}

Relations

If a relation can only exist within another resource, it will be represent by its URI, for example: /threads/123/messages/45. This URI represents the message with ID #45 of the forum thread with ID #123.

If a resources can stand by its own, such as users it won't be added as a sub-resource. Relations are always returned as URIs, which the client can hit.

@TBD: Shall there be a functionality to request the embedding of elements, for relations that are commonly requested alongside the resource (to save requests)?

Filtering, sorting and searching

Filter, sort and search requests are added as query parameters to the resource URI.

For filtering the objects returned by a resource URI, the name of an object's attribute is added as a query parameter with the required value.

For example, get all active user elements GET /users?status=active


For sorting the objects returned by a resource URI, the query parameter sort is added with the object's sort attribute(s) as the value.

For example, sort all users by their last and first name GET /users?sort=lastname,firstname


For full text search the objects returned by a resource URI, the query parameter q is added with the value to search for.

For example, GET /users?q=Muell will return users named Mueller as well as the ones living at Muellhaldenstrasse.

Pagination

@TODO

REST API documentation

Reseller resource

Ressource representing a reseller.

Reseller creation (POST)

Create a new reseller.

Base URI: https://api.example.com/v1/resellers

TBD: Table of all attributes (including optional ones)

Reseller creation (POST) example

Request:

POST /v1/resellers/ HTTP 1.1
HOST: api.example.com 
Accept: application/json
Content-Type: application/json
{
  "isCompany": true,
  "billingAddress":
  {
    "organizationName": "Reseller Ltd.",
    "gender": 'm',
    "givenName": "Name",
    "surname": "Surname",
    "postalAddress": "Street Number",
    "countryCode": "CH",
    "postalCode": "Postal Code",
    "localityName": "Locality",
    "preferredLanguage": "en-GB",
    "mail": "name.surname@example.com",
    "telephoneNumber": "+41 00 000 00 00",
    "mobileTelephoneNumber": "+41 00 000 00 00",
    "websiteURL": "https://www.example.com/"
  }
}

Answer:

HTTP/1.1 201 Created
Content-Type: application/json; charset=UTF-8 
Location: https://api.example.com/v1/resellers/4000001
{
  "id": 4000001,
  "location": "https://api.example.com/v1/resellers/4000001",
}

Reseller retrieval (GET)

Reseller collection retrieval (GET) example

Request:

GET /v1/resellers/ HTTP 1.1
HOST: api.example.com 
Accept: application/json

Answer:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8 
[
  {
    "id": 4000000,
    "location": "https://api.example.com/v1/resellers/4000000",
    "isCompany": true,
    "descriptiveName": "stepping stone GmbH"
  },
  {
    "id": 4000001,
    "location": "https://api.example.com/v1/resellers/4000001",
    "isCompany": true,
    "descriptiveName": "Company Name or Givenname Surname"
  }
]
Reseller element retrieval (GET) example

Request:

GET /v1/resellers/4000001 HTTP 1.1
HOST: api.example.com 
Accept: application/json

Answer:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8 
{
  "id": 4000001,
  "isCompany": true,
  "billingAddress":
  {
    "organizationName": "Reseller Ltd.",
    "gender": 'm',
    "givenName": "Name",
    "surname": "Surname",
    "postalAddress": "Street Number",
    "countryCode": "CH",
    "postalCode": "Postal Code",
    "localityName": "Locality",
    "preferredLanguage": "en-GB",
    "mail": "name.surname@example.com",
    "telephoneNumber": "+41 00 000 00 00",
    "mobileTelephoneNumber": "+41 00 000 00 00",
    "websiteURL": "https://www.example.com/"
  }
  "shippingAddresses":
  [
    TBD,
  ]
}

Reseller update (PUT)

Updates an existing Reseller, the PUT method requires one to sent the complete record, thus the work-flow is normally as follows:

  1. A GET request on the Reseller element URI will be made to fetch the whole document.
  2. Update the fields which content has changed
  3. Send a PUT request with all the reseller data

Request:

PUT /v1/resellers/12345678 HTTP 1.1
HOST: api.example.com 
Accept: application/json
Content-Type: application/json
{
  "id": 12345678
  "isCompany": true,
  "billingAddress":
  {
    "organizationName": "Reseller Ltd.",
    "gender": 'm',
    "givenName": "Name",
    "surname": "Surname",
    "postalAddress": "Street Number",
    "countryCode": "CH",
    "postalCode": "Postal Code",
    "localityName": "Locality",
    "preferredLanguage": "en-GB",
    "mail": "name.surname@example.com",
    "telephoneNumber": "+41 00 000 00 00",
    "mobileTelephoneNumber": "+41 00 000 00 00",
    "websiteURL": "https://www.example.com/"
  }
}

Answer:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8 
 

Reseller partly update (PATCH)

Reseller deletion (DELETE)