Usage

REST architecture style:

  • Client–server
  • Stateless
  • Cacheable
  • Layered system
  • Code on demand (optional)
  • Uniform interface

Content type

All data are available in the following format:

  • XML (application/xml)

Endpoint

A dedicated cluster has been setup to allow access in better conditions to

https://api.libcast.com

instead of

https://console.libcast.com/services/

which is deprecated and won’t be reachable at 2015-10-25

No need to use anymore /services/

Services browsing

As a REST respectful web service implementation, Libcast web services declare just a few root services addresses, and all other addresses have to be discovered browsing the Libcast services. / gives access to platform list and files services.

Each data response includes a href property to address the current data, other related data and browse the services.

This way, the client do not have to construct URLs to request our resources. In order to minimize the amount of request in returning sessions, the REST client should cache the URLs and direct access them the next times.

Pagination

If you want to paginate the results, include a Range HTTP header in the request. By default, no pagination is applied.

GET /platform/eagle-tv/users HTTP/1.0
Range: entities=30-40

The server response is then:

HTTP/1.0 200 OK
Content-type: application/xml
Content-Range: entities 30-40/342

The pagination is based on start/end offsets, not page index/size. Indices are 0-indexed, so Range: entities=0-4 with return 5 entities, from index 0 to index 4. Note that the response contains the total number of entities (342 in the above example). You can use this information to control fetching.

At the moment, the range-unit you specify must be entities. Depending on the URL you are fetching, the paginated entities will be users, streams, resources… Furthermore, the RFC allows several ranges to be asked in a request but we handle only the first one since it would make little to no sense to query discontinued ranges of entities.

Keep in mind that by requesting entities via ranged requests, you could miss some entities or have the same entity being sent twice because of insertions and deletions that may occur between two requests.

Although we don’t recommend it since you can experience slow response times, you can issue a request without range rule if you really need a full set of entities.

Error codes

REST web services make usage of HTTP codes then you can easily understand what happened just from the returned HTTP code.

The following codes are used in our services:

  • Common user-side errors:
    • 400 Bad Request: the request can not be interpreted
    • 401 Authentication Required: authentication is required to access the web services
    • 404 Page Not Found: there is no resource at the HTTP address requested
    • 406 Not Acceptable: the requested content type is not supported by Libcast web services
    • 412 Erroneous Data: the submitted entity is invalid (a required field is missing, a constrained field is invalid…)
    • 416 Requested Range Not Satisfiable: the Range HTTP header is malformed
  • Common server-side errors:
    • 500 Internal Error: the server encountered an internal error
  • List action:
    • 200 Ok
  • Info action:
    • 200 Ok
  • Update action:
    • 200 Ok: update successful
  • Delete action:
    • 204 No Content: deletion successful

Other status can be used in the future. All status codes respect the HTTP 1.1 specification.

Authentication

Libcast web services use HTTP Digest HTTP Digest Authentication) to authenticate the emitter of an API request. Each Libcast user has an API key which does not change in time (unlike the user password) and is used to issue signed API request.

An initial request is required to ask the realm to the server:

GET / HTTP/1.0

Server response:

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Digest realm="libcast",
                        domain="/", qop=auth, algorithm=MD5,
                        nonce="2a6daa37f26518b89ca2537eb0602aaf",
                        opaque="94619f8a70068b2591c2eed622525b0e"

Let assume that the client user is scott and his API key is t1.g3/R.

The client first declares some variables:

uri = /
nc = 00000001 // request counter
cnonce = MDE1OTgz // client nonce

And then the digest is generated:

ha1 = md5(username:realm:api_key) = f420c8e397552883627180504b2604d2
ha2 = md5(method:uri) = c7bdbbd1236664e402675e9205623312
response = md5(ha1:nonce:nc:cnonce:qop:ha2) = ce03e928168e154c7a9fc20b817a6d8a

And a new request is issued:

GET / HTTP/1.0
WWW-Authenticate: Digest username="scott", realm="libcast",
                        nonce="2a6daa37f26518b89ca2537eb0602aaf",
                        uri="/services/", cnonce="MDE1OTgz",
                        nc=00000001, qop="auth", algorithm=MD5,
                        response="39f88fe9328a8b6a7763a47998534833",
                        opaque="94619f8a70068b2591c2eed622525b0e"
HTTP/1.0 200 OK
Content-Type: application/xml
[..]

The nonce has a defined lifetime so other requests can be issued with the same nonce. The client has to change the cnonce, the nc and, consequently, the response for in additional request.

Note that the HTTP Digest authentication can be easily tested with cURL:

$ curl --digest -u scott:t1.g3/R http://domain/services/

$ curl --digest -u scott:t1.g3/R https://api.libcast.com
GET / HTTP/1.0
HTTP/1.0 401 Unauthorized
WWW-Authenticate: Digest realm="libcast",
                        domain="/", qop=auth, algorithm=MD5,
                        nonce="2a6daa37f26518b89ca2537eb0602aaf",
                        opaque="94619f8a70068b2591c2eed622525b0e"
GET / HTTP/1.0
WWW-Authenticate: Digest username="scott", realm="libcast",
                        nonce="2a6daa37f26518b89ca2537eb0602aaf",
                        uri="/", cnonce="MDE1OTgz",
                        nc=00000001, qop="auth", algorithm=MD5,
                        response="39f88fe9328a8b6a7763a47998534833",
                        opaque="94619f8a70068b2591c2eed622525b0e"
HTTP/1.0 200 OK
Content-Type: application/xml
[..]

Curl automatically sends a first request to get the realm, and then an authenticated request to get the data.

Partial update

When issuing an update, you can provide only the updated fields in the request, the other fields will not be modified. If you want to clear the content of a field, you have to include it in the request with no value.

A partial update is issued with a HTTP PUT method since the PATCH method (RFC proposal for partial updates) is not available in the framework used by the web services.

Caching

Some services returns links to media files (thumbnail, video…). These medias are accessible while authenticated via the API client. If you want to access these medias later, you have to re authenticate but you can also cache them on the client side. This is specially interesting for thumbnails which are light but often used in client applications.

Upload a file from an external form

It is possible to upload a file from a form hosted on an external host under conditions. The usage of Plupload is required.

Sample file

This is a commented example. Adapt the $config parameters to match your needs.

<?php
/**
 * This is a sample of an upload on the Libcast Platform using Composer, Plupload and Guzzle
 * Composer installation instructions https://getcomposer.org/
 * Guzzle installation instructions http://guzzle.readthedocs.org/en/latest/overview.html#installation
 * Plupload installation instructions http://www.plupload.com/download/
 *
 * In order to load the dependencies, type in the following commands:
 * composer init
 * composer require guzzlehttp/guzzle
 * composer require moxiecode/plupload
 * composer update
 */

// Configuration
$config = [
  // The folder URL is retrieved from the API
  // The root folder is located at /files
  // Other folders are located at /files/<folder>
  'folder_url'   => 'https://api.libcast.com/files',
  // Provide your login/password information. The digest protocol must NOT be changed
  'auth'         => ['login', 'password', 'digest'],
];

require_once 'vendor/autoload.php';

/*
 * STEP 1/2 : get the form URL with a webservice call
 */
// The webservice must be call with a POST method
$serviceUrl = $config['folder_url'] . '/upload_links';
$client = new GuzzleHttp\Client;
$response = $client->post($serviceUrl, ['auth' => $config['auth']]);

// Extract URL from XML response
$uploadUrlElem = new SimpleXMLElement($response->getBody());
foreach ($uploadUrlElem as $link) {
  // Valid response formats are 'html', 'json'
  // We choose the json response format
  if ($link['rel'] == 'json') {
    $uploadUrl = $link['href'];
  }
}

/*
 * STEP 2/2 : Build the form with Plupload and chunks enabled
 * See http://www.plupload.com/docs/Chunking
 */

// This minimal sample is adapted from the official documentation:
// http://www.plupload.com/docs/Getting-Started
?>
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Libcast Upload Sample with Guzzle</title>
  <script type="text/javascript" src="vendor/moxiecode/plupload/js/plupload.min.js"></script>
</head>
<body>

<ul id="filelist"></ul>
<br />

<div id="container">
  <a id="browse" href="javascript:;">[Browse...]</a>
  <a id="start-upload" href="javascript:;">[Start Upload]</a>
</div>

<br />
<pre id="console"></pre>

<script type="text/javascript">
  var uploader = new plupload.Uploader({
    // Required parameter to allow chunking
    required_features: "send_browser_cookies,chunks",
    // this can be an id of a DOM element or the DOM element itself
    browse_button: 'browse',
    // The upload URL previously generated by the webservice
    url: '<?php echo $uploadUrl ?>',
    // file_data_name must NOT be changed
    file_data_name: 'file[path]',
    // The chunk size on Libcast Platform is 64mb. Any other value may lead to upload failures
    chunk_size          : '64mb',
    multipart_params    : { 'error' : true }
  });

  uploader.init();

  uploader.bind('FilesAdded', function(up, files) {
    var html = '';
    plupload.each(files, function(file) {
      html += '<li id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ') <b></b></li>';
    });
    document.getElementById('filelist').innerHTML += html;
  });

  uploader.bind('UploadProgress', function(up, file) {
    document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = '<span>' + file.percent + "%</span>";
  });

  uploader.bind('Error', function(up, err) {
    document.getElementById('console').innerHTML += "\nError #" + err.code + ": " + err.message;
  });

  document.getElementById('start-upload').onclick = function() {
    uploader.start();
  };
</script>
</body>
</html>

Testing the file

Save this file as index.php.

Run php -S localhost:8000

Type in your browser at http://localhost:8000

Advices and good practices

Use curl to test the web services. Be aware that properties order can change in the response, and that Libcast modules can add some properties to the responses too.