It's easy to get access to the API. You can build something for yourself, or something that helps other users, just be considerate and don't hammer our poor little API for no good reason. If something feels inefficient, ask us for help and we'll do our best to make it work better.
There are 2 ways of accessing the Smashrun API:
It's very simple. All you need to do is use the client id: "client" along with the implicit OAuth flow.
Note: If you're accessing the API from a mobile app. You should request an API key instead.
Smashrun API uses OAuth 2.0 to provide access tokens to protected resources. Client applications can make use of either an access code flow for web server applications or an implicit flow for mobile apps and JavaScript applications.
Once you have an access token you can authorize requests to the the API by either including it as either a url parameter or an authorization header in any subsequent request.
If you're experienced with the OAuth standard then all you really need to know are the the specifics of our implementation.
Endpoint: | https://secure.smashrun.com/oauth2/authenticate |
Parameters: | client_id, scope, redirect_uri, response_type, state |
Response: | access_token (implicit flow) or code (code flow) |
Error response: | error, error_description |
Endpoint: | https://secure.smashrun.com/oauth2/token |
Parameters: | client_id, client_secret, code, grant_type, state |
Sample JSON Response: |
{ "access_token":"oAfoij284AADSfdoijaf", "expires_in":3920, "token_type":"Bearer", "state": "uid-128012", "refresh_token": "94ddgJaad4hfdGPS52" } |
Error JSON response: |
{ "error":"invalid_client", "error_description": "Could not authenticate with client_id and client_secret" } |
The basic steps making use of the Smashrun API (or any OAuth enabled API) go like this:
This all sounds pretty straightforward right? The place where people commonly get hung up is that 2nd step Obtain an access token.
This is the heart of the whole OAuth thing. It's actually pretty straightforward, but if you're new to OAuth, then it might seem a lot more complicated than it actually is. The reason why is because there are a lot of different types of OAuth, and there are different possible flows for each version. But, rest assured, once you've got that nailed down, the rest is cake walk (UK/piece of piss).
Smashrun like many new APIs uses OAuth 2.0 and supports the code and implicit based flows. The other common standards you might hear about are OAuth 1.0 and 1.0a. These aren't worse standards in the way you might think version 2 is automatically better than version 1. They're just different. OAuth 2 is a lot easier to work with and it's also a little less secure. Twitter uses OAuth 1.0a, but Facebook uses OAuth 2.0. The big difference is that OAuth 2 requires signing requests, and signing requests offers additional security, but it's really easy to screw up.
In a nutshell OAuth 2.0 flow looks like this: first you pass the user from your app/website to an API provider's website. The user then signs in and approves a certain set of permissions that you included in the link when you passed them over. Once they sign into the provider (i.e. Smashrun), the provider redirects the user back to your app/website.
This is where the code and implicit flows start to deviate...
If you're using the code based flow, then when you the user gets redirected back, the redirect url includes a code, and if you're using the implicit flow (aka token flow) the redirect contains a token instead. That token is like gold. It's all you need to make requests to the API on a user's behalf for as long as you need to, well that is, until it expires.
However, if you're using the code based flow then you still need to take one more step. The code you got back needs to be exchanged for that all important token. You do that via a simple POST request on the server side passing the client_id, client_secret, code, and grant_type=authorization_code.
Note:
The response from that request includes not only a token but, as an added bonus because you went through the effort of doing the 2 step authorization code flow, you also get a refresh token. A refresh token is a pretty handy thing because you can use use it to get a new auth token anytime you need one, no need to have the user sign in again.
If that still seems a bit confusing. Check out this blog post. It's one of the best explanations of the OAuth 2.0 flow and terminology that you'll find.
Here's the beautiful thing about a standardized specification: one implementation is pretty much the same as the next. You could, for example, just read Google's OAuth 2.0 Documentation and do everything they say, but replace their OAuth urls with ours and everything should pretty much work just fine. Their docs are really comprehensive and professional, because, well they're Google.
This flow is little less secure and you don't get a refresh token, but on the other hand it's just one step and you don't need any server side code.
The code flow is very similar to the implicit flow, but there's another step at the end. Instead of getting the token directly, you get a code instead. And then send us that code back along with your client credentials (client_id and client_secret) and we send you the auth token as well as a refresh token you can use to get a new auth token anytime you want.
On the one hand, there's a second step, but on the other hand you get to authenticate your client and you get a refresh token so you don't have to bother with the whole process again.
Look familiar? This is almost the same url as the one we used in the 1 step implicit flow. The only difference is that, this time, the response_type is set to code instead of token.
Now that you've got your authorization code, the next step is to send that code, along with the client id and client secret we gave you, so that you can get an auth token and a refresh token.
Unlike the first step, this is one is a POST request from your server that returns a JSON response.
The response comes back as JSON
{ "access_token":"oAfoij284AADSfdoijaf", "expires_in":3920, "token_type":"Bearer", "state": "some_state", "refresh_token": "94ddgJaad4hfdGPS52" }
You get an access token that you can use to make requests to the Smashrun API, and you also get a refresh_token.
When a user's access token expires, getting a new one is simple. All you need to do is make the same call you made to exchange the authorization code for a token, only this time, you're exchanging a refresh token.
The response will be JSON and will look almost exactly like the one you got when you exchanged the code
{ "access_token":"8fffij184gdD5fdo7daa", "expires_in":3920, "token_type":"Bearer", "state": "some_state"
Just store this new access token, and repeat the process whenever the token expires. The access token will last 12 weeks, and the refresh token will last forever, unless a user explicitly disconnects the app.
You can easily check the status and validity of an access token at any time.
Example response:
{ "scopes":["read_activity","write_activity"], "token":"8G12414afgggHgAyCUNwT80CmvO2hzcw5DzY73Y0", "client_id":"yourclientid", "expires_in":7243186 }
You can also revoke your own access by deleting a single token, or delete all access tokens for a given client id
To revoke one token:You can POST GPX, TCX, or JSON formatted data to the activities endpoint. The API will identify the content type and import it into the authenticated user's account.
If you want to keep your transactions lightweight and avoid data issues (and the countless support problems that come with them), you'll almost certainly want to post JSON activity objects as outlined below. Yes, I know. It's yet another format. But don't think of it as another format. Think of it as little packets of run data, overflowing with accurate measurements and devoid of ambiguity. Think of it as an investment in the future. Visualize user satisfaction in a nice tidy JSON format.
The following operations are supported for activities: GET, POST (insert), PUT (update), DELETE (flag as deleted), and PATCH (update selected fields)
Updates are performed against the complete object, so you must include the same full JSON object you would with a POST. The only difference is the
necessary inclusion of an activityId field that matches an existing id of a run owned by the authenticated user. Currently PATCH only supports
the notes, duration, distance, and startDateTimeLocal fields of the activity object. If you have a requirement to update other fields please send us an email.
Inserts via POST are processed through a de-duplication algorithm. If a run matches an existing run in the system the following logic is applied:
The JSON activity object is a highly compressed format designed specifically for fitness activity (running in particular). It attempts to address some of the failings of standard XML formats by reducing ambiguity in the way data series are codified. It consists of a flat object containing aggregate summary info for an activity along with a records object, which contains detailed time series data. Where time series data is present, the summary data will be recalculated and any provided values overwritten in order to keep consistency between detail and summary views.
The time at the location where the run occurred (including timezone offset).
Must be ISO 8601. For example: 2014-07-01T09:28:56.321-10:00
The distance of the activity in kilometers (decimal).
Activities returned from the API may have summary distances that don't match the last distance in the recordings.
This can be the result of either runs that have a manual edit by the user, or runs where the source (usually TCX or API)
has supplied a different summary distance than the last individual recordings. One common cause of this is incorrectly flagged pause.
The duration of the activity in round seconds, excluding any pauses (integer).
Like distance, the summary data may conflict with the recording. However, when saving a run from
a direct source (i.e. you're the app that recorded it) you should ensure that the last value in the recordings array
for duration (and distance) corresponds exactly to the summary value supplied here.
The type of activity. For now, only "running" is accepted. At his time all other activity types will be rejected and shouldn't be sent.
A description of the run entered by the user. (Max 800 chars)
Average steps per minute (one leg).
Typical values: 60 - 120. Should be a weighted average by duration between recordings.
Minimum steps per minute (one leg).
Maximum steps per minute (one leg).
Average Beats Per Minute
Typical values: 60 - 220. Should be a weighted average by duration.
Maximum heart rate beats per minute
Minimum heart rate beats per minute
The weight in kilograms of the athlete on the date of the run
One of the following string values: [Unstoppable, great, soso, tired, injured]
One of the following string values: [road, trail, track, treadmill, beach, snowpack]
Average temperature in round degrees Celsius for the duration of the run.
One of the following string values: [indoor, clear, cloudy, rain, storm, snow]
Percentage relative humidity expressed as a round integer (0-100).
Average wind speed over the duration of run expressed as a round integer in kph.
The type of device on which the software which made the recordings was running. Valid values include: Google, Apple, RIM, Microsoft, Symbian, or Other.
An id used internally by your app to uniquely identify this run. If there's an existing run in Smashrun sent to a user's account by your app with the same id then it will be considered an update to an existing run rather than a new run.
The current version of the software on which the run was recorded.
An array containing the 0 based indexes of the recording values immediately following any pauses. The duration of the pause is equal to the difference between the clock recordingValue at this index, and the preceding index.
An array of name value pairs specifying the lap and the type of effort. Units are meters and seconds.
For most accurate display, send distance based laps by distance and duration based laps as time.
If both duration and distance are sent, duration will be used.
Examples:
laps: [{lapType: "recovery", "endTime": 60,}, {lapType: "interval", "endDuration": 120}]
laps: [{lapType: "recovery", "endDistance": 250,}, {lapType: "interval", "endDistance": 500}]
An array of songs that were playing during the run. Song start and end time is specified by the clock time (not duration)
from the start of the run. BPM is an optional field for the tempo of the song in beats per minute. It can be included if known.
Example:
"songs": [{ "album": "13 Songs", "artist": "Fugazi", "song": "Waiting Room", "BPM": 155, "startClock": 0, "endClock": 150 },
{ "album": "The Suburbs", "artist": "Arcade Fire", "song": "We Used to Wait", "BPM": 145,"startClock": 151, "endClock": 400 }
]
These are heart rate values recorded after the end of a run.
They are specified as an array of heart rate recordings consisting of a heartRate(bpm) and duration(in secs).
You may include any frequency of data available from 1 recording per second to a single recording at a set interval of 30 seconds, 1 minute, or 2 minutes for example.
Naturally the more data that is sent the better the end user experience, because we can detail the arc of the recovery curve.
Example:
"heartRateRecovery": [
{ "duration": 0, "heartRate": 190 },
{ "duration": 5, "heartRate": 180 },
{ "duration": 10, "heartRate": 168 }
....
Recordings is an array with each element containing another array of floating point values. It's a collection of various time series data recorded at set intervals. The time series can be added in any order. However, when you include a data series in the recordings object, you also must include its name in the recordingKeys array. We use the position of the name in recordingKeys to find the position of the data series you added.
The only required series are Duration and (one or both of Distance and Latitude/Longitude). If the latitude and longitude series are included, but distance series is not then distance will be calculated automatically. However, since there are a variety of techniques for calculating distance it is best to include both distance and lat/lon if both are available.
Here's the full list of available data series. If there's an additional series you think might be useful, shoot us an email and we'll consider adding it.
An array of arrays of floating point values containing the recordings specified in recordingKeys
It's important to realize that the GPS coordinates recorded for a given run are, to some degree, independent of the distance and, therefore, the speed of that run in aggregate or at any point in time. This is, of course, a bit unexpected. You might easily expect that the GPS coordinates are the verifiable truth of a given run, and that distance and pace can simply and easily be derived from these coordinates by applying certain standard equations, but you'd be wrong. Why?
Fitness devices interpret their own GPS readings. A given device knows something about it's own failings. In the most blunt type of interpretation, a given device might know that its GPS chip set overestimates distance by 10-15% on average. To counteract this, it might simply lob off 12% from the distance of every recording calculated compared to what's calculated from the raw GPS points. Or a device might have access to additional information. It might know from a footpod or an accelerometer that you were still moving at a steady pace, even though the GPS points might say otherwise.
There is no one accepted GPS coordinate to distance calculation. There are dozens of ways to calculate distance from GPS coordinates. Some methods are verifiably better than others, some take into account altitude gained, some discard that data, some use a model of the earth as a sphere, others may use one of many different reference ellipsoids. There's no way of knowing which technique a given device employs.
What all this means in practice is that, if you're saving activities, you should always include a distance time series in addition to the GPS coordinates. And if you're reading activity data, you should always make use of the distance time series rather than trying to apply any type of calculations to the GPS coordinates. And it follows that if no distance time series is available, then it will be very difficult to match the metrics that a given device reports.
FIT files are a preferred to GPX or TCX files. They provide more consistent structured data, and since thay are a binary format they are also incredibly compact. To upload a FIT file through the Smashrun API all you need to do is execute a multipart/form-data POST containing the FIT data.
Smashrun makes use of the following FIT messages and fields:
The TCX file was specifically designed for fitness data. There are established ways of including additional data series, like heart rate or cadence. And, if you follow all the guidelines below, the file should import exactly as it was recorded and everyone will love you dearly. That said, the number of TCX files we receive which follow all of the import specifications below is vanishingly close to zero. The reason for this is simple: it's hard. And it's not really documented anywhere (except here, now, sort of).
GPX files contain timestamps and location information. Any additional data needs to be marked up and included with proprietary extensions. In this way, it is much leaner than TCX files, but it is also poorly suited to fitness data. Runs on treadmills can't be represented because they have no GPS points, but distance is travelled. Additionally, since there are many different methods for calculating distance from latitude and longitude points, distances can vary depending on the calculation used. In short, you should really use the Activity JSON object instead. But, let's say there's a good reason not to. Here's what you need to remember:
In addition to GPX and TCX files you can also post FIT files directly to Smashrun via the API. This is done by simply posting the file to the /activities endpoint as multipart/form-data.
You can get the full details of any run for the authenticated user's account by passing a run id to the activities endpoint. This id maps to the main Smashrun site like this http://smashrun.com/chris.lukic/run/1236343
To get an array of run summaries you can can simply make a request to activities with no arguments
But, because that can frequently return an excess of data, it's more likely you'll want to return only a specific subset of runs. To do this you can use the activities/search endpoint.
You can use search to page data like so...
You can use search to grab only new and updated runs from a date. This might be handy for syncing...
Or, if you want to minimize bandwidth and the load on our tiny servers, maybe you just want to grab run ids.
Or if you need a bit a bit more than just ids adding briefs returns a set of the basic info needed to describe a run as unique.
On the other hand you may need an extended set of run data, for example, if you need all of the data related to the badge calculations for a run. In the case you can request extended run summaries.
Notables provide context for each run by comparing one run against all runs that proceeded it over a variety of dimensions. Each notable is composed of two or three pieces of information.
Notable object:
To retrieve notables
Goals can be retrieved by passing either a year only or a year/month combination. A goal can include a textual description, such as "Improve my running economy this year", or they may include a goal distance, for example 1,000 km. Or they can include both items. You can retrieve goals by passing either a year, or year/month combination. January = 1, so requesting /goals/2019/1 will return the goal set for January.
Note: If no goal is set for a given period the response from the API will be null.
Goal object:
The values retrieved include many of the aggregate metrics displayed on the Smashrun home page. These metrics can be returned for All Time (no arguments), one year(year only), and one month views (specify both year and month).
Stats object:
You can return the speed and and average heart rate (if available) at 1 mile or 1 kilometer splits.
To retrieve 1 km splits for a run
To retrieve 1 mile splits for a run
You can update the description field for a run by using the http method PATCH and passing an object with a notes field.
You can save or retrieve information about an athlete through the body endpoint.
To update a user's weight you can post a json object containing the attribute "weightInKilograms" and optionally "date".
If date isn't included today's date will be used.
Example: {weightInKilograms: 90, "date": "2015-04-01"}
You can save or retrieve information about the user associated with the current bearer token through the userinfo endpoint. This provides useful information such as configuration options (kg/lbs, mi/km, etc), but it can also be used to track when a users run information has changed by checking the property: dateTimeUTCOfLastRunDataUpdated.
A permanent unique id for the user. At present this is always a 64 bit integer, but you should store it as a string.
The first name recorded by the user
The last name recorded by the user
The username portion of the user's smashrun url http://smashrun.com/chris.lukic
UTC timestamp of when the user's last run started
UTC timestamp of the last time the user updated runs to smashrun from any source
One of either 'k' for kilometers or 'm' for miles
One of either 'k' for kilograms or 'p' for pounds
One of either 'm' for meters or 'f' for feet
One of either 'c' for Celsius or 'f' for Fahrenheit
Two or four letter ISO code for the user's configured language
'n' if the user last logged in from a location North of the equator, 's' for South of the equator
If configured returns the current resting heart rate of the user
If configured returns the current max heart rate of the user
The number of users currently following this user
The number of users that this user is following
The user's timezone offset in minutes (excluding the effects of Daylight savings time)
The user's timezone offset in minutes (with daylight savings time included)
The user's registration data (the date you can first start earning badges)
If pro then, the user's pro badge earn date (the date they can start earning pro badges)
Smashrun is a small company run by its 3 founders. Our love of running may be big, but our servers are small, so please be considerate when calling our API. If you're in doubt whether something's a good idea just shoot us an email before implementing it. We'll do our best to get back to you as soon as we can (usually this means immediately, but sometimes we're all out running). We might be able to make adjustments, add a new interface for you, or offer suggestions to help things work more efficiently.