Resources for developers
-
On this page, a work in progress, I will collect useful information for developers of rowing data apps and hardware.
I presume you have an app (smartphone app, dedicated hardware, web site) where your users (customers) generate, collect or store their rowing related workout data. You can now offer your users easy ways to get their data on this site.
-
There are three ways to allow your users to get data to Rowsandall.com.
File based export from your app
Enable export of TCX, FIT or CSV formatted files from your app. The users upload the file to Rowsandall.com.
- Advantages
- User sees immediate results
- Disadvantages
- It is a multi-step process: Download from your app, store, upload.
Email from your app
Similar as above, generate TCX, FIT or CSV formatted files and email them to workouts@rowsandall.com directly from your app. The From: field should be the email address of the registered user.
- Advantages
- It's a simple process, which can be automated.
- Disadvantages
- It may take up to five minutes for the workout to show up on the site.
Using the REST API
We are building a REST API which will allow you to post and receive stroke data from the site directly.
The REST API is a work in progress. We are open to improvement suggestions (provided they don't break existing apps). Please send email to info@rowsandall.com with questions and/or suggestions. We will get back to you as soon as possible.
- Advantages
- Once it is set up, this is a one-click operation.
- You can read a user's workout data from the site and use them in your app.
- This is not limited to workout data. You could make a full mobile version of our site.
- Disadvantages
- The API is not stable and not fully tested yet.
- You need to register your app with us. We can revoke your permissions if you misuse them.
- The user user must grant permissions to your app.
- You need to manage authorization tokens.
- Advantages
-
Quick Links
Accepted file formats
All files adhering to the standards TCX and FIT formats will be parsed.
However, some rowing related parameters are not supported by TCX and FIT. Therefore, we are supporting the CSV format that is documented in the following link.
Using this standard will guarantee that your user's data are accepted without complaints.
API related documentation
Registering an app
We have disabled the self service app link for security reasons. We will replace it with a secure self service app link soon. If you need to register an app, please send email to info@rowsandall.com
Authentication
Standard Oauth2 authentication. Get authorization code by pointing your user to the authorization URL. Exchange authorization code for an access token. When access token expires, use the refresh token to refresh it.
The redirect URI for user authentication has to be https. Developers of iOS or Android apps should contact me directly if this doesn't work for them. I can add exceptions.
- Authorization URL: https://rowsandall.com/rowers/o/authorize
- Access Token request: https://rowsandall.com/rowers/o/token/
- Access Token refresh: https://rowsandall.com/rowers/o/token/
- Handy utility for testing: http://django-oauth-toolkit.herokuapp.com/consumer/
API endpoints
Once you have a registered app, you have gone through the authorization and have successfully obtained an access token, you can use it to place POST, GET and PUT requests.
The workout summary data and the stroke data are obtained and sent separately.
In the table below {id} indicates the ID of the item.
Item Access Points Permissions Example Workout /rowers/api/workouts/
/rowers/api/workouts/{id}GET, POST, DELETE { "id": 18, "name": "2x", "date": "2020-10-15", "workouttype": "water", "starttime": "08:40:50", "distance": 9751, "duration": "00:52:40", "averagehr": 104, "maxhr": 122, "notes": "", "summary": "Workout Summary - media/97ec07a851-20201015-192157-95763ffd-fd4e-4b5f-bf5f-8a8495c16639.csv\n--|Total|-Total----|--Avg--|-Avg-|Avg-|-Avg-|-Max-|-Avg\n--|Dist-|-Time-----|-Pace--|-Pwr-|SPM-|-HR--|-HR--|-DPS\n--|09751|00:52:40.0|02:42.0|173.0|18.0|104.4|122.0|10.3\nW-|09751|00:52:40.0|02:42.0|173.0|18.0|104.4|122.0|10.3\nR-|00000|00:00:00.0|00:00.0|000.0|00.0|000.0|122.0|00.0\nWorkout Details\n#-|SDist|-Split-|-SPace-|-Pwr-|SPM-|AvgHR|MaxHR|DPS-\n00|09751|52:40.0|02:42.0|173.0|18.0|104.4|122.0|10.3\n", "boattype": "2x", "timezone": "Europe/Amsterdam", "forceunit": "N", "inboard": 0.88, "oarlength": 2.89, "privacy": "visible", "rankingpiece": false }
Favorite Chart /rowers/api/charts/
/rowers/api/charts/{id}GET, DELETE { "id": 7, "xparam": "time", "yparam1": "hr", "yparam2": "power", "plottype": "line", "workouttype": "otw", "reststrokes": true, "user": 1 }
Planned Session /rowers/api/plannedsessions/
/rowers/api/plannedsessions/{id}GET, POST, DELETE { "id": 103, "name": "ExampleSession", "comment": "", "startdate": "2021-03-01", "enddate": "2021-03-06", "preferreddate": "2021-03-01", "sessiontype": "session", "sessionvalue": 74, "sessionunit": "min", "sessionmode": "time", "course": null, "approximate_distance": 16440, "approximate_duration": 74, "fitfile": "https://rowsandall.com/media/a7a7a621ad-20210302-074033-tmp2zcd7i_0.fit" }
Challenge /rowers/api/challenges/
/rowers/api/challenges/{id}GET { "id": 1, "name": "Alphen", "course": 1, "registration_closure": "2020-10-18T08:45:00+02:00", "evaluation_closure": "2020-10-18T12:16:00+02:00", "start_time": "08:45:00", "end_time": "12:00:00", "country": "Nederland", "timezone": "Europe/Amsterdam", "contact_phone": "", "contact_email": "", "entries": [ { "id": 1, "username": "Sander Roosendaal", "teamname": null, "boattype": "1x", "sex": "male", "age": 48, "adaptiveclass": "None", "skillclass": "Open", "coursecompleted": false, "distance": 0, "duration": "00:00:00", "points": 0.0, "entrycategory": null } ], "coursestandards": null }
Challenge Entry /rowers/api/entries/
/rowers/api/entries/{id}GET, POST { "id": 1, "teamname": null, "adaptiveclass": "None", "skillclass": "Open", "race": { "id": 1, "name": "Alphen", "course": 1, "registration_closure": "2020-10-18T08:45:00+02:00", "evaluation_closure": "2020-10-18T12:16:00+02:00", "start_time": "08:45:00", "end_time": "12:00:00", "country": "Nederland", "timezone": "Europe/Amsterdam", "contact_phone": "", "contact_email": "", "entries": [ { "id": 1, "username": "Sander Roosendaal", "teamname": null, "boattype": "1x", "sex": "male", "age": 48, "adaptiveclass": "None", "skillclass": "Open", "coursecompleted": false, "distance": 0, "duration": "00:00:00", "points": 0.0, "entrycategory": null } ], "coursestandards": null }, "distance": 0, "duration": "00:00:00", "points": 0.0, "boattype": "1x", "sex": "male", "age": 48, "entrycategory": null }
Course /rowers/api/geocourses/
/rowers/api/geocourses/{id}GET Example shortened for brevity (omitted gates 2-14): { "id": 1, "name": "Alphen - Alphen aan den Rijn", "distance": 14847, "country": "Nederland", "notes": "\n\n", "polygons": [ { "id": 1, "name": "Start", "order_in_course": 0, "points": [ { "id": 1, "latitude": 52.14611068342334, "longitude": 4.704149601313898, "order_in_poly": 0 }, { "id": 2, "latitude": 52.14606840788696, "longitude": 4.704648516706039, "order_in_poly": 1 }, { "id": 3, "latitude": 52.14626893773362, "longitude": 4.704642182077736, "order_in_poly": 2 }, { "id": 4, "latitude": 52.14628828501986, "longitude": 4.704151599747837, "order_in_poly": 3 }, { "id": 5, "latitude": 52.14611068342334, "longitude": 4.704149601313898, "order_in_poly": 4 } ] }, { "id": 15, "name": "Finish", "order_in_course": 14, "points": [ { "id": 71, "latitude": 52.1461319705247, "longitude": 4.70414987291546, "order_in_poly": 0 }, { "id": 72, "latitude": 52.14607111930849, "longitude": 4.704561170436561, "order_in_poly": 1 }, { "id": 73, "latitude": 52.14626893773362, "longitude": 4.704642182077736, "order_in_poly": 2 }, { "id": 74, "latitude": 52.14628831020436, "longitude": 4.70415735390207, "order_in_poly": 3 }, { "id": 75, "latitude": 52.1461319705247, "longitude": 4.70414987291546, "order_in_poly": 4 } ] } ] }
Course Time Standard Collection /rowers/api/standardcollections/
/rowers/api/standarcollections/{id}GET Example shortened for brevity (showing only first three): { "id": 1, "name": "Charles River Times Standards", "notes": "", "active": true, "standards": [ { "id": 1, "name": "M1x", "coursedistance": 4700, "coursetime": "17:15.0", "agemin": 0, "agemax": 120, "boatclass": "water", "boattype": "1x", "sex": "male", "weightclass": "hwt", "adaptiveclass": "None", "skillclass": "Open", "standardcollection": 1 }, { "id": 2, "name": "MLW1x", "coursedistance": 4700, "coursetime": "17:15.0", "agemin": 0, "agemax": 120, "boatclass": "water", "boattype": "1x", "sex": "male", "weightclass": "lwt", "adaptiveclass": "None", "skillclass": "Open", "standardcollection": 1 }, { "id": 3, "name": "MYouth1x", "coursedistance": 4700, "coursetime": "18:30.0", "agemin": 15, "agemax": 18, "boatclass": "water", "boattype": "1x", "sex": "male", "weightclass": "hwt", "adaptiveclass": "None", "skillclass": "Open", "standardcollection": 1 }, ] }
Individual Course Time Standard /rowers/api/standards/
/rowers/api/standards/{id}GET, POST { "id": 1, "name": "M1x", "coursedistance": 4700, "coursetime": "17:15.0", "agemin": 0, "agemax": 120, "boatclass": "water", "boattype": "1x", "sex": "male", "weightclass": "hwt", "adaptiveclass": "None", "skillclass": "Open", "standardcollection": 1 }
Stroke Data (v1) /rowers/api/workouts/{id}/strokedata GET, POST { "distance": [23, 46, 48], "time": [3200, 6700, 10099], "spm": [16.4, 21.2, 19.8], "pace": [155068, 144402, 138830], "power": [84.6, 117.2, 141.3], "hr": [85, 91, 95] }
You can only post stroke data to an existing workout with workout number {id}. If the workout already has stroke data, you will get a duplication error. This functionality will be expanded in the future to enable updating stroke data.
For both v1 and v2, mandatory data fields are:
- time: Time (milliseconds since workout start)
- distance: Distance (meters)
- pace: Pace (milliseconds per 500m)
- spm Stroke rate (strokes per minute)
Optional data fiels are:
- power: Power (Watt)
- latitude: GPS position (latitude)
- longitude: GPS position (longitude)
- drivelength: Drive length (meters)
- dragfactor: Drag factor
- drivetime: Drive time (ms)
- strokerecoverytime: Recovery time (ms)
- averagedriveforce: Average handle force (lbs)
- peakdriveforce: Peak handle force (lbs)
- lapidx: Lap identifier
- hr: Heart rate (beats per minute)
- wash: Wash as defined per Empower oarlock (degrees)
- catch: Catch angle per Empower oarlock (degrees)
- finish: Finish angle per Empower oarlock (degrees)
- peakforceangle: Peak Force Angle per Empower oarlock (degrees)
- slip: Slip as defined per Empower oarlock (degrees)
For both API versions, consistency checks will be done and the stroke data will be refused if the mandatory data fields don't pass the checks. All mandatory data fields must have the same number of records. If an optional data field fails a test, its values are silently replaced by zeros.
Stroke Data (v2) /rowers/api/v2/workouts/{id}/strokedata GET, POST { "data": [ { "time": 3200.0000476837, "pace": 155068.4885951763, "hr": 85.7857142857, "power": 84.6531131591, "distance": 23, "spm": 16.380952381 }, { "time": 6700.0000476837, "pace" : 144402.6407586741, "hr": 91.2142857143, "power": 117.458827834, "distance": 36, "spm": 21.1666666667 }, { "time": 10099.9999046326, "pace": 138830.8712654931, "hr": 95.7142857143, "power": 141.31057207, "distance": 48, "spm": 19.8095238095 } ] }
You can only post stroke data to an existing workout with workout number {id}. If the workout already has stroke data, you will get a duplication error. This functionality will be expanded in the future to enable updating stroke data.
For both v1 and v2, mandatory data fields are:
- time: Time (milliseconds since workout start)
- distance: Distance (meters)
- pace: Pace (milliseconds per 500m)
- spm Stroke rate (strokes per minute)
Optional data fiels are:
- power: Power (Watt)
- latitude: GPS position (latitude)
- longitude: GPS position (longitude)
- drivelength: Drive length (meters)
- dragfactor: Drag factor
- drivetime: Drive time (ms)
- strokerecoverytime: Recovery time (ms)
- averagedriveforce: Average handle force (lbs)
- peakdriveforce: Peak handle force (lbs)
- lapidx: Lap identifier
- hr: Heart rate (beats per minute)
- wash: Wash as defined per Empower oarlock (degrees)
- catch: Catch angle per Empower oarlock (degrees)
- finish: Finish angle per Empower oarlock (degrees)
- peakforceangle: Peak Force Angle per Empower oarlock (degrees)
- slip: Slip as defined per Empower oarlock (degrees)
For both API versions, consistency checks will be done and the stroke data will be refused if the mandatory data fields don't pass the checks. All mandatory data fields must have the same number of records. If an optional data field fails a test, its values are silently replaced by zeros.
Stroke Data (v3) /rowers/api/v3/workouts/ POST { "distance": 100, "elapsedTime": 29000, "duration": "0:00:29.0", "name": "Test Workout (GO)", "startdatetime": "2023-01-16 17:54:35.588838+00:00", "workouttype": "water", "boattype": "1x", "notes": "some\nnotes", "strokes": { "data": [ { "time": 3200.0000476837, "pace": 155068.4885951763, "hr": 85.7857142857, "power": 84.6531131591, "distance": 23, "spm": 16.380952381 }, { "time": 6700.0000476837, "pace" : 144402.6407586741, "hr": 91.2142857143, "power": 117.458827834, "distance": 36, "spm": 21.1666666667 }, { "time": 10099.9999046326, "pace": 138830.8712654931, "hr": 95.7142857143, "power": 141.31057207, "distance": 48, "spm": 19.8095238095 } ] } }
With v3, you can post stroke data and workout metadata in one JSON object.
For v3, mandatory data fields are:
- time: Time (milliseconds since workout start)
- distance: Distance (meters)
- pace: Pace (milliseconds per 500m)
- spm Stroke rate (strokes per minute)
Optional data fiels are:
- power: Power (Watt)
- latitude: GPS position (latitude)
- longitude: GPS position (longitude)
- drivelength: Drive length (meters)
- dragfactor: Drag factor
- drivetime: Drive time (ms)
- strokerecoverytime: Recovery time (ms)
- averagedriveforce: Average handle force (lbs)
- peakdriveforce: Peak handle force (lbs)
- lapidx: Lap identifier
- hr: Heart rate (beats per minute)
- wash: Wash as defined per Empower oarlock (degrees)
- catch: Catch angle per Empower oarlock (degrees)
- finish: Finish angle per Empower oarlock (degrees)
- peakforceangle: Peak Force Angle per Empower oarlock (degrees)
- slip: Slip as defined per Empower oarlock (degrees)
Also for this API version, consistency checks will be done and the stroke data will be refused if the mandatory data fields don't pass the checks. All mandatory data fields must have the same number of records. If an optional data field fails a test, its values are silently replaced by zeros.