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.
There are several practices used in this documentation to keep things shorter. They are documented here.
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.
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 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.
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
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 } }
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
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.
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.
All objects contain the following minimum shared set of properties.
A unique id that will never change. It is a 36 character GUID (global unique id).
The date this object came into existence.
The last time this object was written to the database.
A link to where you can see this object on the Tabletop.Events web site.
A link to where you can edit this object on the Tabletop.Events web site.
A string representing the type of object this is in case you need to identify it in the future.
A human friendly version of the object_type.
Dates are always returned in the format of YYYY-MM-DD HH:MM:SS
and are represented as the UTC time zone.
Wing will always return a JSON response in the form of an object.
{ "result" : { "success" : 1 } }
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]" } } }
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" : "..." }, ... ] } }
Defaults to 25. Minimum 1. Maximum 100. The number of items to be returned in a given result set.
Defaults to 1. The page of results to return.
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.
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.
By default result sets are returned in ascending order (asc
). You can set _sort_order
equal to desc
to reverse the order.
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.
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" } ], ... } }
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.
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
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.
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.
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" : {...}, ... }
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.
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>
There are clients available to help you interface with our APIs.
https://metacpan.org/pod/Wing::Client
https://github.com/mwisconsin/tabletop.js
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.