Skip to content

Road and XC results bulk upload

Update, replace, or append results to a simple road or cross country race.

All our competitions have a URL target ending with roadxc_upload/ which accept PUT requests from competition directors. The example below demonstrates how to modify the results of a test competition at https://test-data.opentrack.run/en-gb/x/2026/GBR/api-test

Note that the URL you will send to is of this format, starting with api, unlike the URLs for humans to browse, which beging with x, but otherwise match the year-country-slug pattern: https://servername/api/2026/GBR/api-test/

1. Authenticate as a user with 'Director' permissions

import requests
import zlib

username = os.environ.get("username", "api-test@mailinator.com")
password = os.environ.get("password", "apitest")
BASE_URL = 'https://test-data.opentrack.run/'

r1 = requests.post(BASE_URL + 'api/get-auth-token/',
                   data=dict(username=username,
                             password=password))
token = r1.json()["token"]

2. Send a results dictionary to update one race

To update a single race in a competition, you need to identify it by eventid, round and heat. You also supply a mode parameter, which works as follows:

  • replace: use this when your array is the full results, with all bibs and times
  • append: use this to add more finishers to the end of a race
  • update: any new bibs will be added, but any existing finishers not listed will remain in the results

The optional ignore_unknowns flag specifies whether a bib not in the start list should be stripped out altogether, or left in to appear as an unknown athlete. This can be useful when you are timing a mass race, but OpenTrack is treating it as several distinct races for display purposes.

For road and cross results, there may be hundreds of even thousands of results. Therefore, if you wish, you may compress the payload with zlib. If it looks like json, we will treat it as json, and if it looks like zlib-compressed data, we will unzip it.

You may also change any top-level attributes of the unit, such as the name or note field on it, even if no data array is supplied

COMP_URL = 'https://test-data.opentrack.run/api/2026/GBR/api-test/'
sample_data = dict(
            {"eventid": "R1", # must correspond to the ID given to the race in the Event Grid
            "round": 1,
            "heat": 1,
            "name": "My 5k",
            # choose between modes: 
            #   update (modify existing results)
            #   append (add any new results, leave existing ones unchanged)
            #   replace (delete all existing results and replace with this batch)
            "mode": "update",
            "ignore_unknowns": "True",  # bibs below not entered in the race will be ignored
            "data": [
                    ["349", "14:25.10"], # bib, finish-time
                    ["347", "14:33.00"],
                    ["393", "14:36.00"],
                    ["355", "14:39.00"],
                    ["266", "14:44.00"],
                    ["242", "14:45.00"],
                    ["234", "14:47.00"],
                    ["409", "14:47.00"],
                    ["261", "14:47.00"],
                    ["295", "14:53.00"]]
            }
        )

r2 = requests.put(
        comp_url + "roadxc_upload/",
        headers={'Authorization': "Token " + token},
        data=zlib.compress(json.dumps(data).encode('ascii'))
        # or data=json.dumps(data)
    )

3. Update more than one race

You can update multiple races at once by sending a payload which is a list of the above structures, each identifying an event/round/heat.

COMP_URL = 'https://test-data.opentrack.run/api/2026/GBR/api-test/'
sample_data = [
            {"eventid": "R1", 
            "round": 1,
            "heat": 1,
            "mode": "replace",
            "ignore_unknowns": "True", 
            "data": [
                    ["349", "14:25.10"], 
                    ["347", "14:33.00"],
                    ["393", "14:36.00"],
                    ["355", "14:39.00"],
                    ["266", "14:44.00"],
                    ["242", "14:45.00"],
                    ["234", "14:47.00"],
                    ["409", "14:47.00"],
                    ["261", "14:47.00"],
                    ["295", "14:53.00"]]
            },

            {"eventid": "R2", 
            "round": 1,
            "heat": 1,
            "mode": "replace",
            "ignore_unknowns": "True", 
            "data": [
                    ["600", "17:25.10"], 
                    ["647", "17:33.00"],
                    ["690", "DNF"]]
            }


        ]

r2 = requests.put(
        comp_url + "roadxc_upload/",
        headers={'Authorization': "Token " + token},
        data=zlib.compress(json.dumps(data).encode('ascii'))
    )