# 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<string, unknown>;
  };
  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 `<owner>/<name>` 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.
