Wi-Flight Integration API

An API (Application Programming Interface) is provided on the Wi-Flight servers for the following purposes:

The API is RESTful, based on HTTP, with data in XML format. Most operations require that clients be logged in, although specifically designated public data may be accessed without logging in.

Table of contents

Typical use case

First, the client logs in by making the following HTTP request:

After logging in, an HTTP cookie will be sent back to the client. This cookie must be sent together with all future requests in order to access resources which require logging in. Some HTTP client frameworks provide automatic cookie jar management.

Example of retreiving details for a flight:

Example of creating an aircraft reservation:

Logging in

You may send an HTTP POST request to log in to the API. In order to log in, you must have a valid username and password. This is the same username and password that can be used to log in to the Wi-Flight web interface the end users. Depending on which acount you log in to, different operations will be possible. Typically, the following types of accounts will exist:

Upon successful login, the HTTP server will return an HTTP cookie. This cookie must be sent along with all future requests in order for permissions to be granted.

API login: /auth/login

Clients may retain the session cookie in persistent storage for the duration of its validity period. However, in the interest of simplicity of client development, it is expected that many clients will choose to omit the complexity of persistent storage, and retain the cookie only while the client script or function call is running and forget it afterwards. We recommend that such clients ask for a short expiration period (such as 5 minutes) in order to avoid creating many long-term sessions that are immediately forgotten.

API logout: /auth/logout

This API call can be used to terminate a valid login session. You can also just let the session expire instead.

OpenID login: /auth/openid_enter

It is possible to log in using an OpenID instead of a password. This interface is intended to be used in a web browser, not in API client software (because logging in with OpenID generally involves HTML forms and interactions with the end user).

OpenID logins can be used to enable you to provide hyperlinks to Wi-Flight applications (e.g. a hyperlink to a flight playback) which you can present in your own web applications and which will allow the user to navigate from your application to the Wi-Flight application without logging in to each application separately.

In order to use this feature, the association between an OpenID and the Wi-Flight account must be already configued. It is possible to add OpenIDs to Wi-Flight end-user accounts when they are created.

Please remember to %-escape special characters in both URLs.

Example: hyperlink to view flight playback of flight #3189 after logging in using Google OpenID:

		https://www.wi-flight.net/auth/openid_enter?openid=http%3a%2f%2fwww.google.com%2fprofiles%2fvandry&url=%2fflightview%2f3189
	

If the user agent is already logged in (has a valid session cookie) then it will simply be redirected to the value of the url parameter. It will not terminate the old session and log in again using OpenID.

Flight and aircraft data

Flight and aircraft data can be accessed using URLs of the following form:

All requests use HTTP GET. Most requests accept optional query string parameters to specify, for example, offsets from the beginning of the flight.

These API nodes are used by Wi-Flight's applications such as flight playback (https://www.wi-flight.net/flightview/). No third party usage of these APIs has been anticipated at this time. Integrators who are interested in using them are welcom to do so, and should request additional documentation.

Reservations

Reservations are used to inform the system of which airplanes are scheduled to be flying, when they are scheduled to fly, and who is aboard. Reservations are usually created before the airplane flies, but they can also be created retrospectively.

Whenever reservations are created or updated, the system checks to see if the reservation can be matched against any outstanding flights. Similarily, whenever a new flight is registered, the system checks to see if it can be matched with a previously created reservation. When a match is found, the flight is associated with the reservation, which has the following consequences:

Reservation objects

Reservations are represented as XML documents with the following structure:

<reservation name="unique-name"> Unique name for this reservation, must have format identifier@domain
<aircraft id="number"> Aircraft IDs are the same ones used in URLs of the form https://www.wi-flight.net/a/aircraft/aircraft-id/
(aircraft details) Aircraft details will be seen on output (GET result) but can be omitted from input (PUT document)
</aircraft>  
<aircraft tail="registration" /> On input (PUT document) you may specify the aircraft by tail number instead of by ID. This tag will not be returned in output (GET result)
<start> Reservation start time in ISO8601 format, zulu time
YYYYmmddThhmmssZ
</start>
<end> Reservation end time (UTC, aka zulu time). Note capital T.
YYYYmmddThhmmssZ
</end>
<notify_profile> Optional element. If absent, the system will not send notifications when flights are matched.
text-string Describes how to notify an external system when flights are matched. See below.
</notify_profile>  
<crew> 0 or more <user> elements appear under this section
<user name="username"> End user account name, conventionally user@domain
<fullname> User details:
  • On reservation retrieval (GET): omitted
  • On PUT if user already exists: optional, ignored
  • On PUT if user does not exist: required
 
text-string  
</fullname>  
<email> Optional
user@host Used if the user will be configured to receive alerts
</email>  
<openid> Optional, may be repeated to associate more than one OpenID
url Enable the user to log in using this OpenID
</openid>  
</user>  
</crew>  
</reservation>  

Each reservation must have a unique name. If reservations in your database have a unique key, you may use this key together with your domain name as the reservation name. Alternatively, you may use another kind of unique identifier for each reservation, such as a UUID.

Creating an updating reservations

The reservation name in the URL should match the reservation name in the XML document body.

If a new reservation is created, the HTTP status will be "201 Created". If an existing reservation is updated, the HTTP status will be "200 OK".

Fleet administrator accounts have permission to create reservations with names ending in designated domains. For example, the administrator account belonging to "Example Flight School" can create reservations with names ending in "@example.com".

Fleet administrator accounts have permission to create new users for use as crew members, with usernames ending in designated domains, for example "foo@example.com".

When creating or updating a reservation, accounts will be automatically created for crew members who don't already exist using the details inside the <user> element. If using crew members that already exist, the <user> details are ignored. At this time there is no API to modify or delete an existing user.

Retreiving a reservation

Deleting a reservation

Reservations can be deleted so long as no flights have been matched with them. After a flight has been matched to a reservation, the reservation can no longer be deleted.

Notifications

If you wish to be notified with flight details when flights are matched to reservations, you must use the <notify_profile> element in the reservation.

Please contact Wi-Flight and supply:

If we can accommodate your protocol, then we will program it into our server and assign a name for it. If you create reservations which specify this name under <notify_profile> then you will receive notifications using your protocol when flights are matched to those reservations.

Integration implementation notes

Sample code

#!/usr/bin/python

import urllib
import urllib2
import cookielib
from xml.dom import minidom

class WiFlightAPIRequest(urllib2.Request):
    """Thin wrapper around urllib2.Request to
    specify the HTTP request method directly in
    the constructor"""
    def __init__(self, url, method, data=None):
        urllib2.Request.__init__(self, url, data)
        self.method = method

    def get_method(self):
        return self.method

class WiFlightAPIResource(object):
    """Represents an API object which lives at a particular URL.
    contents is an XML object, and etag tracks the version of
    the object that was last downloaded from the server."""
    def __init__(self, url, contents=None):
        self.url = url
        self.etag = None
        self.contents = contents

    def __repr__(self):
        "Pretty-print the resource"
        s = "WiFlightAPIResource(" + repr(self.url) + ")"
        if self.etag is not None:
            s += ' etag=' + repr(self.etag)
        if self.contents is not None:
            s2 = self.contents.toprettyxml(newl="\n| ")
            if s2:
                s += "\n| " + s2[:-3]
        return s

class WiFlightAPIClient(object):
    base_url = 'https://www.wi-flight.net'
    username = '********'
    password = '********'

    def __init__(self):
        cj = cookielib.LWPCookieJar()
        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))

    def login(self):
        lr = WiFlightAPIRequest(
            self.base_url + '/auth/login', 'POST', urllib.urlencode({
                'expires': 60,
                'username': self.username, 'password': self.password
            })
        )
        self.opener.open(lr)

    def get(self, r):
        """Get a resource.
        
        Usage:
          resource = WiFlightAPIResource("url")
          client.get(resource)

        get will populate the contents of the resource, replacing
        whatever was already in the WiFlightAPIResource object."""
        lr = WiFlightAPIRequest(self.base_url + r.url, 'GET')
        result = self.opener.open(lr)
        r.contents = minidom.parse(result)
        h = result.headers
        if 'ETag' in h:
            r.etag = h['ETag']
        else:
            r.etag = None

    def put(self, r):
        lr = WiFlightAPIRequest(
            self.base_url + r.url, 'PUT',
            r.contents.toxml("UTF-8")
        )
        # If this resource originally came from the server
        # and we are saving it back to the server (after
        # possibly making some changes locally), make sure
        # it has not changed on the server in the meantime
        if r.etag is not None:
            lr.add_header('If-Match', r.etag)
        lr.add_header('Content-Type', 'text/xml')
        self.opener.open(lr)

    def delete(self, r):
        lr = WiFlightAPIRequest(self.base_url + r.url, 'DELETE')
        self.opener.open(lr)
        r.etag = None
        r.contents = None

###
### Example usage
###

import datetime

def iso8601(d):
    return "%d%02d%02dT%02d%02d%02dZ" % (d.year, d.month, d.day, d.hour, d.minute, d.second)

if __name__ == '__main__':
    # Build a document for a new reservation
    start = datetime.datetime.utcnow()
    end = start + datetime.timedelta(seconds=3600)
    r = WiFlightAPIResource(
        '/a/reservation/test_reservation@example.com',
        minidom.parseString("""
            <reservation name="test_reservation@example.com">
                <aircraft tail="N69165" />
                <start>%s</start>
                <end>%s</end>
                <crew>
                    <user name="vandry@example.com" />
                </crew>
            </reservation>
        """ % (iso8601(start), iso8601(end)))
    )

    # initialize a client and log in
    c = WiFlightAPIClient()
    c.login()

    # try to create the reservation on the server
    c.put(r)

    # try to fetch it again (should get the same result back!)
    c.get(r)
    print r

    # change the reservation end time to two hours from now
    end_element = r.contents.getElementsByTagName('end')[0]
    end_element.removeChild(end_element.firstChild)
    new_end = r.contents.createTextNode(iso8601(
        start + datetime.timedelta(seconds=7200)
    ))
    end_element.appendChild(new_end)
    c.put(r)