Introduction to the Wing API

Tabletop.Events uses the Wing API. The Wing API is a restful protocol developed by Plain Black Corporation (http://www.plainblack.com). It is designed to make web services easy to use, easy to implement, easy to wrap in a software layer, and most important, maintain absolute consistency. You can learn more about Wing at http://www.wingapi.com.

Tabletop.Events does allow clients to use our API to access some parts of our system, however, technical support for this access is not provided by TTE. Tabletop.Events also reserves the right to make changes to the API at any time and without notice.

Practices

There are several practices used in this documentation to keep things shorter. They are documented here.

Ellipsis

We often shorten pieces of return values with ellipsis (three dots: ...) to show that there would be additional data there, but it is not directly relevent to the documentation at hand.

No Result Wrappers

Though all Wing results have a wrapper like:

 { "result" : { ... } }

Or

 { "error" : { ... } }

They are left out in most of the documentaiton for the sake of brevity. Only the {...} portion is discussed in most cases.

ID's

ID's everywhere are represented as 3 x's: xxx. If you see xxx anywhere that means that would be replaced by a legitimate ID and shouldn't be interpreted literally. Also, ID's are case-sensitive strings, so store them as such.

Prefix

When referencing any object herein, the following prefix is assumed:

 https://tabletop.events/

Therefore if you were going to fetch information about a convention, you'd do:

 GET https://tabletop.events/api/convention/xxx

Requests and Responses

To make a request to a Wing web service you need nothing more than a command line tool like curl, but you can of course use any network aware programming language as well. Here's an example request:

Create an object:

 curl -X POST -F title="Ethics in Prisons" author="Andy Dufresne" http://example.com/api/article

 {
   "result" : {
     "id" : "xxx",
     "author" : "Andy Dufresne",
     "title" : "Ethics in Prisons"
   }
 }

Read an object:

 curl http://example.com/api/article/xxx

 {
   "result" : {
     "id" : "xxx",
     "author" : "Andy Dufresne",
     "title" : "Ethics in Prisons"
   }
 }

Update an object:

 curl -X PUT -F body="..." http://example.com/api/article/xxx

 {
   "result" : {
     "id" : "xxx",
     "author" : "Andy Dufresne",
     "title" : "Ethics in Prisons",
     "body" : "..."
   }
 }

Delete an object:

 curl -X DELETE http://example.com/api/article/xxx

 {
   "result" : { "success" : 1 }
 }

Authenticated Requests

With each request you can choose to either send a cookie with session_id or pass it in the query params. If you choose not to pass the session_id, then the result you receive will be the public result set. If you do pass the session_id then you'll get the private result set (provided your session has the privileges to receive the private result set). For example, if you request information about your user account without specifying a session_id then all you'd get back is an ID and some other basic information, like this:

 {
    "id" : "xxx",
    "display_name" : "Andy Dufresne"
 }

But if you request your account information with your session_id, then you'd get a result set with everything we know about you:

 {
    "id" : "xxx",
    "display_name" : "Andy Dufresne",
    "username" : "andy",
    "email" : "[email protected]",
    ...
 }

However, if I requested information about your account, and specified my own session_id, then I would only get the public data. Because I don't have the privileges necessary to access your private information.

See Also: Session

Rate Limiting

We have the ability to enforce rate limiting, but we only do it when users abuse our servers. We ask that you limit your API usage to no more than 1 request per second. If you have greater need, please contact us at [email protected] to discuss your needs. If you are found to be abusing our services, you will be blocked from using them at all. Thank you for respecting our request.

Consistency

A big part of the Wing specification is that you can reliably expect it to do the same thing in all circumstances. Here are a few key points of consistency.

Object Properties

All objects contain the following minimum shared set of properties.

id

A unique id that will never change. It is a 36 character GUID (global unique id).

date_created

The date this object came into existence.

date_updated

The last time this object was written to the database.

view_uri

A link to where you can see this object on the Tabletop.Events web site.

edit_uri

A link to where you can edit this object on the Tabletop.Events web site.

object_type

A string representing the type of object this is in case you need to identify it in the future.

object_name

A human friendly version of the object_type.

Dates

Dates are always returned in the format of YYYY-MM-DD HH:MM:SS and are represented as the UTC time zone.

Response Format

Wing will always return a JSON response in the form of an object.

 {
   "result" : { "success" : 1 }
 }

Success

Results will always start with a top level element called "result" and the data contained therein will always be returned as a hash.

 {
    "result" : {
      "session_id" : "xxx",
      "user" : {
        "username" : "andy",
        "email" : "[email protected]"
      }
    }
 }

Success With Pagination

Paginated lists are always handled exactly the same way, and always have the same minimum set of parameters for manipulation.

 curl -F _items_per_page=25 _page_number=3 http://example.com/api/article

You can tell how many items per page to return and which page number to return. That will give you a result set like this:

 {
   "result" : {
     "paging" : {
       "total_items" : 937,
       "page_number" : 3,
       "items_per_page" : 25,
       "total_pages" : 313,
       "next_page_number" : 4,
       "previous_page_number" : 2,
     },
     "items" : [
       {
         "id" : "xxx",
         "title" : "Ethics in Prisons",
         "author" : "Andy Dufresne",
         "body" : "..."
       },
       ...
     ]
   }
 }
_items_per_page

Defaults to 25. Minimum 1. Maximum 100. The number of items to be returned in a given result set.

_page_number

Defaults to 1. The page of results to return.

_max_items

If you wish to abitrarily limit the number of items that can be pulled from a query, rather than allowing pagination until you run out of records in the database, then you can set this to that limit.

_order_by

By default result set order is determined by the server, and will be different based upon the object you're accessing. However, you can order by any field that you can edit.

_sort_order

By default result sets are returned in ascending order (asc). You can set _sort_order equal to desc to reverse the order.

Exceptions

Exceptions will always start with a top level element called error and then will have a hash of 3 properties: code, message, and data.

 {
   "error" : {
     "code" : 500,
     "message" : "An unknown error has occurred.",
     "data" : null
   }
 }

The code is always an integer and conforms to the standard list of Wing ErrorCodes. These numbers are used consistently so that app developers can trap and handle specific types of exceptions more gracefully.

The message is a human readable message that you can display to a user.

The data element many times will be null, but it can have important debug information. For example, if a required field was left empty, the field name could be put in the data element so that the app could highlight the field for the user.

Warnings

In addition to exceptions there can be less severe issues that come up. These are handled via warnings. Warnings are just like exceptions, but they don't cause execution to halt. As such there can be any number of warnings. And warnings are returned with the result.

 {
    "result" : {
        "_warnings" : [
            {
                "code" : 445,
                "message" : "Logo image is too big.",
                "data" : "logo"
            }
        ],
        ...
    }
 }

Relationships

All objects can have relationships to each other. When you fetch an object, you can pass _include_relationships=1 as a parameter if you want to get the relationship data as well.

 curl -F _include_relationships=1  http://example.com/api/article/xxx

 {
   "result" : {
     "id" : "xxx",
     "author" : "Andy Dufresne",
     "title" : "Ethics in Prisons",
     "body" : "...",
     "user_id" : "xxx",
     "editor_id" : "xxx",
     "_relationships" : {
        "user" : "/api/user/xxx",
        "related_articles" : "/api/article/xxx/related-articles"
     }
   }
 }

You can then in-turn call the URI provided by each relationship to fetch the items in that list.

Queryable

Some relationships will allow you to use a query parameter on the URL that will allow you to search the result set. The documentation will tell you when this is the case and which fields will be searched to provide you with a result set.

 GET /api/article/xxx/related-articles?query=prison

Qualifiers

In search engines these are sometimes called facets. They are criteria that allow you to filter the result set that comes back from a relationship. The documentation will tell you when a relationship has a qualifier. To use it you'd add a parameter of the name of the qualifier to the URL along with the value you want to search for.

 GET /api/article/xxx/related-articles?user_id=xxx

That will search for all related articles with a user_id of xxx.

You can also modify the qualifier by prepending operators such as >, >=, <=, and <> onto the value. For example:

 GET /api/article/xxx/related-articles?word_count=>=100

Get all related articles with a word count greater than or equal to 100.

You can also request that a qualifier be limited to a null value.

 GET /api/article/xxx/related-articles?user_id=null

If you did this with an empty value rather than specifically null then this qualifier will be skipped.

Related Objects

Likewise you can request that the short version of the related objects be included directly in the result by adding _include_related_objects as a parameter, referencing the type of object you wish to include:

 curl -F _include_related_objects=user -F _include_related_objects=editor http://example.com/api/article/xxx

 {
   "result" : {
     "id" : "xxx",
     "author" : "Andy Dufresne",
     "title" : "Ethics in Prisons",
     "body" : "...",
     "user_id" : "xxx",
     "editor_id" : "xxx",
     "user" : {
        "id" : "xxx",
        "display_name" : "Andy Dufresne"
     },
     "editor" : {
        "id" : "xxx",
        "display_name" : "Warden Norton",
     },
   }
 }

All related objects are also inherently relationships of the object. Therefore the documentation will leave them out of the list of relationships in each object, but will include them in the list of related objects.

NOTE: The only related objects that can be returned in this manner are 1:1 relationships. If the relationship is 1:N as in the case of related articles above, then those cannot not be included in the result, and must be fetched separately.

Includes

Some objects will allow for Includes, and will show this in the documenation. Includes are extra bits of data you can pull back when you request the object. If you want to include a single thing you can add this to the URL:

 _include=foo

And you can add multiple includes to the URL if you want to include many things. The result will then have foo in it like so:

 {
    "id" : "xxx",
    "foo" : {...},
    ...
 }

Options

Sometimes an object will have fields that require you to choose an option from an enumerated list. There are two ways to see what those options are:

This way would be most often used when you need the list of options in order to create an object.

 curl http://example.com/api/article/_options

 {
    "result" : {
        "book_type" : ["hardcover","paperback","ebook"],
        "_book_type" : {
            "paperback" : "Paperback",
            "ebook" : "E-Book",
            "hardcover" : "Hardcover",
        },
    }
 }

This way would be most often used when you need the list of options to update an object, because you can get the properties of the object and the options in one call.

 curl -F _include_options=1  http://example.com/api/article/xxx

 {
   "result" : {
     "id" : "xxx",
     "author" : "Andy Dufresne",
     "title" : "Ethics in Prisons",
     "body" : "...",
     "book_type" : "hardcover"
     "_options" : {
        "book_type" : ["hardcover","paperback","ebook"],
        "_book_type" : {
            "paperback" : "Paperback",
            "ebook" : "E-Book",
            "hardcover" : "Hardcover",
        },
     }
   }
 }

Options will always be returned as an array and as an object of human readable labels.

HTTP Method Tunnelling

Sometimes it's not possible to use all HTTP methods. For example if you were to trigger a form post through Javascript the browser only knows how to do GET and POST. You can get around this by using XmlHttpRequest, but there is another way: HTTP Method Tunnelling.

Instead of doing a request like:

 curl -X DELETE http://example.com/api/article/xxx

You can instead do a POST and pass the actual method via an HTTP Header like this:

 curl -H "X-HTTP-Method: DELETE" -X POST http://example.com/api/article/xxx

Or you can pass it as part of the URI like:

 curl -X POST http://example.com/api/article/xxx?X-HTTP-Method=DELETE

Here's the same example as an HTML form:

 <form method="POST" action="http://example.com/api/article/xxx?X-HTTP-Method=DELETE">
 ...
 </form>

Clients

There are clients available to help you interface with our APIs.

Perl

https://metacpan.org/pod/Wing::Client

AngularJS 1

Prereq: https://tabletop.events/wing.js
Library: https://tabletop.events/wing.angular.js
Documentation: https://github.com/plainblack/Wing/blob/master/lib/Wing/WingAngular.pod

Language Specific Examples

Perl

PerlExample

Node.js

https://github.com/mwisconsin/tabletop.js

Testing

If you don't want to use an available client, but instead write your own, there is a Test API that can help make sure your client is working before you start using the real web service.