Live Timing Standard v1.1
Version 2 of Live Timing!
Version 2 of the Live Timing API is now available.
This builds on the documentation below.
We highly recommend using this to benefit from better validation and error responses. Click here for more details.The previous version will remain available and you may use this as a fallback in case of any issues with v2.
Set up and Test
- The first step is to register for an account and an application here: Register & Manage Your App.
- Then email us webmaster[at]triathlon.org to upgrade your application for timing privileges.
- You can now start sending us messages! See examples and detailed information below.
- To view the data sent to us, please use our viewer:
https://data.triathlon.org/timing/v2/{progId} where progId is the program id.
Overview
Timing Message Format
- Each timing message should contain complete timing information such that a user may be able to retrieve all information by receiving the latest timing message.
- All message characters should be UTF-8 formatted.
- All times and dates should be formatted according to the ISO 8601 standard and millisecond data should be included if available.
- Data should only be included if available e.g. segment values should only be included once an athlete has completed the segment.
Program ids, event ids and athlete ids?
These are the ids we use in our database. A program ID is the equivalent of a race ID in our database.
You can find them here: https://www.triathlon.org/utilities/admin-tools
Example Messages
The following messages only contain 2 athletes for brevity and you should refer to the extended examples below for full timing messages.
{
"date":"2017-03-04T18:25:11+04:00",
"start_time":"2017-03-04T15:55:00+04:00",
"event_id":"107158",
"event_name":"2017 ITU World Triathlon Abu Dhabi",
"prog_id":"307158",
"prog_name":"Elite Men",
"wetsuit":false,
"sandbox":false,
"num_athletes":"2",
"status":"live",
"latest":{
"segment_id":"2",
"segment_name":"Swim Exit",
"num_athletes":"1"
},
"conditions":{
"air_temp":"28.8",
"water_temp":"23.2"
},
"athletes":[
{
"id":"5695",
"start_num":"26",
"name":"Javier Gomez Noya",
"country":"ESP",
"segment_id":"18",
"time":"00:18:37.911",
"difference":"00:00:00.000",
"position":"1",
"status":null,
"incidents":null,
"splits":[
{
"id":"1",
"name":"Swim Lap 1",
"total_time":"00:10:54.799",
"time":"00:10:54.799",
"difference":"00:00:11.001",
"position":"10"
},
{
"id":"2",
"name":"Swim Exit",
"total_time":"00:18:37.911",
"time":"00:07:43.112",
"difference":"00:00:11.320",
"position":"14"
}
]
},
{
"id":"18719",
"start_num":"22",
"name":"Thomas Bishop",
"country":"GBR",
"segment_id":"1",
"time":"00:10:53.996",
"difference":"00:00:10.198",
"position":"2",
"status":null,
"incidents":null,
"splits":[
{
"id":"1",
"name":"Swim Lap 1",
"total_time":"00:10:53.996",
"time":"00:10:53.996",
"difference":"00:00:10.198",
"position":"9"
}
]
}
]
}
Complete Timing Examples
A full timing example in JSON is available here.
Timing Parameters
A breakdown of each parameter is listed below.
Parameter | Example | Description | Format |
---|---|---|---|
date | 2017-01-01T13:34:59.123+00:00 | An ISO 8601 formatted date of the time the message was published | string |
start_time | 2017-01-01T13:34:59.123+00:00 | An ISO 8601 formatted date corresponding to the start time of the race | string |
event_id | 123456 | The unique event_id corresponding to the event_id in the Events API | integer |
event_name | 2016 ITU World Triathlon Abu Dhabi | A descriptive name for the event corresponding to the Events API | string |
prog_id | 123456 | The unique prog_id corresponding to the prog_id in the Events API | integer |
prog_name | Elite Men | A descriptive name for the race corresponding to the Events API | string |
wetsuit | true | Boolean to indicate whether it is a wetsuit swim | boolean |
sandbox | true | Boolean to indicate whether to use the sandbox environment for development (defaults to true) | boolean |
num_athletes | 12 | The number of athletes in the race (which must correspond to the number of items in the athletes array) | integer |
status | live | The status of the results (one of prerace, live, official). Once confirmed by race referee the status should be updated to official. Prior to the race a status of prerace should be used and once timing has started changed to live. | string |
latest | The latest object contains information regarding the latest timing point data (may be null if no timing data) | object | |
latest.segment_id | 1 | The segment_id containing the latest result | integer |
latest.segment_name | Swim Exit | The segment name containing the latest results | string |
latest.num_athletes | 12 | The number of athletes that have passed through the timing point | number |
conditions | An object containing the latest course conditions | object | |
air_temp | 12.5 | The official air temperature in degrees Celsius | float |
water_temp | 18.4 | The official water temperature in degrees Celsius | float |
athletes | An object containing an array of all athlete objects in the race | object | |
athletes.athlete | An object containing the timing data for an individual athlete | object | |
athlete.id | 123456 | The unique athlete_id corresponding to the athlete_id in the Athletes API | integer |
athlete.start_num | 12 | The start number of the athlete | integer |
athlete.name | Alistair Brownlee | The name of the athlete | string |
athlete.country | GBR | The three letter IOC country code | string |
athlete.segment_id | 1 | The latest (current) segment_id of the athlete | integer |
athlete.position | 12 | The position of the athlete (may be null if no data) at the latest timing point | number |
athlete.time | 00:17:16.145 | An ISO 8601 formatted time of the athlete's latest timing point. Provides actual time. For TV or other displays (e.g finish gantry), please round up as soon as crosses 0.001 seconds. See Display Rounding section for more details. | string |
athlete.difference | 00:01:13.134 | An ISO 8601 formatted time corresponding to the difference at the latest timing point | string |
athlete.status | DNF | An optional field to include additional information regarding the race status of the athlete which should be one of DNS, DNF, DSQ, LAP or NC | string |
athlete.incidents | 10 second penalty for bike infringement | An optional field to include additional information regarding any incidents such as penalties that an athlete has incurred formatted according to the standard incident guidelines. | string |
athletes.splits | An array of split objects containing data for each segment completed by the athlete (may be _null _if no data) | object | |
athletes.splits.segment | A segment object containing athlete timing data for each individual segment. | object | |
segment.id | 3 | The segment_id of the split | integer |
segment.name | Swim Exit | The name of the timing segment | string |
segment.time | 00:08:16.345 | The time to complete this segment | string |
segment.total_time | 00:14:12.345 | The cumulative time of all segments to this point | string |
segment.position | 12 | The position of the athlete at this timing point | integer |
segment.difference | 00:01:07.123 | An ISO 8601 formatted time of difference of the athlete to the leader at this timing point | string |
Sending JSON data
From 2018 onwards you may only send JSON formatted data and you must include a header of
Content-Type
set toapplication/json
.
Athlete Ordering
Athletes should be ordered in the athletes
array according to the position at the respective timing points. Naturally, athletes who have passed the latest timing point will be ordered first followed by previous timing points. At the start of the race or where ordering is not possible due to non-completion of segments athletes should by default be ordered by start number.
Default to ordering by start_num where no further ordering is possible
Timing Points
Segment IDs should be named according to the example below.
- You can add/remove segments based on the timing points you have for a race. If you have more timing points, e.g Swim Lap 2, please provide these
- Important: Segment ids should increase sequentially for each segment.
- The first timing segment can either be the start or the first swim times.
Example Standard Distance Race
Segment ID | Segment Name |
---|---|
0 | Start |
1 | Swim Lap 1 |
2 | Swim Exit |
3 | Transition 1 |
4 | Bike Lap 1 |
5 | Bike Lap 2 |
6 | Bike Lap 3 |
7 | Bike Lap 4 |
8 | Bike Lap 5 |
9 | Bike Lap 6 |
10 | Bike Lap 7 |
11 | Bike End |
12 | Transition 2 |
13 | Run Lap 1 |
14 | Run Lap 2 |
15 | Run Lap 3 |
16 | Finish |
Example Sprint Distance Race
Segment ID | Segment Name |
---|---|
0 | Start |
1 | Swim Exit |
2 | Transition 1 |
3 | Bike Lap 1 |
4 | Bike Lap 2 |
5 | Bike Lap 3 |
6 | Bike End |
7 | Transition 2 |
8 | Run Lap 1 |
9 | Finish |
Timing Delivery
Timing must be sending via a HTTP Post Request.
Timing messages should be delivered as soon as feasible. Data messages should be sent as soon as an athlete crosses a timing point or when an external event occurs (such as the race start or a penalty is awarded). For occasions when there would be an overwhelming number of updates e.g. a large bike pack crossing a timing point, messages should be combined into a single message such that push frequency does not exceed one push every 1000ms. If POSTing data, messages should only be pushed when there is a change of information in the message. If that is not possible, messages can be sent every 2 to 3 seconds.
Messages should be sent with a Content-Type
of application/json
.
Authentication
Timers will use the standard API authentication method adding authentication parameters to the header of all requests sent. Each timer will be provided a unique API key to use specific to their timing application. This API key may alsop be used to access all other public endpoints of the API such as start lists.
Response Codes
The Triathlon.org API will respond with a 2xx response on successful receipt of the message. All other response codes should be considered a failure. Badly formatted or unauthenticated messages will result in a 4xx code. For reference, our general response codes are available here.
Retry Policy
Timers should implement a retry policy to primarily cover the case of a loss of internet connectivity at the venue. As each message contains entire timing information if a later message is available there is no need to retry an earlier message. For reference you may also consult the retries policy of the Subscription API.
Message Persistence
Timers do not need to persist timing messages as this will be handled via the Triathlon API platform with specific messages available for historical analysis / replay via the Live API.
Concurrent Races
Often there will be more than one race occurring at the same time (defined by prog_id
). Each race should be considered a single entity and formatted according to the specification. Logic should be present to handle instances when multiple athletes from different races cross a timing point at the same time.
Timing Implementation
- An initial start list message will be published once the final start lists have been confirmed (which occurs at the race briefing). This will detail the
start_time
, theevent_id
andprog_id
as well as containing the data for each athlete on the start list (you may use the athlete_id to get further information for each athlete from the Athletes API). Any athletes that withdraw between the briefing and the start of the race are consideredDNS
athletes. - Any changes to the published start times, condition information or any
DNS
(indicated in the `athlete.status' field) will be indicated by a new timing message. - Any messages sent prior to the start of the race will be indicated by a status of
prerace
. - As soon as the race begins the
start_time
containing the accurate start time will be published and thestatus
will be updated to live indicating that the race is underway. - As soon as the first athlete crosses the first timing point the
latest
block will be updated to include the details of the segment (i.e. segment_id and segment_name) as well as how many athletes have crossed the timing point (which may be used to create leaderboards). Whilst timing messages are pushed whenever there is new data, on occasions timing messages will be combined into a single message e.g. a bike pack crossing a timing point will not have a message for each athlete. As each timing message always contains full timing information you should simply act on the latest message received. - This process continues through each segment of the race with the latest block always indicating the latest timing point. Any athlete that withdraws from the race, is disqualified or incurs a penalty should have their
status
updated accordingly. - Once the race has finished and the results are confirmed a final message with a status of
official
will be published.
Other Timing Notes
- If an athlete is disqualified during the race, the status of the athlete should be updated to
DSQ
and the athlete should be ordered at the bottom of the athlete array. The reason for disqualification should be added to theincidents
key using the standard incident guidelines. Athletes should only be set asDSQ
on confirmation from the Chief Technology Officer. As disqualified athletes may continue on the course (in lieu of appealing the decision) all disqualified athlete times should continue to be delivered in the timing messages. If an athlete successfully protests theDSQ
the athlete should be reinstated in the corresponding position in the athlete array once confirmed by the Chief Technology Officer. - If an athlete withdraws from the race, once confirmed by the Chief Technology Officer, the status of the athlete should be updated to
DNF
. If aDNF
reason is available it should be added to theincidents
key according to the format specified by the standard incident guidelines. - If an athlete has a penalty confirmed by the Chief Technology Officer the
incidents
key of the athlete object should be updated to indicate the penalty to be served in accordance with the standard incident guidelines. Once an athlete has served the penalty, and has been confirmed by the Chief Technology Officer, theincidents
key should be updated by prepending the wordServed
e.g.Served x second penalty for infringement name
.
Display rounding
For TV, finish gantry or other displays where the milliseconds component of time does not need to be shown, please display times with the raw millisecond data rounded up to the next full second.
For example:
00:09:14.000 -> 00:09:14
00:09:14.001 -> 00:09:15
00:09:14.500 -> 00:09:15
00:09:14.999 -> 00:09:15
00:09:15.000 -> 00:09:15
Standard Incident Guidelines
When adding penalty or disqualification information the following guidelines should be followed for reporting as notified by the Chief Technology Officer. The message should be updated to include when the penalty has been served.
- x second penalty for infringement name
- Served x second penalty for infringement name
- DSQ for infringement name
Examples:
- 15 second penalty for cutting the swim course
- Served 15 second penalty for cutting the swim course
- DSQ for cutting the swim course
- Withdrew from the race due to bike crash.
In the event where there are multiple incidents for an athlete in an individual program, multiple incidents should be included and delimited by a comma.
Mixed Relay Timing
Mixed Relay timing follows a very similar syntax as individual timing with an additional layer of information which comprises team member data. As with the individual data, all athletes and teams should be included in all timing messages.
The following example shows a curtailed response of a single team with, in this example, the second athlete on the team to have exited the swim portion of the race.
For the segment/split naming you can use prepend Leg {x} where x is the leg raced (from 1 to 4 typically).
{
"date":"2017-06-11T17:03:12+0000",
"start_time":"2017-06-11T14:39:00+0000",
"event_id":"107161",
"event_name":"2017 ITU World Triathlon Hamburg",
"prog_id":"307248",
"prog_name":"Elite Mixed Relay",
"wetsuit":true,
"sandbox":false,
"num_teams":1,
"status":"live",
"latest":{
"segment_id":"8",
"segment_name":"Leg 2 Swim Finish",
"num_teams":1
},
"conditions":{
"air_temp":"17.1",
"water_temp":"16.6"
},
"teams":[
{
"id":"1234",
"start_num":"1",
"name":"Team GBR I",
"country":"GBR",
"segment_id":"8",
"time":"00:49:51.000",
"difference":"00:00:00.000",
"position":1,
"status":null,
"incidents":null,
"athletes":[
{
"id":"9876",
"team_order":"1",
"name":"Non Stanford",
"country":"GBR",
"splits":[
{
"id":1,
"name":"Leg 1 Swim Exit",
"total_time":"00:16:38.000",
"time":"00:08:20.000",
"difference":"00:00:05.000",
"position":7
},
{
"id":2,
"name":"Leg 1 Bike Start",
"total_time":"00:17:41.000",
"time":"00:01:04.000",
"difference":"00:00:02.000",
"position":4
},
{
"id":3,
"name":"Leg 1 Bike Lap 1 Start",
"total_time":"00:35:05.000",
"time":"00:17:24.000",
"difference":"00:00:00.000",
"position":1
},
{
"id":4,
"name":"Leg 1 Bike Finish",
"total_time":"01:15:04.000",
"time":"00:05:55.000",
"difference":"00:00:00.000",
"position":2
},
{
"id":5,
"name":"Leg 1 Run Lap 1 Start",
"total_time":"01:15:28.000",
"time":"00:25.000",
"difference":"00:00:00.000",
"position":2
},
{
"id":6,
"name":"Leg 1 Run Lap 1 Finish",
"total_time":"01:22:58.000",
"time":"00:07:31.000",
"difference":"00:00:00.000",
"position":1
},
{
"id":7,
"name":"Leg 1 Finish",
"total_time":"01:46:51.000",
"time":"00:08:10.000",
"difference":"00:00:00.000",
"position":1
}
]
},
{
"id":"7788",
"team_order":"2",
"name":"Alistair Brownlee",
"country":"GBR",
"splits":[
{
"id":8,
"name":"Leg 2 Swim Exit",
"total_time":"00:16:38.000",
"time":"00:08:20.000",
"difference":"00:00:00.000",
"position":1
}
]
},
{
"id":"5586",
"team_order":"3",
"name":"Vicky Holland",
"country":"GBR",
"splits": null
},
{
"id":"11378",
"team_order":"4",
"name":"Jonathan Brownlee",
"country":"GBR",
"splits": null
}
]
}
]
}
Mixed Relay Notes
segment_ids
should be consecutive and unique being indexed from 1 i.e. Leg 1 Swim Finish and Leg 2 Swim Finish are uniquesegment_ids
.- Timing point names should follow the suggested format as indicated here but prefixed with the corresponding leg number i.e. Leg 3 Swim Exit
- In the
latest
blocknum_teams
is used as opposed tonum_athletes
- As with individual races messages, all teams and all athletes should be included in every message. If the athlete has not yet competed their splits value will be
null
Paratriathlon
Where applicable a Paratriathlon object should be included within each athlete object to specify additional Paratriathlon-specific information such as guide names and additional meta information.
{
"athlete_id": "123456",
.....
"paratriathlon":{
"class":"PTWC1",
"guide_name":"Guide Name",
"guide_id":"654321",
"compensation_time":"00:03:46"
}
}
A breakdown of each parameter is listed below:
Parameter | Example | Description | Format |
---|---|---|---|
paratriathlon | The paratriathlon object contains specific information regarding the paratriathlete and is only present for Paratriathlon events | object | |
paratriathlon.class | PTWC1 | Indicates the Paratriathlon class as multiple classes may compete in a single program | string |
paratriathlon.guide_name | Athlete Name | The name of the paratriathlon guide for visual impairment categories | string |
paratriathlon.guide_id | 123456 | The unique athlete_id corresponding to the athlete_id in the Athletes API | integer |
compensation_time | 00:00:30 | An ISO 8601 formatted time of the start time offset. May be set to null if not applicable. | string |
Distribution
Timing will be distributed by the Triathlon API platform to applications through the Live REST API, Subscriptions API and Streaming APIs to all connected applications.