Skip to content
Developers
OpenAPI
GET /api/v1/races/{raceId}/trackscope: tracking:read

Track

API v1 Last updated 2026-10-01 View as Markdown

#Track

The raw ~1 Hz position history of one start group — the same points Race live only ever shows the newest of. Use it to draw a trail behind each boat while racing, or to replay the whole race afterwards; the capture window (gun − 5 min … gun + 3 h, or each boat's own finish) and the opt-in rule are the same as every other tracking:read call, and this endpoint keeps answering after the race is over.

#Headers

Header Required Value
Authorization yes Bearer <access_token>

#Path parameters

Param Type Note
raceId UUID from Event live status; unknown, malformed, or another club's ⇒ 404 RACE_NOT_FOUND

#Query parameters

Param Type Required Note
startGroupId UUID only if the race has more than one start group the group to read; omit it for a single-start-group race
since date-time (ISO 8601 UTC) yes inclusive lower bound
until date-time (ISO 8601 UTC) no exclusive upper bound; default and max is since + 10 min; must be after since

until is silently cut at now − delaySeconds if your requested value would reach past it — read the response's own until back, don't assume you got what you asked for.

#Request

curl -s "https://sail-club-server.cloud.run/api/v1/races/9a1c0d2b-7e4a-46f8-9c1d-2e3f9a1c0d2b/track?startGroupId=2b6a9c4e-7f10-4a1c-8d2b-7e4a6f8c1d2e&since=2027-05-01T11:40:00Z" \
  -H "Authorization: Bearer $TOKEN"
const url = new URL(`https://sail-club-server.cloud.run/api/v1/races/${raceId}/track`);
url.searchParams.set('startGroupId', startGroupId);
url.searchParams.set('since', sinceIso);
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const page = await res.json();
res = requests.get(
    f"https://sail-club-server.cloud.run/api/v1/races/{race_id}/track",
    params={"startGroupId": start_group_id, "since": since_iso},
    headers={"Authorization": f"Bearer {token}"},
    timeout=10,
)
page = res.json()
var res = await client.GetAsync($"/api/v1/races/{raceId}/track?startGroupId={startGroupId}&since={sinceIso}");
res.EnsureSuccessStatusCode();
var page = await res.Content.ReadFromJsonAsync<PublicTrackPageDto>();
<?php
$query = http_build_query(["startGroupId" => $startGroupId, "since" => $sinceIso]);
$ch = curl_init("https://sail-club-server.cloud.run/api/v1/races/$raceId/track?$query");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"]]);
$page = json_decode(curl_exec($ch), true);

#Response

{
  "raceId": "9a1c0d2b-7e4a-46f8-9c1d-2e3f9a1c0d2b",
  "startGroupId": "2b6a9c4e-7f10-4a1c-8d2b-7e4a6f8c1d2e",
  "since": "2027-05-01T11:40:00Z",
  "until": "2027-05-01T11:50:00Z",
  "nextSince": "2027-05-01T11:50:00Z",
  "delaySeconds": 30,
  "boats": [
    {
      "entryId": "e3f9a1c0-d2b7-4e4a-96f8-c1d2e3f9a1c0",
      "sailNumber": "TUR 1",
      "boatName": "Poyraz",
      "t": [1935654000000, 1935654001000],
      "lat": [40.97, 40.9701],
      "lon": [28.78, 28.7801],
      "sogKn": [6.1, null],
      "cogDeg": [215.0, 214.8]
    }
  ]
}

200 — a window ending at least 2 minutes before the current cutoff is Cache-Control: max-age=3600 (it's immutable, that minute of track never changes again); a window that still reaches close to "now" is max-age=2.

#Response fields

Field Type Null? Note
since date-time no echoes what you requested
until date-time no the requested until, cut at now − delaySeconds if that was earlier — always after since
nextSince date-time yes pass this straight back as since on your next call to keep paging forward; null once the start group's capture window (gun + 3 h) has closed — stop polling this group. For a group with no gun time yet, equals until
boats[] array no (may be empty) only boats with ≥ 1 point inside [since, until), by sail number
boats[].entryId / sailNumber / boatName uuid / string / string no same id/fields as Race live
boats[].t long[] no epoch milliseconds, strictly ascending, ~1 Hz; one entry per sample
boats[].lat / lon double[] no WGS84, same index as t
boats[].sogKn / cogDeg (double|null)[] elements nullable same index as t; null where the phone didn't report it for that sample

Every per-boat array (t, lat, lon, sogKn, cogDeg) is the same length and index-aligned — t[i]/lat[i]/ lon[i] are one position fix.

#Errors

HTTP Code
400 API_PARAM_INVALID — params.name is startGroupId, since, or until
404 RACE_NOT_FOUND
404 START_GROUP_NOT_FOUND
404 TRACKING_NOT_SHARED
401 API_TOKEN_MISSING / API_TOKEN_INVALID
403 API_SCOPE_MISSING / API_IP_NOT_ALLOWED
429 RATE_LIMIT_EXCEEDED
503 API_UNAVAILABLE

#Caching

Each 10-minute-or-smaller window is its own cache entry. Once a window ends more than 2 minutes before the current delayed cutoff it never changes again — cache it forever on your side too, keyed by raceId + startGroupId + since + until.

Prompt for AI agents ready to paste into an agent
Implement getTrackPage(raceId, startGroupId, since, until?) for the Sail Club API.

GET https://sail-club-server.cloud.run/api/v1/races/{raceId}/track?startGroupId=&since=&until=
Authorization: Bearer <access_token>
Scope required: tracking:read

Success 200: { raceId, startGroupId, since, until, nextSince, delaySeconds,
  boats: [ { entryId, sailNumber, boatName, t: [epoch_ms...], lat: [...], lon: [...], sogKn: [...|null],
  cogDeg: [...|null] } ] } — arrays are index-aligned per boat. Fetch GET /api/v1/openapi.json for the exact
  schema.
400 API_PARAM_INVALID (params.name: startGroupId|since|until): fix that parameter, don't retry as-is.
404 RACE_NOT_FOUND / START_GROUP_NOT_FOUND: not retryable.
404 TRACKING_NOT_SHARED: sharing is off for this event — not retryable, needs an organiser change.
401/403/429/503: same handling as every other call — see the full-integration prompt on /docs/overview.

Page forward by calling again with since = the previous response's nextSince, stopping when nextSince is null
(the capture window has closed). Cache each window permanently once its own until is more than 2 minutes behind
"now minus delaySeconds" — it will never change again.

Adapt to your actual stack — the endpoint, path param and response shape above are language-neutral.