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

  1. The first step is to register for an account and an application here: Register & Manage Your App.
  2. Then email us webmaster[at]triathlon.org to upgrade your application for timing privileges.
  3. You can now start sending us messages! See examples and detailed information below.
  4. 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.

ParameterExampleDescriptionFormat
date2017-01-01T13:34:59.123+00:00An ISO 8601 formatted date of the time the message was publishedstring
start_time2017-01-01T13:34:59.123+00:00An ISO 8601 formatted date corresponding to the start time of the racestring
event_id123456The unique event_id corresponding to the event_id in the Events APIinteger
event_name2016 ITU World Triathlon Abu DhabiA descriptive name for the event corresponding to the Events APIstring
prog_id123456The unique prog_id corresponding to the prog_id in the Events APIinteger
prog_nameElite MenA descriptive name for the race corresponding to the Events APIstring
wetsuittrueBoolean to indicate whether it is a wetsuit swimboolean
sandboxtrueBoolean to indicate whether to use the sandbox environment for development (defaults to true)boolean
num_athletes12The number of athletes in the race (which must correspond to the number of items in the athletes array)integer
statusliveThe 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
latestThe latest object contains information regarding the latest timing point data (may be null if no timing data)object
latest.segment_id1The segment_id containing the latest resultinteger
latest.segment_nameSwim ExitThe segment name containing the latest resultsstring
latest.num_athletes12The number of athletes that have passed through the timing pointnumber
conditionsAn object containing the latest course conditionsobject
air_temp12.5The official air temperature in degrees Celsiusfloat
water_temp18.4The official water temperature in degrees Celsiusfloat
athletesAn object containing an array of all athlete objects in the raceobject
athletes.athleteAn object containing the timing data for an individual athleteobject
athlete.id123456The unique athlete_id corresponding to the athlete_id in the Athletes APIinteger
athlete.start_num12The start number of the athleteinteger
athlete.nameAlistair BrownleeThe name of the athletestring
athlete.countryGBRThe three letter IOC country codestring
athlete.segment_id1The latest (current) segment_id of the athleteinteger
athlete.position12The position of the athlete (may be null if no data) at the latest timing pointnumber
athlete.time00:17:16.145An 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.difference00:01:13.134An ISO 8601 formatted time corresponding to the difference at the latest timing pointstring
athlete.statusDNFAn optional field to include additional information regarding the race status of the athlete which should be one of DNS, DNF, DSQ, LAP or NCstring
athlete.incidents10 second penalty for bike infringementAn 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.splitsAn array of split objects containing data for each segment completed by the athlete (may be _null _if no data)object
athletes.splits.segmentA segment object containing athlete timing data for each individual segment.object
segment.id3The segment_id of the splitinteger
segment.nameSwim ExitThe name of the timing segmentstring
segment.time00:08:16.345The time to complete this segmentstring
segment.total_time00:14:12.345The cumulative time of all segments to this pointstring
segment.position12The position of the athlete at this timing pointinteger
segment.difference00:01:07.123An ISO 8601 formatted time of difference of the athlete to the leader at this timing pointstring

👍

Sending JSON data

From 2018 onwards you may only send JSON formatted data and you must include a header of Content-Type set to application/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.

  1. 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
  2. Important: Segment ids should increase sequentially for each segment.
  3. The first timing segment can either be the start or the first swim times.

Example Standard Distance Race

Segment IDSegment Name
0Start
1Swim Lap 1
2Swim Exit
3Transition 1
4Bike Lap 1
5Bike Lap 2
6Bike Lap 3
7Bike Lap 4
8Bike Lap 5
9Bike Lap 6
10Bike Lap 7
11Bike End
12Transition 2
13Run Lap 1
14Run Lap 2
15Run Lap 3
16Finish

Example Sprint Distance Race

Segment IDSegment Name
0Start
1Swim Exit
2Transition 1
3Bike Lap 1
4Bike Lap 2
5Bike Lap 3
6Bike End
7Transition 2
8Run Lap 1
9Finish

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, the event_id and prog_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 considered DNS 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 the status 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 the incidents key using the standard incident guidelines. Athletes should only be set as DSQ 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 the DSQ 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 a DNF reason is available it should be added to the incidents 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, the incidents key should be updated by prepending the word Served 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 unique segment_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 block num_teams is used as opposed to num_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:

ParameterExampleDescriptionFormat
paratriathlonThe paratriathlon object contains specific information regarding the paratriathlete and is only present for Paratriathlon eventsobject
paratriathlon.classPTWC1Indicates the Paratriathlon class as multiple classes may compete in a single programstring
paratriathlon.guide_nameAthlete NameThe name of the paratriathlon guide for visual impairment categoriesstring
paratriathlon.guide_id123456The unique athlete_id corresponding to the athlete_id in the Athletes APIinteger
compensation_time00:00:30An 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.