# Event live status

# Event live status

The event's races and start groups, with the live flags a broadcast overlay needs to decide what to poll next.
Start here, then call [Race live](/docs/api/races-live) for whichever race is actually live.

Requires **both** the `tracking:read` scope and the event's organiser having turned on **Share live tracking
with API partners** (panel: Advanced settings → Mobile broadcast & tracking) — sharing off answers
`404 TRACKING_NOT_SHARED` for every call below, scope or no scope.

## Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | yes | `Bearer <access_token>` |

## Path parameters

| Param | Type | Note |
|---|---|---|
| `eventId` | UUID or legacy id | from [List events](/docs/api/events); another club's, unpublished, or unknown ⇒ `404 EVENT_NOT_FOUND` |

#### Request

```bash
curl -s https://sail-club-server.cloud.run/api/v1/events/1143117a-9c2b-4e7a-8f1d-2b6a9c4e7f10/live \
  -H "Authorization: Bearer $TOKEN"
```

```javascript
const res = await fetch(`https://sail-club-server.cloud.run/api/v1/events/${eventId}/live`, {
  headers: { Authorization: `Bearer ${token}` },
});
if (res.status === 404) { /* not found, not published, or sharing is off for this event */ }
const live = await res.json();
```

```python
res = requests.get(
    f"https://sail-club-server.cloud.run/api/v1/events/{event_id}/live",
    headers={"Authorization": f"Bearer {token}"},
    timeout=10,
)
live = res.json()
```

```csharp
var res = await client.GetAsync($"/api/v1/events/{eventId}/live");
res.EnsureSuccessStatusCode();
var live = await res.Content.ReadFromJsonAsync<PublicEventLiveDto>();
```

```php
<?php
$ch = curl_init("https://sail-club-server.cloud.run/api/v1/events/$eventId/live");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"]]);
$live = json_decode(curl_exec($ch), true);
```

#### Response

```json
{
  "eventId": "1143117a-9c2b-4e7a-8f1d-2b6a9c4e7f10",
  "delaySeconds": 30,
  "dataAsOf": "2027-05-01T12:00:00.000Z",
  "races": [
    {
      "raceId": "9a1c0d2b-7e4a-46f8-9c1d-2e3f9a1c0d2b",
      "raceNo": 1,
      "name": "Race 1",
      "status": "Started",
      "isLive": true,
      "startGroups": [
        {
          "startGroupId": "2b6a9c4e-7f10-4a1c-8d2b-7e4a6f8c1d2e",
          "name": "Start A",
          "state": "Started",
          "startTimeUtc": "2027-05-01T11:30:00Z",
          "finishTimeUtc": null,
          "courseVersion": 3
        }
      ]
    }
  ]
}
```

`200` — same caching headers as every data endpoint, a shared 5-second server-side cache per event.

## Response fields

| Field | Type | Null? | Note |
|---|---|---|---|
| `eventId` | string | no | canonical event id |
| `delaySeconds` | int | no | 0–600, the organiser's current delay for this event |
| `dataAsOf` | date-time (UTC) | no | when this body was built; also `X-Data-As-Of` |
| `races[]` | array | no (may be empty) | every race of the event, in `raceNo` order — filter on `isLive` for an overlay |
| `races[].raceId` / `raceNo` / `name` / `status` | uuid / int / string / string | no | `status` is `PublicRaceItemDto.status` |
| `races[].isLive` | bool | no | the race's mobile-broadcast live flag |
| `races[].startGroups[]` | array | no | start order within the race |
| `startGroups[].startGroupId` / `name` | uuid / string | no | pass `startGroupId` to [Track](/docs/api/races-track) |
| `startGroups[].state` | string | no | `Scheduled`, `StartSequence`, `Started`, `Finished`, `Postponed`, `GeneralRecall`, `Abandoned`, `APEarly`, `StartLineReady`, `PostponedAshore`, `PostponedToday`, `AbandonedAshore`, `AbandonedToday` |
| `startGroups[].startTimeUtc` | date-time | yes | null before a gun time is set |
| `startGroups[].finishTimeUtc` | date-time | yes | null while racing |
| `startGroups[].courseVersion` | int | no | increments on every course change — refetch [Course](/docs/api/races-course) when it does |

## Errors

| HTTP | Code |
|---|---|
| 404 | [`EVENT_NOT_FOUND`](/docs/errors#EVENT_NOT_FOUND) |
| 404 | [`TRACKING_NOT_SHARED`](/docs/errors#TRACKING_NOT_SHARED) |
| 401 | [`API_TOKEN_MISSING`](/docs/errors#API_TOKEN_MISSING) / [`API_TOKEN_INVALID`](/docs/errors#API_TOKEN_INVALID) |
| 403 | [`API_SCOPE_MISSING`](/docs/errors#API_SCOPE_MISSING) / [`API_IP_NOT_ALLOWED`](/docs/errors#API_IP_NOT_ALLOWED) |
| 429 | [`RATE_LIMIT_EXCEEDED`](/docs/errors#RATE_LIMIT_EXCEEDED) |
| 503 | [`API_UNAVAILABLE`](/docs/errors#API_UNAVAILABLE) |

## Caching

One shared cache entry per `eventId`, refreshed every 5 seconds. Poll this one call to decide which race to
follow; don't poll it faster than its own `Cache-Control: max-age` — see [Rate limits](/docs/rate-limits) for
the separate, higher `tracking:read` bucket (120/min) this endpoint shares with the other three tracking calls.

```prompt
Implement getEventLive(eventId) for the Sail Club API.

GET https://sail-club-server.cloud.run/api/v1/events/{eventId}/live
Authorization: Bearer <access_token>
Scope required: tracking:read

Success 200: { eventId, delaySeconds, dataAsOf, races: [ { raceId, raceNo, name, status, isLive,
  startGroups: [ { startGroupId, name, state, startTimeUtc, finishTimeUtc, courseVersion } ] } ] } — fetch
  GET /api/v1/openapi.json for the exact schema.
404 EVENT_NOT_FOUND: unknown/other club's/unpublished event — not retryable.
404 TRACKING_NOT_SHARED: the organiser hasn't turned on live tracking sharing for this event — not retryable,
  not fixable by the caller; the organiser must enable it in their panel.
401/403/429/503: same handling as every other call — see the full-integration prompt on /docs/overview.

For each race where isLive is true, call GET /api/v1/races/{raceId}/live for boat positions, wind and the
unofficial order; use startGroups[].startGroupId with GET /api/v1/races/{raceId}/track for the 1Hz trail.

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