Back to top

Concept2 Logbook API

This is the documentation for the Concept2 Logbook API. To use it, you’ll need to get an API key from Concept2.

If you have any questions on the API, please contact ranking@concept2.com.

URL

Before using the live API, you must first develop against the development server and database on https://log-dev.concept2.com. When ready, please contact Concept2 for approval for the live API.

Note: Results and users in the development database may occasionally be reset. You should not rely on data persisting there.

Security

All requests must be made over HTTPS.

HTTP Verbs

Where possible, this API uses appropriate HTTP verbs for each action.

e.g.

Verb Description Example
GET Used for retrieving resources. GET api/users/me/results
fetches all the workouts for the authenticated user
POST Used for creating resources. POST api/users/me/results
with the correct payload will create a new workout for the authenicated user
PATCH Used for updating resources. A PATCH request may accept one or more of the attributes to update the resource. As PATCH is a relatively new HTTP verb, resource endpoints currently also accept POST requests. PATCH api/users/me/results/123
will update a specific workout
DELETE Used for deleting resources. DELETE api/users/me/results/123
will delete a specific workout

Versioning

The current version of the API is v1. By default, this is the version of the API that will be used. In order to avoid potential issues, you are encouraged to explicitly request this version via the Accept header.

Accept: application/vnd.c2logbook.v1+json

Rate Limiting

The API is not currently rate limited. This may change in the future. Abuse of the API will result in either rate limits or removal of access.

Pagination

For requests that return more than one item (e.g. GET /users/1/results), the result set may be paginated. An additional meta element will be returned as such:

{
    "data": [...]
    "meta": {
        "pagination": {
            "total": 11,
            "count": 3,
            "per_page": 3,
            "current_page": 1,
            "total_pages": 4,
            "links": {
                "next": "http://log.concept2.com/api/users/1/results?number=3&page=2"
            }
        }
    }
}
  • total is the number of results

  • count is the number in the current set

  • per_page is the number per set

  • current_page is the current page/set

  • total_pages is the number of pages available

  • links contains next/prev links to help in fetching more results

The default number of results returned per page is 50. If you pass number=[number] as a query string parameter you can change the number of results you get back. The maximum number of results you can return in any call is 250.

Page numbering is 1-based and omitting the ?page parameter will return the first page.

HTTP Status Codes

All requests are returned with an HTTP Status Code. This can be used to test for errors.

For example, if you try to fetch the details of a user you do not have the rights to access, you will get a 403 Forbidden error.

GET /api/users/5 HTTP/1.1
Host: log.concept2.com
Content-Type: application/json
Authorization: Bearer HA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq

will return

HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8

{
    "message": "User does not have rights to this resource",
    "status_code": 403
}

In case of an error, the status code is additionally passed in the response to the request along with a more descriptive message.

HTTP Status Codes include:

CodeDescription
200Generic OK
201Resource created
401Invalid access token
403User forbidden from this endpoint
404Endpoint not found
405Method not allowed (e.g. trying to DELETE a user is currently not supported)
409Duplicate result
422Can't be processed. Generally means a well-formed request which fails validation.
500Error on behalf of the API
503API temporarily unavailable

Metadata

As well as sending across the request body, you can also set optional HTTP custom headers to help with debugging and analytics. These can be sent over with every call and are as follows:

HeaderDescriptionExample
X-Client-VersionThe version number of your client1.2.34
X-PM-VersionThe performance monitor number, e.g. 3, 4, 55
X-Firmware-VersionThe version number of the firmware running on the monitor707
X-DeviceThe name of the device the client is running oniPhone 6
X-Device-OSThe operating system the device is runningiOS
X-Device-OS-VersionThe version of the operating system the device is running8.3
X-OtherAdditional logging or debugging information. For example, if your app can use the USB LogBook or PM memory or BLE, you can pass these for analytics.USB
GET /api/users/5 HTTP/1.1
Host: log.concept2.com
Content-Type: application/json
Authorization: Bearer HA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq
X-Client-Version: 6.42
X-PM-Version: 5
X-Device: Mac
X-Device-OS: OSX
X-Device-OS-Version: 10.10

Authentication

OAuth2

The Logboook API uses OAuth2 for authentication.

All developers need to register their application with Concept2 before getting started. A registered OAuth application is assigned a unique Client ID and Client Secret. The Client Secret should not be shared. If using the Authorization Grant, you’ll also need to register your redirection endpoint.

The OAuth Grant types implemented are Authorization Code, Refresh, Client Credentials and Password. These grants are used as follows:

Authorization Code: The main grant type for most applications. Users are directed to the Logbook to authorize the application, before being returned to the application with an authorization code which can be exchanged for an access token. The user never has to provide their username/password to the application.

Refresh: Access tokens only last for a certain period of time. They also come with a reresh token which, when the access token has expired can be exchanged for a new access token.

Client Credentials: This grant type is only available for certain applications and is used for activities such as creating a user account. The method through which the client obtains the user credentials is beyond the scope of this specification. If using the password grant, the client MUST discard the credentials once an access token has been obtained.

Password: This grant type is only available for certain trusted applications. Rather than the user authorize the application on the Logbook, instead the application directly exhanges the user’s password and username credentials for an access token.

All applications have access to the Authorization Code and Refresh grant types.

Scopes

Scopes let you specify exactly what type of access you need. Scopes limit access for OAuth tokens. They do not grant any additional permission beyond that which the user already has.

For the web flow using the authorization grant, requested scopes will be displayed to the user on the authorize form.

In order to prevent users rejecting authorization, you are encouraged to ask only for the permissions you currently need. You can request additional scopes by revisting the authorization flow at a later date.

Below are a list of current scopes. Note: requesting the write version of a permission will also include the read version. You can request therefore “results:write” without also needing to request “results:read”.

To get more than one scope, they should be concatenated with a comma, e.g. to have read access to a user’s profile and their results, pass user:read,results:read

NameDescription
user:readGrants read access to a user's profile
user:writeGrants read/write access to a user's profile
results:readGrants read access to a user's results
results:writeGrants read/write access to a user's results

Scopes should be passed when fetching an authorization code, and getting an access token (either with an autorization code or when using a refresh token). You should pass your original scope(s) for all subsequent calls. It is possible to request fewer scopes but not to request additional scopes after the initial authorization code.

Important: If a scope is not passed, it currently defaults to having user:read,results:write as the scopes. This is for backwards compatibility with existing clients and this behaviour may change in the future. Do not rely on passing nothing as a scope. You may either receive fewer permissions than expected, or receive an authorization error.

Authorization Code

Get Authorization Code
GET/oauth/authorize?client_id={client_id}&scope={scope}&response_type={response_type}&redirect_uri={redirect_uri}

When you want to to authenticate a user via the Authorization Code grant, you need to first send them to the Logbook’s login and authorization system.

If the user then allows your application access, they will then be redirected to the callback URL you registered with ?code={authorization_code} appended. You can then exchange this code for an access token.

Example URI

GET https://log.concept2.com/oauth/authorize?client_id=ugdsra2alx7okz94smztckk6q6vc314xdem6l6hj&scope=user:read,results:write&response_type=code&redirect_uri=http:/example.com/logbook
URI Parameters
HideShow
client_id
string (required) Example: ugdsra2alx7okz94smztckk6q6vc314xdem6l6hj
scope
string (required) Example: user:read,results:write
response_type
string (required) Example: code
redirect_uri
string (required) Example: http://example.com/logbook
Response  200

Access Token

Fetch authorization token
POST/oauth/access_token

The client makes a request to the token endpoint by adding the following parameters using the “application/x-www-form-urlencoded” format with a character encoding of UTF-8 in the HTTP request entity-body.

This will return an access token which can used to verify API calls. In order to minimise the effect of tokens being intercepted, each access token is only valid for a certain period, specified by the expiry_in time, which is the lifetime in seconds of the token.

When an access token has expired, rather than request a new token with username and password, you should use the refresh_token to get a new one. The lifetime of the refresh token is currently one year. Set the grant_type to refresh_token and pass the correct refresh_token value. When you use it, as well as a new access_token, you will also get back a new refresh token, meaning you can use the API indefinitely without the user needing to reauthenticate. If the user does not use the client for over a year, they will need to log back in.

Note: If using the password grant, it is important to use the refresh token rather than storing user credentials and reauthenticating that way.

Once you have got a valid access token, you can then use it in the HTTP headers of your API calls, for example:

GET /api/users/me HTTP/1.1
Host: log.concept2.com
Content-Type: application/json
Authorization: Bearer TA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq
Authorization Parameters
NameRequiredDescriptionExample
client_idYesObtained from Concept2ugdsra2alx7okz94smztckk6q6vc314xdem6l6hj
client_secretYesObtained from Concept2e3403lr6o03klmh5yp6ldrimv45tfgiab8upinpr
grant_typeYesMust be one of:
  • authorization_code
  • password
  • client_credentials
  • refresh_token
authorization_code
scopeYesA list of comma-separated permissions. See "Scopes" above for the full list.user:read,results:write
codeNoNeeded when using the authorization_code grant_typec6YG5nTu3c9hfQCqsABV2x607znfmPEjqisPNlZG
usernameNoNeeded when using the password grant_typeDavid Hart
passwordNoNeeded when using the password grant_typesupersecret
redirect_uriNoNeeded when using the authorization_code grant_type. This must match the value sent in the call to oauth/authorize.myiphoneapp://oauth/callback
refresh_tokenNoNeeded when using the refresh grant_typewHJhFzCfOOKB8oyiayubhLAlxaMkG3ruC1E8YxaP

Example URI

POST https://log.concept2.com/oauth/access_token
Request  Initial Authentication
HideShow
Headers
Content-Type: application/x-www-form-urlencoded
Body
client_id=ugdsra2alx7okz94smztckk6q6vc314xdem6l6hj&client_secret=e3403lr6o03klmh5yp6ldrimv45tfgiab8upinpr&grant_type=authorization_code&redirect_uri=myiphoneapp://oauth/callback&code=c6YG5nTu3c9hfQCqsABV2x607znfmPEjqisPNlZG&scope=user:read
Request  Refresh Token
HideShow
Headers
Content-Type: application/x-www-form-urlencoded
Body
client_id=ugdsra2alx7okz94smztckk6q6vc314xdem6l6hj&client_secret=e3403lr6o03klmh5yp6ldrimv45tfgiab8upinpr&&grant_type=refresh_token&refresh_token=wHJhFzCfOOKB8oyiayubhLAlxaMkG3ruC1E8YxaP&scope=user:read
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "access_token": "TA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq",
  "token_type": "Bearer",
  "expires_in": 604800,
  "refresh_token": "jHJhFzCfOOKB8oyiayubhLAlxaMkG3ruC1E8YxaR"
}
Response  400
HideShow

Invalid request - generally due to one or more of the request parameters is missing.

Headers
Content-Type: application/json
Body
{
  "error": "invalid_request",
  "error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Check the \"client_secret\" parameter."
}
Response  401
HideShow

Incorrect login or client credentials. The error and error description will change depending which is incorrect.

Headers
Content-Type: application/json
Body
{
  "error": "invalid_credentials",
  "error_description": "The user credentials were incorrect."
}

Logbook Users

When dealing with logbook accounts (users, workouts) this is the main resource root.

Users

Create User
POST/api/users

This endpoint is used for creating a new user. To do this, you need to use a client access token rather than a user access token. Following are the list of values that can be sent as part of the message body.

NameRequiredTypeDescriptionExample
usernameYesstringMust be uniquePeter Parker
first_nameYesstringPeter
last_nameYesstringParker
genderYesstringMust be one of
  • F
  • M
M
passwordYesstringMust be a minimum of 6 characters longsupersecret
dobYesdateDate of birth in YYYY-MM-DD format1962-08-01
emailYesstringMust be a valid email addresspeterp@concept2.com
countryYesstringMust be a valid three-letter IOC code.USA
Return Values

As well as returning the fields used when creating a user the following additional fields will be returned:

NameTypeDescriptionExample
profile_imagestringThe full URL for the profile image. By changing the URL, you can get different size images [square (cropped to 75x75), medium (maximum 320px wide), large (maximum 640px wide)]http://media.concept2.com/assets/uploads/profiles/2/small/mypicture.jpg
age_restrictedbooleanIf the user is under 13, then certain details (full surname etc) are not stored unless the parents or guardian of the user has filled in a COPPA registration form.false

Example URI

POST https://log.concept2.com/api/users
Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidClientAccessToken
Body
{
  "username": "Peter Parker",
  "password": "supersecret",
  "first_name": "Peter",
  "last_name": "Parker",
  "gender": "M",
  "dob": "1962-08-01",
  "email": "peterp@concept2.com",
  "country": "USA"
}
Response  201
HideShow
Headers
Content-Type: application/json
Body
{
  "data": {
    "id": 1,
    "username": "David Hart",
    "first_name": "David",
    "last_name": "Hart",
    "gender": "M",
    "dob": "1977-08-19",
    "email": "davidh@concept2.com",
    "country": "GBR",
    "profile_image": "http://media.concept2.com/assets/uploads/profiles/1/small/mypicture.jpg",
    "age_restricted": false
  }
}
Response  422
HideShow

Validation error - one or more fields is missing or incorrect.

Headers
Content-Type: application/json
Body
{
  "message": "Could not create user.",
  "status_code": 422,
  "errors": {
    "gender": [
      "The gender field is required."
    ],
    "username": [
      "The username has already been taken."
    ]
  }
}

User

Get User
GET/api/users/{user}

Get a user by id.

Example URI

GET https://log.concept2.com/api/users/me
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": {
    "id": 1,
    "username": "David Hart",
    "first_name": "David",
    "last_name": "Hart",
    "gender": "M",
    "dob": "1977-08-19",
    "email": "davidh@concept2.com",
    "country": "GBR",
    "profile_image": "http://media.concept2.com/assets/uploads/profiles/1/small/mypicture.jpg",
    "age_restricted": false
  }
}

Edit User
PATCH/api/users/{user}

Edit an existing user. You can send across either the entire resource or just any changed values.

Example URI

PATCH https://log.concept2.com/api/users/me
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "email": "peterp@concept2.co.uk"
}
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": {
    "id": 2,
    "username": "Peter Parker",
    "first_name": "Peter",
    "last_name": "Parker",
    "gender": "M",
    "dob": "1962-08-01",
    "email": "peterp@concept2.co.uk",
    "country": "USA"
  }
}
Response  422
HideShow

Validation error - one or more fields is incorrect.

Headers
Content-Type: application/json
Body
{
  "message": "Could not update user.",
  "status_code": 422,
  "errors": {
    "email": [
      "The email format is invalid."
    ]
  }
}

Results

Get Results
GET/api/users/{user}/results

Get all results for the current user. The response is paginated. See Pagination for more information on how to work with paginated result sets.

It’s possible to filter the results and return only results that match certain criteria. To do this, pass the filter criteria as query string variables.

The following filter criteria are available:

KeyDescriptionExample
fromFetches only results where the workout date is on or after this. Should be in YYYY-MM-DD format. Note: You can also use full "YYYY-MM-DD H:M:S" if required.2015-05-01
toFetches only results where the workout date is on or before this. Should be in YYYY-MM-DD format. Note: You can also use full "YYYY-MM-DD H:M:S" if required.2015-05-01
typeFetches only results of this type. Must be one of
  • rower
  • skierg
  • bike
  • dynamic
  • slides
  • paddle
  • water
  • snow
rower
updated_afterFetches only results created or updated on or after this date. Should be in YYYY-MM-DD format. Note: You can also use full "YYYY-MM-DD H:M:S" if required. The timezone of the updated_after date is GMT, so you should convert to this when making the call.2015-05-01 12:54:23

For example, to get all rower results in May 2015, call:

GET /api/users/me/results?from=2015-05-01&to=2015-05-31&type=rower

Example URI

GET https://log.concept2.com/api/users/me/results
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": [
    {
      "id": 3,
      "user_id": 1,
      "date": "2013-06-21 00:00:00",
      "timezone": null,
      "date_utc": null,
      "distance": 23000,
      "type": "rower",
      "time": 152350,
      "time_formatted": "4:13:55.0",
      "workout_type": "unknown",
      "source": "Web",
      "weight_class": "H",
      "verified": false,
      "ranked": false,
      "comments": null
    },
    {
      "id": 8,
      "user_id": 1,
      "date": "2013-05-10 00:00:00",
      "timezone": null,
      "date_utc": null,
      "distance": 42195,
      "type": "skierg",
      "time": 262700,
      "time_formatted": "7:17:50.0",
      "workout_type": "unknown",
      "source": "Web",
      "weight_class": "H",
      "verified": false,
      "ranked": false,
      "comments": null
    }
  ],
  "meta": {
    "pagination": {
      "total": 9,
      "count": 9,
      "per_page": 50,
      "current_page": 1,
      "total_pages": 1,
      "links": []
    }
  }
}

Add Result
POST/api/users/{user}/results

Add a new workout. Following are the list of values that can be sent as part of the message body. Note: the Logbook filters for duplicate workouts, so will return a Duplicate Entry error if you post a workout which has the same date, time and distance as an existing workout. Note: We recommend you use our Online Validator tool to check workouts for errors, especially if uploading interval workouts.

NameRequiredTypeDescriptionExample
typeYesstringMust be one of
  • rower
  • skierg
  • bike
  • dynamic
  • slides
  • paddle
  • water
  • snow
rower
dateYesdatetimeEither date or datetime in yyyy-mm-dd hh:mm:ss format2015-05-01
timezoneNostringIf present, must be a valid timezone format from the tz database.America/New_York
distanceYesintegerIn meters. Note: for interval workouts this is work distance only. Rest distance is set separately (see below).5000
timeYesintegerTime in tenths of a second. e.g. one minute would be 600. Note: for interval workouts this is work time only. Rest time is set separately (see below).1200
weight_classDependsstringRequired if type is rower, dynamic or slides. Value must be either H or LH
verifiedoptionalbooleanWhether the result should be considered verified. Only trusted clients are able to verify workouts. Please contact Concept2 for more information.false
commentsNostringNo markup or formatting is curently supported apart from line breaks and paragraphs using \r and \n.First workout of the year.\r\n\r\nDone at the gym.
workout_typeNostringMust be one of
  • unknown
  • JustRow
  • FixedDistanceSplits
  • FixedTimeSplits
  • FixedCalorie
  • FixedTimeInterval
  • FixedDistanceInterval
  • FixedCalorieInterval
  • VariableInterval
  • VariableIntervalUndefinedRest
FixedDistanceInterval
stroke_rateNointegerAverage stroke rate for a workout36
heart_rateNoobjectobject of strings containing the following optional values:
  • average
  • min
  • max
  • ending
  • recovery
"heart_rate": {
    "ending": 160,
    "recovery": 70
}
stroke_countNointegerTotal number of strokes in a workout236
calories_totalNointegerTotal calories in a workout436
drag_factorNointegerAverage drag factor (to nearest whole number)115
rest_distanceDependsintegerFor interval workouts only. This is the total distance in meters of distance covered in rest intervals.335
rest_timeDependsintegerFor interval workouts only. This is the value in tenths of a second of total time spent in rest intervals.600
workoutNoarrayArray of objects containing split or interval data. See below for more info.
stroke_dataNoarrayArray of objects containing stroke data. See below for more info.
metadataNoobjectObject containing meta data. See below for more info.
Workout Details

These are the possible values for the workout field.

The following fields are possible. Note: split and interval data are validated for type and expected values. Sending across a decimal value or a string where an integer is expected (e.g. stroke_rate or calories_total) will result in the workout failing.

Possible values are

NameRequiredType
splitsNoarray
intervalsNoarray
Split/Interval Workouts

Splits and intervals are an array of objects. Each object can contain the following fields:

NameRequiredTypeDescriptionExample
distanceYesintegerIn meters. Note: for interval workouts this is work distance only. Rest distance is set separately (see below).5000
timeYesintegerTime in tenths of a second. e.g. one minute would be 600. Note: for interval workouts this is work time only. Rest time is set separately where available (see below).1200
stroke_rateNointegerAverage stroke rate34
calories_totalNointegerTotal calories26
heart_rateNoobjectAn object with integer values for one or more of the following optional fields:
  • average
  • min
  • max
  • ending
  • rest
  • recovery
"heart_rate": {
    "average": 140,
    "ending": 160,
    "rest": 70
}

The following fields are also for interval workouts only.

NameRequiredTypeDescriptionExample
typeYesstringMust be one of:
  • time
  • distance
  • calorie
time
rest_timeYesintegerThis is the value in tenths of a second of the time spent in rest intervals.300
rest_distanceNointegerThis is the distance in meters spent in rest intervals. This should be included for Variable interval workouts only.50
Strokes

Strokes are an array of objects which can contain the following fields for each stroke:

NameRequiredTypeDescriptionExample
tNointegerTime. In tenths of a second. e.g. 23 is 2.3 seconds.23
dNointegerDistance. In decimeters, e.g. 155 is 15.5 meters155
pNointegerPace. Pace in tenths of a second, e.g. 971 is a pace of 1:37.1. This is pace per 500m for the rower and SkiErg, and pace per 1000m for the BikeErg.971
spmNointegerStrokes Per Minute. Value as of current stroke rate.35
hrNointegerHeart Rate. Value of current heart rate156

Note: Time and distance are incremental rather than the difference between the previous stroke. For interval workouts, time and distance start again at 0 for each interval.

Metadata

When adding results, instead of using headers to send across metadata, you can also send them across as part of the result body. This is especially useful if using the bulk results endpoint and submitting multiple results at once which may have been rowed on different machines. All values are optional. Note: If sending across both metadata headers and as part of the result body, the ones sent as part of the result body will be used.

NameDescriptionExample
client_versionThe version number of your client1.2.34
pm_versionThe performance monitor number, e.g. 3, 4, 55
firmware_versionThe version number of the firmware running on the monitor707
serial_numberThe serial number of the monitor430395351
deviceThe name of the device the client is running oniPhone 6
device_osThe operating system the device is runningiOS
device_os_versionThe version of the operating system the device is running8.3
otherAdditional logging or debugging information. For example, if your app can use the USB LogBook or PM memory or BLE, you can pass these for analytics.USB

Example URI

POST https://log.concept2.com/api/users/me/results
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

Request  Simple workout
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "type": "rower",
  "date": "2015-08-05 13:15:41",
  "timezone": "Europe/London",
  "distance": 5649,
  "time": 8649,
  "weight_class": "H",
  "workout_type": "JustRow",
  "comments": null
}
Request  Just Row workout
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "date": "2017-05-15 16:40:00",
  "timezone": "Europe/London",
  "workout_type": "JustRow",
  "type": "rower",
  "weight_class": "H",
  "time": 4861,
  "distance": 1217,
  "drag_factor": 104,
  "calories_total": 60,
  "stroke_rate": 30,
  "stroke_count": 250,
  "workout": {
    "splits": [
      {
        "distance": 741,
        "time": 3000,
        "stroke_rate": 32,
        "calories_total": 37,
        "heart_rate": {
          "ending": 140
        }
      },
      {
        "distance": 477,
        "time": 1861,
        "stroke_rate": 29,
        "calories_total": 23,
        "heart_rate": {
          "ending": 150
        }
      }
    ]
  }
}
Request  Single Time workout
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "date": "2017-05-16 17:24:00",
  "timezone": "US/Pacific",
  "workout_type": "FixedTimeSplits",
  "type": "rower",
  "weight_class": "H",
  "time": 6000,
  "distance": 1789,
  "stroke_count": 314,
  "drag_factor": 134,
  "stroke_rate": 31,
  "calories_total": 90,
  "workout": {
    "splits": [
      {
        "time": 1200,
        "calories_total": 18,
        "stroke_rate": 33,
        "distance": 354
      },
      {
        "time": 1200,
        "calories_total": 18,
        "stroke_rate": 31,
        "distance": 355
      },
      {
        "time": 1200,
        "calories_total": 18,
        "stroke_rate": 32,
        "distance": 357
      },
      {
        "time": 1200,
        "calories_total": 18,
        "stroke_rate": 31,
        "distance": 363
      },
      {
        "time": 1200,
        "calories_total": 18,
        "stroke_rate": 30,
        "distance": 361
      }
    ]
  }
}
Request  Distance Interval workout
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "date": "2015-08-30 14:24:00",
  "timezone": "Europe/London",
  "distance": 440,
  "time": 762,
  "type": "rower",
  "weight_class": "H",
  "heart_rate": {
    "average": 140
  },
  "workout_type": "FixedDistanceInterval",
  "rest_distance": 43,
  "rest_time": 1200,
  "workout": {
    "intervals": [
      {
        "type": "distance",
        "time": 415,
        "rest_time": 600,
        "stroke_rate": 35,
        "distance": 220,
        "heart_rate": {
          "ending": 160,
          "rest": 60
        }
      },
      {
        "type": "distance",
        "time": 347,
        "rest_time": 600,
        "stroke_rate": 45,
        "distance": 220,
        "heart_rate": {
          "ending": 170,
          "rest": 70
        }
      }
    ]
  }
}
Request  Time Interval workout
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "date": "2015-08-30 14:24:00",
  "timezone": "Europe/London",
  "distance": 440,
  "time": 762,
  "type": "rower",
  "weight_class": "H",
  "heart_rate": {
    "average": 140
  },
  "workout_type": "FixedDistanceInterval",
  "rest_distance": 43,
  "rest_time": 1200,
  "workout": {
    "intervals": [
      {
        "type": "distance",
        "time": 415,
        "rest_time": 600,
        "stroke_rate": 35,
        "distance": 220,
        "heart_rate": {
          "ending": 160,
          "rest": 60
        }
      },
      {
        "type": "distance",
        "time": 347,
        "rest_time": 600,
        "stroke_rate": 45,
        "distance": 220,
        "heart_rate": {
          "ending": 170,
          "rest": 70
        }
      }
    ]
  }
}
Request  Variable Interval workout
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "date": "2017-05-01 14:33:00",
  "timezone": "Australia/Melbourne",
  "workout_type": "VariableInterval",
  "type": "rower",
  "weight_class": "H",
  "time": 16800,
  "distance": 6721,
  "rest_distance": 236,
  "rest_time": 2700,
  "calories_total": 427,
  "drag_factor": 175,
  "stroke_count": 996,
  "stroke_rate": 33,
  "workout": {
    "intervals": [
      {
        "type": "time",
        "time": 2400,
        "distance": 1011,
        "rest_time": 600,
        "rest_distance": 43,
        "stroke_rate": 35,
        "calories_total": 68
      },
      {
        "type": "time",
        "time": 3000,
        "distance": 1229,
        "rest_time": 600,
        "rest_distance": 59,
        "stroke_rate": 34,
        "calories_total": 80
      },
      {
        "type": "time",
        "time": 3000,
        "distance": 1190,
        "rest_time": 600,
        "rest_distance": 59,
        "stroke_rate": 33,
        "calories_total": 75
      },
      {
        "type": "time",
        "time": 2400,
        "distance": 971,
        "rest_time": 750,
        "rest_distance": 44,
        "stroke_rate": 34,
        "calories_total": 62
      },
      {
        "type": "time",
        "time": 6000,
        "distance": 2320,
        "rest_time": 150,
        "rest_distance": 31,
        "stroke_rate": 32,
        "calories_total": 142
      }
    ]
  }
}
Response  201
HideShow
Headers
Content-Type: application/json
Body
{
  "data": {
    "id": 339,
    "user_id": 1,
    "date": "2015-08-05 13:15:41",
    "timezone": "Europe/London",
    "date_utc": "2015-08-05 12:15:41",
    "distance": 5649,
    "type": "rower",
    "time": 8649,
    "time_formatted": "14:24.9",
    "workout_type": "JustRow",
    "source": "ErgData",
    "weight_class": "H",
    "verified": true,
    "ranked": false
  }
}
Response  409
HideShow

Duplicate result - the workout you are trying to add has the same time, distance and date as an existing workout.

Headers
Content-Type: application/json
Body
{
    "message": "Duplicate result",
    "status_code": 409,
}
Response  422
HideShow

Validation error - one or more fields is missing or incorrect.

Headers
Content-Type: application/json
Body
{
  "message": "Could not create user.",
  "status_code": 422,
  "errors": {
    "distance": [
      "The distance field is required."
    ]
  }
}

Multiple Results

Add Results
POST/api/users/{user}/results/bulk

If you want to add more than one workout at once, you can post to /api/users/me/results/bulk.

This takes an array of results. The return response will be an array of results or error messages, similar to posting individual results, but with an additional status code. The status code of the responsed will always be 200.

Example URI

POST https://log.concept2.com/api/users/me/results/bulk
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
[
  {
    "type": "rower",
    "date": "2015-05-14 03:15:41",
    "distance": 5649,
    "time": 8649,
    "weight_class": "H",
    "workout_type": "JustRow"
  },
  {
    "type": "rower",
    "date": "2015-05-14 03:15:41",
    "distance": 5649,
    "time": 8649,
    "weight_class": "H",
    "workout_type": "JustRow"
  }
]
Response  200
HideShow
Headers
Content-Type: application/json
Body
[
  {
    "status_code": 201,
    "data": {
      "id": 371,
      "user_id": 1,
      "date": "2015-05-05 03:15:41",
      "timezone": null,
      "date_utc": null,
      "distance": 5649,
      "type": "rower",
      "time": 8649,
      "time_formatted": "14:24.9",
      "workout_type": "JustRow",
      "source": "ErgData",
      "weight_class": "H",
      "verified": true,
      "ranked": false,
      "comments": null
    }
  },
  {
    "status_code": 409,
    "message": "Duplicate result"
  }
]

Result

Get Result
GET/api/users/{user}/results/{result_id}

Get an individual result.

Example URI

GET https://log.concept2.com/api/users/me/results/1
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

result_id
number (required) Example: 1

The integer id of the workout

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": {
    "id": 3,
    "user_id": 1,
    "date": "2013-06-21 00:00:00",
    "distance": 23000,
    "type": "rower",
    "time": 152350,
    "time_formatted": "4:13:55.0",
    "workout_type": "unknown",
    "source": "Web",
    "weight_class": "H",
    "verified": false,
    "ranked": false,
    "comments": null
  }
}

Edit Result
PATCH/api/users/{user}/results/{result_id}

Edit an existing workout. You can send across either the entire resource or just any changed values.

Example URI

PATCH https://log.concept2.com/api/users/me/results/1
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

result_id
number (required) Example: 1

The integer id of the workout

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Body
{
  "weight_class": "L",
  "comments": "Second row of the year."
}
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": {
    "id": 339,
    "user_id": 1,
    "date": "2015-05-05 03:15:41",
    "distance": 5649,
    "type": "rower",
    "time": 8649,
    "time_formatted": "14:24.9",
    "workout_type": "JustRow",
    "source": "ErgData",
    "weight_class": "L",
    "verified": true,
    "ranked": false,
    "comments": "Second row of the year."
  }
}
Response  422
HideShow

Validation error - one or more fields is incorrect.

Headers
Content-Type: application/json
Body
{
  "message": "Could not update result.",
  "status_code": 422,
  "errors": {
    "distance": [
      "The distance field is required."
    ]
  }
}

Delete Result
DELETE/api/users/{user}/results/{result_id}

Delete a result. Note: This cannot be undone.

Example URI

DELETE https://log.concept2.com/api/users/me/results/1
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

result_id
number (required) Example: 1

The integer id of the workout

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "message": "Result deleted successfully"
}
Response  404
HideShow
Headers
Content-Type: application/json
Body
{
  "message": "This workout does not exist for this user",
  "status_code": 404
}
Response  403
HideShow
Headers
Content-Type: application/json
Body
{
  "message": "User does not have rights to this resource",
  "status_code": 403
}

Stroke Data

Get Stroke Data
GET/api/users/{user}/results/{result_id}/strokes

Get stroke data for a workout. See Add Result for information on structure.

Example URI

GET https://log.concept2.com/api/users/me/results/9/strokes
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

result_id
number (required) Example: 9

The integer id of the workout

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": [
    {
      "t": 0,
      "d": 0,
      "p": 0,
      "spm": 0,
      "hr": 0
    }
  ]
}
Response  404
HideShow
Headers
Content-Type: application/json
Body
{
  "message": "This workout does not have any stroke data associated with it",
  "status_code": 404
}

Delete Strokes
DELETE/api/users/{user}/results/{result_id}/strokes

Delete stroke data. Note: This cannot be undone.

Example URI

DELETE https://log.concept2.com/api/users/me/results/9/strokes
URI Parameters
HideShow
user
number or string (required) Example: me

Either the integer id of the user or ‘me’ as shorthand for authenticated user.

result_id
number (required) Example: 9

The integer id of the workout

Request
HideShow
Headers
Content-Type: application/json
Authorization: Bearer aValidAccessToken
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "message": "Result deleted successfully"
}
Response  404
HideShow
Headers
Content-Type: application/json
Body
{
  "message": "This workout does not exist for this user",
  "status_code": 404
}
Response  403
HideShow
Headers
Content-Type: application/json
Body
{
  "message": "User does not have rights to this resource",
  "status_code": 403
}

Reminders

Endpoints for users who can’t remember their username and/or passwords. If the username is known, a reset email can be sent to the associated email address. If the username is not known, a list of usernames for that email address can be sent.

Password Reset

Create Password Reset
POST/api/reminder/password

Send a password reset email to the email account associated with a username.

Example URI

POST https://log.concept2.com/api/reminder/password
Request
HideShow
Headers
Content-Type: application/json
Body
{
  "username": "Peter Parker"
}
Response  200
HideShow
Body
{
  "message": "A password reset email has been sent to the email address for this username."
}
Response  422
HideShow

Missing username.

Headers
Content-Type: application/json
Body
{
  "message": "Could not get user",
  "status_code": 422,
  "errors": {
    "username": [
      "Missing username"
    ]
  }
}
Response  404
HideShow

No known user.

Headers
Content-Type: application/json
Body
{
  "message": "There are no users with that username.",
  "status_code": 404
}

Username Reminder

Create Username Reminder
POST/api/reminder/username

Send any usernames associated with an email account to the specified email.

Example URI

POST https://log.concept2.com/api/reminder/username
Request
HideShow
Headers
Content-Type: application/json
Body
{
  "email": "peterp@concept2.com"
}
Response  200
HideShow
Body
{
  "message": "A username reminder has been sent to your email address."
}
Response  422
HideShow

Missing or incorrect email.

Headers
Content-Type: application/json
Body
{
  "message": "Could not get usernames",
  "status_code": 422,
  "errors": {
    "email": [
      "You must enter a valid email address"
    ]
  }
}
Response  404
HideShow

Unknown email address.

Headers
Content-Type: application/json
Body
{
  "message": "There are no users with that email address.",
  "status_code": 404
}

Logbook Challenges

This resource is for fetching a list of Logbook challenges.

There is no need for an authorization token for these endpoints.

All challenges

Get all challenges
GET/api/challenges

Fetch a paginated collection of all challenges.

Example URI

GET https://log.concept2.com/api/challenges
Request
HideShow
Headers
Content-Type: application/json
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": [
    {
      "season": "2014",
      "start": "2013-05-01",
      "end": "2013-05-15",
      "activity": "Row/Ski",
      "name": "Global Marathon",
      "category": "Individual",
      "description": "Row or ski either a full (42,195m) or half (21,097m) marathon in one workout",
      "short_description": "Complete either a full or half marathon in one workout"
    },
    {
      "season": "2014",
      "start": "2013-06-21",
      "end": "2013-06-21",
      "activity": "Row/Ski",
      "name": "Summer Solstice",
      "category": "Individual",
      "description": "Row and/or ski 21,000 meters",
      "short_description": false
    },
    {
      "season": "2014",
      "start": "2013-08-01",
      "end": "2013-08-28",
      "activity": "Row/Ski",
      "name": "Dog Days of Summer",
      "category": "Individual",
      "description": "Row and/or ski a different distance goal each week",
      "short_description": false
    },
    {
      "season": "2014",
      "start": "2013-09-15",
      "end": "2013-10-15",
      "activity": "Row/Ski",
      "name": "Fall Team Challenge",
      "category": "Team",
      "description": "Complete as many meters as you can during the timeframe indicated",
      "short_description": "Help your team row and ski as many meters as possible"
    },
    {
      "season": "2014",
      "start": "2013-10-25",
      "end": "2013-10-31",
      "activity": "Row/Ski",
      "name": "Skeleton Crew",
      "category": "Individual",
      "description": "Row and/or ski at least 31,000 meters",
      "short_description": false
    },
    {
      "season": "2014",
      "start": "2013-11-28",
      "end": "2013-12-24",
      "activity": "Row/Ski",
      "name": "Holiday Challenge",
      "category": "Individual",
      "description": "Row and/or ski 100,000 meters or 200,000 meters",
      "short_description": false
    }
  ],
  "meta": {
    "pagination": {
      "total": 24,
      "count": 6,
      "per_page": 6,
      "current_page": 1,
      "total_pages": 4,
      "links": {
        "next": "http://log.concept2.com/api/challenges?page=2"
      }
    }
  }
}

Current challenges

Get current challenges
GET/api/challenges/current

Get current active challenges only.

Example URI

GET https://log.concept2.com/api/challenges/current
Request
HideShow
Headers
Content-Type: application/json
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": [
    {
      "season": "2014",
      "start": "2013-05-01",
      "end": "2013-05-15",
      "activity": "Row/Ski",
      "name": "Global Marathon",
      "category": "Individual",
      "description": "Row or ski either a full (42,195m) or half (21,097m) marathon in one workout",
      "short_description": "Complete either a full or half marathon in one workout"
    }
  ]
}

Challenges for Season

Get challenges for a season
GET/api/challenges/season/{season}

Get challenges for a specific season.

Example URI

GET https://log.concept2.com/api/challenges/season/2014
URI Parameters
HideShow
season
number (required) Example: 2014

The logbook season (which runs May 1 through April 30) you need the challenges for.

Request
HideShow
Headers
Content-Type: application/json
Response  200
HideShow
Headers
Content-Type: application/json
Body
{
  "data": [
    {
      "season": "2014",
      "start": "2013-05-01",
      "end": "2013-05-15",
      "activity": "Row/Ski",
      "name": "Global Marathon",
      "category": "Individual",
      "description": "Row or ski either a full (42,195m) or half (21,097m) marathon in one workout",
      "short_description": "Complete either a full or half marathon in one workout"
    },
    {
      "season": "2014",
      "start": "2013-06-21",
      "end": "2013-06-21",
      "activity": "Row/Ski",
      "name": "Summer Solstice",
      "category": "Individual",
      "description": "Row and/or ski 21,000 meters",
      "short_description": false
    }
  ]
}

Generated on 16 Oct 2018