# SAIR Competition Public API — Full Text Generated by `pnpm aio:generate`. Do not edit this file directly. This file concatenates the public Markdown documentation into a single plain-text bundle for AI agents and answer engines. ## Document index - [SAIR Competition Public API](https://docs.sair.foundation/docs/) — https://docs.sair.foundation/docs-md/README.md - [Authentication](https://docs.sair.foundation/docs/authentication/) — https://docs.sair.foundation/docs-md/authentication.md - [Quick start](https://docs.sair.foundation/docs/guides/quick-start/) — https://docs.sair.foundation/docs-md/guides/quick-start.md - [Request & response](https://docs.sair.foundation/docs/conventions/) — https://docs.sair.foundation/docs-md/conventions.md - [Pagination](https://docs.sair.foundation/docs/pagination/) — https://docs.sair.foundation/docs-md/pagination.md - [Rate limiting](https://docs.sair.foundation/docs/rate-limiting/) — https://docs.sair.foundation/docs-md/rate-limiting.md - [Errors](https://docs.sair.foundation/docs/errors/) — https://docs.sair.foundation/docs-md/errors.md - [Account](https://docs.sair.foundation/docs/endpoints/account/) — https://docs.sair.foundation/docs-md/endpoints/account.md - [Competitions](https://docs.sair.foundation/docs/endpoints/competitions/) — https://docs.sair.foundation/docs-md/endpoints/competitions.md - [Mathematics Distillation Challenge: Equational Theories - Stage 1](https://docs.sair.foundation/docs/endpoints/competitions/mathematics-distillation-stage1/) — https://docs.sair.foundation/docs-md/endpoints/competitions/mathematics-distillation-stage1.md - [Mathematics Distillation Challenge: Equational Theories - Stage 2](https://docs.sair.foundation/docs/endpoints/competitions/mathematics-distillation-stage2/) — https://docs.sair.foundation/docs-md/endpoints/competitions/mathematics-distillation-stage2.md - [Modular Arithmetic Challenge](https://docs.sair.foundation/docs/endpoints/competitions/modular-arithmetic-challenge/) — https://docs.sair.foundation/docs-md/endpoints/competitions/modular-arithmetic-challenge.md - [Inverse Galois Problem (IGP24)](https://docs.sair.foundation/docs/endpoints/competitions/igp24/) — https://docs.sair.foundation/docs-md/endpoints/competitions/igp24.md - [Playground](https://docs.sair.foundation/docs/endpoints/playground/) — https://docs.sair.foundation/docs-md/endpoints/playground.md - [Mathematics Distillation Stage 1 Playground](https://docs.sair.foundation/docs/endpoints/playground/mathematics-distillation-stage1/) — https://docs.sair.foundation/docs-md/endpoints/playground/mathematics-distillation-stage1.md - [Mathematics Distillation Stage 2 Playground](https://docs.sair.foundation/docs/endpoints/playground/mathematics-distillation-stage2/) — https://docs.sair.foundation/docs-md/endpoints/playground/mathematics-distillation-stage2.md - [Modular Arithmetic Challenge Playground](https://docs.sair.foundation/docs/endpoints/playground/modular-arithmetic-challenge/) — https://docs.sair.foundation/docs-md/endpoints/playground/modular-arithmetic-challenge.md - [Contributor Network](https://docs.sair.foundation/docs/endpoints/contributor-network/) — https://docs.sair.foundation/docs-md/endpoints/contributor-network.md - [List competitions](https://docs.sair.foundation/docs/endpoints/competitions/api/list-competitions/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/list-competitions.md - [Get competition detail](https://docs.sair.foundation/docs/endpoints/competitions/api/get-competition/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-competition.md - [Get my competition participation](https://docs.sair.foundation/docs/endpoints/competitions/api/get-my-participation/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-my-participation.md - [Get competition leaderboard](https://docs.sair.foundation/docs/endpoints/competitions/api/get-leaderboard/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-leaderboard.md - [Get my leaderboard standing](https://docs.sair.foundation/docs/endpoints/competitions/api/get-my-standing/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-my-standing.md - [Get IGP24 team placements](https://docs.sair.foundation/docs/endpoints/competitions/api/get-team-placements/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-team-placements.md - [Search the IGP24 leaderboard](https://docs.sair.foundation/docs/endpoints/competitions/api/search-leaderboard/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/search-leaderboard.md - [Submit a competition entry](https://docs.sair.foundation/docs/endpoints/competitions/api/submit/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/submit.md - [Get IGP24 submission batch status](https://docs.sair.foundation/docs/endpoints/competitions/api/get-submission-batch-status/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-submission-batch-status.md - [List my submissions](https://docs.sair.foundation/docs/endpoints/competitions/api/list-my-submissions/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/list-my-submissions.md - [Get my submission (legacy)](https://docs.sair.foundation/docs/endpoints/competitions/api/get-my-submission-legacy/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-my-submission-legacy.md - [Get a submission by ID](https://docs.sair.foundation/docs/endpoints/competitions/api/get-submission/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-submission.md - [Download a submission body](https://docs.sair.foundation/docs/endpoints/competitions/api/download-submission/) — https://docs.sair.foundation/docs-md/endpoints/competitions/api/download-submission.md - [Submission spec](https://docs.sair.foundation/docs/reference/submission-spec/) — https://docs.sair.foundation/docs-md/reference/submission-spec.md - [Submission kinds and DTOs](https://docs.sair.foundation/docs/reference/submission-kinds/) — https://docs.sair.foundation/docs-md/reference/submission-kinds.md - [Cheatsheets](https://docs.sair.foundation/docs/endpoints/cheatsheets/) — https://docs.sair.foundation/docs-md/endpoints/cheatsheets.md - [Solver templates & Lean problems](https://docs.sair.foundation/docs/endpoints/solver-templates/) — https://docs.sair.foundation/docs-md/endpoints/solver-templates.md --- URL: https://docs.sair.foundation/docs/ Markdown: https://docs.sair.foundation/docs-md/README.md Source: docs/README.md # SAIR Competition Public API Use the SAIR Competition Public API for competition automation: CLIs, scripts, CI pipelines, and training jobs. ## What's covered - **[Account](./endpoints/account.md)** — confirm a key and its owner. - **[Competitions](./endpoints/competitions.md)** — metadata, eligibility, leaderboards, submissions, and per-competition API guides. - **[Playground](./endpoints/playground.md)** — per-competition practice runs: models, problem sets, results, SSE event stream, usage. - **[Cheatsheets](./endpoints/cheatsheets.md)** — your private Playground cheatsheet library. - **[Solver templates](./endpoints/solver-templates.md)** — your private Playground Lean 4 solver and custom problem library. - **[Contributor Network](./endpoints/contributor-network.md)** — browse, publish, favorite, comment on, and view lineage for shared cheatsheets, solver templates, and model references. Benchmark data is available for platform-benchmarked items. ## Where to start 1. **[Authentication](./authentication.md)** — authenticate with an API key and choose scopes. 2. **[Quick start](./guides/quick-start.md)** — from an API key to a first submission with `curl`. 3. **[Conventions](./conventions.md)** — envelope, status codes, methods, naming. ## Reference - [Errors](./errors.md) - [Pagination](./pagination.md) - [Rate limiting](./rate-limiting.md) - [Submission spec](./reference/submission-spec.md) - [Submission kinds and DTOs](./reference/submission-kinds.md) ## Working examples - **`examples/python/`** — client plus end-to-end scripts. - **`examples/typescript/`** — same flows in TypeScript. The clients are meant to be copied into your own codebase rather than installed as a dependency. --- URL: https://docs.sair.foundation/docs/authentication/ Markdown: https://docs.sair.foundation/docs-md/authentication.md Source: docs/authentication.md # Authentication Every documented API key call authenticates with a personal **API key**. There is no application-level credential and no anonymous access for these endpoints. ## Use a key ```bash curl https://api.sair.foundation/api/public/v1/me \ -H "Authorization: Bearer $SAIR_API_KEY" ``` Set `$SAIR_API_KEY` to your key. Keys are accepted only via the `Authorization` header. Query string, cookie, and form-field credentials are rejected. Key format: `sair__`, e.g. `sair_a1b2c3d4_zR9...`. ## Scopes `.` — two segments, lowercase, kebab-case domain. | Scope | Grants | | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `competition.read` | Competition metadata, your participation status, leaderboards, your submissions, and stored text bodies. | | `competition.write` | Create competition submissions (resubmit to revise). | | `playground.read` | Models and problem sets; your cheatsheets and solver templates; your runs, results, and usage. | | `playground.write` | Create / modify your cheatsheets and solver templates; submit and cancel runs. | | `contributor-network.read` | Browse shared items per competition, read benchmark results, walk the lineage graph, read comments. | | `contributor-network.write` | Publish or withdraw items, favorite items, post or delete comments. Does **not** grant read. | Rules: - `.write` does **not** imply `.read` — request both if you need both. - `playground.*` is not subdivided by submission kind. - Scopes are the **upper bound** of what a key can attempt. Every request is re-checked against your account state (enrollment, team role, email verification, deadlines, resource ownership). A `competition.write` key can't submit to a competition you didn't enroll in; a `playground.write` key can't edit a cheatsheet you don't own. --- URL: https://docs.sair.foundation/docs/guides/quick-start/ Markdown: https://docs.sair.foundation/docs-md/guides/quick-start.md Source: docs/guides/quick-start.md # Quick start From an API key to a first submission, using only `curl`. ## Before you start: create an API key To call the public API you need an API key. Create one from your SAIR account settings: 1. Sign in to SAIR and open **Account settings → API keys**. 2. Create a key and select the scopes your use case needs (this walk-through uses `competition.read` and `competition.write`). 3. Copy the key — it is shown only once. See [Authentication](../authentication.md) for the key format and scope reference. ## 1. Set environment variables Use an API key with `competition.read` and `competition.write` scopes for this walk-through. ```bash export SAIR_API_KEY="" # e.g. sair_a1b2c3d4_zR9... export SAIR_COMPETITION_ID="modular-arithmetic-challenge" ``` ## 2. Confirm the key ```bash curl https://api.sair.foundation/api/public/v1/me \ -H "Authorization: Bearer $SAIR_API_KEY" ``` Response includes the key ID, its scopes, and the owner: ```json { "ok": true, "data": { "key": { "id": "key_a1b2c3d4", "scopes": ["competition.read", "competition.write"], "expiresAt": "2026-08-01T00:00:00Z" }, "owner": { "sairId": "U-9f7e1c00", "email": "you@example.com" } } } ``` ## 3. Read the submission spec ```bash curl "https://api.sair.foundation/api/public/v1/competitions/$SAIR_COMPETITION_ID" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` `data.submissionSpec` tells you what `payload` should look like. For `modular-arithmetic-challenge` it's `kind: model-reference` with required `modelName` and `commitHash`. For `igp24`, it is `kind: igp24-polynomial` with required `polynomials`. Example response excerpt: ```json { "ok": true, "data": { "submissionSpec": { "kind": "model-reference", "schema": { "...": "JSON Schema for payload" }, "limits": { "maxBytes": null, "maxEntries": 3 } } } } ``` ## 4. Check eligibility ```bash curl "https://api.sair.foundation/api/public/v1/competitions/$SAIR_COMPETITION_ID/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` `data.canSubmit` should be `true`. Otherwise `data.submitBlockedReason` tells you what to fix. ## 5. Submit The `payload` below is for `modular-arithmetic-challenge` (`kind: model-reference`). Other competitions use a different `payload` — build it from the `data.submissionSpec` you read in step 3. The IGP24 shape is shown right after. ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/$SAIR_COMPETITION_ID/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "slot": 1, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" } }' ``` You'll get a `submissionId`. For `model-reference`, `slot` defaults to `1` and must not exceed `submissionSpec.limits.maxEntries`. To read all current entries later, call the unified collection endpoint: ```bash curl "https://api.sair.foundation/api/public/v1/competitions/$SAIR_COMPETITION_ID/submissions/mine" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` It always returns `{ "items": [...], "nextCursor": null }` inside `data`, including for competitions that allow only one current entry. The older `/submissions/me` endpoint remains available with its existing kind-specific response. To retrieve the stored text body (for `kind`s that have one), call `GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download`. ### Inverse Galois Problem (IGP24) submit shape For `kind: igp24-polynomial`, submit one official polynomial-line string per item in `payload.polynomials`. A line may include a trailing comment; the exact `poly_disc_primes=[...]` comment supplies an optional `quicknfdisc` ramp hint: ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "payload": { "polynomials": [ "3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 # poly_disc_primes=[2,3]", "1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1" ] } }' ``` After submitting, call `GET /api/public/v1/competitions/igp24/submissions/mine` to read stored successful `verifiedPolynomials` from your submission history. ## Next - For competitions that support Playground runs, iterate on your entry with [`POST /api/public/v1/competitions/{competitionId}/playground/runs`](../endpoints/playground.md#submit-a-run) before re-submitting. - Check `submissionSpec.kind` and `submissionSpec.limits` before building competition-specific payloads. - Browse community work in the [Contributor Network](../endpoints/contributor-network.md), including cheatsheets, solver templates, and model references. For `cheatsheet` and `solver-participation` submissions, you can record lineage via `meta.contributorNetworkItemId`; `model-reference` submissions do not accept that metadata field. - Wire it into Python or TypeScript: see `examples/python/` and `examples/typescript/`. --- URL: https://docs.sair.foundation/docs/conventions/ Markdown: https://docs.sair.foundation/docs-md/conventions.md Source: docs/conventions.md # Request & response These conventions apply to every documented API key endpoint under `/api/public/v1`. ## Base URL ``` https://api.sair.foundation/api/public/v1 ``` URL segments are `kebab-case`; JSON fields are `camelCase`. ## Envelope Success: ```json { "ok": true, "data": { "...": "..." } } ``` Error: ```json { "ok": false, "error": { "code": "ERROR_CODE", "message": "Human-readable English", "details": {} } } ``` - The top-level value is always an object with `ok` plus either `data` or `error`. - Write endpoints return the response documented by each endpoint. `DELETE` returns `204` with no body unless documented otherwise. - `error.code` is a stable `UPPER_SNAKE_CASE` enum suitable for programmatic handling. - `error.message` is human-readable English and may change between releases. - `error.details` is endpoint-specific (e.g. `fieldErrors[]` for `422`). Endpoints that return non-JSON content do not use this envelope — the Playground [run-events](./endpoints/playground.md#stream-events) SSE stream and the submission [text-body download](./endpoints/competitions.md#download-the-stored-text-body) return their bodies directly. ## HTTP status | Status | When | | :----: | :------------------------------------------------------------------ | | `200` | Successful read or update | | `201` | Resource created | | `204` | `DELETE` succeeded | | `400` | Malformed request (bad JSON, unknown top-level field, type mismatch)| | `401` | Missing or invalid key | | `403` | Scope or business rule denies the action | | `404` | Resource doesn't exist or isn't visible to you | | `409` | Conflict with current state (e.g. cancel an already-terminal run) | | `413` | Payload too large | | `422` | Request schema valid but semantics invalid (e.g. unknown enum) | | `429` | Rate limit hit | | `5xx` | Server error | ## Identifiers Resource IDs are opaque strings. Format varies by resource type and may change between minor releases. The only ID constructed by clients is the **competition ID**, a human-readable slug (e.g. `modular-arithmetic-challenge`). All other IDs are generated by the API and surfaced in responses. ## Time and naming - Timestamps are ISO 8601 UTC with a trailing `Z`: `2026-05-18T12:34:56Z`. - All numeric times are milliseconds when explicitly labelled (e.g. `elapsedMs`). - Field names: `camelCase`. URL segments: `kebab-case`. Enum values are lowercase (multi-token use `-`, e.g. `model-reference`). Error codes use `UPPER_SNAKE_CASE`; see [Errors](./errors.md). ## Content type Use `Content-Type: application/json` for requests that send a body. Text values are carried in JSON fields such as strings or arrays of strings. ## Unknown fields Request bodies with unknown top-level fields return `400`. New optional fields may appear in responses; clients should ignore unrecognized fields. --- URL: https://docs.sair.foundation/docs/pagination/ Markdown: https://docs.sair.foundation/docs-md/pagination.md Source: docs/pagination.md # Pagination Every list endpoint is cursor-paginated. ## Request | Parameter | Type | Default | Max | Notes | | :-------- | :------ | :------ | :---- | :---------------------------------------------------------- | | `cursor` | string | — | — | Opaque token from the previous response's `nextCursor`. | | `limit` | integer | `25` | `100` | Number of items in the page. | Cursors are opaque tokens; the encoded format is not guaranteed stable. ## Response ```json { "ok": true, "data": { "items": [{ "id": "..." }, { "id": "..." }], "nextCursor": "eyJ0Ijoi..." } } ``` `nextCursor: null` means there are no more pages. ## Loop pattern `resp` is the parsed JSON envelope (`{ ok, data }`), so `items` and `nextCursor` are read from `resp["data"]`: ```python cursor = None while True: resp = client.get("/competitions", cursor=cursor, limit=50) data = resp["data"] for item in data["items"]: process(item) cursor = data.get("nextCursor") if not cursor: break ``` There's no `offset` and no `?page=` parameter — neither would be stable under writes. ## Filters Filters are listed per endpoint and applied **before** the cursor. Changing filters invalidates the cursor — start a new walk. --- URL: https://docs.sair.foundation/docs/rate-limiting/ Markdown: https://docs.sair.foundation/docs-md/rate-limiting.md Source: docs/rate-limiting.md # Rate limiting Several buckets protect the service. They are advertised by response headers so clients can pace themselves before hitting the limit. ## Headers Every response includes: ``` X-RateLimit-Limit: X-RateLimit-Remaining: X-RateLimit-Reset: ``` Most buckets use a per-minute window; action-specific buckets may use a longer window (e.g. the IGP24 submit bucket refills daily). `X-RateLimit-Reset` always reflects the applicable window. Exceeding a bucket returns `429` with `Retry-After: `. ## Buckets | Bucket | Default quota | When it applies | | :--------------------- | :------------ | :----------------------------------------------------------- | | Per-IP auth failure | 30 / min | Requests without a valid key. Resets on a successful auth. | | Per-account general | 600 / min | Authenticated requests, shared across **all keys** under the same account. | | Per-team IGP24 submit | 5 / day initially; 1000 / day after 5 distinct scoreable pairs | IGP24 `submissions` calls, per active IGP24 team, shared across the competition website and API. | ## Business limits are not rate limits Some action-specific limits are enforced as business rules, not rate-limit buckets. They return their own error codes rather than `429`: - `RUN_BUDGET_EXCEEDED` (403) — practice runs exceed your remaining credit balance. ## Error codes | Code | When | | :------------------------- | :------------------------------------------------------------ | | `AUTH_RATE_LIMIT_EXCEEDED` | Per-IP bucket exhausted (no valid key was presented). | | `ACCOUNT_RATE_LIMIT_EXCEEDED` | Per-account bucket exhausted. | | `IGP24_SUBMISSION_RATE_LIMIT_EXCEEDED` | Per-team IGP24 submission quota exhausted. | ## Backoff `Retry-After` is authoritative: when you get a `429`, wait that long before retrying. Cursor pagination controls traversal order, not request rate — it does not by itself keep you under the per-account bucket. For long batches, watch `X-RateLimit-Remaining` and `X-RateLimit-Reset` and pace requests proactively. --- URL: https://docs.sair.foundation/docs/errors/ Markdown: https://docs.sair.foundation/docs-md/errors.md Source: docs/errors.md # Errors All errors use the envelope from [Conventions](./conventions.md). `error.code` is stable; `error.message` may change. ## Auth | Code | HTTP | When | | :------------------------- | :--: | :----------------------------------------------------- | | `API_KEY_REQUIRED` | 401 | No `Authorization` header. | | `INVALID_API_KEY` | 401 | Format wrong or hash mismatch. | | `API_KEY_REVOKED` | 401 | Key was revoked. | | `API_KEY_EXPIRED` | 401 | Past `expiresAt`. | | `API_KEY_SCOPE_DENIED` | 403 | Key lacks the required scope. | | `ACCOUNT_RATE_LIMIT_EXCEEDED` | 429 | Per-account rate limit hit. | | `AUTH_RATE_LIMIT_EXCEEDED` | 429 | Per-IP rate limit hit without a valid key. | | `IGP24_SUBMISSION_RATE_LIMIT_EXCEEDED` | 429 | Per-team IGP24 submission quota hit. | ## Request shape | Code | HTTP | When | | :---------------------------- | :--: | :-------------------------------------------------------------------------- | | `MALFORMED_BODY` | 400 | JSON failed to parse, or contained unknown top-level fields. | | `NOT_FOUND` | 404 | The requested resource does not exist or is not visible to you. | | `SUBMISSION_PAYLOAD_INVALID` | 422 | `payload` failed `submissionSpec.schema`; `details.fieldErrors[]` says what.| | `SUBMISSION_META_INVALID` | 422 | `meta` failed `submissionSpec.metaSchema`; `details.fieldErrors[]` says what.| | `SUBMISSION_SLOT_INVALID` | 422 | `slot` is not an integer from `1` through `submissionSpec.limits.maxEntries`.| | `NO_DOWNLOAD_FOR_KIND` | 404 | The submission kind has no downloadable text body. | | `RUN_PARAMS_INVALID` | 422 | Run references an unknown model, problem, or artifact ID. | | `INVALID_MODEL_REFERENCE` | 422 | Model-reference run fields failed repository, commit, or problem-set validation. | | `RESOURCE_FIELD_INVALID` | 422 | A resource field, filter, or enum value failed endpoint-specific validation.| | `INVALID_CASE` | 422 | `caseId` is not a valid case for the competition's playground check. | | `LEAN_SOURCE_INVALID` | 422 | `solverCode` or custom-problem text failed the Lean 4 parser. | | `POLYNOMIAL_INPUT_INVALID` | 422 | IGP24 polynomial input failed validation (e.g. empty list, or a coefficient line failed format checks). | | `COMMENT_BODY_INVALID` | 422 | Comment body empty or over the length limit. | `fieldErrors[]` entries are JSON Pointers into the object named by the code: ```json { "code": "SUBMISSION_PAYLOAD_INVALID", "message": "payload failed validation", "details": { "fieldErrors": [ { "path": "/modelName", "code": "PATTERN_MISMATCH", "message": "Must match Hugging Face repo ID pattern" }, { "path": "/commitHash", "code": "REQUIRED", "message": "Required" } ] } } ``` ## Business rules Shared across SAIR — same code, same meaning, same HTTP everywhere it appears. | Code | HTTP | When | | :---------------------------------- | :--: | :------------------------------------------------------------------------- | | `ENROLL_REQUIRED` | 403 | You haven't enrolled. Enroll at `https://competition.sair.foundation/competitions/{competitionId}`. | | `TEAM_OWNER_REQUIRED` | 403 | Caller lacks the team role required by `submissionSpec.permission`. | | `EMAIL_NOT_VERIFIED` | 403 | Your account email isn't verified. Manage in [SAIR ID](https://id.sair.foundation). | | `SUBMISSION_WINDOW_NOT_OPEN` | 403 | Submission window hasn't opened yet. | | `SUBMISSION_WINDOW_CLOSED` | 403 | Submission window has closed. | | `FILE_TOO_LARGE` | 413 | Payload exceeds `submissionSpec.limits.maxBytes`. | | `COMPANION_COMPETITION_REQUIRED` | 403 | This competition is gated to participants enrolled in a companion competition for the current season. | | `CHEATSHEET_LOCKED` | 403 | Cheatsheet is published on the Contributor Network. Withdraw it first. | | `SOLVER_TEMPLATE_LOCKED` | 403 | Solver template is published on the Contributor Network. Withdraw it first. | | `RUN_BUDGET_EXCEEDED` | 403 | Run would exceed your remaining credit balance. | | `RUN_NOT_CANCELLABLE` | 409 | Run is already `done`, `failed`, or `cancelled`. | | `ACTIVE_RUN_EXISTS` | 409 | A model-reference run is already queued or running for this account. | | `RESOURCE_NOT_OWNED` | 403 | The source resource you tried to publish isn't owned by you. | | `ITEM_NOT_AUTHOR` | 403 | You can only withdraw / delete items you authored. | | `COMMENT_NOT_AUTHOR` | 403 | You can only delete comments you wrote or that target your own item. | | `GRAPH_TOO_LARGE` | 413 | Lineage graph response would exceed 2 000 nodes — narrow with filters. | | `IGP24_VERIFY_UNAVAILABLE` | 503 | IGP24 verifier service is not configured or unavailable. | | `IGP24_VERIFY_TIMEOUT` | 504 | IGP24 verifier service timed out. | | `IGP24_VERIFY_FAILED` | 502 | IGP24 verifier service returned an invalid or failed response. | ## `submitBlockedReason` → error code [`GET /api/public/v1/competitions/{competitionId}/me`](./endpoints/competitions.md#my-participation) returns a `submitBlockedReason` enum so clients can decide whether to even attempt a submission. Attempt it anyway and you'll get the equivalent error: | `submitBlockedReason` | Equivalent `error.code` on `POST /competitions/{competitionId}/submissions` | | :-------------------- | :--------------------------------------------------------------------------- | | `EMAIL_NOT_VERIFIED` | `EMAIL_NOT_VERIFIED` | | `NOT_ENROLLED` | `ENROLL_REQUIRED` | | `NOT_TEAM_OWNER` | `TEAM_OWNER_REQUIRED` | | `BEFORE_WINDOW` | `SUBMISSION_WINDOW_NOT_OPEN` | | `AFTER_WINDOW` | `SUBMISSION_WINDOW_CLOSED` | --- URL: https://docs.sair.foundation/docs/endpoints/account/ Markdown: https://docs.sair.foundation/docs-md/endpoints/account.md Source: docs/endpoints/account.md # Account Introspect the API key used for the request. ## Get current key ```http GET /api/public/v1/me ``` **Scope**: none. Works with any valid key. Returns the key in use and its owner. Call this to verify that a key is valid and inspect its granted scopes. ### Response fields | Field | Type | Description | | :------ | :---------------------------------------- | :---------------------------------------- | | `key` | [ApiKeySummary](#api-key-summary-fields) | API key metadata. The secret is never returned. | | `owner` | [AccountOwner](#account-owner-fields) | Account that owns the key. | ### API key summary fields | Field | Type | Description | | :---------- | :------- | :------------------------------------- | | `id` | string | API key ID. | | `name` | string | Key label set when the key was created. | | `scopes` | string[] | Scopes granted to this key. | | `expiresAt` | string | ISO 8601 UTC expiry timestamp. | ### Account owner fields | Field | Type | Description | | :------- | :----- | :---------------------------------------------- | | `sairId` | string | SAIR ID of the account that owns the key. | | `email` | string | Email address of the account that owns the key. | ```json { "ok": true, "data": { "key": { "id": "key_a1b2c3d4", "name": "Local training submitter", "scopes": ["competition.read", "competition.write"], "expiresAt": "2026-08-01T00:00:00Z" }, "owner": { "sairId": "U-9f7e1c00", "email": "you@example.com" } } } ``` This endpoint is distinct from [`GET /api/public/v1/competitions/{competitionId}/me`](./competitions.md#my-participation), which returns participation status in one competition. ## Errors See [Errors](../errors.md) for the envelope shape. | Code | HTTP | When | | :----------------- | :--: | :--------------------------- | | `API_KEY_REQUIRED` | 401 | No `Authorization` header. | | `INVALID_API_KEY` | 401 | Format wrong or hash mismatch. | | `API_KEY_REVOKED` | 401 | Key was revoked. | | `API_KEY_EXPIRED` | 401 | Past `expiresAt`. | --- URL: https://docs.sair.foundation/docs/endpoints/competitions/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions.md Source: docs/endpoints/competitions.md # Competitions Browse competitions, check eligibility, read leaderboards, submit entries, and retrieve stored submission bodies. ## Competition API guides Start with the guide for the competition you are automating. Each guide uses the real competition ID and links every supported operation to its concrete request and response examples. - [Mathematics Distillation Challenge: Equational Theories - Stage 1](./competitions/mathematics-distillation-stage1.md) - [Mathematics Distillation Challenge: Equational Theories - Stage 2](./competitions/mathematics-distillation-stage2.md) - [Modular Arithmetic Challenge](./competitions/modular-arithmetic-challenge.md) - [Inverse Galois Problem (IGP24)](./competitions/igp24.md) ## Competition API reference Each operation has its own reference page. Submission operations expand the complete concrete DTO for every documented competition rather than showing only a generic union. | Task | Endpoint | Scope | | :--- | :--- | :--- | | List competitions | [`GET /api/public/v1/competitions`](./competitions/api/list-competitions.md) | `competition.read` | | Get competition detail | [`GET /api/public/v1/competitions/{competitionId}`](./competitions/api/get-competition.md) | `competition.read` | | Get my participation | [`GET /api/public/v1/competitions/{competitionId}/me`](./competitions/api/get-my-participation.md) | `competition.read` | | Get leaderboard | [`GET /api/public/v1/competitions/{competitionId}/leaderboard`](./competitions/api/get-leaderboard.md) | `competition.read` | | Get my standing | [`GET /api/public/v1/competitions/{competitionId}/leaderboard/me`](./competitions/api/get-my-standing.md) | `competition.read` | | Get IGP24 team placements | [`GET /api/public/v1/competitions/{competitionId}/leaderboard/teams/{teamId}/placements`](./competitions/api/get-team-placements.md) | `competition.read` | | Search the IGP24 leaderboard | [`GET /api/public/v1/competitions/{competitionId}/leaderboard/search`](./competitions/api/search-leaderboard.md) | `competition.read` | | Submit an entry | [`POST /api/public/v1/competitions/{competitionId}/submissions`](./competitions/api/submit.md) | `competition.write` | | Get IGP24 batch status | [`GET /api/public/v1/competitions/{competitionId}/submissions/batch/{batchId}`](./competitions/api/get-submission-batch-status.md) | `competition.read` | | List my submissions | [`GET /api/public/v1/competitions/{competitionId}/submissions/mine`](./competitions/api/list-my-submissions.md) | `competition.read` | | Get my submission (legacy) | [`GET /api/public/v1/competitions/{competitionId}/submissions/me`](./competitions/api/get-my-submission-legacy.md) | `competition.read` | | Get a submission by ID | [`GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}`](./competitions/api/get-submission.md) | `competition.read` | | Download a submission body | [`GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download`](./competitions/api/download-submission.md) | `competition.read` | The three IGP24-only operations above have complete standalone DTO references. IGP24 also exposes label-progress and remaining-pair operations documented in the [IGP24 guide](./competitions/igp24.md#endpoint-map). ## Submission behavior by competition | Competition | Submission kind | Current-entry behavior | Concrete examples | | :--- | :--- | :--- | :--- | | Mathematics Distillation Stage 1 | `cheatsheet` | Zero or one current cheatsheet. | [Submit](./competitions/api/submit.md#mathematics-distillation-stage-1) · [Read](./competitions/api/list-my-submissions.md#mathematics-distillation-stage-1) | | Mathematics Distillation Stage 2 | `solver-participation` | One current entry per track. | [Submit](./competitions/api/submit.md#mathematics-distillation-stage-2) · [Read](./competitions/api/list-my-submissions.md#mathematics-distillation-stage-2) | | Modular Arithmetic Challenge | `model-reference` | Up to `submissionSpec.limits.maxEntries` occupied slots, currently three. | [Submit](./competitions/api/submit.md#modular-arithmetic-challenge) · [Read](./competitions/api/list-my-submissions.md#modular-arithmetic-challenge) | | IGP24 | `igp24-polynomial` | Cursor-paginated submission history. | [Submit](./competitions/api/submit.md#inverse-galois-problem-igp24) · [Read](./competitions/api/list-my-submissions.md#inverse-galois-problem-igp24) | The named union definitions remain in [Submission kinds and DTOs](../reference/submission-kinds.md). The operation pages above include full JSON envelopes and competition-specific examples so callers do not need to assemble an example from several pages. ## List competitions Moved to [List competitions](./competitions/api/list-competitions.md). ## Get competition detail Moved to [Get competition detail](./competitions/api/get-competition.md). ## My participation Moved to [Get my competition participation](./competitions/api/get-my-participation.md). ## Leaderboard Moved to [Get competition leaderboard](./competitions/api/get-leaderboard.md). ## My leaderboard standing Moved to [Get my leaderboard standing](./competitions/api/get-my-standing.md). ## Submit Moved to [Submit a competition entry](./competitions/api/submit.md). ## Get my submissions Moved to [List my submissions](./competitions/api/list-my-submissions.md). ## Get my submission (legacy) Moved to [Get my submission (legacy)](./competitions/api/get-my-submission-legacy.md). ## Get a submission by ID Moved to [Get a submission by ID](./competitions/api/get-submission.md). ## Download the stored text body Moved to [Download a submission body](./competitions/api/download-submission.md). ## Errors Each operation page lists its endpoint-specific errors. See [Errors](../errors.md) for the shared error envelope and stable code catalog. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/mathematics-distillation-stage1/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/mathematics-distillation-stage1.md Source: docs/endpoints/competitions/mathematics-distillation-stage1.md # Mathematics Distillation Challenge: Equational Theories - Stage 1 Competition ID: `mathematics-distillation-challenge-equational-theories-stage1` Use this guide when automating Stage 1 cheatsheet practice, publication, and formal submissions. ## Endpoint map Use `competitionId = mathematics-distillation-challenge-equational-theories-stage1`. **Competition endpoints** | Task | Endpoint | Scope | | :--- | :--- | :--- | | Read schema | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1`](./api/get-competition.md#mathematics-distillation-stage-1) | `competition.read` | | Check participation | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/me`](./api/get-my-participation.md#mathematics-distillation-stage-1) | `competition.read` | | Submit cheatsheet | [`POST /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions`](./api/submit.md#mathematics-distillation-stage-1) | `competition.write` | | Read current submissions | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/mine`](./api/list-my-submissions.md#mathematics-distillation-stage-1) | `competition.read` | | Read the current submission (legacy) | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/me`](./api/get-my-submission-legacy.md#mathematics-distillation-stage-1) | `competition.read` | | Read a submission by ID | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/{submissionId}`](./api/get-submission.md#mathematics-distillation-stage-1) | `competition.read` | | Download submitted cheatsheet text | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/{submissionId}/download`](./api/download-submission.md#mathematics-distillation-stage-1) | `competition.read` | | Read leaderboard | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/leaderboard`](./api/get-leaderboard.md#mathematics-distillation-stage-1-example) | `competition.read` | | Read my standing | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/leaderboard/me`](./api/get-my-standing.md#null-example) | `competition.read` | **Companion APIs** - Manage private cheatsheet drafts with the [Cheatsheets](../cheatsheets.md) endpoints. - Run practice jobs with the [Playground](../playground.md) endpoints. - Publish or browse shared cheatsheets with the [Contributor Network](../contributor-network.md) endpoints. ## Submission payload The competition uses `submissionSpec.kind: "cheatsheet"`. **Example request body** ```json { "payload": { "content": "When the equation has a neutral element, first try..." }, "meta": { "description": "v3 - adds order-five heuristics.", "contributorNetworkItemId": "cn_01HX0PA..." } } ``` Payload fields: - `payload.content` is required. It is the cheatsheet text submitted for formal evaluation. - `meta.description` is optional. Use it for a human-readable note in your own submission history. - `meta.contributorNetworkItemId` is optional. Use it to record the Contributor Network item this submission references or builds on. ## Recommended workflow 1. Create or update a private cheatsheet with the [Cheatsheets](../cheatsheets.md) endpoints. 2. Use the [Playground](../playground.md) endpoints to test candidate cheatsheets against available problem sets and models. 3. Optionally publish useful work through [Contributor Network](../contributor-network.md). 4. Submit the final cheatsheet text through the competition submission endpoint. 5. Read `/submissions/mine` or download the stored text body to confirm the active submission. The collection contains zero or one item for this competition. ## Submit ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "content": "When the equation has a neutral element, first try..." }, "meta": { "description": "Playground-tested cheatsheet." } }' ``` See [Competitions](../competitions.md) for the shared endpoint field tables. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/mathematics-distillation-stage2/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/mathematics-distillation-stage2.md Source: docs/endpoints/competitions/mathematics-distillation-stage2.md # Mathematics Distillation Challenge: Equational Theories - Stage 2 Competition ID: `mathematics-distillation-challenge-equational-theories-stage2` Use this guide when automating Stage 2 Lean solver practice, publication, and formal submissions. ## Endpoint map Use `competitionId = mathematics-distillation-challenge-equational-theories-stage2`. **Competition endpoints** | Task | Endpoint | Scope | | :--- | :--- | :--- | | Read schema | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2`](./api/get-competition.md#mathematics-distillation-stage-2) | `competition.read` | | Check participation | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/me`](./api/get-my-participation.md#mathematics-distillation-stage-2) | `competition.read` | | Submit solver | [`POST /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions`](./api/submit.md#mathematics-distillation-stage-2) | `competition.write` | | Read current submissions across tracks | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/mine`](./api/list-my-submissions.md#mathematics-distillation-stage-2) | `competition.read` | | Read one track (legacy) | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/me?track=solo`](./api/get-my-submission-legacy.md#mathematics-distillation-stage-2) | `competition.read` | | Read a submission by ID | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/{submissionId}`](./api/get-submission.md#mathematics-distillation-stage-2) | `competition.read` | | Download submitted solver source | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/{submissionId}/download`](./api/download-submission.md#mathematics-distillation-stage-2) | `competition.read` | | Read leaderboard | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/leaderboard`](./api/get-leaderboard.md#mathematics-distillation-stage-2-example) | `competition.read` | | Read my standing | [`GET /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/leaderboard/me`](./api/get-my-standing.md#null-example) | `competition.read` | **Companion APIs** - Manage solver templates and custom Lean problems with the [Solver templates](../solver-templates.md) endpoints. - Run practice jobs with the [Playground](../playground.md) endpoints. - Publish or browse shared solver templates with the [Contributor Network](../contributor-network.md) endpoints. ## Submission payload The competition uses `submissionSpec.kind: "solver-participation"`. **Example request body** ```json { "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "theorem solver : ... := by ..." }, "meta": { "description": "Marathon-tuned solver.", "contributorNetworkItemId": "cn_01HX0PA..." } } ``` Payload fields: - `payload.track` is required. It must be a track ID from `submissionSpec.catalog.tracks[].id`. - `payload.modelId` is required. It must be a model ID from `submissionSpec.catalog.models[].id`. - `payload.solverCode` is required. It is the Lean 4 solver source. - `meta.description` is optional. Use it for a human-readable note in your own submission history. - `meta.contributorNetworkItemId` is optional. Use it to record the Contributor Network item this submission references or builds on. One submission is stored per track. Re-submitting the same track replaces the active entry for that track. ## Recommended workflow 1. Read `submissionSpec.catalog.tracks[]` and `submissionSpec.catalog.models[]` from the competition detail. 2. Create or update reusable solver code with the [Solver templates](../solver-templates.md) endpoints. 3. Use the [Playground](../playground.md) endpoints to run solver practice jobs. 4. Optionally publish useful solver templates through [Contributor Network](../contributor-network.md). 5. Submit the final `track`, `modelId`, and `solverCode` through the competition submission endpoint. 6. Read `/submissions/mine` to confirm current submissions across all tracks, or download one stored solver source by submission ID. ## Submit ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "theorem solver : ... := by ..." }, "meta": { "description": "Playground-tested solver." } }' ``` See [Competitions](../competitions.md) for the shared endpoint field tables. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/modular-arithmetic-challenge/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/modular-arithmetic-challenge.md Source: docs/endpoints/competitions/modular-arithmetic-challenge.md # Modular Arithmetic Challenge Competition ID: `modular-arithmetic-challenge` Use this guide when automating submissions for the Modular Arithmetic Challenge. This competition accepts a pinned external model artifact rather than an uploaded file. ## Endpoint map Use `competitionId = modular-arithmetic-challenge`. **Competition endpoints** | Task | Endpoint | Scope | | :--- | :--- | :--- | | Read schema | [`GET /api/public/v1/competitions/modular-arithmetic-challenge`](./api/get-competition.md#modular-arithmetic-challenge) | `competition.read` | | Check participation | [`GET /api/public/v1/competitions/modular-arithmetic-challenge/me`](./api/get-my-participation.md#modular-arithmetic-challenge) | `competition.read` | | Create or replace one submission slot | [`POST /api/public/v1/competitions/modular-arithmetic-challenge/submissions`](./api/submit.md#modular-arithmetic-challenge) | `competition.write` | | Read all current submission slots | [`GET /api/public/v1/competitions/modular-arithmetic-challenge/submissions/mine`](./api/list-my-submissions.md#modular-arithmetic-challenge) | `competition.read` | | Read slot 1 (legacy) | [`GET /api/public/v1/competitions/modular-arithmetic-challenge/submissions/me`](./api/get-my-submission-legacy.md#modular-arithmetic-challenge) | `competition.read` | | Read a submission by ID | [`GET /api/public/v1/competitions/modular-arithmetic-challenge/submissions/{submissionId}`](./api/get-submission.md#modular-arithmetic-challenge) | `competition.read` | | Read leaderboard | [`GET /api/public/v1/competitions/modular-arithmetic-challenge/leaderboard`](./api/get-leaderboard.md#modular-arithmetic-challenge-example) | `competition.read` | | Read my standing | [`GET /api/public/v1/competitions/modular-arithmetic-challenge/leaderboard/me`](./api/get-my-standing.md#null-example) | `competition.read` | **Contributor Network endpoints** | Task | Endpoint | Scope | | :--- | :--- | :--- | | Publish a shared model reference | `POST /api/public/v1/contributor-network/items` | `contributor-network.write` | | Browse shared model references | `GET /api/public/v1/contributor-network/items?competitionId=modular-arithmetic-challenge&kind=model-reference` | `contributor-network.read` | | Read one shared model reference | `GET /api/public/v1/contributor-network/items/{itemId}` | `contributor-network.read` | There is no downloadable text body for this competition. [`GET /submissions/{submissionId}/download`](./api/download-submission.md#modular-arithmetic-challenge) returns `NO_DOWNLOAD_FOR_KIND`. Use the [Modular Arithmetic Challenge Playground guide](../playground/modular-arithmetic-challenge.md) when you want to evaluate a pinned Hugging Face model revision before creating the official competition submission. ## Submission payload The competition uses `submissionSpec.kind: "model-reference"`. Read `submissionSpec.limits.maxEntries` before submitting. Modular Arithmetic Challenge currently returns `3`, so the team's current entries may occupy slots `1`, `2`, and `3`. **Example request body** ```json { "slot": 2, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Fine-tuned on a modular-arithmetic dataset." } } ``` Payload fields: - `slot` is optional and defaults to `1`. It must be an integer from `1` through `submissionSpec.limits.maxEntries`. - `payload.modelName` is required. It must be a Hugging Face model name in `/` form. - `payload.commitHash` is required. It must be the full 40-character lowercase hex commit SHA. - `meta.description` is optional. Use it for a human-readable note in your own submission history. ## Submit ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slot": 2, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Pinned model revision for public evaluation." } }' ``` The response is a `model-reference` submission record with `submissionId`, `competitionId`, `kind`, `slot`, `payload`, `meta`, `createdAt`, and `updatedAt`. Re-submitting slot `2` replaces only slot `2`; the API does not choose an empty slot automatically. ## Read all current slots ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/submissions/mine" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` The collection response uses the same shape even when no entry exists: ```json { "ok": true, "data": { "items": [ { "submissionId": "sub_01HX0WT...", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "slot": 2, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Pinned model revision for public evaluation." }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-20T08:11:02Z" } ], "nextCursor": null } } ``` ## Contributor Network Publishing to the Contributor Network shares `modelName` and `commitHash` with other participants. It does not create, replace, update, or withdraw any official competition submission. ```bash curl -X POST "https://api.sair.foundation/api/public/v1/contributor-network/items" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "source": { "from": "model-reference", "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "parentContributorNetworkItemId": null, "remark": "Model revision for public comparison." }' ``` Browse shared Modular Arithmetic Challenge model references: ```bash curl "https://api.sair.foundation/api/public/v1/contributor-network/items?competitionId=modular-arithmetic-challenge&kind=model-reference" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ## Workflow 1. Read the competition detail and confirm `submissionSpec.kind` is `model-reference`; use `submissionSpec.limits.maxEntries` to build the slot selector. 2. Optionally evaluate the pinned model revision through the Modular Arithmetic Challenge Playground workflow. 3. Call `/me` and submit only when `canSubmit` is `true`. 4. Choose a slot and submit the pinned model reference with the exact commit hash. 5. Read `/submissions/mine` to confirm all current slots. `/submissions/me` remains a legacy slot-1 read. 6. Optionally publish `modelName` and `commitHash` to the Contributor Network. See [Competitions](../competitions.md) for the shared endpoint field tables. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/igp24/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/igp24.md Source: docs/endpoints/competitions/igp24.md # Inverse Galois Problem (IGP24) Competition ID: `igp24` Use this guide when planning Inverse Galois Problem (IGP24) searches and automating formal polynomial submissions. ## Endpoint map Use `competitionId = igp24`. **Competition endpoints** | Task | Endpoint | Scope | Notes | | :--- | :--- | :--- | :--- | | Read schema | [`GET /api/public/v1/competitions/igp24`](./api/get-competition.md#inverse-galois-problem-igp24) | `competition.read` | Complete IGP24 `submissionSpec`. | | Check participation | [`GET /api/public/v1/competitions/igp24/me`](./api/get-my-participation.md#inverse-galois-problem-igp24) | `competition.read` | Active members may submit. | | Read label progress | [`GET /api/public/v1/competitions/igp24/labels/progress`](#label-progress) | `competition.read` | Aggregate participant progress by label and signature. | | Read remaining pairs | [`GET /api/public/v1/competitions/igp24/remaining-pairs`](#remaining-pairs) | `competition.read` | Canonical pairs not yet discovered by a participant team. | | Create a new submission | [`POST /api/public/v1/competitions/igp24/submissions`](./api/submit.md#inverse-galois-problem-igp24) | `competition.write` | Each call creates a fresh `submissionId`. | | Poll submission verification | [`GET /api/public/v1/competitions/igp24/submissions/batch/{batchId}`](./api/get-submission-batch-status.md) | `competition.read` | Per-polynomial status for the submitted `batchId`. | | Get my submissions | [`GET /api/public/v1/competitions/igp24/submissions/mine`](./api/list-my-submissions.md#inverse-galois-problem-igp24) | `competition.read` | Unified collection endpoint; newest first and cursor-paginated. | | Get my submissions (legacy) | [`GET /api/public/v1/competitions/igp24/submissions/me`](./api/get-my-submission-legacy.md#inverse-galois-problem-igp24) | `competition.read` | Existing IGP24 collection contract remains unchanged. | | Read a submission by ID | [`GET /api/public/v1/competitions/igp24/submissions/{submissionId}`](./api/get-submission.md#inverse-galois-problem-igp24) | `competition.read` | JSON omits coefficient strings. | | Download | [`GET /api/public/v1/competitions/igp24/submissions/{submissionId}/download`](./api/download-submission.md#inverse-galois-problem-igp24) | `competition.read` | Returns polynomial lines as `text/plain`. | | Read leaderboard | [`GET /api/public/v1/competitions/igp24/leaderboard`](./api/get-leaderboard.md#igp24-example) | `competition.read` | Cursor-paginated public ranking. | | Read my standing | [`GET /api/public/v1/competitions/igp24/leaderboard/me`](./api/get-my-standing.md#igp24-example) | `competition.read` | Current caller's public ranking. | | Read team placements | [`GET /api/public/v1/competitions/igp24/leaderboard/teams/{teamId}/placements`](./api/get-team-placements.md) | `competition.read` | Cursor-paginated public placements for one team. | | Search leaderboard placements | [`GET /api/public/v1/competitions/igp24/leaderboard/search`](./api/search-leaderboard.md) | `competition.read` | Match placements by `t`, `r`, or both. | IGP24 is not supported by Playground. Submit polynomial batches through [`POST /api/public/v1/competitions/igp24/submissions`](./api/submit.md#inverse-galois-problem-igp24). ## Rate limit IGP24 submissions are shared across the competition website and API. Each team starts with **5 submissions per day**. After the team has been credited with at least **5 distinct scoreable `(24Tt, r)` pairs**, its cap increases to **1000 submissions per day**. Each `POST /submissions` counts as one submission regardless of how many polynomials it carries. Exceeding the active cap returns `429` with `IGP24_SUBMISSION_RATE_LIMIT_EXCEEDED` and a `Retry-After` header; pace yourself using the `X-RateLimit-*` headers. See [Rate limiting](../../rate-limiting.md). ## Label progress ```http GET /api/public/v1/competitions/igp24/labels/progress ``` **Scope**: `competition.read`. This endpoint is cursor-paginated. Returns aggregate participant progress for IGP24, grouped by transitive group label and real-root count/signature `r`. It is designed for search planning and batch analysis without scraping the competition website. This is a bulk aggregate endpoint. It intentionally returns `labels[]` rather than the usual `items[]`, and its page-size maximum is higher than the default list endpoint maximum so clients can traverse the 25,000-label IGP24 universe efficiently. ### Query parameters | Parameter | Type | Required | Description | | :------------- | :------ | :------: | :---------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Number of labels to return. Default `100`, maximum `5000`. | | `label` | string | No | Repeatable label filter, e.g. `?label=24T1&label=24T3`. | | `labels` | string | No | Comma-separated label filter, e.g. `?labels=24T1,24T3,24T25000`. | | `includeEmpty` | boolean | No | Defaults to `true`. When `false`, only labels with at least one discovered signature are returned; returned labels still include their full signature breakdown. | Filters are applied before the cursor. Changing `label`, `labels`, or `includeEmpty` invalidates a previous cursor; start a new traversal when filters change. ### Response fields | Field | Type | Description | | :------------ | :--------------------------------------------------- | :---------- | | `generatedAt` | string | ISO 8601 UTC timestamp for the generated page. | | `labels[]` | [Igp24LabelProgress](#igp24-label-progress-fields)[] | Label-level aggregate progress records. Empty when `meta.published` is `false`. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | | `meta` | [Igp24ProgressMeta](#igp24-progress-metadata-fields) | Publication state for the progress data. | ### IGP24 progress metadata fields | Field | Type | Description | | :------------ | :------------- | :---------- | | `published` | boolean | Whether IGP24 leaderboard/progress data is currently published. | | `publishedAt` | string \| null | ISO 8601 UTC publication timestamp, or `null` when unpublished. | ### IGP24 label progress fields | Field | Type | Description | | :--------------------- | :---------------------------------------------------------- | :---------- | | `label` | string | Transitive group label, e.g. `24T25000`. | | `t` | integer | Numeric transitive group index. | | `allowedR` | integer[] | All allowed real-root counts/signatures for this label. | | `teamCount` | integer | Number of distinct participant teams currently credited with this label. The display-only LMFDB baseline/system team is not counted. | | `minimumDiscAbs` | string \| null | Minimum absolute scoring discriminant currently credited for this label, or `null` when undiscovered. This is not the submitted polynomial discriminant. | | `discoveredSignatures` | integer[] | Allowed `r` values discovered by at least one participant team. | | `remainingSignatures` | integer[] | Allowed `r` values not yet discovered by a participant team. | | `signatures[]` | [Igp24SignatureProgress](#igp24-signature-progress-fields)[] | Per-`r` progress records for this label. | ### IGP24 signature progress fields | Field | Type | Description | | :--------------- | :------------- | :---------- | | `r` | integer | Real-root count/signature. | | `teamCount` | integer | Number of distinct participant teams currently credited with this `(label, r)`. | | `minimumDiscAbs` | string \| null | Minimum absolute scoring discriminant currently credited for this `(label, r)`, or `null` if undiscovered. This is not the submitted polynomial discriminant. | | `discovered` | boolean | Whether this `(label, r)` has been discovered by a participant team. | These progress fields reflect participant-team discoveries. LMFDB baseline records do not affect them. Each response is generated as a short-lived page-level snapshot. Responses may be cached for up to 120 seconds per distinct query. The endpoint does not guarantee one stable global snapshot across a multi-page traversal; results are eventually consistent and may change between pages. For batch analysis, request up to `limit=5000` and continue with `nextCursor` until it is `null`. The full IGP24 label set contains 25,000 labels, so the complete dataset can be retrieved in approximately five paginated requests at the maximum page size. **Example request** ```bash curl -H "Authorization: Bearer $SAIR_API_KEY" \ "https://api.sair.foundation/api/public/v1/competitions/igp24/labels/progress?limit=1000" ``` **Example response** ```json { "ok": true, "data": { "generatedAt": "2026-06-23T05:30:11Z", "labels": [ { "label": "24T25000", "t": 25000, "allowedR": [0, 2, 4], "teamCount": 5, "minimumDiscAbs": "12345678901234567890", "discoveredSignatures": [0, 2], "remainingSignatures": [4], "signatures": [ { "r": 0, "teamCount": 2, "minimumDiscAbs": "12345678901234567890", "discovered": true }, { "r": 2, "teamCount": 3, "minimumDiscAbs": "22345678901234567890", "discovered": true }, { "r": 4, "teamCount": 0, "minimumDiscAbs": null, "discovered": false } ] } ], "nextCursor": null, "meta": { "published": true, "publishedAt": "2026-06-18T02:50:30Z" } } } ``` ### Errors See [Errors](../../errors.md) for the full code table. Endpoint-specific: | Code | HTTP | When | | :----------------------- | :--: | :--- | | `MALFORMED_BODY` | 400 | `limit` is outside `1..5000`, a query parameter cannot be parsed, or the cursor is malformed while progress is published. | | `RESOURCE_FIELD_INVALID` | 422 | A label filter is semantically invalid, e.g. it does not use the `24Tt` form. | ## Remaining pairs ```http GET /api/public/v1/competitions/igp24/remaining-pairs ``` **Scope**: `competition.read`. This endpoint is cursor-paginated. Returns canonical valid `(24Tt, r)` pairs not yet discovered by a participant team. ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :---------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Number of pairs to return. Default `100`; minimum `1`, maximum `5000`. | ### Response fields | Field | Type | Description | | :------------ | :------------------------------------------------------------- | :---------- | | `generatedAt` | string | ISO 8601 UTC timestamp for the generated page. | | `items[]` | [Igp24RemainingPair](#igp24-remaining-pair-fields)[] | Pairs not yet discovered by a participant team, ordered by ascending `(t, r)`. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | | `meta` | [Igp24ProgressMeta](#igp24-progress-metadata-fields) | Publication state for the remaining-pair data; when `published` is `false`, `items` is empty. | ### IGP24 remaining-pair fields | Field | Type | Description | | :------ | :------ | :---------- | | `label` | string | Transitive group label, e.g. `24T25000`. | | `t` | integer | Numeric transitive group index. | | `r` | integer | Allowed real-root count/signature for the label. | Pairs are ordered by ascending `t`, then ascending `r`. Continue until `nextCursor` is `null` to traverse all matching pairs. Each response is a page-level snapshot. Participant progress may change between pages, so a multi-page traversal is eventually consistent rather than a single frozen global snapshot. **Example request** ```bash curl -H "Authorization: Bearer $SAIR_API_KEY" \ "https://api.sair.foundation/api/public/v1/competitions/igp24/remaining-pairs?limit=1000" ``` **Example response** ```json { "ok": true, "data": { "generatedAt": "2026-07-13T10:30:00Z", "items": [ { "label": "24T100", "t": 100, "r": 2 }, { "label": "24T100", "t": 100, "r": 6 } ], "nextCursor": "WzEwMCw2XQ==", "meta": { "published": true, "publishedAt": "2026-06-18T02:50:30Z" } } } ``` ### Errors See [Errors](../../errors.md) for the full code table. Endpoint-specific: | Code | HTTP | When | | :--------------- | :--: | :--- | | `MALFORMED_BODY` | 400 | `limit` is outside `1..5000`, a query parameter cannot be parsed, or the cursor is malformed while progress is published. | ## Submission payload The competition uses `submissionSpec.kind: "igp24-polynomial"`. Read `submissionSpec.permission` from the competition detail before submitting. For IGP24 it is `teamRole: "activeMember"`, so any active member of the IGP24 team may submit polynomial batches. **Example request body** ```json { "payload": { "polynomials": [ "3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 # poly_disc_primes=[2,3]", "1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1" ] }, "meta": { "description": "Adapted from a public polynomial table; reproduction script: search batch 12." } } ``` Payload fields: - `payload.polynomials[]` is required. Each item is one candidate polynomial line in the official submission-text format. - `meta.description` is optional and capped by `submissionSpec.metaSchema.properties.description.maxLength` (currently 500 characters for IGP24). Use it for provenance, source citations, or reproduction notes. Before any trailing `#` comment, each polynomial line must contain exactly 25 comma-separated integers in ascending powers: `a_0,a_1,...,a_24`. `a_0` must be nonzero, `a_24` must be `1` (submissions must be monic), and the coefficient gcd must be `1`. The Public API accepts JSON only; it does not upload an IGP24 file. Blank lines and full-line comments are not items in `payload.polynomials[]`. A valid polynomial string may include a trailing `#` comment. Ordinary trailing comments are preserved with the submitted line but ignored by verification. The exact trailing-comment body `poly_disc_primes=[...]` supplies an optional prime-factor ramp hint for `quicknfdisc`: ```text 3,0,...,0,1 # poly_disc_primes=[2,3] ``` List known prime factors as strictly increasing decimal integers greater than 1, without exponents. Values are transported as text and may exceed the JavaScript safe-integer range. The list may be incomplete. Additional prose in the same comment prevents the reserved field from being recognized. A malformed, structurally invalid, or otherwise unusable hint is silently ignored; if the acceleration path is unavailable, the ordinary discriminant workflow also continues. Neither case rejects an otherwise valid polynomial or changes its Magma verification result. The persisting submissions endpoint accepts hints; dry-run verification endpoints ignore them because they do not compute discriminants. The maximum accepted count is published as `submissionSpec.limits.maxPolynomials`. The size cap is published as `submissionSpec.limits.maxBytes`. The canonical limit is the raw `submission.txt` body size: 1,000,000 bytes. For IGP24 Public API requests, join the complete `payload.polynomials` strings, including trailing comments, with newline separators and measure the UTF-8 byte length. The separately published `submissionSpec.limits.maxRequestBytes` limit (currently 1,310,720 bytes) applies to the normalized JSON request envelope, including `payload`, `meta`, and JSON quoting or escaping overhead. Requests must satisfy both byte limits. ## Submit ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "polynomials": [ "3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 # poly_disc_primes=[2,3]", "1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1" ] }, "meta": { "description": "Adapted from a public polynomial table; reproduction script: search batch 12." } }' ``` Validation starts during submission and may continue asynchronously. Submission responses and subsequent reads expose `verifiedPolynomials`, `failedPolynomials`, and `payload.queuedPolynomials`; accepted records include `label`, `t`, and `r` once verification completes. Result items do not echo coefficient strings or expose `prime_hint_status` or `nfdisc_method`. Use the download endpoint to retrieve submitted lines for a submission accessible to the caller; current IGP24 team submissions are team-scoped. Duplicate canonical polynomials are verified and stored only once internally, using the first occurrence's prime hint, while every input index is still represented in the verified, failed, or queued result lists. ## Download submitted polynomials ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions/$SUBMISSION_ID/download" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -o "igp24-$SUBMISSION_ID-polynomials.txt" ``` The response is `text/plain; charset=utf-8` and contains the original submitted polynomial strings for that submission, one polynomial per line, including any trailing comments. It does not include verification results, labels, computed discriminants, or submission metadata. See [Submission kinds](../../reference/submission-kinds.md#igp24-polynomial) for the full DTO. See [Competitions](../competitions.md) for the shared endpoint field tables. --- URL: https://docs.sair.foundation/docs/endpoints/playground/ Markdown: https://docs.sair.foundation/docs-md/endpoints/playground.md Source: docs/endpoints/playground.md # Playground Use Playground endpoints to discover available models and problem sets and to run practice evaluations for a competition, before sending a formal entry through [`POST /api/public/v1/competitions/{competitionId}/submissions`](./competitions.md#submit). Call Playground endpoints only when the competition's `capabilities.playground` is `true`. ## Playground API guides Start with the guide for the Playground workflow you are automating: - [Mathematics Distillation Stage 1](./playground/mathematics-distillation-stage1.md): cheatsheet practice runs. - [Mathematics Distillation Stage 2](./playground/mathematics-distillation-stage2.md): Lean solver practice runs. - [Modular Arithmetic Challenge](./playground/modular-arithmetic-challenge.md): model-reference evaluation workflow. Some Playground workflows use supporting resources with their own CRUD endpoints: - [Cheatsheets](./cheatsheets.md) store reusable text configurations for `cheatsheet` runs. - [Solver templates](./solver-templates.md) store reusable Lean 4 solver code and custom problems for `solver-participation` runs. The run shape is decided by the competition's `submissionSpec.kind`: | Competition kind | Run inputs | Per-cell result | | :------------------------------- | :------------------------------------------------------------- | :----------------------------------------- | | `cheatsheet` | `models × problems × configurations`, optionally repeated. | Correct / incorrect against a gold answer. | | `solver-participation` | One Lean 4 `solverCode` against a list of problem IDs. | `proved` / `failed` / `timeout`. | | `model-reference` | One pinned model artifact against one evaluation problem set. | Per-case prediction and correctness. | ## List models ```http GET /api/public/v1/competitions/{competitionId}/playground/models ``` **Scope**: `playground.read`. This endpoint is cursor-paginated. The model IDs accepted in a run for this competition. The response metadata includes the per-cell credit price for the competition. Model IDs are opaque strings. Some competitions use OpenRouter-style `provider/name` IDs (e.g. `meta-llama/llama-3.3-70b-instruct`); others use hyphenated IDs (e.g. `google-gemma-4-31b-it`). Treat the value as an opaque token — do not parse it. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :----------------------------------------------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------------------- | :------------ | :------------------------------------------- | | `items[]` | [PlaygroundModel](#playground-model-fields)[] | Model records accepted in run requests. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | | `meta` | [PlaygroundModelListMeta](#playground-model-list-metadata-fields) | Pricing metadata for run requests. | ### Playground model fields | Field | Type | Description | | :----- | :----- | :-------------------------------- | | `id` | string | Model ID to send in run requests. | | `name` | string | Human-readable model name. | ### Playground model list metadata fields | Field | Type | Description | | :----------- | :----- | :-------------------------------------- | | `creditRate` | number | Credit price per executed cell/problem. | **Example response** ```json { "ok": true, "data": { "items": [ { "id": "meta-llama/llama-3.3-70b-instruct", "name": "Meta: Llama 3.3 70B Instruct" } ], "nextCursor": null, "meta": { "creditRate": 0.001 } } } ``` ## List problem sets ```http GET /api/public/v1/competitions/{competitionId}/playground/problem-sets ``` **Scope**: `playground.read`. This endpoint is cursor-paginated. Lists the problem sets available for practice runs in this competition. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :-------------------------------------------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :---------------- | :------------ | :----------------------------------- | | `items[]` | [ProblemSetSummary](#problem-set-summary-fields)[] | Problem set summaries. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Problem set summary fields | Field | Type | Description | | :------ | :------ | :----------------------------- | | `id` | string | Problem set ID. | | `title` | string | Human-readable title. | | `count` | integer | Number of problems in the set. | ## Get a problem set ```http GET /api/public/v1/competitions/{competitionId}/playground/problem-sets/{problemSetId} ``` **Scope**: `playground.read`. Returns every problem in the set. This endpoint is not paginated. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | | `problemSetId` | string | Yes | Problem set ID. | ### Response fields | Field | Type | Description | | :----------- | :----- | :----------------------------------------------- | | `id` | string | Problem set ID. | | `title` | string | Human-readable title. | | `items[]` | [EquationProblem](#equation-problem-fields)[] \| [LeanProblem](#lean-problem-fields)[] | Problem rows. Shape depends on competition kind. | `cheatsheet` competitions return [EquationProblem](#equation-problem-fields) rows; `solver-participation` competitions return [LeanProblem](#lean-problem-fields) rows. Model-reference competitions do not return problem-set rows here — their per-case structure is surfaced through [run results](#model-reference-result-fields). ### Equation problem fields For `cheatsheet` competitions, each item carries: | Field | Type | Description | | :------------- | :------ | :-------------------------------------------- | | `index` | integer | Problem index to send in run requests. | | `equation1` | string | Left equation text. | | `equation2` | string | Right equation text. | | `goldAnswer` | boolean | Expected equivalence answer. | **Example response for a `cheatsheet` competition** ```json { "ok": true, "data": { "id": "pset_hard3", "title": "Hard v3", "items": [ { "index": 0, "equation1": "...", "equation2": "...", "goldAnswer": true } ] } } ``` ### Lean problem fields For `solver-participation` competitions, each item carries: | Field | Type | Description | | :---------- | :------ | :------------------------------------------------------------ | | `id` | string | Problem ID to send in `problemIds`. | | `lhsName` | string | Name of the left-side Lean declaration. | | `lhsText` | string | Left-side Lean source. | | `rhsName` | string | Name of the right-side Lean declaration. | | `rhsText` | string | Right-side Lean source. | | `isCustom` | boolean | `true` for private custom problems created by the key owner. | **Example response for a `solver-participation` competition** ```json { "ok": true, "data": { "id": "pset_lean-easy", "title": "Lean — Easy", "items": [ { "id": "prob_01HX...", "lhsName": "add_zero", "lhsText": "theorem add_zero (n : Nat) : n + 0 = n := by ...", "rhsName": "add_zero_canonical", "rhsText": "theorem add_zero_canonical (n : Nat) : n + 0 = n := by ...", "isCustom": false } ] } } ``` ## Submit a run ```http POST /api/public/v1/competitions/{competitionId}/playground/runs ``` **Scope**: `playground.write`. Creates a practice run. The request body shape depends on the competition's `submissionSpec.kind`. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | ### Request fields for `cheatsheet` | Field | Type | Required | Description | | :--------------------- | :------ | :------: | :-------------------------------------------------------------------------- | | `models` | string[] | Yes | Model IDs from [List models](#list-models). | | `problems` | [RunProblem](#run-problem-fields)[] | Yes | Set references or inline custom problems. | | `configurations` | [RunConfiguration](#run-configuration-fields)[] | No | Conditions to compare. Omit for an unconditioned baseline. | | `repeat` | integer | No | Re-runs each cell. Default `1`, maximum `5`. | When `configurations` is present, every item must reference a cheatsheet. To create the unconditioned baseline, omit `configurations` instead of sending `{}`. An explicit empty array is accepted and creates a run with zero equation cells. ### Run problem fields `RunProblem` is either a problem-set reference or an inline custom problem. Problem-set references must provide both `problemSet` and `index`; inline custom problems must provide only `custom`. | Field | Type | Required | Description | | :----------- | :------------- | :------: | :--------------------------------------------------------------------- | | `problemSet` | string | Conditional | Problem set ID for a set reference. Required with `index`. | | `index` | integer | Conditional | Problem index for a set reference. Required with `problemSet`. | | `custom` | [InlineProblem](#inline-problem-fields) | Conditional | Inline custom problem. Cannot be combined with `problemSet` / `index`. | ### Inline problem fields | Field | Type | Required | Description | | :----------- | :------ | :------: | :---------------------------------------------- | | `equation1` | string | Yes | Left equation text for an inline custom problem. | | `equation2` | string | Yes | Right equation text for an inline custom problem. | | `goldAnswer` | boolean | Yes | Expected equivalence answer. | ### Run configuration fields | Field | Type | Required | Description | | :----------- | :----- | :------: | :---------------------------------- | | `cheatsheet` | string | Yes | Cheatsheet ID owned by the key owner. | **Example request body for `cheatsheet`** ```json { "models": ["meta-llama/llama-3.3-70b-instruct"], "problems": [ { "problemSet": "pset_hard3", "index": 0 }, { "custom": { "equation1": "x + y = y + x", "equation2": "y + x = x + y", "goldAnswer": true } } ], "configurations": [{ "cheatsheet": "cs_01HXYZ..." }], "repeat": 1 } ``` ### Request fields for `solver-participation` | Field | Type | Required | Description | | :-------------- | :------- | :------: | :-------------------------------------------------------------------------- | | `solverCode` | string | Yes | Lean 4 source. The run stores a copy. | | `solverName` | string | No | Label shown in the run list. | | `problemIds` | string[] | Yes | Set problem IDs or [custom problem](./solver-templates.md#list-custom-problems) IDs. | | `allowedModels` | string[] | No | Restricts the model pool. Defaults to every ID from [List models](#list-models). | Later edits to a saved [solver template](./solver-templates.md) do not affect an existing run. **Example request body for `solver-participation`** ```json { "solverCode": "theorem solver : ... := by ...", "solverName": "induction-v3", "problemIds": ["prob_01HX...", "prob_01HY..."], "allowedModels": ["openai-gpt-oss-120b"] } ``` ### Request fields for `model-reference` | Field | Type | Required | Description | | :------------- | :----- | :------: | :---------------------------------------------------------- | | `modelName` | string | Yes | Hugging Face model repository in `/` form. | | `commitHash` | string | Yes | Full 40-character lowercase hex commit SHA. | | `problemSetId` | string | Yes | Evaluation problem set ID from [List problem sets](#list-problem-sets). | | `hfToken` | string | No | Hugging Face token for private or gated repositories. Never returned by the API. | | `note` | string | No | Private note shown in run history. Maximum `500` characters. | **Example request body for `model-reference`** ```json { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567", "problemSetId": "mps_eval_01", "hfToken": "hf_...", "note": "Candidate revision for hard-tier evaluation." } ``` ### Response fields | Field | Type | Description | | :------- | :----- | :------------------------------------------ | | `runId` | string | Run ID for status, results, and events. | | `status` | string | Initial run status. Usually `pending`. The full set of run statuses is `pending`, `running`, `done`, `failed`, `cancelled` (see [Playground run fields](#playground-run-fields)). | **Example response** ```json { "ok": true, "data": { "runId": "run_01HXZAB...", "status": "pending" } } ``` ## List runs ```http GET /api/public/v1/competitions/{competitionId}/playground/runs ``` **Scope**: `playground.read`. This endpoint is cursor-paginated. Returns runs created by the key owner for this competition. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | ### Query parameters | Parameter | Type | Required | Description | | :----------- | :------ | :------: | :------------------------------------------------------------------------ | | `status` | string | No | `pending`, `running`, `done`, `failed`, or `cancelled`. | | `modelId` | string | No | Returns runs that include this model ID. | | `problemSet` | string | No | Equation-style competitions only. Returns runs referencing this problem set. | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------ | :----------------------------------- | | `items[]` | [PlaygroundRun](#playground-run-fields)[] | Run records. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | **Example response** ```json { "ok": true, "data": { "items": [ { "runId": "run_01HXZAB...", "status": "done", "params": { "...": "..." }, "summary": { "...": "..." }, "createdAt": "2026-05-20T09:14:02Z", "finishedAt": "2026-05-20T09:16:48Z" } ], "nextCursor": null } } ``` Each item is a [PlaygroundRun](#playground-run-fields); Lean runs also include `verdicts[]`. ## Get a run ```http GET /api/public/v1/competitions/{competitionId}/playground/runs/{runId} ``` **Scope**: `playground.read`. The response carries `params` (the original body), `summary`, and timing. For Lean runs it additionally includes `verdicts[]`. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | | `runId` | string | Yes | Run ID from Submit a run. | ### Response fields Returns a [PlaygroundRun](#playground-run-fields). For Lean competitions, `summary` is `{ "accepted": , "rejected": , "errors": }` and `verdicts[]` is included. Equation-style and model-reference runs omit `verdicts[]`. ### Playground run fields | Field | Type | Description | | :----------- | :------------ | :----------------------------------------------------------------- | | `runId` | string | Run ID. | | `status` | string | `pending`, `running`, `done`, `failed`, or `cancelled`. | | `params` | [EquationRunParams](#equation-run-params-fields) \| [LeanRunParams](#lean-run-params-fields) \| [ModelReferenceRunParams](#model-reference-run-params-fields) | Original run request body. | | `summary` | [EquationRunSummary](#equation-run-summary-fields) \| [LeanRunSummary](#lean-run-summary-fields) \| [ModelReferenceRunSummary](#model-reference-run-summary-fields) | Shape depends on competition kind. | | `verdicts[]` | [LeanVerdict](#lean-verdict-fields)[] | Lean competitions only. Per-problem verdicts. | | `createdAt` | string | ISO 8601 UTC timestamp. | | `finishedAt` | string \| null | ISO 8601 UTC timestamp when terminal, otherwise `null`. | ### Equation run params fields | Field | Type | Description | | :--------------- | :----- | :----------------------------------------------------- | | `models` | string[] | Model IDs from [List models](#list-models). | | `problems` | [RunProblem](#run-problem-fields)[] | Set references or inline custom problems. | | `configurations` | [StoredRunConfiguration](#stored-run-configuration-fields)[] | Conditions compared in the run. | | `repeat` | integer | Re-runs each cell. | ### Stored run configuration fields Run responses return stored configurations. A cheatsheet-backed configuration includes `cheatsheet`; an unconditioned baseline created by omitting `configurations` from the request is returned as `{}`. | Field | Type | Required | Description | | :----------- | :----- | :------: | :---------------------------------- | | `cheatsheet` | string | No | Cheatsheet ID for a cheatsheet-backed configuration. Missing for the unconditioned baseline. | ### Lean run params fields | Field | Type | Description | | :-------------- | :------- | :----------------------------------- | | `solverCode` | string | Lean 4 source copied into the run. | | `solverName` | string | Label shown in the run list. | | `problemIds` | string[] | Problem IDs included in the run. | | `allowedModels` | string[] | Model IDs allowed for this run. | ### Model-reference run params fields | Field | Type | Description | | :------------- | :------ | :--------------------------------------------------------- | | `modelName` | string | Hugging Face model repository. | | `commitHash` | string | Full 40-character commit SHA. | | `problemSetId` | string | Evaluation problem set ID. | | `hasToken` | boolean | Whether a Hugging Face token was supplied. Raw tokens are never returned. | | `note` | string \| null | Private note, or `null` when omitted. | ### Equation run summary fields | Field | Type | Description | | :----------- | :----- | :-------------------------------------------- | | `groups[]` | [EquationRunSummaryGroup](#equation-run-summary-group-fields)[] | Aggregate metrics grouped by model and configuration. | | `creditRate` | number | Credit conversion rate used by the run. | ### Equation run summary group fields | Field | Type | Description | | :---------------- | :------ | :---------------------------------- | | `model` | string | Model ID. | | `cheatsheet` | string \| null | Cheatsheet ID for the configuration, or `null` for an unconditioned baseline. | | `count` | integer | Result count in this group. | | `accuracy` | number | Accuracy for this group, from `0` to `1`. | | `meanCost` | number | Mean execution cost before credit conversion. | | `totalCost` | number | Total execution cost before credit conversion. | | `totalCreditCost` | number | Total credits charged for this group. | | `meanElapsed` | number | Mean elapsed time in milliseconds. | ### Lean run summary fields | Field | Type | Description | | :--------- | :------ | :--------------------- | | `accepted` | integer | Accepted proof count. | | `rejected` | integer | Rejected proof count. | | `errors` | integer | Error count. | ### Model-reference run summary fields | Field | Type | Description | | :------------------- | :------ | :----------------------------------------------- | | `overallAccuracy` | number \| null | Overall accuracy after scoring, or `null` before completion. | | `highestTierAbove90` | integer \| null | Highest tier with at least 90% accuracy, or `null` before completion. | | `tiers[]` | [ModelReferenceTierScore](#model-reference-tier-score-fields)[] | Per-tier score breakdown. | | `deterministic` | boolean \| null | Whether the evaluated model was deterministic. | | `artifactBytes` | integer \| null | Downloaded artifact size in bytes. | | `inferenceTime` | number \| null | Total model inference time in seconds. | | `error` | string \| null | Failure message when the run failed. | ### Model-reference tier score fields | Field | Type | Description | | :---------- | :------ | :---------------------------------- | | `tierId` | integer | Difficulty tier ID. | | `total` | integer | Cases in the tier. | | `correct` | integer | Correct predictions in the tier. | | `accuracy` | number | Tier accuracy from `0` to `1`. | | `completed` | boolean | Whether every tier case was scored. | ### Lean verdict fields | Field | Type | Description | | :------------- | :------------ | :------------------------------------ | | `problemId` | string | Problem ID. | | `verdict` | string | `proved`, `failed`, or `timeout`. | | `elapsedMs` | integer | Elapsed time in milliseconds. | | `errorSnippet` | string \| null | Lean parser or judge message, if any. | **Example response for a cheatsheet run** ```json { "ok": true, "data": { "runId": "run_01HXZAB...", "status": "done", "params": { "...": "..." }, "summary": { "groups": [ { "model": "meta-llama/llama-3.3-70b-instruct", "cheatsheet": "cs_01HXYZ...", "count": 200, "accuracy": 0.78, "meanCost": 0.0012, "totalCost": 0.24, "totalCreditCost": 280.5, "meanElapsed": 1820 } ], "creditRate": 0.001 }, "createdAt": "2026-05-20T09:14:02Z", "finishedAt": "2026-05-20T09:16:48Z" } } ``` ## Cancel a run ```http POST /api/public/v1/competitions/{competitionId}/playground/runs/{runId}/cancel ``` **Scope**: `playground.write`. Cancels a pending or running run. Already-terminal runs return `RUN_NOT_CANCELLABLE` (409). ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | | `runId` | string | Yes | Run ID from Submit a run. | ### Response fields | Field | Type | Description | | :------- | :----- | :----------------------------- | | `runId` | string | Run ID. | | `status` | string | Always `cancelled` on success. | ## List results ```http GET /api/public/v1/competitions/{competitionId}/playground/runs/{runId}/results ``` **Scope**: `playground.read`. This endpoint is cursor-paginated. Returns one row per executed cell/problem. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | | `runId` | string | Yes | Run ID from Submit a run. | ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :-------------------------------------------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------ | :------------------------------------ | | `items[]` | [EquationResult](#equation-result-fields)[] \| [LeanResult](#lean-result-fields)[] \| [ModelReferenceResult](#model-reference-result-fields)[] | Result rows. Shape depends on kind. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Equation result fields For equation-style competitions, each row carries: | Field | Type | Description | | :-------------- | :------------- | :---------------------------------------- | | `resultId` | string | Result row ID. | | `modelId` | string | Model ID. | | `problemSet` | string | Problem set ID for set references. Inline custom problems are reported as `custom`. | | `problemIndex` | integer | Problem index inside the set. Inline custom problems are reported as `0`. | | `cheatsheet` | string \| null | Cheatsheet ID for the configuration. | | `repeatIndex` | integer | Zero-based repeat index. | | `answer` | boolean | Model answer. | | `scoredCorrect` | integer \| null | Correct count for this row. | | `scoredTotal` | integer | Scored total for this row. | | `elapsedMs` | integer | Elapsed time in milliseconds. | | `output` | string \| null | Raw model output. `null` when the model produced no response (e.g. pre-flight failure or LLM error). | **Example response** (one `EquationResult` row inside the paginated envelope) ```json { "ok": true, "data": { "items": [ { "resultId": "res_01HX...", "modelId": "meta-llama/llama-3.3-70b-instruct", "problemSet": "pset_hard3", "problemIndex": 0, "cheatsheet": "cs_01HXYZ...", "repeatIndex": 0, "answer": true, "scoredCorrect": 1, "scoredTotal": 1, "elapsedMs": 1820, "output": "..." } ], "nextCursor": null } } ``` ### Lean result fields For Lean competitions, each row carries: | Field | Type | Description | | :------------- | :------------ | :--------------------------------------- | | `resultId` | string | Result row ID. | | `problemId` | string | Problem ID. | | `verdict` | string | `proved`, `failed`, or `timeout`. | | `elapsedMs` | integer | Elapsed time in milliseconds. | | `errorSnippet` | string \| null | Lean parser or judge message, if any. | ### Model-reference result fields For model-reference competitions, each row carries one evaluated case: | Field | Type | Description | | :---------- | :------------- | :------------------------------------- | | `resultId` | string | Result row ID. | | `caseId` | string | Evaluation case ID. | | `tierId` | integer | Difficulty tier ID. | | `prediction` | string \| null | Model prediction, or `null` if unavailable. | | `correct` | boolean | Whether the prediction was correct. | | `expected` | string | Expected canonical answer. | ## Stream events ```http GET /api/public/v1/competitions/{competitionId}/playground/runs/{runId}/events ``` **Scope**: `playground.read`. Available only for `solver-participation` competitions. Returns a Server-Sent Events stream of verdicts as the model finishes each problem. The connection closes when the run reaches a terminal status. Clients should send `Accept: text/event-stream`; successful responses use `Content-Type: text/event-stream`. This endpoint does not use the `{ ok, data }` envelope. Events are unnamed SSE `message` events — one `data:` line of compact JSON per [LeanVerdict](#lean-verdict-fields), separated by a blank line, with no `event:` name: ```text data: {"problemId":"prob_01HX...","verdict":"proved","elapsedMs":1234,"errorSnippet":null} ``` ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | | `runId` | string | Yes | Run ID from Submit a run. | ### Event data fields | Field | Type | Description | | :------------- | :------------ | :------------------------------------ | | `problemId` | string | Problem ID. | | `verdict` | string | `proved`, `failed`, or `timeout`. | | `elapsedMs` | integer | Elapsed time in milliseconds. | | `errorSnippet` | string \| null | Lean parser or judge message, if any. | ## Usage ```http GET /api/public/v1/competitions/{competitionId}/playground/usage ``` **Scope**: `playground.read`. Your accumulated practice spend on this competition. Usage counters are scoped to this competition; `credits` is the balance snapshot for the corresponding Playground ledger. ### Path parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :-------------------------- | | `competitionId` | string | Yes | Competition slug from [List competitions](./competitions.md#list-competitions). | ### Response fields | Field | Type | Description | | :------------------ | :---------------------------------------- | :----------------------------------------------- | | `credits` | [CreditBalance](#credit-balance-fields) | Credit balance snapshot for this competition ledger. | | `runs` | [RunUsage](#run-usage-fields) | Run counters. | | `cellsExecuted` | [UsageCounter](#usage-counter-fields) | Present for equation-style competitions. | | `problemsAttempted` | [UsageCounter](#usage-counter-fields) | Present for Lean and model-reference competitions. | | `updatedAt` | string | ISO 8601 UTC timestamp for the usage snapshot. | ### Credit balance fields | Field | Type | Description | | :--------- | :----- | :---------------------------------------- | | `balance` | number | Remaining credit balance. | | `currency` | string | Currency label. `credit`. | | `rate` | number | Credit price per executed cell/problem. | ### Run usage fields | Field | Type | Description | | :----------- | :------ | :---------------------------------------------------- | | `total` | integer | Total run count. | | `last30Days` | integer | Runs created in the last 30 days. | ### Usage counter fields | Field | Type | Description | | :----------- | :------ | :---------------------------- | | `total` | integer | Total count. | | `last30Days` | integer | Count from the last 30 days. | **Example response for an equation-style competition.** Depending on the competition kind, a usage response carries either `cellsExecuted` or `problemsAttempted` (not both); see the field table above. ```json { "ok": true, "data": { "credits": { "balance": 124.5, "currency": "credit", "rate": 0.001 }, "runs": { "total": 187, "last30Days": 41 }, "cellsExecuted": { "total": 74800, "last30Days": 16400 }, "updatedAt": "2026-05-20T02:14:00Z" } } ``` ## Errors See [Errors](../errors.md). Endpoint-specific: | Code | HTTP | When | | :------------------------------- | :--: | :----------------------------------------------------------------------------- | | `RUN_PARAMS_INVALID` | 422 | Unknown model, problem-set / index / problem ID, or cheatsheet. | | `RUN_BUDGET_EXCEEDED` | 403 | Run would exceed your remaining credit balance. | | `RUN_NOT_CANCELLABLE` | 409 | Run is already terminal. | | `LEAN_SOURCE_INVALID` | 422 | `solverCode` failed the Lean 4 parser. `solver-participation` competitions only. | | `INVALID_MODEL_REFERENCE` | 422 | `modelName`, `commitHash`, or `problemSetId` is invalid for a model-reference run. | | `ACTIVE_RUN_EXISTS` | 409 | A model-reference run is already queued or running for this account. | | `COMPANION_COMPETITION_REQUIRED` | 403 | `solver-participation` competitions are gated to participants enrolled in a companion competition. | --- URL: https://docs.sair.foundation/docs/endpoints/playground/mathematics-distillation-stage1/ Markdown: https://docs.sair.foundation/docs-md/endpoints/playground/mathematics-distillation-stage1.md Source: docs/endpoints/playground/mathematics-distillation-stage1.md # Mathematics Distillation Stage 1 Playground Competition ID: `mathematics-distillation-challenge-equational-theories-stage1` Use this guide when automating cheatsheet practice runs for Mathematics Distillation Stage 1. ## Endpoint map Use `competitionId = mathematics-distillation-challenge-equational-theories-stage1`. | Task | Endpoint | Scope | | :--- | :--- | :--- | | Read accepted models | `GET /api/public/v1/competitions/{competitionId}/playground/models` | `playground.read` | | Read problem sets | `GET /api/public/v1/competitions/{competitionId}/playground/problem-sets` | `playground.read` | | Read a problem set | `GET /api/public/v1/competitions/{competitionId}/playground/problem-sets/{problemSetId}` | `playground.read` | | Create a practice run | `POST /api/public/v1/competitions/{competitionId}/playground/runs` | `playground.write` | | Read runs, results, and events | Shared Playground run endpoints | `playground.read` | Supporting resources: - Manage reusable cheatsheet text with the [Cheatsheets](../cheatsheets.md) endpoints. - Publish or browse shared cheatsheets with the [Contributor Network](../contributor-network.md) endpoints. - Submit the final formal entry with the [Stage 1 competition guide](../competitions/mathematics-distillation-stage1.md). ## Run payload Stage 1 uses `submissionSpec.kind: "cheatsheet"` for formal submissions. Playground runs compare model answers across selected problems and cheatsheet configurations. **Example request body** ```json { "models": ["llama-3.3-70b-instruct"], "problems": [ { "problemSet": "pset_hard3", "index": 0 }, { "custom": { "equation1": "x + y = y + x", "equation2": "y + x = x + y", "goldAnswer": true } } ], "configurations": [{ "cheatsheet": "cs_01HXYZ..." }], "repeat": 1 } ``` Payload fields: - `models[]` is required. Use model IDs from the Playground models endpoint. - `problems[]` is required. Use either problem-set references or inline custom equation problems. - `configurations[]` is optional. Each provided configuration references one private cheatsheet. Omit `configurations` for the unconditioned baseline; run responses return that baseline as `{}`. - `repeat` is optional. It re-runs each selected cell. ## Recommended workflow 1. Create or update a private cheatsheet with the [Cheatsheets](../cheatsheets.md) endpoints. 2. Read available models and problem sets for the competition. 3. Submit a Playground run with candidate cheatsheet configurations. 4. Read run results and compare configurations. 5. Submit the final cheatsheet text through the Stage 1 competition submission endpoint. See [Playground](../playground.md) for the shared endpoint field tables. --- URL: https://docs.sair.foundation/docs/endpoints/playground/mathematics-distillation-stage2/ Markdown: https://docs.sair.foundation/docs-md/endpoints/playground/mathematics-distillation-stage2.md Source: docs/endpoints/playground/mathematics-distillation-stage2.md # Mathematics Distillation Stage 2 Playground Competition ID: `mathematics-distillation-challenge-equational-theories-stage2` Use this guide when automating Lean solver practice runs for Mathematics Distillation Stage 2. ## Endpoint map Use `competitionId = mathematics-distillation-challenge-equational-theories-stage2`. | Task | Endpoint | Scope | | :--- | :--- | :--- | | Read accepted models | `GET /api/public/v1/competitions/{competitionId}/playground/models` | `playground.read` | | Read problem sets | `GET /api/public/v1/competitions/{competitionId}/playground/problem-sets` | `playground.read` | | Read a problem set | `GET /api/public/v1/competitions/{competitionId}/playground/problem-sets/{problemSetId}` | `playground.read` | | Create a practice run | `POST /api/public/v1/competitions/{competitionId}/playground/runs` | `playground.write` | | Read runs, results, and events | Shared Playground run endpoints | `playground.read` | Supporting resources: - Manage reusable solver code and custom Lean problems with the [Solver templates](../solver-templates.md) endpoints. - Publish or browse shared solver templates with the [Contributor Network](../contributor-network.md) endpoints. - Submit the final formal entry with the [Stage 2 competition guide](../competitions/mathematics-distillation-stage2.md). ## Run payload Stage 2 uses `submissionSpec.kind: "solver-participation"` for formal submissions. Playground runs execute one Lean 4 solver against selected Lean problems. **Example request body** ```json { "solverCode": "theorem solver : ... := by ...", "solverName": "induction-v3", "problemIds": ["prob_01HX...", "prob_01HY..."], "allowedModels": ["openai-gpt-oss-120b"] } ``` Payload fields: - `solverCode` is required. The run stores a copy of the Lean 4 source. - `solverName` is optional. Use it as a readable run label. - `problemIds[]` is required. Use problem IDs from problem sets or private custom problems. - `allowedModels[]` is optional. Use model IDs from the Playground models endpoint. ## Recommended workflow 1. Read `submissionSpec.catalog.tracks[]` and `submissionSpec.catalog.models[]` from the competition detail. 2. Create or update solver code with the [Solver templates](../solver-templates.md) endpoints. 3. Read available Playground models and Lean problem sets. 4. Submit a Playground run for the candidate solver. 5. Read run results and iterate on the solver source. 6. Submit the final `track`, `modelId`, and `solverCode` through the Stage 2 competition submission endpoint. See [Playground](../playground.md) for the shared endpoint field tables. --- URL: https://docs.sair.foundation/docs/endpoints/playground/modular-arithmetic-challenge/ Markdown: https://docs.sair.foundation/docs-md/endpoints/playground/modular-arithmetic-challenge.md Source: docs/endpoints/playground/modular-arithmetic-challenge.md # Modular Arithmetic Challenge Playground Competition ID: `modular-arithmetic-challenge` Use this guide when automating Playground evaluation for a pinned Hugging Face model revision before creating the official model-reference submission. ## Endpoint map Use `competitionId = modular-arithmetic-challenge`. | Task | Endpoint | Scope | | :--- | :--- | :--- | | Read evaluation problem sets | `GET /api/public/v1/competitions/{competitionId}/playground/problem-sets` | `playground.read` | | Create a model-reference evaluation run | `POST /api/public/v1/competitions/{competitionId}/playground/runs` | `playground.write` | | Read runs and case results | Shared Playground run endpoints | `playground.read` | For the official competition submission, use the competition submission endpoint documented in the [Modular Arithmetic Challenge competition guide](../competitions/modular-arithmetic-challenge.md). ## Run payload Modular Arithmetic Challenge uses `submissionSpec.kind: "model-reference"`. Playground runs evaluate one pinned Hugging Face model revision against one selected evaluation problem set. **Example request body** ```json { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567", "problemSetId": "mps_eval_01", "hfToken": "hf_...", "note": "Candidate revision for hard-tier evaluation." } ``` Payload fields: | Field | Type | Required | Description | | :------------- | :----- | :------: | :---------------------------------------------------------- | | `modelName` | string | Yes | Hugging Face model repository in `/` form. | | `commitHash` | string | Yes | Full 40-character lowercase hex commit SHA. Branch names are not accepted. | | `problemSetId` | string | Yes | Evaluation problem set ID from the Playground problem sets endpoint. | | `hfToken` | string | No | Hugging Face token for private or gated repositories. The token is never returned by the API. | | `note` | string | No | Private note shown in run history. Maximum `500` characters. | ## Submit a run ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/playground/runs" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567", "problemSetId": "mps_eval_01", "note": "Candidate revision for hard-tier evaluation." }' ``` The response returns a `runId`. Read the run and its results through the shared Playground run endpoints. ## Result model Completed runs include a model-reference summary with `overallAccuracy`, `highestTierAbove90`, per-tier scores, determinism, artifact size, and inference time. Run results return one row per evaluated case with `caseId`, `tierId`, `prediction`, `correct`, and `expected`. ## Workflow 1. Read the competition detail and confirm `submissionSpec.kind` is `model-reference`. 2. Read the available Playground problem sets and choose an evaluation set. 3. Submit a model-reference Playground run with `modelName`, `commitHash`, and `problemSetId`. 4. Poll the run and read case results until the run reaches a terminal status. 5. Create the official submission with the same `modelName` and `commitHash`. 6. Optionally publish the same model reference through Contributor Network. See [Playground](../playground.md) for the shared endpoint field tables. --- URL: https://docs.sair.foundation/docs/endpoints/contributor-network/ Markdown: https://docs.sair.foundation/docs-md/endpoints/contributor-network.md Source: docs/endpoints/contributor-network.md # Contributor Network Browse, publish, favorite, comment on, and view lineage for shared cheatsheets, Lean 4 solver templates, and model references. Benchmark data is available for platform-benchmarked items. Items are scoped to a competition. Call Contributor Network endpoints for a competition only when its `capabilities.contributorNetwork` is `true`. Each item may carry a human-shareable `publicCode`: `{competitionPrefix}-{6-digit number}` for cheatsheets and model references, such as `EQT01-000042` or `MAC01-000042`; solver templates insert an `S` or `M` track letter, such as `EQT01-S00042`. Track and model values for solver-template items are IDs from the competition's `submissionSpec.catalog.tracks[].id` and `submissionSpec.catalog.models[].id`. ## List items ```http GET /api/public/v1/contributor-network/items ``` **Scope**: `contributor-network.read`. This endpoint is cursor-paginated. Lists visible Contributor Network items. The list view omits `content`, `solverCode`, `modelName`, `commitHash`, and `parent`; call [Get an item](#get-an-item) for the full payload. ### Query parameters | Parameter | Type | Required | Description | | :-------------- | :------ | :------: | :-------------------------------------------------------------------------- | | `competitionId` | string | No | Restrict to one competition. | | `kind` | string | No | `cheatsheet`, `solver-template`, or `model-reference`. | | `track` | string | No | Track ID filter, such as `solo` or `marathon`. Applies when `kind=solver-template`. | | `search` | string | No | Substring match on title and author display name. | | `publicCode` | string | No | Exact lookup by shareable code. Returns a single-item page or empty page. | | `sort` | string | No | `newest` (default) or `earliest`. | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. Ignored with `publicCode`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------ | :----------------------------------- | | `items[]` | [ContributorNetworkItemSummary](#contributor-network-item-summary-fields)[] | Contributor Network item summaries. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Contributor Network item summary fields | Field | Type | Description | | :------------------- | :------------- | :----------------------------------------------------------- | | `id` | string | Contributor Network item ID. | | `publicCode` | string \| null | Human-shareable code. | | `competitionId` | string | Competition slug. | | `kind` | string | `cheatsheet`, `solver-template`, or `model-reference`. | | `title` | string | Item title. | | `remark` | string \| null | Short note shown on the item card. | | `author` | [Author](#author-fields) | Item author. | | `track` | string \| null | Solver-template track ID; `null` otherwise. | | `modelId` | string \| null | Solver-template model ID; `null` otherwise. | | `favoriteCount` | integer | Number of favorites. | | `viewerHasFavorited` | boolean | Whether the calling account has favorited the item. | | `publishedAt` | string | ISO 8601 UTC publish timestamp. | ### Contributor Network item fields `ContributorNetworkItem` includes all [ContributorNetworkItemSummary](#contributor-network-item-summary-fields) fields plus the matching kind-specific payload and parent: | Field | Type | Description | | :----------- | :------------- | :----------------------------------------------------------- | | `content` | string | Cheatsheet body. Present for `kind=cheatsheet`. | | `solverCode` | string | Lean source. Present for `kind=solver-template`. | | `modelName` | string | Hugging Face model name. Present for `kind=model-reference`. | | `commitHash` | string | Model commit SHA. Present for `kind=model-reference`. | | `parent` | [ContributorNetworkParent](#contributor-network-parent-fields) \| null | Parent item this item was forked from, or `null`. | ### Author fields | Field | Type | Description | | :------------ | :----- | :-------------------------- | | `sairId` | string | SAIR ID of the author. | | `displayName` | string | Display name of the author. | ### Contributor Network parent fields | Field | Type | Description | | :----------- | :------------- | :-------------------- | | `id` | string | Parent item ID. | | `publicCode` | string \| null | Human-shareable code. | ```json { "ok": true, "data": { "items": [ { "id": "cn_01HX0PA...", "publicCode": "EQT01-000042", "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "kind": "cheatsheet", "title": "Modular tricks v3", "remark": "Adds prime-modulus shortcut.", "author": { "sairId": "U-9f7e1c00", "displayName": "alice" }, "track": null, "modelId": null, "favoriteCount": 4, "viewerHasFavorited": true, "publishedAt": "2026-04-12T09:00:00Z" }, { "id": "cn_01JMAC...", "publicCode": "MAC01-000042", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "title": "your-team/your-model", "remark": "Model revision for public comparison.", "author": { "sairId": "U-1a2b3c00", "displayName": "bob" }, "track": null, "modelId": null, "favoriteCount": 2, "viewerHasFavorited": false, "publishedAt": "2026-06-08T09:00:00Z" } ], "nextCursor": null } } ``` ## Get an item ```http GET /api/public/v1/contributor-network/items/{itemId} ``` **Scope**: `contributor-network.read`. Returns one item with its full payload: `content` for `cheatsheet`, `solverCode` for `solver-template`, or `modelName` / `commitHash` for `model-reference`. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Response fields Returns a [ContributorNetworkItem](#contributor-network-item-fields). **Example response for a cheatsheet item** ```json { "ok": true, "data": { "id": "cn_01HX0PA...", "publicCode": "EQT01-000042", "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "kind": "cheatsheet", "title": "Modular tricks v3", "remark": "Adds prime-modulus shortcut.", "author": { "sairId": "U-9f7e1c00", "displayName": "alice" }, "track": null, "modelId": null, "content": "When the modulus is prime, use Fermat's little theorem...", "parent": { "id": "cn_01HW...", "publicCode": "EQT01-000031" }, "favoriteCount": 4, "viewerHasFavorited": true, "publishedAt": "2026-04-12T09:00:00Z" } } ``` **Example response for a model-reference item** ```json { "ok": true, "data": { "id": "cn_01JMAC...", "publicCode": "MAC01-000042", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "title": "your-team/your-model", "remark": "Model revision for public comparison.", "author": { "sairId": "U-1a2b3c00", "displayName": "bob" }, "track": null, "modelId": null, "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567", "parent": null, "favoriteCount": 2, "viewerHasFavorited": false, "publishedAt": "2026-06-08T09:00:00Z" } } ``` ## Publish ```http POST /api/public/v1/contributor-network/items ``` **Scope**: `contributor-network.write`. Publishes a saved resource, a submitted text body, inline text, or model reference. The discriminator is `source.from`. ### Request fields | Field | Type | Required | Description | | :--------------------------------- | :------------- | :------: | :----------------------------------------------------------------- | | `competitionId` | string | Yes | Competition this item belongs to. | | `kind` | string | Yes | `cheatsheet`, `solver-template`, or `model-reference`. Must match `source`. | | `source` | [PublishSource](#publish-source-fields) | Yes | Source resource, inline body, or model reference to publish. | | `parentContributorNetworkItemId` | string \| null | No | Parent item this publication forks from. | | `remark` | string | No | Short note shown on the item card. | | `track` | string | Conditional | Track ID from `submissionSpec.catalog.tracks[].id`. Required for `kind=solver-template`. | | `modelId` | string | Conditional | Model ID from `submissionSpec.catalog.models[].id`. Required for `kind=solver-template`. | ### Publish source fields | Field | Type | Required | Description | | :----------------- | :----- | :------: | :----------------------------------------------------------------- | | `from` | string | Yes | `cheatsheet`, `solver-template`, `submission`, `inline`, or `model-reference`. | | `cheatsheetId` | string | Conditional | Required when `from=cheatsheet`. | | `solverTemplateId` | string | Conditional | Required when `from=solver-template`. | | `submissionId` | string | Conditional | Required when `from=submission`. Publishes the stored text body of a `cheatsheet` (`content`) or `solver-participation` (`solverCode`) submission. `model-reference` and other bodiless kinds are not publishable this way. | | `title` | string | Conditional | Required when `from=inline`. | | `content` | string | Conditional | Inline cheatsheet body. Required when `from=inline` and `kind=cheatsheet`. | | `solverCode` | string | Conditional | Inline Lean source. Required when `from=inline` and `kind=solver-template`. | | `modelName` | string | Conditional | Hugging Face model name. Required when `from=model-reference`. | | `commitHash` | string | Conditional | Full 40-character lowercase hex commit SHA. Required when `from=model-reference`. | **Example request body for a cheatsheet** ```json { "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "kind": "cheatsheet", "source": { "from": "cheatsheet", "cheatsheetId": "cs_01HXYZ..." }, "parentContributorNetworkItemId": null, "remark": "Adds prime-modulus shortcut." } ``` **Example request body for a model-reference** ```json { "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "source": { "from": "model-reference", "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "parentContributorNetworkItemId": null, "remark": "Model revision for public comparison." } ``` For `kind=model-reference`, `track` and `modelId` must be omitted, and the item `title` is set to `modelName`. ### Response fields Returns the created [ContributorNetworkItem](#contributor-network-item-fields) with its full payload. ## Withdraw an item ```http DELETE /api/public/v1/contributor-network/items/{itemId} ``` **Scope**: `contributor-network.write`. Withdraws an item authored by the calling account. Withdrawn items disappear from listings, direct lookups, graphs, and benchmark results. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Response Returns `204` with no body. ## Favorite an item ```http PUT /api/public/v1/contributor-network/items/{itemId}/favorite ``` **Scope**: `contributor-network.write`. Sets the caller's favorite flag for an item. This operation is idempotent. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Response fields | Field | Type | Description | | :-------------- | :------ | :--------------------------------------- | | `favorited` | boolean | Always `true` on success. | | `favoriteCount` | integer | Updated favorite count for the item. | **Example response** ```json { "ok": true, "data": { "favorited": true, "favoriteCount": 5 } } ``` ## Unfavorite an item ```http DELETE /api/public/v1/contributor-network/items/{itemId}/favorite ``` **Scope**: `contributor-network.write`. Clears the caller's favorite flag for an item. This operation is idempotent. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Response Returns `204` with no body. Refetch the item to read the updated `favoriteCount`. ## List comments ```http GET /api/public/v1/contributor-network/items/{itemId}/comments ``` **Scope**: `contributor-network.read`. This endpoint is cursor-paginated. Lists comments for an item, sorted oldest-first. Soft-deleted comments remain in the list for thread continuity; `body` and `author.displayName` are `null`. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :-------------------------------------------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------ | :----------------------------------- | | `items[]` | [Comment](#comment-fields)[] | Comment records. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Comment fields | Field | Type | Description | | :------------------- | :------------- | :----------------------------------------------------- | | `id` | string | Comment ID. | | `author` | [CommentAuthor](#comment-author-fields) | Comment author. | | `body` | string \| null | Comment body, or `null` when deleted. | | `createdAt` | string | ISO 8601 UTC creation timestamp. | | `isDeleted` | boolean | Whether the comment has been soft-deleted. | ### Comment author fields | Field | Type | Description | | :------------ | :------------- | :------------------------------------- | | `sairId` | string | SAIR ID of the comment author. | | `displayName` | string \| null | Display name, or `null` when deleted. | ```json { "ok": true, "data": { "items": [ { "id": "cmt_01HX...", "author": { "sairId": "U-...", "displayName": "alice" }, "body": "Nice trick — what about composite moduli?", "createdAt": "2026-04-13T10:00:00Z", "isDeleted": false } ], "nextCursor": null } } ``` ## Create a comment ```http POST /api/public/v1/contributor-network/items/{itemId}/comments ``` **Scope**: `contributor-network.write`. Creates a comment on an item. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Request fields | Field | Type | Required | Description | | :----- | :----- | :------: | :------------------------------------------- | | `body` | string | Yes | Comment body. Must be non-empty after trim, maximum 5 000 characters. | ### Response fields Returns the created [Comment](#comment-fields). ## Delete a comment ```http DELETE /api/public/v1/contributor-network/items/{itemId}/comments/{commentId} ``` **Scope**: `contributor-network.write`. Soft-deletes a comment. You may delete your own comments, and item authors may delete any comment on their own items. ### Path parameters | Parameter | Type | Required | Description | | :---------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | | `commentId` | string | Yes | Comment ID. | ### Response Returns `204` with no body. ## Get item lineage graph ```http GET /api/public/v1/contributor-network/items/{itemId}/graph ``` **Scope**: `contributor-network.read`. Returns the connected subgraph rooted at `itemId`: ancestors back to the root and descendants. `depth` is relative to the anchor; ancestors are negative, and the anchor is `0`. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Response fields | Field | Type | Description | | :-------- | :-------- | :--------------------------- | | `nodes[]` | [GraphNode](#graph-node-fields)[] | Graph nodes. | | `edges[]` | [GraphEdge](#graph-edge-fields)[] | Directed parent-child edges. | ### Graph node fields | Field | Type | Description | | :----------- | :------------- | :---------------------------- | | `id` | string | Item ID. | | `publicCode` | string \| null | Human-shareable code. | | `title` | string | Item title. | | `kind` | string | `cheatsheet`, `solver-template`, or `model-reference`. | | `depth` | integer | Depth relative to the anchor. | ### Graph edge fields | Field | Type | Description | | :------- | :----- | :-------------- | | `parent` | string | Parent item ID. | | `child` | string | Child item ID. | ```json { "ok": true, "data": { "nodes": [ { "id": "cn_01HW...", "publicCode": "EQT01-000031", "title": "...", "kind": "cheatsheet", "depth": -1 }, { "id": "cn_01HX0PA...", "publicCode": "EQT01-000042", "title": "...", "kind": "cheatsheet", "depth": 0 } ], "edges": [{ "parent": "cn_01HW...", "child": "cn_01HX0PA..." }] } } ``` ## Get global lineage graph ```http GET /api/public/v1/contributor-network/graph ``` **Scope**: `contributor-network.read`. Returns the lineage graph across all visible items. The response is capped at 2 000 nodes; add filters if you receive `GRAPH_TOO_LARGE`. ### Query parameters | Parameter | Type | Required | Description | | :-------------- | :----- | :------: | :---------------------------------------- | | `competitionId` | string | No | Restrict to one competition. | | `kind` | string | No | `cheatsheet`, `solver-template`, or `model-reference`. | ### Response fields | Field | Type | Description | | :-------- | :------------------------------ | :----------------------------------------------- | | `nodes[]` | [GraphNode](#graph-node-fields)[] | Graph nodes. In the global graph, `depth` is `0`. | | `edges[]` | [GraphEdge](#graph-edge-fields)[] | Directed parent-child edges. | ## List benchmarks ```http GET /api/public/v1/contributor-network/benchmarks ``` **Scope**: `contributor-network.read`. This endpoint is cursor-paginated. Lists platform-run benchmark summaries for published items. Benchmarks are not user-triggered; the platform sweeps newly published items on a schedule. ### Query parameters | Parameter | Type | Required | Description | | :--------------- | :------ | :------: | :------------------------------------------------------------ | | `competitionId` | string | No | Restrict to one competition. | | `problemSet` | string | No | Restrict to one benchmark problem set. | | `modelId` | string | No | Restrict to one model. | | `minAvgAccuracy` | number | No | Minimum average accuracy, from `0` to `1`. | | `search` | string | No | Substring match on author display name or team number. | | `sortBy` | string | No | `avgAccuracy` (default), `publishedAt`, or `scoredTotal`. | | `sortDirection` | string | No | `desc` (default) or `asc`. | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------- | :----------------------------------- | | `items[]` | [BenchmarkSummary](#benchmark-summary-fields)[] | Benchmark summary rows. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Benchmark summary fields | Field | Type | Description | | :--------------------- | :------------- | :------------------------------------------------ | | `itemId` | string | Contributor Network item ID. | | `publicCode` | string \| null | Human-shareable code. | | `title` | string | Item title. | | `author` | [Author](#author-fields) | Item author. | | `team` | [BenchmarkTeam](#benchmark-team-fields) \| null | Team metadata when available. | | `status` | string | `pending`, `running`, `done`, `failed`, or `cancelled`. | | `problemSet` | string | Benchmark problem set ID. | | `modelIds` | string[] | Models included in this row. | | `avgAccuracy` | number \| null | Average accuracy. Only `done` rows expose scores. | | `scoredCorrect` | integer \| null | Correct scored examples. Only `done` rows expose scores. | | `scoredTotal` | integer \| null | Total scored examples. Only `done` rows expose scores. | | `viewerHasFavorited` | boolean | Whether caller has favorited the item. | | `publishedAt` | string | ISO 8601 UTC item publish timestamp. | | `completedAt` | string \| null | ISO 8601 UTC completion timestamp, or `null`. | ### Benchmark team fields | Field | Type | Description | | :----------- | :------------- | :------------------ | | `teamNumber` | string | Public team number. | ```json { "ok": true, "data": { "items": [ { "itemId": "cn_01HX0PA...", "publicCode": "EQT01-000042", "title": "Modular tricks v3", "author": { "sairId": "U-...", "displayName": "alice" }, "team": { "teamNumber": "EQT01-T00001" }, "status": "done", "problemSet": "pset_hard3", "modelIds": ["llama-3.3-70b-instruct", "openai-gpt-oss-120b"], "avgAccuracy": 0.78, "scoredCorrect": 312, "scoredTotal": 400, "viewerHasFavorited": false, "publishedAt": "2026-04-12T09:00:00Z", "completedAt": "2026-04-12T09:18:44Z" } ], "nextCursor": null } } ``` ## Get item benchmark ```http GET /api/public/v1/contributor-network/items/{itemId}/benchmark ``` **Scope**: `contributor-network.read`. Returns per-model benchmark breakdown for one item. ### Path parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------- | | `itemId` | string | Yes | Contributor Network item ID. | ### Response fields | Field | Type | Description | | :-------------------------- | :------------- | :----------------------------------------------- | | `itemId` | string | Contributor Network item ID. | | `status` | string | `pending`, `running`, `done`, `failed`, or `cancelled`. | | `problemSet` | string | Benchmark problem set ID. | | `perModel[]` | [BenchmarkModelMetric](#benchmark-model-metric-fields)[] | Per-model metrics. | | `completedAt` | string \| null | ISO 8601 UTC completion timestamp, or `null`. | ### Benchmark model metric fields | Field | Type | Description | | :-------------- | :------------- | :------------------------------- | | `modelId` | string | Model ID. | | `scoredCorrect` | integer \| null | Correct scored examples. Only `done` rows expose scores. | | `scoredTotal` | integer \| null | Total scored examples. Only `done` rows expose scores. | | `avgAccuracy` | number \| null | Average accuracy for this model. | ```json { "ok": true, "data": { "itemId": "cn_01HX0PA...", "status": "done", "problemSet": "pset_hard3", "perModel": [ { "modelId": "llama-3.3-70b-instruct", "scoredCorrect": 152, "scoredTotal": 200, "avgAccuracy": 0.76 }, { "modelId": "openai-gpt-oss-120b", "scoredCorrect": 160, "scoredTotal": 200, "avgAccuracy": 0.8 } ], "completedAt": "2026-04-12T09:18:44Z" } } ``` ## Errors See [Errors](../errors.md). Endpoint-specific: | Code | HTTP | When | | :--------------------- | :--: | :-------------------------------------------------------------------- | | `RESOURCE_NOT_OWNED` | 403 | Source cheatsheet, solver template, or submission is not owned by caller. | | `ITEM_NOT_AUTHOR` | 403 | Caller cannot withdraw or moderate this item. | | `COMMENT_NOT_AUTHOR` | 403 | Caller can only delete comments they can moderate. | | `COMMENT_BODY_INVALID` | 422 | Comment body is empty or too long. | | `GRAPH_TOO_LARGE` | 413 | Graph exceeds 2 000 nodes; add filters. | | `RESOURCE_FIELD_INVALID` | 422 | Filter or request field is invalid for the endpoint, including invalid model names or commit hashes. | | `NOT_FOUND` | 404 | Withdrawn, not-visible, and non-existent items are hidden uniformly. | --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/list-competitions/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/list-competitions.md Source: docs/endpoints/competitions/api/list-competitions.md # List competitions ```http GET /api/public/v1/competitions ``` **Scope**: `competition.read`. Returns the active competitions visible through the Public API. The endpoint is cursor-paginated. ## Query parameters | Parameter | Type | Required | Default | Description | | :--- | :--- | :---: | :--- | :--- | | `cursor` | string | No | — | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | `25` | Page size from `1` through `100`. | ## Response DTO ```ts type ListCompetitionsResponse = { ok: true; data: { items: CompetitionSummary[]; nextCursor: string | null; }; }; type CompetitionSummary = { id: string; title: string; submissionKind: "cheatsheet" | "igp24-polynomial" | "model-reference" | "solver-participation"; capabilities: { playground: boolean; contributorNetwork: boolean; leaderboard: boolean; }; }; ``` ## Example request ```bash curl "https://api.sair.foundation/api/public/v1/competitions?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ## Complete example response ```json { "ok": true, "data": { "items": [ { "id": "igp24", "title": "Inverse Galois Problem (IGP24)", "submissionKind": "igp24-polynomial", "capabilities": { "playground": false, "contributorNetwork": true, "leaderboard": true } }, { "id": "mathematics-distillation-challenge-equational-theories-stage1", "title": "Mathematics Distillation Challenge: Equational Theories - Stage 1", "submissionKind": "cheatsheet", "capabilities": { "playground": true, "contributorNetwork": true, "leaderboard": false } }, { "id": "mathematics-distillation-challenge-equational-theories-stage2", "title": "Mathematics Distillation Challenge: Equational Theories - Stage 2", "submissionKind": "solver-participation", "capabilities": { "playground": true, "contributorNetwork": true, "leaderboard": false } }, { "id": "modular-arithmetic-challenge", "title": "Modular Arithmetic Challenge", "submissionKind": "model-reference", "capabilities": { "playground": true, "contributorNetwork": true, "leaderboard": false } } ], "nextCursor": null } } ``` `capabilities.leaderboard` reflects publication state and may change from `false` to `true` without changing the DTO. ## Errors See [Errors](../../../errors.md) for authentication, scope, cursor, and rate-limit errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-competition/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-competition.md Source: docs/endpoints/competitions/api/get-competition.md # Get competition detail ```http GET /api/public/v1/competitions/{competitionId} ``` **Scope**: `competition.read`. Returns one competition and the complete `submissionSpec` clients must use to construct submissions. ## Path parameters | Parameter | Type | Required | Description | | :--- | :--- | :---: | :--- | | `competitionId` | string | Yes | Competition ID returned by [List competitions](./list-competitions.md). | ## Response DTO ```ts type GetCompetitionResponse = { ok: true; data: { id: string; title: string; description: string | null; capabilities: { playground: boolean; contributorNetwork: boolean; leaderboard: boolean; }; leaderboardPublished: boolean; submissionSpec: SubmissionSpec; }; }; ``` `SubmissionSpec` is a kind-specific object. The examples below expand every field instead of replacing schemas with placeholders. See [Submission spec](../../../reference/submission-spec.md) for field semantics. ## Modular Arithmetic Challenge ### Example request ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ### Complete example response ```json { "ok": true, "data": { "id": "modular-arithmetic-challenge", "title": "Modular Arithmetic Challenge", "description": "Submit a pinned model artifact for modular arithmetic evaluation.", "capabilities": { "playground": true, "contributorNetwork": true, "leaderboard": false }, "leaderboardPublished": false, "submissionSpec": { "kind": "model-reference", "schema": { "type": "object", "required": ["modelName", "commitHash"], "properties": { "modelName": { "type": "string", "pattern": "^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$" }, "commitHash": { "type": "string", "pattern": "^[0-9a-fA-F]{40}$" } }, "additionalProperties": false }, "metaSchema": { "type": "object", "properties": { "description": { "type": "string", "maxLength": 5000 } }, "additionalProperties": false }, "permission": { "teamRole": "owner" }, "window": { "opensAt": "2026-04-01T00:00:00Z", "closesAt": "2026-09-30T23:59:59Z" }, "limits": { "maxBytes": null, "maxEntries": 3 } } } } ``` ## Mathematics Distillation Stage 1 ### Example request ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ### Complete example response ```json { "ok": true, "data": { "id": "mathematics-distillation-challenge-equational-theories-stage1", "title": "Mathematics Distillation Challenge: Equational Theories - Stage 1", "description": "Submit a cheatsheet for formal evaluation.", "capabilities": { "playground": true, "contributorNetwork": true, "leaderboard": false }, "leaderboardPublished": false, "submissionSpec": { "kind": "cheatsheet", "schema": { "type": "object", "required": ["content"], "properties": { "content": { "type": "string", "minLength": 1 } }, "additionalProperties": false }, "metaSchema": { "type": "object", "properties": { "description": { "type": "string", "maxLength": 5000 }, "contributorNetworkItemId": { "type": "string" } }, "additionalProperties": false }, "permission": { "teamRole": "owner" }, "window": { "opensAt": "2026-04-01T00:00:00Z", "closesAt": "2026-09-30T23:59:59Z" }, "limits": { "maxBytes": 10240, "maxEntries": 1 } } } } ``` ## Mathematics Distillation Stage 2 ### Example request ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ### Complete example response ```json { "ok": true, "data": { "id": "mathematics-distillation-challenge-equational-theories-stage2", "title": "Mathematics Distillation Challenge: Equational Theories - Stage 2", "description": "Submit Lean solver source for one competition track.", "capabilities": { "playground": true, "contributorNetwork": true, "leaderboard": false }, "leaderboardPublished": false, "submissionSpec": { "kind": "solver-participation", "schema": { "type": "object", "required": ["track", "modelId", "solverCode"], "properties": { "track": { "type": "string", "enum": ["solo", "marathon"] }, "modelId": { "type": "string", "minLength": 1, "maxLength": 100, "enum": ["openai-gpt-oss-120b"] }, "solverCode": { "type": "string", "minLength": 1 } }, "additionalProperties": false }, "metaSchema": { "type": "object", "properties": { "description": { "type": "string", "maxLength": 5000 }, "contributorNetworkItemId": { "type": "string" } }, "additionalProperties": false }, "permission": { "teamRole": "owner" }, "window": { "opensAt": "2026-04-01T00:00:00Z", "closesAt": "2026-09-30T23:59:59Z" }, "limits": { "maxBytes": 10240, "maxEntries": 1 }, "catalog": { "tracks": [ { "id": "solo", "label": "Solo" }, { "id": "marathon", "label": "Marathon" } ], "models": [ { "id": "openai-gpt-oss-120b", "name": "OpenAI: GPT OSS 120B" } ] } } } } ``` Catalog values are live configuration. Always use the IDs returned by the response rather than hard-coding the example IDs. ## Inverse Galois Problem (IGP24) ### Example request ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ### Complete example response ```json { "ok": true, "data": { "id": "igp24", "title": "Inverse Galois Problem (IGP24)", "description": "Submit degree 24 polynomial candidates.", "capabilities": { "playground": false, "contributorNetwork": true, "leaderboard": true }, "leaderboardPublished": true, "submissionSpec": { "kind": "igp24-polynomial", "schema": { "type": "object", "required": ["polynomials"], "properties": { "polynomials": { "type": "array", "minItems": 1, "maxItems": 1000, "items": { "type": "string" } } }, "additionalProperties": false }, "metaSchema": { "type": "object", "properties": { "description": { "type": "string", "maxLength": 500 } }, "additionalProperties": false }, "permission": { "teamRole": "activeMember" }, "window": { "opensAt": "2026-04-01T00:00:00Z", "closesAt": "2026-09-30T23:59:59Z" }, "limits": { "maxBytes": 1000000, "maxEntries": 1, "maxPolynomials": 1000, "maxRequestBytes": 1310720 } } } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `404` | `NOT_FOUND` | The competition is not active or does not exist. | See [Errors](../../../errors.md) for the common error envelope. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-my-participation/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-my-participation.md Source: docs/endpoints/competitions/api/get-my-participation.md # Get my competition participation ```http GET /api/public/v1/competitions/{competitionId}/me ``` **Scope**: `competition.read`. Returns enrollment, team context, submission eligibility, and Contributor Network quota for the calling account. ## Path parameters | Parameter | Type | Required | Description | | :--- | :--- | :---: | :--- | | `competitionId` | string | Yes | Competition ID returned by [List competitions](./list-competitions.md). | ## Response DTO ```ts type GetMyCompetitionParticipationResponse = { ok: true; data: { competitionId: string; enrolled: boolean; team: { teamId: string; teamName: string; teamNumber: string | null; role: string; } | null; canSubmit: boolean; submitBlockedReason: | "EMAIL_NOT_VERIFIED" | "NOT_ENROLLED" | "NOT_TEAM_OWNER" | "BEFORE_WINDOW" | "AFTER_WINDOW" | null; contributorNetwork: { publishCap: number; activeCount: number; capRemaining: number; }; }; }; ``` ## Modular Arithmetic Challenge ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "competitionId": "modular-arithmetic-challenge", "enrolled": true, "team": { "teamId": "teamv2_0e52064c5e864ffd9f4e7532f4de63d0", "teamName": "Example Team", "teamNumber": "MAC-T00001", "role": "owner" }, "canSubmit": true, "submitBlockedReason": null, "contributorNetwork": { "publishCap": 50, "activeCount": 4, "capRemaining": 46 } } } ``` ## Mathematics Distillation Stage 1 ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "enrolled": true, "team": { "teamId": "sair_01HUSER", "teamName": "Ada Example", "teamNumber": "MDC1-T00001", "role": "owner" }, "canSubmit": true, "submitBlockedReason": null, "contributorNetwork": { "publishCap": 50, "activeCount": 2, "capRemaining": 48 } } } ``` ## Mathematics Distillation Stage 2 ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "competitionId": "mathematics-distillation-challenge-equational-theories-stage2", "enrolled": true, "team": { "teamId": "teamv2_19ac4fb862d64502ae7837852e18fb4a", "teamName": "Lean Explorers", "teamNumber": "MDC2-T00003", "role": "owner" }, "canSubmit": true, "submitBlockedReason": null, "contributorNetwork": { "publishCap": 50, "activeCount": 6, "capRemaining": 44 } } } ``` ## Inverse Galois Problem (IGP24) IGP24 allows an active team member to submit, so `team.role` does not need to be `owner` when `canSubmit` is `true`. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "competitionId": "igp24", "enrolled": true, "team": { "teamId": "teamv2_a7a863ce80e6447881ad068bab82498e", "teamName": "Galois Wranglers", "teamNumber": "IGP24-T00007", "role": "member" }, "canSubmit": true, "submitBlockedReason": null, "contributorNetwork": { "publishCap": 50, "activeCount": 1, "capRemaining": 49 } } } ``` ## Not enrolled example ```json { "ok": true, "data": { "competitionId": "modular-arithmetic-challenge", "enrolled": false, "team": null, "canSubmit": false, "submitBlockedReason": "NOT_ENROLLED", "contributorNetwork": { "publishCap": 50, "activeCount": 0, "capRemaining": 50 } } } ``` ## Errors See [Errors](../../../errors.md) for common authentication, scope, not-found, and rate-limit errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-leaderboard/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-leaderboard.md Source: docs/endpoints/competitions/api/get-leaderboard.md # Get competition leaderboard ```http GET /api/public/v1/competitions/{competitionId}/leaderboard ``` **Scope**: `competition.read`. Returns a cursor-paginated public leaderboard. Before publication, every competition returns an empty page with `meta.published: false`. ## Parameters | Parameter | Location | Type | Required | Default | Description | | :--- | :--- | :--- | :---: | :--- | :--- | | `competitionId` | path | string | Yes | — | Competition ID. | | `cursor` | query | string | No | — | Opaque cursor from the previous page. | | `limit` | query | integer | No | `25` | Page size from `1` through `100`. | ## Response DTO ```ts type GetCompetitionLeaderboardResponse = { ok: true; data: { items: LeaderboardEntry[]; nextCursor: string | null; meta: { published: boolean; publishedAt: string | null; }; }; }; type LeaderboardEntry = { rank: number; teamId?: string; teamNumber: string | null; teamName: string; score: number; scoreablePairs?: number; }; ``` Stage 1 entries do not expose `teamId`. IGP24 entries additionally include `scoreablePairs`. ## Modular Arithmetic Challenge example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/leaderboard?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "rank": 1, "teamId": "teamv2_0e52064c5e864ffd9f4e7532f4de63d0", "teamNumber": "MAC-T00001", "teamName": "Example Team", "score": 0.987 } ], "nextCursor": null, "meta": { "published": true, "publishedAt": "2026-10-01T00:00:00Z" } } } ``` ## Mathematics Distillation Stage 1 example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/leaderboard?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "rank": 1, "teamNumber": "MDC1-T00001", "teamName": "MDC1-T00001", "score": 0.941 } ], "nextCursor": null, "meta": { "published": true, "publishedAt": "2026-10-01T00:00:00Z" } } } ``` ## Mathematics Distillation Stage 2 example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/leaderboard?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "rank": 1, "teamId": "teamv2_19ac4fb862d64502ae7837852e18fb4a", "teamNumber": "MDC2-T00003", "teamName": "Lean Explorers", "score": 0.963 } ], "nextCursor": null, "meta": { "published": true, "publishedAt": "2026-10-01T00:00:00Z" } } } ``` ## IGP24 example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/leaderboard?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "rank": 1, "teamId": "teamv2_a7a863ce80e6447881ad068bab82498e", "teamNumber": "IGP24-T00007", "teamName": "Galois Wranglers", "score": 42.75, "scoreablePairs": 57 } ], "nextCursor": "eyJhZnRlciI6WzQyLjc1LCJ0ZWFtdjJfYTdhODYzY2U4MGU2NDQ3ODgxYWQwNjhiYWI4MjQ5OGUiXX0", "meta": { "published": true, "publishedAt": "2026-08-01T00:00:00Z" } } } ``` ## Before publication ```json { "ok": true, "data": { "items": [], "nextCursor": null, "meta": { "published": false, "publishedAt": null } } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `400` | `MALFORMED_BODY` | The cursor is invalid. | See [Errors](../../../errors.md) for common errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-my-standing/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-my-standing.md Source: docs/endpoints/competitions/api/get-my-standing.md # Get my leaderboard standing ```http GET /api/public/v1/competitions/{competitionId}/leaderboard/me ``` **Scope**: `competition.read`. Returns the calling account's current public leaderboard entry. The current backend returns a populated entry only for a published IGP24 leaderboard; other competitions return `entry: null`. ## Path parameters | Parameter | Type | Required | Description | | :--- | :--- | :---: | :--- | | `competitionId` | string | Yes | Competition ID. | ## Response DTO ```ts type GetMyLeaderboardStandingResponse = { ok: true; data: { entry: { rank: number; teamId: string; teamNumber: string | null; teamName: string; score: number; scoreablePairs: number; } | null; }; }; ``` ## IGP24 example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/leaderboard/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "entry": { "rank": 7, "teamId": "teamv2_a7a863ce80e6447881ad068bab82498e", "teamNumber": "IGP24-T00007", "teamName": "Galois Wranglers", "score": 4.7321, "scoreablePairs": 12 } } } ``` ## Null example This complete response applies when the leaderboard is unpublished, the caller has no eligible team, the team has no scoring placement, or the competition is not IGP24. For the three non-IGP24 competitions, the concrete requests are: ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/leaderboard/me" \ -H "Authorization: Bearer $SAIR_API_KEY" curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/leaderboard/me" \ -H "Authorization: Bearer $SAIR_API_KEY" curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/leaderboard/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "entry": null } } ``` ## Errors See [Errors](../../../errors.md) for common authentication, scope, not-found, and rate-limit errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-team-placements/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-team-placements.md Source: docs/endpoints/competitions/api/get-team-placements.md # Get IGP24 team placements ```http GET /api/public/v1/competitions/{competitionId}/leaderboard/teams/{teamId}/placements ``` **Scope**: `competition.read`. Returns one IGP24 team's public scoring placements. The response never includes user identities, email addresses, member identities, or submitted polynomial coefficients. This endpoint is available only when `submissionSpec.kind` is `igp24-polynomial`. Before leaderboard publication it returns an empty, unpublished response without querying placement data. ## Parameters | Parameter | Location | Type | Required | Default | Description | | :--- | :--- | :--- | :---: | :--- | :--- | | `competitionId` | path | string | Yes | — | IGP24 competition ID. Use `igp24`. | | `teamId` | path | string | Yes | — | Team ID from an IGP24 leaderboard entry or leaderboard search result. | | `cursor` | query | string | No | — | Opaque cursor returned as `nextCursor` by the previous page. | | `limit` | query | integer | No | `25` | Page size from `1` through `100`. | ## Response DTO ```ts type GetIgp24TeamPlacementsResponse = { ok: true; data: { teamId: string; placements: Igp24Placement[]; nextCursor: string | null; totalPlacements: number; meta: Igp24PublicationMeta; }; }; type Igp24PublicationMeta = { published: boolean; publishedAt: string | null; }; type Igp24Placement = { t: number; r: number; label: string; points: number; kTeams: number; scoringDiscAbs: string; minScoringDiscAbs: string; discSource: 'exact_nfdisc' | 'mixed_disc'; isSolvable: boolean | null; baselineUnlocked?: boolean; baselineDiscAbs?: string | null; }; ``` ## Response fields | Field | Type | Description | | :--- | :--- | :--- | | `teamId` | string | Team whose placements were requested. | | `placements[]` | `Igp24Placement[]` | Current page, ordered by points descending and then by `t` and `r` ascending. | | `nextCursor` | string \| null | Cursor for the next page, or `null` when this is the final page. | | `totalPlacements` | integer | Total number of public placements held by the team across all pages. | | `meta.published` | boolean | Whether the leaderboard is published. | | `meta.publishedAt` | string \| null | ISO 8601 UTC publication time, or `null` before publication. | ### `Igp24Placement` | Field | Type | Required | Description | | :--- | :--- | :---: | :--- | | `t` | integer | Yes | Transitive group index. | | `r` | integer | Yes | Real-root count/signature. | | `label` | string | Yes | Transitive group label, for example `24T105`. | | `points` | number | Yes | Points credited to this team for the pair, rounded to six decimal places. | | `kTeams` | integer | Yes | Number of participant teams credited for this pair. | | `scoringDiscAbs` | string | Yes | Team's absolute scoring discriminant, represented as a decimal string to preserve integer precision. | | `minScoringDiscAbs` | string | Yes | Minimum absolute scoring discriminant currently credited for the pair. | | `discSource` | `exact_nfdisc` \| `mixed_disc` | Yes | Source used for the scoring discriminant. | | `isSolvable` | boolean \| null | Yes | Known solvability flag for the label, or `null` when unavailable. | | `baselineUnlocked` | boolean | No | Present when baseline information is exposed for this placement. | | `baselineDiscAbs` | string \| null | No | Absolute baseline discriminant, represented as a decimal string; may be `null` when no value is available. | ## Example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/leaderboard/teams/teamv2_public/placements?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "teamId": "teamv2_public", "placements": [ { "t": 105, "r": 12, "label": "24T105", "points": 1, "kTeams": 1, "scoringDiscAbs": "100", "minScoringDiscAbs": "100", "discSource": "exact_nfdisc", "isSolvable": true, "baselineUnlocked": true, "baselineDiscAbs": "120" } ], "nextCursor": null, "totalPlacements": 1, "meta": { "published": true, "publishedAt": "2026-05-19T00:00:00Z" } } } ``` ## Before publication ```json { "ok": true, "data": { "teamId": "teamv2_public", "placements": [], "nextCursor": null, "totalPlacements": 0, "meta": { "published": false, "publishedAt": null } } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `400` | `MALFORMED_BODY` | `limit` cannot be parsed, is outside `1..100`, or `cursor` is invalid. | | `404` | `NOT_FOUND` | The competition is unavailable or is not an IGP24 competition. | See [Errors](../../../errors.md) for authentication, authorization, and common errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/search-leaderboard/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/search-leaderboard.md Source: docs/endpoints/competitions/api/search-leaderboard.md # Search the IGP24 leaderboard ```http GET /api/public/v1/competitions/{competitionId}/leaderboard/search ``` **Scope**: `competition.read`. Returns public team placements matching an IGP24 transitive group index `t`, a real-root count `r`, or both. This endpoint is not paginated. The public team DTO deliberately redacts account and member identities: `ownerSairId` is always `null` and `members` is always empty. ## Parameters | Parameter | Location | Type | Required | Default | Description | | :--- | :--- | :--- | :---: | :--- | :--- | | `competitionId` | path | string | Yes | — | IGP24 competition ID. Use `igp24`. | | `t` | query | integer | Conditionally | — | Transitive group index. At least one of `t` or `r` is required. | | `r` | query | integer | Conditionally | — | Real-root count/signature. At least one of `t` or `r` is required. | ## Response DTO ```ts type SearchIgp24LeaderboardResponse = { ok: true; data: { query: { t: number | null; r: number | null; }; placements: Igp24SearchPlacement[]; meta: Igp24PublicationMeta; }; }; type Igp24PublicationMeta = { published: boolean; publishedAt: string | null; }; type Igp24SearchPlacement = { t: number; r: number; label: string; points: number; kTeams: number; scoringDiscAbs: string; minScoringDiscAbs: string; discSource: 'exact_nfdisc' | 'mixed_disc'; isSolvable: boolean | null; baselineUnlocked?: boolean; baselineDiscAbs?: string | null; team: Igp24PublicTeam; }; type Igp24PublicTeam = { teamId: string; teamNumber: string | null; teamName: string; ownerSairId: null; members: []; }; ``` ## Response fields | Field | Type | Description | | :--- | :--- | :--- | | `query.t` | integer \| null | Applied `t` filter, or `null` when omitted. | | `query.r` | integer \| null | Applied `r` filter, or `null` when omitted. | | `placements[]` | `Igp24SearchPlacement[]` | Matching participant and baseline-team placements. Empty when no pair matches or the leaderboard is unpublished. | | `meta.published` | boolean | Whether the leaderboard is published. | | `meta.publishedAt` | string \| null | ISO 8601 UTC publication time, or `null` before publication. | ### `Igp24SearchPlacement` | Field | Type | Required | Description | | :--- | :--- | :---: | :--- | | `t` | integer | Yes | Transitive group index. | | `r` | integer | Yes | Real-root count/signature. | | `label` | string | Yes | Transitive group label, for example `24T105`. | | `points` | number | Yes | Points credited for the placement, rounded to six decimal places. | | `kTeams` | integer | Yes | Number of participant teams credited for the pair. | | `scoringDiscAbs` | string | Yes | Team's absolute scoring discriminant as a decimal string. | | `minScoringDiscAbs` | string | Yes | Minimum absolute scoring discriminant currently credited for the pair. | | `discSource` | `exact_nfdisc` \| `mixed_disc` | Yes | Source used for the scoring discriminant. | | `isSolvable` | boolean \| null | Yes | Known solvability flag for the label, or `null`. | | `baselineUnlocked` | boolean | No | Present when baseline information is exposed. | | `baselineDiscAbs` | string \| null | No | Absolute baseline discriminant as a decimal string. | | `team` | `Igp24PublicTeam` | Yes | Public, identity-redacted team display data. | ### `Igp24PublicTeam` | Field | Type | Description | | :--- | :--- | :--- | | `teamId` | string | Stable team ID. | | `teamNumber` | string \| null | Public competition team number; `null` for the display-only baseline team. | | `teamName` | string | Public team display name. May be empty when display enrichment is unavailable. | | `ownerSairId` | null | Always `null`; owner identity is not public. | | `members` | empty array | Always empty; member identities are not public. | ## Example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/leaderboard/search?t=105&r=12" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "query": { "t": 105, "r": 12 }, "placements": [ { "t": 105, "r": 12, "label": "24T105", "points": 1, "kTeams": 1, "scoringDiscAbs": "100", "minScoringDiscAbs": "100", "discSource": "exact_nfdisc", "isSolvable": true, "team": { "teamId": "teamv2_public", "teamNumber": "IGP24-T00001", "teamName": "Public Team", "ownerSairId": null, "members": [] } } ], "meta": { "published": true, "publishedAt": "2026-05-19T00:00:00Z" } } } ``` ## Before publication ```json { "ok": true, "data": { "query": { "t": 105, "r": 12 }, "placements": [], "meta": { "published": false, "publishedAt": null } } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `400` | `MALFORMED_BODY` | `t` or `r` cannot be parsed as an integer. | | `404` | `NOT_FOUND` | The competition is unavailable or is not an IGP24 competition. | | `422` | `RESOURCE_FIELD_INVALID` | Neither `t` nor `r` was supplied, or a filter is semantically invalid. | See [Errors](../../../errors.md) for authentication, authorization, and common errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/submit/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/submit.md Source: docs/endpoints/competitions/api/submit.md # Submit a competition entry ```http POST /api/public/v1/competitions/{competitionId}/submissions ``` **Scope**: `competition.write`. Creates or replaces an entry according to the competition's `submissionSpec`. Send `Content-Type: application/json`. ## Common contract | Field | Type | Required | Description | | :--- | :--- | :---: | :--- | | `competitionId` | string | Yes | Path parameter identifying the competition. | | `slot` | integer | Conditional | Top-level model-reference slot. Defaults to `1`. | | `payload` | object | Yes | Kind-specific judged payload. | | `meta` | object | No | Kind-specific unjudged metadata. Unknown fields are rejected. | Creating an entry returns `201`. Replacing an overwrite-in-place entry returns `200`. IGP24 always creates a new history record and returns `201`. ## Response DTO ```ts type SubmitCompetitionEntryResponse = { ok: true; data: SubmissionDto; }; ``` `SubmissionDto` is expanded for each concrete competition below. See [Submission record DTOs](../../../reference/submission-kinds.md#submission-record-dtos) for the named union. ## Modular Arithmetic Challenge Request DTO: [`SubmitModelReferenceRequest`](../../../reference/submission-kinds.md#submit-request-dtos). ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slot": 2, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Pinned model revision for official evaluation." } }' ``` Complete request body: ```json { "slot": 2, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Pinned model revision for official evaluation." } } ``` Complete `201 Created` or `200 OK` response: ```json { "ok": true, "data": { "submissionId": "sub_01JMAC00000000000000000001", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "slot": 2, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Pinned model revision for official evaluation." }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-20T08:11:02Z" } } ``` `slot` defaults to `1`, never means "first empty slot", and must be between `1` and `submissionSpec.limits.maxEntries`. `model-reference` does not accept `meta.contributorNetworkItemId`. ## Mathematics Distillation Stage 1 Request DTO: [`SubmitCheatsheetRequest`](../../../reference/submission-kinds.md#submit-request-dtos). ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "content": "When the equation has a neutral element, test substitutions that preserve it before expanding the search." }, "meta": { "description": "Playground-tested cheatsheet.", "contributorNetworkItemId": "cn_01JSTAGE100000000000000001" } }' ``` Complete request body: ```json { "payload": { "content": "When the equation has a neutral element, test substitutions that preserve it before expanding the search." }, "meta": { "description": "Playground-tested cheatsheet.", "contributorNetworkItemId": "cn_01JSTAGE100000000000000001" } } ``` Complete `201 Created` or `200 OK` response: ```json { "ok": true, "data": { "submissionId": "sub_01JSTAGE100000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "kind": "cheatsheet", "payload": { "content": "When the equation has a neutral element, test substitutions that preserve it before expanding the search." }, "meta": { "description": "Playground-tested cheatsheet.", "contributorNetworkItemId": "cn_01JSTAGE100000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" } } ``` ## Mathematics Distillation Stage 2 Request DTO: [`SubmitSolverParticipationRequest`](../../../reference/submission-kinds.md#submit-request-dtos). Use `track` and `modelId` values returned by the current competition detail response. ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "import Mathlib\n\nexample (n : Nat) : n = n := by\n rfl" }, "meta": { "description": "Playground-tested Lean solver.", "contributorNetworkItemId": "cn_01JSTAGE200000000000000001" } }' ``` Complete request body: ```json { "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "import Mathlib\n\nexample (n : Nat) : n = n := by\n rfl" }, "meta": { "description": "Playground-tested Lean solver.", "contributorNetworkItemId": "cn_01JSTAGE200000000000000001" } } ``` Complete `201 Created` or `200 OK` response: ```json { "ok": true, "data": { "submissionId": "sub_01JSTAGE200000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage2", "kind": "solver-participation", "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "import Mathlib\n\nexample (n : Nat) : n = n := by\n rfl" }, "meta": { "description": "Playground-tested Lean solver.", "contributorNetworkItemId": "cn_01JSTAGE200000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" } } ``` One current entry is stored per track. Re-submitting `solo` does not replace `marathon`. ## Inverse Galois Problem (IGP24) Request DTO: [`SubmitIgp24PolynomialRequest`](../../../reference/submission-kinds.md#submit-request-dtos). ```bash curl -X POST "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions" \ -H "Authorization: Bearer $SAIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "polynomials": [ "3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 # poly_disc_primes=[2,3]" ] }, "meta": { "description": "Search batch 12." } }' ``` Complete request body: ```json { "payload": { "polynomials": [ "3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 # poly_disc_primes=[2,3]" ] }, "meta": { "description": "Search batch 12." } } ``` Complete `201 Created` response while background verification is queued: ```json { "ok": true, "data": { "submissionId": "sub_01JIGP24000000000000000001", "competitionId": "igp24", "kind": "igp24-polynomial", "payload": { "queuedPolynomials": [ { "polynomialIndex": 0, "status": "queued" } ] }, "meta": { "description": "Search batch 12." }, "verifiedPolynomials": [], "failedPolynomials": [], "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z", "description": "Search batch 12.", "batchId": "igp24_batch_4fa85f64f18d4dc59eea4d2ef65db490", "submissionStatus": "queued", "queuedCount": 1, "rejectedCount": 0 } } ``` The response intentionally omits submitted coefficient strings. Save the returned `submissionId` for reads and downloads, and use `batchId` with [Get IGP24 submission batch status](./get-submission-batch-status.md) to poll verification status. ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `400` | `MALFORMED_BODY` | The request is not valid JSON or does not use JSON content type. | | `403` | `EMAIL_NOT_VERIFIED` | The account email is not verified. | | `403` | `ENROLL_REQUIRED` | The caller has not enrolled. | | `403` | `TEAM_OWNER_REQUIRED` | The caller does not satisfy the configured owner requirement. | | `403` | `SUBMISSION_WINDOW_NOT_OPEN` | The submission window has not opened. | | `403` | `SUBMISSION_WINDOW_CLOSED` | The submission window has closed. | | `413` | `FILE_TOO_LARGE` | The configured payload or request-size limit was exceeded. | | `422` | `SUBMISSION_PAYLOAD_INVALID` | `payload` failed its kind-specific schema. | | `422` | `SUBMISSION_META_INVALID` | `meta` failed its kind-specific schema. | | `422` | `SUBMISSION_SLOT_INVALID` | `slot` is outside `1..submissionSpec.limits.maxEntries`. | | `429` | `IGP24_SUBMISSION_RATE_LIMIT_EXCEEDED` | The IGP24 team submission quota was exceeded. | Model-reference validation can additionally return the stable Hugging Face lookup errors documented in [Errors](../../../errors.md). --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-submission-batch-status/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-submission-batch-status.md Source: docs/endpoints/competitions/api/get-submission-batch-status.md # Get IGP24 submission batch status ```http GET /api/public/v1/competitions/{competitionId}/submissions/batch/{batchId} ``` **Scope**: `competition.read`. Polls the asynchronous verification status of an official IGP24 submission batch. Use the `batchId` returned by [`POST /submissions`](./submit.md#inverse-galois-problem-igp24). The caller may read batches submitted by their own account and batches belonging to a team they own. An unknown or non-visible batch returns the same `404 BATCH_NOT_FOUND` response. ## Parameters | Parameter | Location | Type | Required | Description | | :--- | :--- | :--- | :---: | :--- | | `competitionId` | path | string | Yes | IGP24 competition ID. Use `igp24`. | | `batchId` | path | string | Yes | Batch ID returned when the official submission was accepted. | ## Response DTO ```ts type GetIgp24SubmissionBatchStatusResponse = { ok: true; data: { batchId: string; batchStatus: 'queued' | 'complete' | 'failed' | 'empty'; totalCount: number; queuedCount: number; verifiedCount: number; failedCount: number; polynomials: Igp24BatchPolynomial[]; }; }; type Igp24BatchPolynomial = | { polynomialIndex: number; status: 'queued'; submittedAt: string; } | { polynomialIndex: number; status: 'ok'; submittedAt: string; label: string; t: number; r: number; scoreable: boolean; scoringStatus: 'pending' | 'scoreable' | 'not_scoreable' | 'no_score'; scoringReason?: string; noScoreReason?: string | null; reason?: string; inBaseline: boolean; baselineUnlocked: boolean; baselineDiscAbs?: string; fieldDiscAbs?: string; discSource?: 'exact_nfdisc' | 'mixed_disc'; } | { polynomialIndex: number; status: 'invalid' | 'error'; submittedAt?: string; reason: string; }; ``` ## Response fields | Field | Type | Description | | :--- | :--- | :--- | | `batchId` | string | Polled batch ID. | | `batchStatus` | `queued` \| `complete` \| `failed` \| `empty` | Aggregate state described below. | | `totalCount` | integer | Number of original submitted polynomial positions represented in `polynomials[]`. | | `queuedCount` | integer | Entries whose verification is still queued. | | `verifiedCount` | integer | Entries whose verification status is `ok`. | | `failedCount` | integer | Entries whose status is `invalid` or `error`. | | `polynomials[]` | `Igp24BatchPolynomial[]` | Per-input-position results ordered by zero-based `polynomialIndex`. Duplicate input polynomials still occupy separate original positions. | ### Aggregate status | Value | Meaning | | :--- | :--- | | `queued` | At least one polynomial is still queued. | | `complete` | Nothing is queued and at least one polynomial verified successfully; failed entries may also be present. | | `failed` | Nothing is queued and no polynomial verified successfully, but at least one failed. | | `empty` | The visible batch contains no polynomial entries. | ### Per-polynomial fields | Field | Type | Status | Description | | :--- | :--- | :--- | :--- | | `polynomialIndex` | integer | All | Zero-based position in the original submission request. | | `status` | `queued` \| `ok` \| `invalid` \| `error` | All | Current verification state. | | `submittedAt` | string | Usually all | ISO 8601 UTC storage time. Omitted for an input rejected before it entered persistent verification storage. | | `label` | string | `ok` | Computed transitive group label. | | `t` | integer | `ok` | Computed transitive group index. | | `r` | integer | `ok` | Computed real-root count/signature. | | `scoreable` | boolean | `ok` | Whether the current scoring state makes the result scoreable. | | `scoringStatus` | string enum | `ok` | One of `pending`, `scoreable`, `not_scoreable`, or `no_score`. | | `scoringReason` | string | `ok`, optional | Stable reason code explaining the scoring state. | | `noScoreReason` | string \| null | `ok`, optional | More specific no-score reason for applicable baseline results. | | `reason` | string | `ok`, optional; failed, required | Human-readable scoring explanation or verification failure reason. | | `inBaseline` | boolean | `ok` | Whether `(t, r)` belongs to the baseline. | | `baselineUnlocked` | boolean | `ok` | Whether this result unlocks and improves the applicable baseline. | | `baselineDiscAbs` | string | `ok`, optional | Absolute baseline discriminant as a decimal string. | | `fieldDiscAbs` | string | `ok`, optional | Exact absolute number-field discriminant as a decimal string. | | `discSource` | `exact_nfdisc` \| `mixed_disc` | `ok`, optional | Discriminant source when available. | ## Example ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions/batch/igp24_batch_4fa85f64f18d4dc59eea4d2ef65db490" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "batchId": "igp24_batch_4fa85f64f18d4dc59eea4d2ef65db490", "batchStatus": "queued", "totalCount": 4, "queuedCount": 1, "verifiedCount": 1, "failedCount": 2, "polynomials": [ { "polynomialIndex": 0, "status": "ok", "submittedAt": "2026-05-18T12:34:56Z", "label": "24T25000", "t": 25000, "r": 1, "scoreable": true, "scoringStatus": "scoreable", "inBaseline": false, "baselineUnlocked": false, "fieldDiscAbs": "12345678901234567890", "discSource": "exact_nfdisc" }, { "polynomialIndex": 1, "status": "queued", "submittedAt": "2026-05-18T12:34:56Z" }, { "polynomialIndex": 2, "status": "invalid", "submittedAt": "2026-05-18T12:34:56Z", "reason": "not irreducible of degree 24" }, { "polynomialIndex": 3, "status": "error", "submittedAt": "2026-05-18T12:34:56Z", "reason": "verification could not be completed" } ] } } ``` Continue polling while `queuedCount` is greater than zero. Apply backoff and the general rate-limit headers described in [Rate limiting](../../../rate-limiting.md). ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `400` | `NOT_IGP24` | Batch polling was requested for a non-IGP24 competition. | | `404` | `NOT_FOUND` | The competition does not exist. | | `404` | `BATCH_NOT_FOUND` | No batch is visible to the caller for this `batchId`. | See [Errors](../../../errors.md) for authentication, authorization, and common errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/list-my-submissions/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/list-my-submissions.md Source: docs/endpoints/competitions/api/list-my-submissions.md # List my submissions ```http GET /api/public/v1/competitions/{competitionId}/submissions/mine ``` **Scope**: `competition.read`. Returns one collection shape for every competition. The meaning of `items` is selected by the competition's submission kind. ## Parameters | Parameter | Location | Type | Required | Description | | :--- | :--- | :--- | :---: | :--- | | `competitionId` | path | string | Yes | Competition ID. | | `cursor` | query | string | IGP24 only | Opaque history cursor. | | `limit` | query | integer | IGP24 only | History page size. Default `25`, maximum `100`. | ## Response DTO ```ts type MySubmissionCollectionResponse = { ok: true; data: { items: SubmissionDto[]; nextCursor: string | null; }; }; ``` See [Submission record DTOs](../../../reference/submission-kinds.md#submission-record-dtos) for the named union members. ## Modular Arithmetic Challenge Returns the occupied slots ordered by `slot`. Empty slots are not represented by placeholder objects. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/submissions/mine" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "submissionId": "sub_01JMAC00000000000000000001", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "slot": 1, "payload": { "modelName": "your-team/model-a", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Primary model." }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-20T08:11:02Z" }, { "submissionId": "sub_01JMAC00000000000000000002", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "slot": 3, "payload": { "modelName": "your-team/model-c", "commitHash": "89abcdef0123456789abcdef0123456789abcdef" }, "meta": {}, "createdAt": "2026-05-19T09:00:00Z", "updatedAt": "2026-05-19T09:00:00Z" } ], "nextCursor": null } } ``` ## Mathematics Distillation Stage 1 Returns zero or one current cheatsheet. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/mine" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "submissionId": "sub_01JSTAGE100000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "kind": "cheatsheet", "payload": { "content": "When the equation has a neutral element, test substitutions that preserve it before expanding the search." }, "meta": { "description": "Playground-tested cheatsheet.", "contributorNetworkItemId": "cn_01JSTAGE100000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" } ], "nextCursor": null } } ``` ## Mathematics Distillation Stage 2 Returns every current track entry. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/mine" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "submissionId": "sub_01JSTAGE200000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage2", "kind": "solver-participation", "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "import Mathlib\n\nexample (n : Nat) : n = n := by\n rfl" }, "meta": { "description": "Solo solver.", "contributorNetworkItemId": "cn_01JSTAGE200000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" }, { "submissionId": "sub_01JSTAGE200000000000000002", "competitionId": "mathematics-distillation-challenge-equational-theories-stage2", "kind": "solver-participation", "payload": { "track": "marathon", "modelId": "openai-gpt-oss-120b", "solverCode": "import Mathlib\n\nexample (a b : Nat) : a + b = b + a := by\n omega" }, "meta": { "description": "Marathon solver." }, "createdAt": "2026-05-19T12:34:56Z", "updatedAt": "2026-05-19T12:34:56Z" } ], "nextCursor": null } } ``` ## Inverse Galois Problem (IGP24) Returns submission history newest first and supports `cursor` and `limit`. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions/mine?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "submissionId": "sub_01JIGP24000000000000000001", "competitionId": "igp24", "kind": "igp24-polynomial", "payload": { "queuedPolynomials": [] }, "meta": { "description": "Search batch 12." }, "verifiedPolynomials": [ { "polynomialIndex": 0, "status": "accepted", "label": "24T25000", "t": 25000, "r": 1, "scoreable": true, "scoringStatus": "scoreable", "inBaseline": false, "baselineUnlocked": true, "baselineDiscAbs": "19342813113834066795298816", "fieldDiscAbs": "1822477585834316228120576", "discSource": "exact_nfdisc" } ], "failedPolynomials": [], "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:40:03Z" } ], "nextCursor": "eyJhZnRlciI6WzEwNDIsInN1Yl8wMUpJR1AyNDAwMDAwMDAwMDAwMDAwMDAwMSJdfQ" } } ``` ## Empty collection Every competition returns the same complete empty shape: ```json { "ok": true, "data": { "items": [], "nextCursor": null } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `400` | `MALFORMED_BODY` | An IGP24 cursor is invalid. | | `422` | `RESOURCE_FIELD_INVALID` | An IGP24 `limit` is not an integer from `1` through `100`. | See [Errors](../../../errors.md) for common errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-my-submission-legacy/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-my-submission-legacy.md Source: docs/endpoints/competitions/api/get-my-submission-legacy.md # Get my submission (legacy) ```http GET /api/public/v1/competitions/{competitionId}/submissions/me ``` **Scope**: `competition.read`. Compatibility endpoint retained for existing clients. New integrations should use [List my submissions](./list-my-submissions.md), which always returns a collection. ## Parameters | Parameter | Location | Type | Required | Description | | :--- | :--- | :--- | :---: | :--- | | `competitionId` | path | string | Yes | Competition ID. | | `track` | query | Stage 2 track ID | Stage 2 only | Selects one solver-participation track. | | `cursor` | query | string | IGP24 only | Opaque history cursor. | | `limit` | query | integer | IGP24 only | History page size. Default `25`, maximum `100`. | ## Response DTO The legacy response is intentionally kind-specific: ```ts type LegacyMySubmissionResponse = | { ok: true; data: CheatsheetSubmission } | { ok: true; data: ModelReferenceSubmission } | { ok: true; data: SolverParticipationSubmission } | { ok: true; data: { items: Igp24PolynomialSubmission[]; nextCursor: string | null } }; ``` The concrete responses are expanded below. New clients should prefer the uniform collection DTO returned by `/submissions/mine`. ## Modular Arithmetic Challenge The legacy endpoint always reads slot `1`, even when slots `2` and `3` exist. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/submissions/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "submissionId": "sub_01JMAC00000000000000000001", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "slot": 1, "payload": { "modelName": "your-team/model-a", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Primary model." }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-20T08:11:02Z" } } ``` If slot `1` is empty, the endpoint returns `404` even when another slot is populated. ## Mathematics Distillation Stage 1 Returns the single current cheatsheet. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/me" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "submissionId": "sub_01JSTAGE100000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "kind": "cheatsheet", "payload": { "content": "When the equation has a neutral element, test substitutions that preserve it before expanding the search." }, "meta": { "description": "Playground-tested cheatsheet.", "contributorNetworkItemId": "cn_01JSTAGE100000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" } } ``` ## Mathematics Distillation Stage 2 `track` is required and selects one current entry. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/me?track=solo" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "submissionId": "sub_01JSTAGE200000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage2", "kind": "solver-participation", "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "import Mathlib\n\nexample (n : Nat) : n = n := by\n rfl" }, "meta": { "description": "Solo solver.", "contributorNetworkItemId": "cn_01JSTAGE200000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" } } ``` ## Inverse Galois Problem (IGP24) IGP24 keeps its historical paginated collection response on the legacy path. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions/me?limit=25" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "items": [ { "submissionId": "sub_01JIGP24000000000000000001", "competitionId": "igp24", "kind": "igp24-polynomial", "payload": { "queuedPolynomials": [] }, "meta": { "description": "Search batch 12." }, "verifiedPolynomials": [ { "polynomialIndex": 0, "status": "accepted", "label": "24T25000", "t": 25000, "r": 1, "scoreable": true } ], "failedPolynomials": [], "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:40:03Z" } ], "nextCursor": null } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `404` | `NOT_FOUND` | No matching current submission exists. | | `422` | `RESOURCE_FIELD_INVALID` | `track` is missing, unexpected, or invalid. | | `400` | `MALFORMED_BODY` | An IGP24 cursor is invalid. | | `422` | `RESOURCE_FIELD_INVALID` | An IGP24 `limit` is not an integer from `1` through `100`. | See [Errors](../../../errors.md) for the common error envelope. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/get-submission/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/get-submission.md Source: docs/endpoints/competitions/api/get-submission.md # Get a submission by ID ```http GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId} ``` **Scope**: `competition.read`. Returns one visible submission record. The response union is selected by `kind`. ## Path parameters | Parameter | Type | Required | Description | | :--- | :--- | :---: | :--- | | `competitionId` | string | Yes | Competition ID. | | `submissionId` | string | Yes | Opaque ID returned by a submission write or collection read. | ## Response DTO ```ts type GetSubmissionResponse = { ok: true; data: SubmissionDto; }; ``` See [Submission record DTOs](../../../reference/submission-kinds.md#submission-record-dtos) for the complete named union. ## Modular Arithmetic Challenge ```bash curl "https://api.sair.foundation/api/public/v1/competitions/modular-arithmetic-challenge/submissions/sub_01JMAC00000000000000000002" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "submissionId": "sub_01JMAC00000000000000000002", "competitionId": "modular-arithmetic-challenge", "kind": "model-reference", "slot": 2, "payload": { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" }, "meta": { "description": "Pinned model revision for official evaluation." }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-20T08:11:02Z" } } ``` ## Mathematics Distillation Stage 1 ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/sub_01JSTAGE100000000000000001" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "submissionId": "sub_01JSTAGE100000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage1", "kind": "cheatsheet", "payload": { "content": "When the equation has a neutral element, test substitutions that preserve it before expanding the search." }, "meta": { "description": "Playground-tested cheatsheet.", "contributorNetworkItemId": "cn_01JSTAGE100000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" } } ``` ## Mathematics Distillation Stage 2 ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/sub_01JSTAGE200000000000000001" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "submissionId": "sub_01JSTAGE200000000000000001", "competitionId": "mathematics-distillation-challenge-equational-theories-stage2", "kind": "solver-participation", "payload": { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "import Mathlib\n\nexample (n : Nat) : n = n := by\n rfl" }, "meta": { "description": "Solo solver.", "contributorNetworkItemId": "cn_01JSTAGE200000000000000001" }, "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:34:56Z" } } ``` ## Inverse Galois Problem (IGP24) Submitted coefficient strings are omitted from JSON reads. Use the download endpoint to retrieve them. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions/sub_01JIGP24000000000000000001" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` ```json { "ok": true, "data": { "submissionId": "sub_01JIGP24000000000000000001", "competitionId": "igp24", "kind": "igp24-polynomial", "payload": { "queuedPolynomials": [] }, "meta": { "description": "Search batch 12." }, "verifiedPolynomials": [ { "polynomialIndex": 0, "status": "accepted", "label": "24T25000", "t": 25000, "r": 1, "scoreable": true, "scoringStatus": "scoreable", "inBaseline": false, "baselineUnlocked": true, "baselineDiscAbs": "19342813113834066795298816", "fieldDiscAbs": "1822477585834316228120576", "discSource": "exact_nfdisc" } ], "failedPolynomials": [], "createdAt": "2026-05-18T12:34:56Z", "updatedAt": "2026-05-18T12:40:03Z" } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `404` | `NOT_FOUND` | The record does not exist, belongs to another competition, or is not visible to the caller. | See [Errors](../../../errors.md) for common errors. --- URL: https://docs.sair.foundation/docs/endpoints/competitions/api/download-submission/ Markdown: https://docs.sair.foundation/docs-md/endpoints/competitions/api/download-submission.md Source: docs/endpoints/competitions/api/download-submission.md # Download a submission body ```http GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download ``` **Scope**: `competition.read`. Returns `text/plain; charset=utf-8` rather than the JSON success envelope. ## Path parameters | Parameter | Type | Required | Description | | :--- | :--- | :---: | :--- | | `competitionId` | string | Yes | Competition ID. | | `submissionId` | string | Yes | Opaque submission ID. | ## Mathematics Distillation Stage 1 ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage1/submissions/sub_01JSTAGE100000000000000001/download" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` Complete response body: ```text When the equation has a neutral element, test substitutions that preserve it before expanding the search. ``` ## Mathematics Distillation Stage 2 ```bash curl "https://api.sair.foundation/api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions/sub_01JSTAGE200000000000000001/download" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` Complete response body: ```lean import Mathlib example (n : Nat) : n = n := by rfl ``` ## Inverse Galois Problem (IGP24) The response contains the original submitted polynomial lines, including trailing comments, separated by newlines. ```bash curl "https://api.sair.foundation/api/public/v1/competitions/igp24/submissions/sub_01JIGP24000000000000000001/download" \ -H "Authorization: Bearer $SAIR_API_KEY" ``` Complete response body: ```text 3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 # poly_disc_primes=[2,3] ``` The response includes a `Content-Disposition` attachment filename for IGP24. ## Modular Arithmetic Challenge Model-reference submissions have no downloadable text body. The complete error response is: ```json { "ok": false, "error": { "code": "NO_DOWNLOAD_FOR_KIND", "message": "This submission kind has no downloadable file", "details": {} } } ``` ## Errors | HTTP | Code | When | | :---: | :--- | :--- | | `404` | `NOT_FOUND` | The submission is missing or not visible to the caller. | | `404` | `NO_DOWNLOAD_FOR_KIND` | The submission kind has no text body. | | `404` | `ORIGINAL_PAYLOAD_UNAVAILABLE` | A legacy IGP24 submission's original polynomial lines cannot be recovered. | See [Errors](../../../errors.md) for common errors. --- URL: https://docs.sair.foundation/docs/reference/submission-spec/ Markdown: https://docs.sair.foundation/docs-md/reference/submission-spec.md Source: docs/reference/submission-spec.md # Submission spec Every competition declares one `submissionSpec` in its detail response. It is the **only** source of truth for how to construct a submission for that competition — fields, validation, eligibility, window, size cap. ```json { "kind": "model-reference", "schema": { "type": "object", "required": ["modelName", "commitHash"], "properties": { "modelName": { "type": "string", "pattern": "^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$" }, "commitHash": { "type": "string", "pattern": "^[0-9a-fA-F]{40}$" } }, "additionalProperties": false }, "metaSchema": { "type": "object", "properties": { "description": { "type": "string", "maxLength": 5000 } }, "additionalProperties": false }, "permission": { "teamRole": "owner" }, "window": { "opensAt": "2026-04-01T00:00:00Z", "closesAt": "2026-09-30T23:59:59Z" }, "limits": { "maxBytes": null, "maxEntries": 3 } } ``` ## Fields ### `kind` Stable enum identifying the submission shape: - [`cheatsheet`](./submission-kinds.md#cheatsheet) — a text body conditioning a model. - [`igp24-polynomial`](./submission-kinds.md#igp24-polynomial) — an array of degree 24 integer polynomial coefficient strings. - [`model-reference`](./submission-kinds.md#model-reference) — a pointer to a model artifact (Hugging Face ID + commit). - [`solver-participation`](./submission-kinds.md#solver-participation) — Lean 4 solver source for a Lean-judged competition. A competition has exactly one `kind` for its lifetime; changing it would be a breaking change. ### `schema` JSON Schema (draft 2020-12) for `payload`. Plug it into a client-side validator (e.g. `ajv` / `jsonschema`) to catch errors before sending. ```json { "type": "object", "required": ["modelName", "commitHash"], "properties": { "modelName": { "type": "string", "pattern": "^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$" }, "commitHash": { "type": "string", "pattern": "^[0-9a-f]{40}$" } }, "additionalProperties": false } ``` ### `metaSchema` JSON Schema for the optional top-level `meta` object. `meta` carries submission metadata and lineage; it is **not** part of the judged payload. ```json { "type": "object", "properties": { "description": { "type": "string", "maxLength": 5000 }, "contributorNetworkItemId": { "type": "string" } }, "additionalProperties": false } ``` `contributorNetworkItemId` records the Contributor Network item this submission references or builds on. It is available only for kinds whose `metaSchema` includes it, currently `cheatsheet` and `solver-participation`. It is not accepted for `model-reference` or `igp24-polynomial`. `description.maxLength` is kind-specific. Read the competition detail response before submitting; for example, IGP24 uses a 500-character description limit. ### `permission` ```json { "permission": { "teamRole": "owner" } } ``` - `teamRole: "owner"` — only the team owner may submit. Returned as `TEAM_OWNER_REQUIRED` (403) for non-owners. - `teamRole: "activeMember"` — any active member of the competition team may submit. - `permission` absent — any enrolled member may submit. This is the sole source for submission permissions; `kind` does not carry permission rules. ### `window` ```json { "window": { "opensAt": "2026-04-01T00:00:00Z", "closesAt": "2026-09-30T23:59:59Z" } } ``` Requests before `opensAt` return `SUBMISSION_WINDOW_NOT_OPEN` (403); after `closesAt`, `SUBMISSION_WINDOW_CLOSED` (403). ### `limits` ```json { "limits": { "maxBytes": 1000000, "maxEntries": 1, "maxPolynomials": 1000, "maxRequestBytes": 1310720 } } ``` `maxBytes: null` means no explicit cap. Unless a submission kind defines a more specific unit, the cap covers the entire `payload` serialized as UTF-8 JSON. `maxEntries` is a positive, kind-specific capacity value. For submission kinds that use numbered slots, such as `model-reference`, it defines the maximum number of current entries and the valid `slot` range from `1` through `maxEntries`. Modular Arithmetic Challenge currently returns `maxEntries: 3`. It does not limit the number of items returned by `/submissions/mine` for track-based or history-based submission kinds; use the kind-specific track rules or collection pagination metadata instead. For `igp24-polynomial`, `maxBytes` uses the same canonical limit as the raw `submission.txt` body: 1,000,000 bytes. Measure the UTF-8 byte length after joining the complete `payload.polynomials` strings, including trailing comments, with newline separators. Blank lines and full-line comments are not array items. `maxPolynomials` is optional. It is present for `igp24-polynomial` and limits `payload.polynomials.length`. `maxRequestBytes` is also present for `igp24-polynomial`. It is a separate 1,310,720-byte guard on the normalized JSON request envelope, including `payload`, `meta`, and JSON quoting or escaping overhead. IGP24 requests must satisfy both `maxBytes` and `maxRequestBytes`. ### `catalog` Optional. Present when a kind expects clients to choose from a finite, public set of IDs. Used by `solver-participation`: ```json { "catalog": { "tracks": [ { "id": "solo", "label": "Solo" }, { "id": "marathon", "label": "Marathon" } ], "models": [ { "id": "google-gemma-4-31b-it", "name": "Google: Gemma 4 31B" }, { "id": "openai-gpt-oss-120b", "name": "OpenAI: GPT OSS 120B" } ] } } ``` Send `catalog.tracks[].id` as `payload.track` and `catalog.models[].id` as `payload.modelId`. The same IDs are also reflected in `schema` as `enum` values. #### Catalog fields | Field | Type | Description | | :--------- | :-------------------------------- | :----------------------------------------------- | | `tracks[]` | [CatalogTrack](#catalog-track-fields)[] | Track IDs accepted by `payload.track`. | | `models[]` | [CatalogModel](#catalog-model-fields)[] | Model IDs accepted by `payload.modelId`. | #### Catalog track fields | Field | Type | Description | | :------ | :----- | :------------------------------------- | | `id` | string | Track ID, such as `solo` or `marathon`. | | `label` | string | Human-readable track label. | #### Catalog model fields | Field | Type | Description | | :----- | :----- | :--------------------------- | | `id` | string | Model ID accepted by payload. | | `name` | string | Human-readable model name. | ## Versioning Non-breaking changes to a `submissionSpec`: - Adding optional fields to `schema` or `metaSchema`. - Adding metadata fields (`name`, `description`) to `catalog` entries. Breaking changes (only on a new competition season): - Removing fields, adding required fields, tightening patterns. - Removing a `catalog` ID that was valid for an active submission window. - Changing `kind`. New `kind` values may appear at any time for new competitions. --- URL: https://docs.sair.foundation/docs/reference/submission-kinds/ Markdown: https://docs.sair.foundation/docs-md/reference/submission-kinds.md Source: docs/reference/submission-kinds.md # Submission kinds and DTOs A competition's `submissionSpec.kind` selects the DTOs used by the submission endpoints: - Submit request DTO: body for `POST /api/public/v1/competitions/{competitionId}/submissions`. - Submission record DTO: `data` returned when creating, replacing, or reading submitted information. DTO names on this page are documentation labels. JSON requests and responses use the object shapes shown here. ## Client workflow 1. List competitions and read `items[].submissionKind`. 2. Fetch the competition detail and read `submissionSpec` for the exact schema, limits, permissions, window, and catalog IDs. 3. To submit, choose the matching `Submit*Request` DTO from [DTO map by kind](#dto-map-by-kind). 4. To read all current entries, call `/submissions/mine` and use the matching `*Submission` records from [Submission record DTOs](#submission-record-dtos). 5. To retrieve only the stored text body, use `/submissions/{submissionId}/download` for kinds that support it. ## `SubmissionKind` enum | Value | Meaning | | :--------------------- | :------------------------------------------------------------- | | `cheatsheet` | Text body that conditions a model. | | `igp24-polynomial` | Array of IGP24 degree 24 polynomial coefficient strings. | | `model-reference` | Pointer to a pinned external model artifact. | | `solver-participation` | Lean 4 solver source for a Lean-judged competition. | ## Competition kind discovery `GET /api/public/v1/competitions` returns `submissionKind` for every visible competition. `GET /api/public/v1/competitions/{competitionId}` returns the full [`submissionSpec`](./submission-spec.md), including the same `kind` plus schema, limits, permissions, and windows. Documented competition IDs: | Competition ID | `submissionKind` | | :------------------------------------------------------------- | :---------------- | | `modular-arithmetic-challenge` | `model-reference` | | `igp24` | `igp24-polynomial` | | `mathematics-distillation-challenge-equational-theories-stage1` | `cheatsheet` | | `mathematics-distillation-challenge-equational-theories-stage2` | `solver-participation` | For active competition IDs not listed above, use the `submissionKind` value returned by the API and select the matching DTO below. ## DTO map by kind | `submissionKind` | Submit request DTO | Submission record DTO | | :--------------------- | :------------------------------------ | :------------------------------ | | `cheatsheet` | `SubmitCheatsheetRequest` | `CheatsheetSubmission` | | `igp24-polynomial` | `SubmitIgp24PolynomialRequest` | `Igp24PolynomialSubmission` | | `model-reference` | `SubmitModelReferenceRequest` | `ModelReferenceSubmission` | | `solver-participation` | `SubmitSolverParticipationRequest` | `SolverParticipationSubmission` | ## Submitted information endpoints The submit and ID endpoints return one `SubmissionDto` inside the standard `{ ok, data }` envelope. The unified current-entry endpoint always returns `MySubmissionCollectionResponse`, including for competitions whose `maxEntries` is `1`: | Endpoint | Meaning | Response DTO | | :----------------------------------------------------------------------- | :------------------------------------------------- | :------------------------------- | | `POST /api/public/v1/competitions/{competitionId}/submissions` | Create or replace one caller entry. | `SubmissionDto` | | `GET /api/public/v1/competitions/{competitionId}/submissions/mine` | Read the caller's current entries as a collection. | `MySubmissionCollectionResponse` | | `GET /api/public/v1/competitions/{competitionId}/submissions/me` | Read through the legacy compatibility endpoint. | Kind-specific legacy response | | `GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}` | Read a visible submission by ID. | `SubmissionDto` | `/submissions/mine` returns `{ items, nextCursor }` for every kind. `cheatsheet` and single-entry `model-reference` competitions return zero or one item; multi-entry `model-reference` competitions return items ordered by `slot`; `solver-participation` returns current entries across all tracks; and `igp24-polynomial` returns revision history newest first. `cursor` and `limit` are supported for IGP24 history. `/submissions/me` remains available without changing its existing contract. It returns one record for `cheatsheet` and `model-reference` (slot `1` for multi-entry competitions), requires `track` for `solver-participation`, and returns the cursor-paginated IGP24 history. `GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download` is different: it returns `text/plain` for text-body kinds, not a `SubmissionDto`. ## Submit request DTOs `payload` is part of the API request body. The kind-specific object inside `payload` is expanded here instead of hidden behind another type alias. ```ts type SubmitCheatsheetRequest = { payload: { content: string; }; meta?: { description?: string; contributorNetworkItemId?: string; }; }; type SubmitIgp24PolynomialRequest = { payload: { // Official submission-text lines; each may include a trailing # comment. polynomials: string[]; }; meta?: { description?: string; }; }; type SubmitModelReferenceRequest = { // Defaults to 1. Must be between 1 and submissionSpec.limits.maxEntries. slot?: number; payload: { modelName: string; commitHash: string; }; meta?: { description?: string; }; }; type SubmitSolverParticipationRequest = { payload: { track: string; modelId: string; solverCode: string; }; meta?: { description?: string; contributorNetworkItemId?: string; }; }; ``` ## Submission record DTOs `SubmissionDto` contains the stored, already-submitted information for a competition entry. The `payload` and `meta` fields are selected by `kind`. ```ts // Applies to endpoints that return a single submission record: POST /submissions, // GET /submissions/{submissionId}, and legacy GET /submissions/me for // cheatsheet and model-reference kinds. type SubmissionRecordResponse = { ok: true; data: SubmissionDto; }; type MySubmissionCollectionResponse = { ok: true; data: { items: SubmissionDto[]; nextCursor: string | null; }; }; type SubmissionDto = | CheatsheetSubmission | Igp24PolynomialSubmission | ModelReferenceSubmission | SolverParticipationSubmission; type CheatsheetSubmission = { submissionId: string; competitionId: string; kind: "cheatsheet"; payload: { content: string; }; meta: { description?: string; contributorNetworkItemId?: string; } | null; createdAt: string; updatedAt: string; }; type Igp24PolynomialSubmission = { submissionId: string; competitionId: string; kind: "igp24-polynomial"; payload: { queuedPolynomials: Igp24QueuedPolynomial[]; legacyResolution?: Record; }; meta: { description?: string; } | null; verifiedPolynomials: Igp24VerifiedPolynomial[]; failedPolynomials: Igp24FailedPolynomial[]; // Present on the asynchronous POST response when a batch was queued. batchId?: string; submissionStatus?: "queued"; queuedCount?: number; rejectedCount?: number; description?: string | null; createdAt: string; updatedAt: string; }; type Igp24VerifiedPolynomial = { polynomialIndex: number; status: "accepted"; label: string; t: number; r: number; scoreable?: boolean; scoringStatus?: string; scoringReason?: string; noScoreReason?: string; reason?: string; inBaseline?: boolean; baselineUnlocked?: boolean; baselineDiscAbs?: string; fieldDiscAbs?: string; discSource?: "exact_nfdisc" | "mixed_disc"; }; type Igp24FailedPolynomial = { polynomialIndex: number; status: "rejected"; reason: string; }; type Igp24QueuedPolynomial = { polynomialIndex: number; status: "queued"; }; type ModelReferenceSubmission = { submissionId: string; competitionId: string; kind: "model-reference"; slot: number; payload: { modelName: string; commitHash: string; }; meta: { description?: string; } | null; createdAt: string; updatedAt: string; }; type SolverParticipationSubmission = { submissionId: string; competitionId: string; kind: "solver-participation"; payload: { track: string; modelId: string; solverCode: string; }; meta: { description?: string; contributorNetworkItemId?: string; } | null; createdAt: string; updatedAt: string; }; ``` When no metadata was submitted, current records return an empty `meta` object; older or kind-specific records may return `null`. For `Igp24PolynomialSubmission`, `payload` intentionally omits submitted coefficient strings. It contains `queuedPolynomials` while verification is pending and may also carry legacy reconciliation metadata for older submissions. `verifiedPolynomials` contains successfully verified records and `failedPolynomials` contains rejected candidates. `polynomialIndex` is the zero-based input index. Accepted records always expose `status: "accepted"`, `polynomialIndex`, `label`, `t`, and `r`; scoring annotations are included when available. Rejected and queued records expose their corresponding status and input index. The response does not duplicate coefficient strings or expose `prime_hint_status` or `nfdisc_method`; use the download endpoint for submitted lines. ## Kind details ### `cheatsheet` A short text body (typically Markdown) that conditions a model when judged against the competition's problem set. **payload** ```json { "content": "When the modulus is prime, use Fermat's little theorem..." } ``` **meta** (optional) ```json { "description": "v3 — adds prime-modulus shortcut", "contributorNetworkItemId": "cn_01HX0PA..." } ``` - `content` is required. Over-size payloads return `FILE_TOO_LARGE`; the cap is published as `submissionSpec.limits.maxBytes`. - The stored text body is downloadable from [`GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download`](../endpoints/competitions.md#download-the-stored-text-body). - `permission.teamRole === "owner"` is common for this kind. ### `igp24-polynomial` Candidate polynomial lines for IGP24, the Inverse Galois Problem in degree 24. Each string is one candidate degree 24 integer polynomial. **payload** ```json { "polynomials": [ "3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 # poly_disc_primes=[2,3]", "1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1" ] } ``` **meta** (optional) ```json { "description": "Adapted from a public polynomial table; reproduction script: search batch 12." } ``` - `polynomials` is required and must contain at least one string. - Before any trailing `#` comment, each string must contain exactly 25 comma-separated integer coefficients in ascending powers: `a_0,a_1,...,a_24`. - `a_0` must be nonzero, `a_24` must be `1` (submissions must be monic), and the coefficient gcd must be `1`. - The Public API accepts JSON only; IGP24 submissions do not upload a file. - Blank lines and full-line comments are not array items. A valid polynomial may carry a trailing `#` comment; ordinary comments are preserved but ignored by verification. - The exact trailing-comment body `poly_disc_primes=[...]` is an optional `quicknfdisc` ramp hint. Use known factors as strictly increasing decimal integers greater than 1, without exponents. The list may be incomplete and values may exceed the JavaScript safe-integer range. - Invalid or unusable hints are silently ignored and the ordinary discriminant workflow continues. They never reject an otherwise valid polynomial or change scoring semantics. - Polynomial notation such as `x^24 + x + 1` and claimed `24Tt`, real-root count, or discriminant values as extra columns remain invalid. - Duplicate canonical coefficient strings are accepted but verified and stored only once; the first occurrence's prime hint is the one used. Result indexes still cover every submitted item. - The maximum polynomial count is published as `submissionSpec.limits.maxPolynomials`. - `meta.description` is optional and capped by `submissionSpec.metaSchema.properties.description.maxLength` (currently 500 characters for IGP24). Use it for batch-level provenance, source citations, or reproduction notes. - For IGP24, `submissionSpec.limits.maxBytes` uses the same canonical limit as the raw `submission.txt` body: 1,000,000 bytes. Measure the complete newline-joined `payload.polynomials` strings, including trailing comments, encoded as UTF-8. Over-size submissions return `FILE_TOO_LARGE`. - The separate `submissionSpec.limits.maxRequestBytes` limit (currently 1,310,720 bytes) covers the normalized JSON request envelope, including `payload`, `meta`, and JSON quoting or escaping overhead. The request must satisfy both byte limits. - Submission reads return `payload.queuedPolynomials`, `meta`, `verifiedPolynomials`, and `failedPolynomials`; result items do not include submitted coefficient strings or prime-hint observability fields. - The original submitted polynomial strings, including trailing comments, are downloadable from [`GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download`](../endpoints/competitions.md#download-the-stored-text-body) as newline-separated `text/plain`. ### `model-reference` A pointer to an external model artifact. Nothing is uploaded — the judge fetches the model from its origin and runs it. **payload** ```json { "modelName": "your-team/your-model", "commitHash": "0123456789abcdef0123456789abcdef01234567" } ``` **meta** (optional) ```json { "description": "Fine-tuned on a modular-arithmetic dataset." } ``` - `modelName` follows the Hugging Face `/` pattern. - `commitHash` is the full 40-character lowercase hex commit SHA — the judge pins to this exact revision. - `slot` is an optional top-level request field, outside `payload`. It defaults to `1` and must be an integer from `1` through `submissionSpec.limits.maxEntries`. - Re-submitting a populated slot replaces only that slot. The API does not automatically choose the first empty slot. - Every `ModelReferenceSubmission` record includes its `slot`. - There is no text-body download for this kind — `GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download` returns `NO_DOWNLOAD_FOR_KIND`. - Model references can also be shared as Contributor Network items through [`POST /api/public/v1/contributor-network/items`](../endpoints/contributor-network.md#publish). Contributor Network sharing does not replace the official competition submission. ### `solver-participation` Lean 4 solver source for a Lean-judged competition. Iterate the solver in the competition's [Playground](../endpoints/playground.md) first, then submit your best one here. **payload** ```json { "track": "solo", "modelId": "openai-gpt-oss-120b", "solverCode": "theorem solver : ... := by ..." } ``` **meta** (optional) ```json { "description": "Marathon-tuned solver.", "contributorNetworkItemId": "cn_01HX0PA..." } ``` - `track` and `modelId` must be IDs from `submissionSpec.catalog.tracks[].id` / `catalog.models[].id`. The same IDs are also reflected in `submissionSpec.schema` as `enum`. - `solverCode` is required and must parse as Lean 4 (otherwise `LEAN_SOURCE_INVALID`). Over-size payloads return `FILE_TOO_LARGE`; the cap is published as `submissionSpec.limits.maxBytes`. - The solver body is downloadable from [`GET /api/public/v1/competitions/{competitionId}/submissions/{submissionId}/download`](../endpoints/competitions.md#download-the-stored-text-body). - One submission per `track`. Re-submitting overwrites your previous entry; the leaderboard always reflects the latest content. --- URL: https://docs.sair.foundation/docs/endpoints/cheatsheets/ Markdown: https://docs.sair.foundation/docs-md/endpoints/cheatsheets.md Source: docs/endpoints/cheatsheets.md # Cheatsheets Manage your private Playground cheatsheet library. A cheatsheet can be referenced from a [Playground run](./playground.md#submit-a-run) (`configurations[].cheatsheet`) and submitted to a `cheatsheet`-kind competition. You only see and modify cheatsheets owned by the calling account. ## List cheatsheets ```http GET /api/public/v1/cheatsheets ``` **Scope**: `playground.read`. This endpoint is cursor-paginated. Lists cheatsheets owned by the calling account. The list view omits `content`; call [Get a cheatsheet](#get-a-cheatsheet) for the body. ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :-------------------------------------------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------ | :----------------------------------- | | `items[]` | [CheatsheetSummary](#cheatsheet-summary-fields)[] | Cheatsheet records without `content`. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Cheatsheet summary fields | Field | Type | Description | | :---------------------------------- | :------------- | :----------------------------------------------------------- | | `id` | string | Cheatsheet ID. | | `title` | string | Cheatsheet title. | | `sourceContributorNetworkItemId` | string \| null | Contributor Network item this cheatsheet was forked from. | | `publishedContributorNetworkItemId` | string \| null | Active Contributor Network item published from this sheet. | | `createdAt` | string | ISO 8601 UTC creation timestamp. | | `updatedAt` | string | ISO 8601 UTC update timestamp. | ### Cheatsheet fields `Cheatsheet` includes all [CheatsheetSummary](#cheatsheet-summary-fields) fields plus: | Field | Type | Description | | :-------- | :----- | :--------------- | | `content` | string | Cheatsheet body. | ```json { "ok": true, "data": { "items": [ { "id": "cs_01HXYZ...", "title": "Modular tricks v3", "sourceContributorNetworkItemId": null, "publishedContributorNetworkItemId": null, "createdAt": "2026-04-12T08:21:30Z", "updatedAt": "2026-05-04T17:02:11Z" } ], "nextCursor": null } } ``` ## Get a cheatsheet ```http GET /api/public/v1/cheatsheets/{cheatsheetId} ``` **Scope**: `playground.read`. Returns one cheatsheet, including `content`. ### Path parameters | Parameter | Type | Required | Description | | :------------- | :----- | :------: | :------------- | | `cheatsheetId` | string | Yes | Cheatsheet ID. | ### Response fields Returns a [Cheatsheet](#cheatsheet-fields), including `content`. ## Create a cheatsheet ```http POST /api/public/v1/cheatsheets ``` **Scope**: `playground.write`. Creates a cheatsheet owned by the calling account. ### Request fields | Field | Type | Required | Description | | :------------------------------- | :----- | :------: | :-------------------------------------------------------- | | `title` | string | Yes | Cheatsheet title. | | `content` | string | Yes | Cheatsheet body. | | `sourceContributorNetworkItemId` | string | No | Contributor Network item this cheatsheet was forked from. | ```json { "title": "Modular tricks v3", "content": "When the modulus is prime, use Fermat's little theorem...", "sourceContributorNetworkItemId": "cn_01HX0PA..." } ``` ### Response fields Returns the created [Cheatsheet](#cheatsheet-fields), including `content`. ## Update a cheatsheet ```http PATCH /api/public/v1/cheatsheets/{cheatsheetId} ``` **Scope**: `playground.write`. Partially updates a cheatsheet. Provide at least one request field. A cheatsheet published on the Contributor Network is read-only; withdraw the published item first. ### Path parameters | Parameter | Type | Required | Description | | :------------- | :----- | :------: | :------------- | | `cheatsheetId` | string | Yes | Cheatsheet ID. | ### Request fields | Field | Type | Required | Description | | :-------- | :----- | :------: | :------------------------ | | `title` | string | No | New cheatsheet title. | | `content` | string | No | New cheatsheet body. | ### Response fields Returns the updated [Cheatsheet](#cheatsheet-fields), including `content`. ## Delete a cheatsheet ```http DELETE /api/public/v1/cheatsheets/{cheatsheetId} ``` **Scope**: `playground.write`. Deletes a cheatsheet owned by the calling account. A cheatsheet published on the Contributor Network is read-only; withdraw the published item first. ### Path parameters | Parameter | Type | Required | Description | | :------------- | :----- | :------: | :------------- | | `cheatsheetId` | string | Yes | Cheatsheet ID. | ### Response Returns `204` with no body. ## Errors See [Errors](../errors.md). Endpoint-specific: | Code | HTTP | When | | :------------------ | :--: | :----------------------------------------------------- | | `CHEATSHEET_LOCKED` | 403 | Cheatsheet is published. Withdraw it first. | --- URL: https://docs.sair.foundation/docs/endpoints/solver-templates/ Markdown: https://docs.sair.foundation/docs-md/endpoints/solver-templates.md Source: docs/endpoints/solver-templates.md # Solver templates & Lean problems Manage your private Playground Lean 4 solver snippets and custom problems for `solver-participation` competitions. Solver templates can be copied into a [Playground run](./playground.md#submit-a-run) and submitted to a competition. You only see and modify resources owned by the calling account. Use track IDs from the target competition's `submissionSpec.catalog.tracks[].id`; for the Stage 2 Lean competition, the track IDs are `solo` and `marathon`. ## List solver templates ```http GET /api/public/v1/solver-templates ``` **Scope**: `playground.read`. This endpoint is cursor-paginated. Lists solver templates owned by the calling account. The list view omits `solverCode`; call [Get a solver template](#get-a-solver-template) for the body. ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :-------------------------------------------- | | `track` | string | No | Track ID filter, such as `solo` or `marathon`. | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------ | :------------------------------------------- | | `items[]` | [SolverTemplateSummary](#solver-template-summary-fields)[] | Solver template records without `solverCode`. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Solver template summary fields | Field | Type | Description | | :---------------------------------- | :------------- | :------------------------------------------------------------- | | `id` | string | Solver template ID. | | `title` | string | Template title. | | `track` | string | Track ID, such as `solo` or `marathon`. | | `sourceContributorNetworkItemId` | string \| null | Contributor Network item this template was forked from. | | `publishedContributorNetworkItemId` | string \| null | Active Contributor Network item published from this template. | | `createdAt` | string | ISO 8601 UTC creation timestamp. | | `updatedAt` | string | ISO 8601 UTC update timestamp. | ### Solver template fields `SolverTemplate` includes all [SolverTemplateSummary](#solver-template-summary-fields) fields plus: | Field | Type | Description | | :----------- | :----- | :------------- | | `solverCode` | string | Lean 4 source. | ```json { "ok": true, "data": { "items": [ { "id": "sst_01HXYZ...", "title": "Nat induction starter", "track": "solo", "sourceContributorNetworkItemId": null, "publishedContributorNetworkItemId": null, "createdAt": "2026-04-12T08:21:30Z", "updatedAt": "2026-05-04T17:02:11Z" } ], "nextCursor": null } } ``` ## Get a solver template ```http GET /api/public/v1/solver-templates/{templateId} ``` **Scope**: `playground.read`. Returns one solver template, including `solverCode`. ### Path parameters | Parameter | Type | Required | Description | | :----------- | :----- | :------: | :------------------ | | `templateId` | string | Yes | Solver template ID. | ### Response fields Returns a [SolverTemplate](#solver-template-fields), including `solverCode`. ## Create a solver template ```http POST /api/public/v1/solver-templates ``` **Scope**: `playground.write`. Creates a solver template owned by the calling account. ### Request fields | Field | Type | Required | Description | | :------------------------------- | :----- | :------: | :------------------------------------------------------- | | `title` | string | Yes | Template title. | | `solverCode` | string | Yes | Lean 4 source. | | `track` | string | No | Track ID, such as `solo` or `marathon`. Default `solo`. | | `sourceContributorNetworkItemId` | string | No | Contributor Network item this template was forked from. | ```json { "title": "Nat induction starter", "solverCode": "theorem starter : ... := by ...", "track": "solo", "sourceContributorNetworkItemId": "cn_01HX0PA..." } ``` ### Response fields Returns the created [SolverTemplate](#solver-template-fields), including `solverCode`. ## Update a solver template ```http PATCH /api/public/v1/solver-templates/{templateId} ``` **Scope**: `playground.write`. Partially updates a solver template. Provide at least one request field. `track` and `sourceContributorNetworkItemId` are fixed after creation. A template published on the Contributor Network is read-only; withdraw the published item first. ### Path parameters | Parameter | Type | Required | Description | | :----------- | :----- | :------: | :------------------ | | `templateId` | string | Yes | Solver template ID. | ### Request fields | Field | Type | Required | Description | | :----------- | :----- | :------: | :---------------- | | `title` | string | No | New title. | | `solverCode` | string | No | New Lean 4 source. | ### Response fields Returns the updated [SolverTemplate](#solver-template-fields), including `solverCode`. ## Delete a solver template ```http DELETE /api/public/v1/solver-templates/{templateId} ``` **Scope**: `playground.write`. Deletes a solver template owned by the calling account. A template published on the Contributor Network is read-only; withdraw the published item first. ### Path parameters | Parameter | Type | Required | Description | | :----------- | :----- | :------: | :------------------ | | `templateId` | string | Yes | Solver template ID. | ### Response Returns `204` with no body. ## List custom problems ```http GET /api/public/v1/lean-problems ``` **Scope**: `playground.read`. This endpoint is cursor-paginated. Lists private Lean statement pairs owned by the calling account. ### Query parameters | Parameter | Type | Required | Description | | :-------- | :------ | :------: | :-------------------------------------------- | | `cursor` | string | No | Opaque cursor from the previous `nextCursor`. | | `limit` | integer | No | Page size. Default `25`, maximum `100`. | ### Response fields | Field | Type | Description | | :----------- | :------------ | :----------------------------------- | | `items[]` | [CustomProblem](#custom-problem-fields)[] | Custom problem records. | | `nextCursor` | string \| null | Cursor for the next page, or `null`. | ### Custom problem fields | Field | Type | Description | | :---------- | :------ | :------------------------------------------- | | `id` | string | Problem ID to send in `problemIds`. | | `lhsName` | string | Name of the left-side Lean declaration. | | `lhsText` | string | Left-side Lean source. | | `rhsName` | string | Name of the right-side Lean declaration. | | `rhsText` | string | Right-side Lean source. | | `isCustom` | boolean | Always `true` for this endpoint. | ## Get a custom problem ```http GET /api/public/v1/lean-problems/{problemId} ``` **Scope**: `playground.read`. Returns one [CustomProblem](#custom-problem-fields) owned by the calling account. ### Path parameters | Parameter | Type | Required | Description | | :---------- | :----- | :------: | :----------------- | | `problemId` | string | Yes | Custom problem ID. | ### Response fields Returns a [CustomProblem](#custom-problem-fields). ## Create a custom problem ```http POST /api/public/v1/lean-problems ``` **Scope**: `playground.write`. Creates an immutable private Lean statement pair. ### Request fields | Field | Type | Required | Description | | :-------- | :----- | :------: | :-------------------------------------------------- | | `lhsName` | string | No | Name of the left-side declaration. Derived if omitted. | | `lhsText` | string | Yes | Left-side Lean source. | | `rhsName` | string | No | Name of the right-side declaration. Derived if omitted. | | `rhsText` | string | Yes | Right-side Lean source. | ```json { "lhsName": "my_lemma", "lhsText": "theorem my_lemma : ... := by ...", "rhsName": "my_lemma_alt", "rhsText": "theorem my_lemma_alt : ... := by ..." } ``` ### Response fields Returns the created [CustomProblem](#custom-problem-fields). ## Delete a custom problem ```http DELETE /api/public/v1/lean-problems/{problemId} ``` **Scope**: `playground.write`. Deletes a custom problem owned by the calling account. Existing runs that referenced the problem keep their stored copy and history. ### Path parameters | Parameter | Type | Required | Description | | :---------- | :----- | :------: | :----------------- | | `problemId` | string | Yes | Custom problem ID. | ### Response Returns `204` with no body. ## Errors See [Errors](../errors.md). Endpoint-specific: | Code | HTTP | When | | :----------------------- | :--: | :--------------------------------------------------------- | | `SOLVER_TEMPLATE_LOCKED` | 403 | Solver template is published. Withdraw it first. | | `LEAN_SOURCE_INVALID` | 422 | `solverCode` or custom-problem text failed the Lean 4 parser. |