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'))
)