# Browse IGP24 Open Data

IGP24 Open Data is an anonymous, immutable publication of the frozen official competition dataset. It contains Accepted and Unaccepted polynomial records, Accepted pair summaries, and public team attribution, without team membership or account details. Post-event validation history is not part of this release.

No API key, enrollment, or login is required.

## Release workflow

1. Resolve the current release once with `GET /api/public/v1/competitions/igp24/open-data/releases/current`.
2. Save the top-level `release` identifier from the response.
3. Substitute that exact identifier for `{release}` in every following list, detail, and download request.

Do not return to `current` while traversing a dataset. The `current` pointer may advance, whereas every URL containing a release identifier refers to one immutable snapshot.

```bash
OPEN_DATA_BASE="https://api.sair.foundation/api/public/v1/competitions/igp24/open-data"
RELEASE=$(curl "$OPEN_DATA_BASE/releases/current" | jq -r '.release')
curl "$OPEN_DATA_BASE/releases/$RELEASE/pairs?page=1&pageSize=50"
```

The `current` response uses `Cache-Control: public, max-age=60`. Pinned API responses and release files use `Cache-Control: public, max-age=31536000, immutable`.

## Release overview

```http
GET /api/public/v1/competitions/igp24/open-data/releases/current
GET /api/public/v1/competitions/igp24/open-data/releases/{release}
```

Both endpoints return the same `data` shape. `current` resolves the release selected by the service; the pinned route resolves only the requested immutable release. A successful response also repeats the resolved identifier in the top-level `release` field.

### Overview fields

| Field | Type | Description |
| :--- | :--- | :--- |
| `dataset` | string | Stable dataset name. |
| `release` | string | Immutable release identifier. It matches the top-level `release`. |
| `schemaVersion` | string | Record schema identifier. Select a compatible parser from this value. |
| `generatedAt` | string | ISO 8601 UTC time when the release files were generated. |
| `snapshotAt` | string | ISO 8601 UTC time of the frozen source snapshot. |
| `submissionCutoffExclusive` | string | ISO 8601 UTC boundary; submissions accepted at or after this instant are excluded. |
| `license.id` | string | License identifier selected for this release. |
| `license.url` | string | Canonical license-information URL. |
| `counts.polynomials` | integer | Total unique polynomial records. Equal to `accepted + unaccepted`. |
| `counts.accepted` | integer | Records in `accepted-polynomials.jsonl.gz`. |
| `counts.unaccepted` | integer | Records in `unaccepted-polynomials.jsonl.gz`. |
| `counts.pairs` | integer | Distinct Accepted `(t, r)` pairs. |
| `counts.teams` | integer | Distinct public team numbers attributed to at least one record. |
| `downloads` | [`OpenDataDownload`](#opendatadownload)[] | Manifest, schema, and compressed JSON Lines files for the release. |

### `OpenDataDownload`

| Field | Type | Description |
| :--- | :--- | :--- |
| `path` | string | File name within the release. |
| `url` | string | Root-relative download URL on the API origin. Resolve it against `https://api.sair.foundation`; do not append it to the Public API base path. |
| `contentType` | `application/json` \| `application/gzip` | Media type returned by the file server. |
| `bytes` | integer \| omitted | Exact byte length of the stored download. Present for `schema.json` and both JSONL gzip files. |
| `records` | integer \| omitted | Number of JSONL records, or `0` for `schema.json`. Present for files declared by the manifest. |
| `sha256` | string \| omitted | `sha256:` followed by the lowercase digest of the downloaded bytes. Present for files declared by the manifest. |

Every overview includes these four downloads:

| File | Contents |
| :--- | :--- |
| `manifest.json` | Release metadata, counts, license, and integrity metadata for the other files. |
| `schema.json` | JSON Schema for each decompressed polynomial record. |
| `accepted-polynomials.jsonl.gz` | Accepted polynomial records. |
| `unaccepted-polynomials.jsonl.gz` | Unaccepted polynomial records. |

`manifest.json` does not report a digest of itself. Static responses include `Content-Length` and `ETag`; use the manifest's `bytes` and `sha256` values to verify each declared file after download.

```bash
DOWNLOAD_URL=$(curl "$OPEN_DATA_BASE/releases/$RELEASE" \
  | jq -r '.data.downloads[] | select(.path == "schema.json") | .url')
curl -LO "https://api.sair.foundation$DOWNLOAD_URL"
```

## Browser endpoints

Pair views:

```http
GET /api/public/v1/competitions/igp24/open-data/releases/{release}/pairs
GET /api/public/v1/competitions/igp24/open-data/releases/{release}/pairs/{t}/{r}
```

The list returns Accepted pair summaries. The detail route returns one Accepted pair summary and its polynomial page.

Polynomial views:

```http
GET /api/public/v1/competitions/igp24/open-data/releases/{release}/polynomials
GET /api/public/v1/competitions/igp24/open-data/releases/{release}/polynomials/{polynomialId}
```

The list includes Accepted and Unaccepted records. The detail route returns one polynomial record.

Team views:

```http
GET /api/public/v1/competitions/igp24/open-data/releases/{release}/teams
GET /api/public/v1/competitions/igp24/open-data/releases/{release}/teams/{teamNumber}
```

The list returns public team summaries. The detail route returns one team summary and its unique polynomial page.

The API and download files describe the same immutable release. Live submissions and later account or team changes do not alter that release.

## Pagination

Every list and every polynomial collection nested in a pair or team detail accepts:

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :---: | :---: | :--- |
| `page` | integer | No | `1` | One-based page number. |
| `pageSize` | integer | No | `50` | Must be `50`, `100`, or `200`. |

An out-of-range page is successful and returns an empty `items` array while preserving the requested page and the actual totals.

```json
{
  "ok": true,
  "data": {
    "items": [],
    "pagination": {
      "page": 3,
      "pageSize": 50,
      "totalItems": 100,
      "totalPages": 2
    }
  },
  "release": "2026-08-immutable-v1"
}
```

Because both the release and every documented ordering are immutable, replaying the same page with the same filters returns the same records.

## Pair queries

Pairs contain Accepted polynomials only and are ordered by `t` ascending, then `r` ascending.

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `t` | integer | Exact transitive group index from `1` through `25000`. |
| `r` | integer | Exact real-root count from `0` through `24`. |
| `q` | string | Case-insensitive substring search on `groupLabel`; an exact decimal `t` or `r` also matches. |

Each pair summary contains:

| Field | Type | Description |
| :--- | :--- | :--- |
| `pair.t` | integer | Transitive group index. |
| `pair.r` | integer | Real-root count. |
| `pair.groupLabel` | string | Equal to `24T{t}`. |
| `acceptedCount` | integer | Accepted polynomials for this pair. |
| `teamCount` | integer | Distinct public teams attributed to those polynomials. |
| `minimumDiscriminant.discAbs` | string | Minimum absolute discriminant, preserved as a decimal string. |
| `minimumDiscriminant.discSource` | `exact` \| `mixed` | Interpretation of the minimum discriminant. |

Pair detail returns `data.summary` with this shape and the paginated collection in `data.polynomials`.

## Polynomial queries

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `accepted` | boolean | Filter by Accepted or Unaccepted status. |
| `group` | string | Exact transitive group label in `24T<number>` form. |
| `t` | integer | Exact group index from `1` through `25000`. |
| `r` | integer | Exact real-root count from `0` through `24`. |
| `teamNumber` | integer | Require attribution to this public team number. |
| `q` | string | Case-insensitive substring search on `polynomialId`. |
| `coefficients` | JSON-string array | Exact canonical coefficient sequence. Send it as one URL-encoded JSON array of decimal strings. |
| `sort` | string | `polynomialId` (default), `discAsc`, or `discDesc`. |

`discAsc` and `discDesc` order by the arbitrary-precision decimal value, then `polynomialId` ascending. Records with no discriminant sort last in both directions.

The same polynomial filters and sorts apply to the collections nested in pair and team detail. A pair collection is already restricted to Accepted records from that pair; incompatible additional filters therefore produce an empty page.

```bash
curl --get "$OPEN_DATA_BASE/releases/$RELEASE/polynomials" \
  --data-urlencode 'coefficients=["9007199254740993","0","1"]' \
  --data-urlencode 'pageSize=50'
```

### Polynomial record

| Field | Type | Description |
| :--- | :--- | :--- |
| `polynomialId` | string | Stable `sha256:` identifier derived from the canonical ascending coefficient strings. |
| `coefficients` | string[] | Canonical ascending coefficient sequence. Every value is a decimal string. |
| `accepted` | boolean | Whether the frozen official pipeline accepted the polynomial. |
| `pair` | object \| null | `{t, r}` for an Accepted record; otherwise `null`. |
| `discAbs` | string \| null | Absolute discriminant as a decimal string, or `null` when unavailable. |
| `discSource` | `exact` \| `mixed` \| null | Discriminant interpretation; `null` exactly when `discAbs` is `null`. |
| `teams` | object[] | Sorted, unique public attributions containing only `teamNumber` and filtered `teamName`. |

Keep `coefficients[]` and `discAbs` as strings. Converting them to JavaScript `number` values can lose integer precision.

## Team queries

Teams are ordered by `teamNumber` ascending.

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `teamNumber` | integer | Exact public team number. |
| `name` | string | Case-insensitive substring search on a non-null public team name. |

Each team summary contains:

| Field | Type | Description |
| :--- | :--- | :--- |
| `teamNumber` | integer | Public team number recorded in the release. |
| `teamName` | string \| null | Filtered public name; `null` when no name is published. |
| `polynomialCount` | integer | Unique Accepted and Unaccepted polynomials attributed to the team. |
| `acceptedCount` | integer | Attributed Accepted polynomials. |
| `unacceptedCount` | integer | Attributed Unaccepted polynomials. |
| `acceptedPairCount` | integer | Distinct Accepted `(t, r)` pairs attributed to the team. |

Team detail returns `data.summary` with this shape and the paginated collection in `data.polynomials`. A polynomial attributed to the team more than once still appears once.

## Errors

Errors use the standard error envelope and `Cache-Control: no-store`.

| HTTP | Code | When |
| :---: | :--- | :--- |
| `400` | `INVALID_PAGINATION` | `page` or `pageSize` is not a valid bounded integer. |
| `400` | `INVALID_PAGE_SIZE` | `pageSize` is valid syntax but is not `50`, `100`, or `200`. |
| `400` | `INVALID_FILTER` | A pair, polynomial, or team filter is outside its allowed domain. |
| `400` | `INVALID_GROUP` | `group` is not in `24T<number>` form or its number is outside `1` through `25000`. |
| `400` | `INVALID_COEFFICIENTS` | `coefficients` is not a non-empty JSON array of canonical decimal strings. |
| `400` | `INVALID_SORT` | `sort` is not a supported polynomial ordering. |
| `400` | `INVALID_PAIR` | A pair-detail path contains an invalid `t` or `r`. |
| `400` | `INVALID_TEAM_NUMBER` | A team-detail path contains an invalid `teamNumber`. |
| `404` | `OPEN_DATA_RELEASE_NOT_FOUND` | The requested immutable release does not exist. |
| `404` | `OPEN_DATA_PAIR_NOT_FOUND` | The pair has no Accepted record in this release. |
| `404` | `OPEN_DATA_POLYNOMIAL_NOT_FOUND` | The polynomial is not present in this release. |
| `404` | `OPEN_DATA_TEAM_NOT_FOUND` | The public team number is not present in this release. |
| `429` | `RATE_LIMITED` | The anonymous query limit for the client IP was exceeded. Follow `Retry-After`. |
| `503` | `OPEN_DATA_NOT_CONFIGURED` | No current Open Data release is configured. |
| `503` | `OPEN_DATA_RELEASE_UNAVAILABLE` | A requested historical release exists but cannot be loaded and verified. |

See [Errors](../../../errors.md) for the shared envelope and retry guidance.
