# Get leaderboard

```http
GET /api/public/v1/competitions/igp24/leaderboard
```

**Scope**: `competition.read`.

Returns the cursor-paginated, frozen official IGP24 ranking. It never includes post-event validation or Discovery-only records.

The published result contains each participant team with a positive official score and may contain one LMFDB baseline reference row. Before publication, the endpoint returns an empty page and exposes no provisional scores or team data.

## Query parameters

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :---: | :--- | :--- |
| `cursor` | string | No | — | Opaque cursor returned by the previous page. |
| `limit` | integer | No | `25` | Page size from `1` through `100`. |

Pass `nextCursor` unchanged to this endpoint. Do not decode, modify, construct, or reuse it with another request flow.

## Publication and pagination

- `meta.published: false` is an information boundary: `items` is empty, `nextCursor` is `null`, and `meta.publishedAt` is `null`.
- When `meta.published` is `true`, ranks, scores, scoreable-pair counts, row kinds, and row order come from the frozen official result. Post-event validation cannot change them.
- A continuation cursor remains bound to that published result. Replaying a page produces the same official rows in the same order.
- `teamName`, `teamNumber`, and `teamDetails` are current public display projections. They may change independently of the official result and never change rank or cursor order. Use `teamId` as the row identity.

The frozen leaderboard remains readable while IGP24 is publicly visible, including after the competition status changes to closed.

## Ranking rules

Higher official scores rank first. The server ranks by the unrounded canonical score, then rounds `score` to six decimal places for the response. Two displayed scores that look equal may therefore have different ranks.

Teams with the same canonical score share one competition rank; the next rank skips the tied row count, such as `1, 1, 3`. The server uses the stable `teamId` only to order rows within an exact tie. Clients must preserve the returned order and must not calculate ranks from the rounded `score` values or current display fields.

The LMFDB row is a display-only baseline reference. It participates in the same score ordering, has `entryKind: "baseline"`, reports `scoreablePairs: 0`, and never exposes team-member details. Clients must use `entryKind`, not a name or a missing team number, to identify it.

## Response fields

### Data

| Field | Type | Description |
| :--- | :--- | :--- |
| `items` | [Leaderboard entry](#igp24leaderboardentry)[] | Current page in frozen official rank order. Empty while results are unpublished. |
| `nextCursor` | string \| null | Opaque cursor for the next page, or `null` when traversal is complete. |
| `meta.published` | boolean | Whether the frozen official leaderboard is public. |
| `meta.publishedAt` | string \| null | ISO 8601 UTC publication time, or `null` before publication. |

### `Igp24LeaderboardEntry`

| Field | Type | Description |
| :--- | :--- | :--- |
| `rank` | integer | One-based official competition rank. Exact score ties share a rank. |
| `entryKind` | `participant` \| `baseline` | Whether the row represents a participant team or the LMFDB reference. |
| `teamId` | string | Opaque, stable row identity. Use this value with [Get team placements](./get-team-placements.md). |
| `teamNumber` | string \| null | Current public competition team number. The baseline row uses `null`. |
| `teamName` | string | Current public display name. It does not affect official rank. |
| `score` | number | Frozen official aggregate score, rounded to six decimal places. |
| `scoreablePairs` | integer | Distinct non-baseline `(24Tt, r)` pairs officially credited to the participant team. The baseline row reports `0`. |
| `teamDetails` | [`TeamMemberDetails`](../../competitions.md#leaderboard-team-details-and-privacy) \| null | Current consent-controlled, server-redacted member projection. Always `null` for the baseline row. |

`teamDetails` is always present but may be `null`. Do not infer whether a team declined publication, the competition disabled the projection, or no display fields were available.

## Example request

```bash
curl "https://api.sair.foundation/api/public/v1/competitions/igp24/leaderboard?limit=25" \
  -H "Authorization: Bearer $SAIR_API_KEY"
```

## Published response

```json
{
  "ok": true,
  "data": {
    "items": [
      {
        "rank": 1,
        "entryKind": "participant",
        "teamId": "teamv2_a7a863ce80e6447881ad068bab82498e",
        "teamNumber": "IGP24-T00007",
        "teamName": "Galois Wranglers",
        "score": 42.75,
        "scoreablePairs": 57,
        "teamDetails": {
          "members": [
            {
              "name": "Alice Example"
            }
          ]
        }
      },
      {
        "rank": 2,
        "entryKind": "baseline",
        "teamId": "teamv2_igp24_lmfdb",
        "teamNumber": null,
        "teamName": "LMFDB",
        "score": 31.25,
        "scoreablePairs": 0,
        "teamDetails": null
      }
    ],
    "nextCursor": "WzIsInRlYW12Ml9pZ3AyNF9sbWZkYiJd",
    "meta": {
      "published": true,
      "publishedAt": "2026-08-18T12:00:00Z"
    }
  }
}
```

## Unpublished response

```json
{
  "ok": true,
  "data": {
    "items": [],
    "nextCursor": null,
    "meta": {
      "published": false,
      "publishedAt": null
    }
  }
}
```

## Errors

| HTTP | Code | When |
| :---: | :--- | :--- |
| `400` | `MALFORMED_BODY` | `cursor` is invalid or `limit` is outside `1` through `100`. |
| `404` | `NOT_FOUND` | IGP24 does not exist or is not publicly visible. |
| `503` | `IGP24_SERVICE_UNAVAILABLE` | The frozen official leaderboard service is temporarily unavailable. |

See [Errors](../../../errors.md) for authentication, scope, rate-limit, and standard error-envelope behavior.
