The Contribly API

Overview

What does the Contribly API do?

The Contriby API is a RESTful API which provides a set of end points for accepting, moderating and publishing user submitted content.

The API provides developers with a ready made system to manage various aspects of the user generated content (UGC) life cycle:

An example Contribly integration

Key concepts

Contribution submission flow

The end to the submission of a contribution would typically involve several steps and corresponding API calls; namely authentication, media upload and submission of the contribution.

Contribution submission flow

Authentication

An access token needs to be obtained before the client can begin to compose a contribution.

The client authenticates the end user and obtains an API access token by making a grant token call to the token end point.

Media submission

The client uses the user's access token to upload any media files associated with their contribution by posting them to the media end point.
This needs to be an authenticated call so that the media files can be assigned the correct ownership.

The submitting application receives a sequence of media elements with unique identifiers in return.

Compose and submit contribution

The client composes a contribution object appending any previously uploaded media elements.

This contribution is submitted to the contributions end point using the same access token that was used to upload the media elements.

The contribution goes into the moderation queue and is visible to moderators in the moderation tool.

An end to end example of the submission flow is visible in the submission controller of the reference application.

Media processing

Contriby provides several media processing functions.

Media processing is an asynchronous process.

The media submission end point will immediately return a media object but this object may not be fully populated. Attributes of the media element which are time consuming to produce will be populated as they become available.

POSTs to the submit media endpoint return HTTP 202 Accepted with a partially populated media object. A response is returned as soon as the submission details and raw file have been persisted and a usable id has been assigned to the media object.

Media elements should be considered immutable. The original media file will be preserved. Edits to media elements (such as rotations) are expressed using the media usage domain object.

The client is free to reference the newly created media id in contributions as soon as it is returned.

The more time consuming media processing operations begin in the background. Subsequent GET requests for the media object will show progressively more media attributes as they become available.

After submission, media files are first scanned to detect the content type. If a supported file format is detected scaled image and video artifacts are produced and appended to the media object.

Supported media formats

Contribly is able to process most standard media formats.

Images

JPEG, PNG, GIF (single frame), WebP and BMP images can be submitted.

Images can be output as JPEG or PNG files.

Videos

MP4, MPEG1, Quicktime, FLV, theora and webm formatted videos can be submitted.
Video sizes up to full HD are supported. This includes videos originally shot on iOS and Android devices.

Videos are output as HTML5 compatiable MP4 files.

Moderation and publishing

New contributions are held in the moderation queue until they are reviewed and approved by a moderator using the Contribly moderation tool.

Once approved the contribution becomes visible on the public contributions end points and it's media artifacts are published to public URLs.

Geolocation

User supplied locations

Users may submit contribution location information as a latitude/longitude pair, a Google place id or an OpenStreetMap id.

Google place and OSM ids arguably preserve more context that a simple latitude/longitude point. User interfaces which use of Google of OpenStreetMap location suggestions should capture the Google or OSM id and pass them to Contribly if possible.

Geocoding

User supplied latitude/longitude locations are geocoded into human readable locations.
OpenStreetMap Nominatim is the default geocoding service.

Redacting user supplied locations

A user's precise location may be deemed to be sensitive.
The geo resolution setting of an assignment can be used to obscure this potentially sensitive information.

The assignment geo resolution effects publicly visible location of contributions as follows:

Geo resolution Description
Hidden Location information is completely removed from publicly visible contribution.
Submitted Publicly visible location is exactly as provided by the end user.
City Location is rounded off to an approximately +/- 10 kilometre margin of error.
Street Location is rounded off to an approximately +/- 1 kilometre margin of error.

Rounded locations are geocoded to ensure that the original precise location is not preserved in the location name.
The unaltered user supplied location remains visible to moderators in the user supplied location block of the moderation tool.

Dates

All dates and date times are output in ISO8601 date time format with a UTC time zone (including milliseconds).
All date and date time field inputs are expected to be in this format.

i.e.

2016-01-27T12:24:33.406Z

The equivalent Joda formatter is ISODateTimeFormat.dateTime

Domain model

The Contribly domain model can be considered a glossary of terms which are used to describe various types of data in the system.

Artifact

Represents a specific size or format a media object. Original media files submitted by users will be converted and scaled into
the required artifact formats which an approved contribution is published.

Field Description
label The label of the artifact format for this artifact
url The absolute URL for this artifact
width Width in pixels (optional)
height Height in pixels (optional)
contentType The artifact format expressed as a MIME type. ie. 'image/jpeg'
contentLength The size of the artifact in bytes

Artifact format

An artifact format describes an output format for the static assets which will be produced when a contribution's media elements are published.

For example, a particular application may require images to be scaled to fit a mobile screen. This requirement can be expressed using an artifact format.
The current artifact formats for your Contribly instance can be viewed in the Settings / Publishing section of the Contribly moderation tool.

Field Description
label The identifier for this format. ie. small, large, thumbnail
forContentType Indicates which content types this format is applicable to. ie. image / video
x, y The target dimensions for this format in pixels. The target media will always fit within a bounding box of this size (but may not always fill it depending on the preserveAspectRatio and upscaleAllowed settings).
preserveAspectRatio Boolean. Indicates if the original media's aspect ratio will be preserved when scaling to fit the requested dimensions.
upscaleAllowed Boolean. Indicates that the original media may be upscaled to fill the requested dimensions.
fileExtension The 3 letter file extension for this format. ie. jpg, mp4
contentType The MIME content type for this format. ie, image/jpeg, video/mp4

Assignment

A assignment is a topic your users can contribute to. Assignments can be used to frame your call outs for contributions.

Field Description
id Unique identifier
name The name of this assignment
description Description of this assignment
created The creation date/time for this assignment
ends An optional end date/time. If set no further contributions will be accepted after this time.
embargo If set approved contributions will not become publicly visible until this date. Allows for pre moderation of contributions.
starts If set this assignment will not be advertised or accept contributions until this time. Allows assignments to be setup ahead of their intended launch.
geoResolution The geo resolution which will be applied to to publicly visible location shown for contributions to the assignment.
mediaRequired Boolean. Contributions lacking media attachments will be rejected if set to true.
allowsAnonymousContributions Boolean. Indicates that this assignment allows contributions to be submitted anonymously (see anonymous grant type).

Authority

An authority record represents an access token which can be used preform authenticated actions (such as submitting contributions or moderating content).

Field Description
id The id of this authority. This value is used as the token when authenticating API calls.
user The user associated with this authority (optional in the case of anonymous users)
client The application associated with this authority
scope An array of the authentication scopes attached to this authority (eg. permissions)

Client

An API client (or app). Authenticated calls the the API are made using an authority obtained using a client.

Field Description
id The unique client id
name Client or app name.

Flag

A flag represents a potential issue with a live contribution.
Flags allow other users or members of the public to bring their concerns about a contribution to the attention of moderators.

Field Description
id The identifier for this flag
date When was this flag received
type The flag type (ie. spam, offensive etc) (Optional)
email A contact email supplied by the user raising the flag (Optional)
notes Free text from the end user detailing their issue with the contribution (Optional)
authority The authority used to raise the flag if it came from a registered, signed in user (Optional)
open Boolean. Is the flag currently open.

Form response submission

An end user form response submission.

Field Description
form The id of the form this response applies to).
contribution The id of the contribution this response applies to (optional).
responses Map of form fields names to user response values.

Georesolution

In some contexts precise location information contained in user submissions may be sensitive.
The assignment geo resolution setting provides a way to control the publicly visible location information attached to user submissions
to that assignment. The original user submitted location remains available to moderators.

Geo resolution Description
submitted Location data is presented with the same precision as submitted by the user with no filtering.
street Publicly visible location is obscured to a resolution of several hunderd metres.
city Publicly visible location is obscured to a resolution of several kilometres.
hidden All location information is stripped from the publically visible submission.

Media

Represents a single media element such as an image or video file.

Field Description
id Unique identifier
type The detected type of this media element if known (image or video) (Optional)
fileExtension A suggested file extension for this media element based on it's detected content type (if available) (Optional)
artifacts A list of currently available artifacts for this media element.
user The user who submitted this media element.
metadata An array (String to String) of metadata key value pairs extracted from this file.

Media elements can be considered to be large immutable. Fields may be appended to a media element
during the content type detection and artifact production parses but once set cannot be altered.
The appearance of a media element can be manipulated by editing it's corresponding media usage.

Media usage

Represents a usage of a given media element, potentially with edits applied to it.

Field Description
media A media element
rotation Degrees of rotation applied to the media element (0, 90, 180, 270) (Optional)

Moderation action

A workflow action taken by a moderator on a specific contribution.

ie. Approve, rejected etc

Typically a moderation action would be used to change the moderation state of a contribution.

Moderation state

The current moderation state of a given contribution.

ie. Approved, rejected, referred to legal etc.

Place

Field Description
name The resolved place name (Optional)
latLong The latitude / longitude if known (Optional)
osm The OpenStreetMap id if known (Optional)
geohash The geohash if available (Optional)
country Two letter country code if known. (Optional)

Tag

Tags can be applied to contributions and assignments.

Field Description
id Unique identifier
name The name of this tag
place An optional place associated with this tag (Optional)
tagSet The tag set which the tag belongs to (Optional)

Tagset

A tag set represents a grouping of related tags.

Field Description
id Unique identifier
label
name The name of this tag set
owner The owner of this tag set

Endpoints

API end points are also documented as a Swagger defination (see developer resources).

OPTIONS /1/artifact-formats

Documentation end point which returns a list of the currently available artifact formats.

GET /1/assignments

Lists assignments.

Parameter Description
page pagination page number
pageSize Maximum number of results to return
ownedBy Limited results to assignments owned by this user
q Search by text
open Restrict results to currently open or closed assignments (true
group Restrict results to assignments assigned to the given group
urlWords Find assignment by urlWords.

POST /1/assignments

Requires user authentication.

Submit a assignment

The request body should contain a JSON representation of your new assignment.

Field Description
name The name of your new assignment
description Description test for this assignment
featured Optional boolean flag. Defaults to false.
geoCodeResolution The optional geocoding resolution to be applied to contributions posted to this assignment
group The optional group to assign this assignment to
ends Optional date time when submissions to this assignment will close
starts Optional date time before which sumissions to this assignment will not be accepted
embargo Optional date time before which approved contributions to this assignment will not be made public

GET /1/assignments/{id}

Display a single assignment by id

POST /1/assignments

Create an assignment.

GET /1/contributions

Lists contributions.

Parameter Description
page Pagination page number
pageSize Maximum number of results to return
assignment Limit results to contributions from the given assignment
ownedBy Limit results to assignments owned by this user
user Limit results to those owned by the given user
tag Limit results to contributions tagged with the given tag
geohash Limit results to contributions with locations within this geohash
latLong Limit results to contributions with location near this latitude and longitude (comma seperated lat/long pair)
mediaType Limit results to contributions with contains at least one media item of the given type. Typically 'image' or 'video'.
hasLocation Limit results to contributions which have location information attached
countryCode Limit results to contributions which have a location within the given country specified by two letter country code.
state Limit results to contributions currently in this state (awaiting, approved, refected).
moderationState Limit results to contributions currently in this moderation state.
createdBefore Limit results to contributions created before this date time.
createdAfter Limit results to contributions created after this date time.
refinements Request refinement data. Comma seperated list of refinement names (assignment, user, moderationState, countryCode, via, created, mediaType)
via Limit results to those contributed via the given client id
openFlags Limit results to those with at least this many open flags against them
maximumFlags Limit results to those with no more than this number of open flags
mediaHashes Limit results to those with a media usage whose media element has one of these comma seperated hashes.
minimumLikes Limit results to those with at least this many likes.
order Specify a specific output order ('desc' or 'asc').
sortBy Specify field to sort by (created
urlWords Find contribution by url words.
likedBy Restrict results to those liked by a specific user.
mediaUsage Returns the contribution containing this media usage.

POST /1/contributions

Requires user authentication.

Submit a contribution.

Sample request

Authorization: Basic an-access-token
Content-Type: application/json

{
    "headline":"Test with media element 41891db7-5791-4b7d-a795-1544e500aab8",
    "body":"The quick brown fox jumped over the lazy media element",
    "mediaUsages":[
        {
            "media":{
                "id":"e1a75569-fe56-4238-9a99-c3bbcd848eb7"
            }
        }
    ]
}

Sample response

{
   "id" : "6768e8b0-5c70-40f4-b2c3-2767fe043742",
   "headline" : "Test with media element da19a985-fb35-41d9-b761-a381ebc67f2b",
   "body" : "The quick brown fox jumped over the lazy media element",   
   "urlWords" : "bc69cabf-8cdc-4309-ad2f-4baa3b1863b7",
   "likes" : 0,
   "mediaUsages" : [
      {
         "id" : "9c44feec-9bc1-4578-aee2-1dc45b415037",
         "media" : {
            "id" : "c43823f9-4eeb-47e4-8ca4-041e1f964cbd"
         },
         "artifacts" : [],
      }
   ],
   "created" : "2016-10-16T16:06:36.883Z",
   "via" : {
      "authority" : {
         "user" : {
            "username" : "dfb6b7f0-4645-4bde-9d32-09a71b34c729",
            "registered" : "2016-10-16T16:06:36.509Z",
            "via" : {
               "id" : "test-client",
               "name" : "Test client"
            },
            "displayName" : "dfb6b7f0-4645-4bde-9d32-09a71b34c729"
         },
         "client" : {
            "id" : "test-client",
            "name" : "Test client"
         }
      }
   }
}

If the new contribution JSON was in a local file, the corresponding curl command line would be:

curl -i  -H "Authorization: Bearer an-access-token" -H "Content-Type: application/json" -X POST --data "@contribution.json" https://api.contribly.com/1/contributions

GET /1/contributions/{id}

Returns a single contribution.

POST /1/contributions/{id}/flag

Optional user authentication.

Raise a flag against this contribution (ie. offensive content etc).

The request body should contain a JSON representation of a flag object.

If a a user authentication token is included the flagging user will be recorded. Anonymous flags can be raised by making an unauthenticated request.

Field Description
name The name of your new assignment
description Description test for this assignment
geoCodeResolution The optional geocoding resolution to be applied to contributions posted to this assignment
POST /1/contributions/{id}/like

Like (or unlike if already set) this contribution.

POST /1/contributions/{id}/moderate

Apply a moderation action to this contribution.

The POST body should contain a JSON moderation history item.

Field Description
action An optional moderation action (Optional)
notes Notes (Optional)

OPTIONS /1/docroots

Documentation end point which returns a list of the docroots which your Contribly instance is currently publishing to.

GET /1/flag-types

Documentation end point which returns a list of valid flag types which may be assigned to contribution flags.

POST /1/form-responses

Requires user authentication.

Post a response to this form.

Expects a form response submission object on the request body.

GET /1/groups

Parameter Description
ownedBy Limited results to groups owned by this user

GET /1/groups/{id}

Returns a single group

POST /1/groups

Submit a new group.

POST /1/media

Requires user authentication.

Accepts a media file presented as the raw binary body of the POST.

Media processing is an asynchronous operation. This end point will immediately return HTTP 202 and a sparsely populated media element.
The media element id can used to track its processing state by polling the get media end point.
The media element can be immediately referenced in contributions even if all media processing steps have yet complete.

See media processing below.

Sample response

HTTP/1.1 202 Accepted
Content-Type: application/json

{
    "id":"8ea47f7e-6097-490f-8458-8d98df557b76"
}

The equilivent curl command is:

curl -i  -H "Authorization: Bearer an-access-token" -X POST --data "@an-image.jpg" https://api.contribly.com/1/media

GET /1/media/{id}

Retrieve the details for a given media element.

Media processing is an asynchronous operation. After a media element is submitted, fields progressively become available as media processing operations complete.

Sample response

{
	"id" : "8ea47f7e-6097-490f-8458-8d98df557b76",
	"user" : {
		"id" : "7b9f6834-8db9-44d1-bff5-ffae90424ea5",
		"username" : "69fa0804-4e78-4746-8832-3bd10a8ee3ab",
		"displayName" : "69fa0804-4e78-4746-8832-3bd10a8ee3ab",
		"via" : {
			"id" : "test-client",
			"name" : "Test client"
		}
	},
	"metadata" : {
		"Exif Image Height" : "533 pixels",
		"Exif Image Width" : "800 pixels",
		"Date/Time" : "2015:04:23 19:05:19",
		"Model" : "Canon EOS 350D DIGITAL",
		"Exposure Time" : "1/200 sec",
		.html5" : "87e9e12f4e189fac10e7d86d146d5f79",
		"contentType" : "image/jpeg",
		"Max Aperture Value" : "F3.4"
	},
	"contentLength" : 314731,
	"type" : "image",
	"fileExtension" : "jpg",
	.html5" : "87e9e12f4e189fac10e7d86d146d5f79",
	"orientation" : "landscape",
	"contentType" : "image/jpeg"
}

GET /1/media/{id}/artifacts/{label}.{extension}

Requires user authentication.

Retrieve a preview of a media element artifact.
The call is authenticated to allow the preview of items which have not yet been moderated.
The label and extension placeholders should match the label and file extension of an artifact format.

This endpoint supports HTTP Range by byte requests making it compatible with the HTML 5 video components of most major browsers.

GET /1/media/{id}/original

Requires user authentication.

Retrieve the original file for a given media item.

GET /1/moderation/actions

Retrieves a list of the available moderation actions.

GET /1/tags

Lists all tags

Parameter Description
ownedBy Limit results to those owned by this user

GET /1/tags/{id}

Display a single tag

GET /1/users

Search for users.

Field Description
ownedBy Limit results to those users owned by this account
assignment Limit results to users who have contributed to this assignment
q Free text search match against usernames
refinements Request refinement data. Comma seperated list of refinement names (assignment)
pageSize Maximum number of results to return
minimumContributions Limit results to user who have made at least this many contributions
linkedProfile Limited results to users with a linked profile of this type.

POST /1/users

Requires client authorisation.

Register a new user.

The request body should contain a JSON representation of the new user.
A client authorisation header must be included to identify your application.

Field Description
username The username of the new user
password The new user's initial password
email The users's email address (optional)

GET /1/users/{id}

Display a single user's profile

POST /1/verify

Requires user authentication.

The verify end point can be used to confirm the validately and ownership of an access token.

Header Value
Authorization Bearer {your token}

Verifies an access token.
If the Bearer token presented on the Authorization header is valid, returns the user details for the user who the token belongs to.

Sample response

{
   "client" : {
      "name" : "A test client",
      "ownedBy" : "test-owner",
      "id" : "a-test-client-id"
   },
   "user" : {
      "displayName" : "A test user",
      "username" : "testuser",
      "registered" : "2016-08-26T11:00:31.361Z",
      "id" : "test-user-id"      
   },
   "scopes" : [
      "contact-user",
      "create-assignment",
      "edit-assignment",
      "create-form",
      "edit-form",
      "view-form-responses",
      "create-client",
      "edit-client",
      "edit-groups",
      "create-group",
      "edit-profile",
      "edit-smtp",
      "edit-notifications",
      "edit-assignment",
      "edit-assignment-urlwords",
      "preview-media",
      "original-media"
   ]
}

WS /1/ws/activity

Requires user authentication

Provides a real time activity log via a secure WebSocket connection. Events arrive as JSON objects with the format described below.

Field Description
type The type of event (created, submitted, moderated, flagged, thumbnail-available)
user The user involved in the event (optional)
report The contribution involved (optional)
noticeboards The noticeboard involved (optional)
mediaUsage The media usage involved (optional)

Authentication

All POST and WebSocket end points as well as GET requests which reveal non public data require authentication to identify the user or application making the request.

OAuth2 is the authentication system used. To authenticate a request the end user must first obtain an access token and then append it to their API requests (see using an access token below).

Obtaining an access token

Access tokens are issued to users. To obtain an access token some sort of credentials must be presented and exchanged for an API access token.
This exchange is dealt with by the token end point.

POST /1/token

Requires client authorisation.

Grants an access token.

Several different grant types are available. The format of the request will vary slightly depending of the choosn grant type.

In all cases, a client authorisation header must be included to identify your application.

Username and password

If the user is already registered on the system then their username and password credentials can be swapped for an access token, as per the OAuth2 Resource Owner Password Credentials Grant pattern.

The following parameters should be presented in "application/x-www-form-urlencoded" encoded format.

Parameter Value
grant_type 'password'
username The user's username
password The user's password
scope Not used

Your API client must also present it's client credentials on the Authorization header (see client authorisation below).

Sample request

Authorization: Basic dtysXC1jbG22bnQ6dGVzdC1zZWNyABC=
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=db4d174c-d801-41a1-b3fd-b13a32bb737a&password=db4d174c-d801-41a1-b3fd-b13a32bb737a

Sample response

{
    "access_token":"77b3fc34-9394-4f6d-ac7d-130a97adf9b4",
    "scope":"full"
}
Facebook access token

If the user has granted your local application a valid Facebook access token for a user who has previously registered on the system then this can be used to obtain an API token for that user.

The following parameters should be presented in "application/x-www-form-urlencoded" encoded format.

Parameter Value
grant_type 'facebook'
token A valid Facebook access token
register Optional boolean; defaults to true. Allows an API user for this Facebook user to be created on the fly if this Facebook user has not been seen before.

Your API client must also present it's client authorisation on the Authorization header.

Google access token

The following parameters should be presented in "application/x-www-form-urlencoded" encoded format.

Parameter Value
grant_type google
token A valid Google access token
register Should a user be automatically registered if this Google token belongs to a previously unseen user? Defaults to true

Your API client must also present it's client authorisation on the Authorization header.

Twitter access token

The following parameters should be presented in "application/x-www-form-urlencoded" encoded format.

Parameter Value
grant_type twitter
token A valid Twitter access token
secret The secret for the Twitter access token
register Should a user be automatically registered if this Twitter token belongs to a previously unseen user? Defaults to true

Your API client must also present it's client authorisation on the Authorization header.

Contribly token

Exchange an existing valid Contribly token for an another with different scopes.
Typically used be a client application to obtain a less privileged token for a specific operation (such as viewing media previews).
The requested scopes must be a subset of those granted to the initial token.

Parameter Value
grant_type token
token A valid Contribly access token
scopes Space delimited list of required scopes (must be a subset of those granted to the current token)

Your API client must also present it's client authorisation on the Authorization header.

Anonymous

Anonymous contributions should be submitted using an anonymous token.
Once the anonymous token has been granted it can be used to contribute in a simular manner to any other token.
Anonymous tokens have limited privileges.

Parameter Value
grant_type anonymous

Using an access token

Include your access token in your requests by appending a header in this format:

Header Value
Authorization Bearer {your token}

You can check the validity and ownership and available scopes of an access token by submitting it to the verify end point.

Client authorisation

Client authorisation is required for some endpoints (typically those involving the granting of user access tokens).
The propose of client authorisation is to allow the calling application to identify itself.

Header Value
Authorization Basic {HTTP Basic auth encoding of the concatenation of client_id colon client_secret}

Developer resources

Swagger

The Contribly API provides a Swagger definition. A Swagger definition describes an API in a machine readable way allowing for easlier integration with third party systems.

The Swagger defination url is:

API end points can be browsed using Swagger UI:

The Swagger Code Generator can be used to generate native API clients for various platforms.

SDKs

Native SDKs can be generated from Contribly API's Swagger definition using Swagger codegen.

Android

Generate the SDK source code using Swagger codegen:

git clone https://github.com/swagger-api/swagger-codegen.git
mvn clean install
java -jar modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate -i https://api.contribly.com/1/swagger.json -l java --group-id com.contribly.client --api-package com.contribly.client.api --invoker-package com.contribly.client --model-package com.contribly.client.model --library=okhttp-gson -DserializableModel=true -o /tmp/contribly

Build the client and publish to your local Maven repo:

cd /tmp/contribly
mvn clean package

The SDK should now be accessible to Android Studio and gradle.

Add the following to your Android project's build.gradle:

compile ('com.contribly.client:swagger-java-client:1.0.0') { transitive true }

Refer to the Reference Android app for a working example of the generated Android SDK integrated into an Android app.

Swift

Generate the SDK source code using Swagger codegen:

git clone https://github.com/swagger-api/swagger-codegen.git
mvn clean install
java -jar modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate -i https://api.contribly.com/1/swagger.json -l swift -o /tmp/contriby

Add to the Podfile of your Xcode project:

platform :ios, '10.0'
use_frameworks!

target 'My iPhone app' do
    pod 'SwaggerClient', :path => '/tmp/contriby'
end

Reference web front end

An example web front end application which demonstrates the main API operations such as authenticating a user and submitting a contribution.
This reference application can be browsed on Github:

This application is a Play Framework app written in Scala. The same API usages should be applicable to other langauges and client side Javascript applications.

Reference Android app

A reference Android application demonstrating the Contribly API been used in a mobile setting.

The Android app makes use of a client library auto generated from the API's Swagger definition.

Scaling considerations

Scaling considerations are addressed here.