# Submit solver

```http
POST /api/public/v1/competitions/mathematics-distillation-challenge-equational-theories-stage2/submissions
Content-Type: application/json
```

**Scope**: `competition.write`.

Creates or replaces the active competition team's solver for one Stage 2 `(track, modelId)` selection. The caller must be the team owner and [Get my participation](./get-my-participation.md) must currently return `canSubmit: true`.

Before submitting, read [Get competition detail](./get-competition-detail.md) for the live payload schema, catalog, window, and byte limit. Do not hard-code the example track, model, or limit.

## Request body

```ts
type SubmitSolverRequest = {
  idempotencyKey: string;
  payload: {
    track: string;
    modelId: string;
    solverCode: string;
  };
  meta?: {
    description?: string;
    contributorNetworkItemId?: string;
  };
};
```

| Field | Required | Description |
| :---- | :------: | :---------- |
| `idempotencyKey` | Yes | Non-empty client-generated retry key, at most 200 UTF-8 bytes after surrounding whitespace is removed. |
| `payload.track` | Yes | Case-sensitive track ID from the live `submissionSpec.catalog.tracks`. |
| `payload.modelId` | Yes | Case-sensitive model ID from the live `submissionSpec.catalog.models`. Together with `track`, it identifies the current formal entry to create or replace. |
| `payload.solverCode` | Yes | Python source for `solver.py` containing at least one non-whitespace character. Its UTF-8 byte length must not exceed the live `submissionSpec.limits.maxBytes`. |
| `meta.description` | No | Human-readable note of at most 5,000 characters. |
| `meta.contributorNetworkItemId` | No | Attribution reference to an active `solver-template` Contributor Network item from this competition. It does not copy source or change the selected track or model. |

`solverCode` is a Python implementation of the Stage 2 solver protocol. It receives the current problem and evaluation budget, may request completions from the selected model, and submits a proposed verdict and Lean proof to the official judge.

The pair `(payload.track, payload.modelId)` is the entry identity. Submitting a different model for the same track creates a separate participation; replacing one pair does not modify any other track/model pair.

## Idempotency

Generate one opaque `idempotencyKey` for each intended create or replacement operation. Reuse that key only when retrying the exact same normalized `payload` and `meta` after a timeout or uncertain response.

An exact replay returns the operation's recorded result with `200 OK` and does not update the current entry or create another Participation. Reusing the key with a different track, model, solver, or metadata returns `409 IDEMPOTENCY_CONFLICT`. Use a new key for a deliberate replacement.

## Example request

```bash
export SAIR_IDEMPOTENCY_KEY="stage2-submit-$(openssl rand -hex 16)"

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 '{
    "idempotencyKey": "'"$SAIR_IDEMPOTENCY_KEY"'",
    "payload": {
      "track": "solo",
      "modelId": "openai-gpt-oss-120b",
      "solverCode": "import json\n\ncontext = json.loads(input())\nproblem = context[\"problem\"]\nprompt = \"Prove that {} is equivalent to {} in Lean.\".format(problem[\"equation1\"], problem[\"equation2\"])\nprint(json.dumps({\"call\": \"llm\", \"context\": {\"messages\": [{\"role\": \"user\", \"content\": prompt}]}}), flush=True)\ncompletion = json.loads(input())\nprint(json.dumps({\"call\": \"judge\", \"verdict\": \"true\", \"code\": completion.get(\"response\", \"\")}), flush=True)\njudge_result = json.loads(input())"
    },
    "meta": {
      "description": "Playground-tested Solo solver.",
      "contributorNetworkItemId": "cn_01JSTAGE200000000000000042"
    }
  }'
```

The solver is a protocol example, not a general proof strategy. A competition solver must determine the correct verdict and produce the corresponding proof or counterexample certificate.

## Response

The first entry for a `(track, modelId)` pair returns `201 Created`. A new logical operation that replaces that same pair returns `200 OK` and retains the stable `submissionId`. A different pair returns a separate entry with `201 Created`. An exact idempotency replay returns `200 OK` without changing timestamps or evaluation state.

```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 json\n\ncontext = json.loads(input())\nproblem = context[\"problem\"]\nprompt = \"Prove that {} is equivalent to {} in Lean.\".format(problem[\"equation1\"], problem[\"equation2\"])\nprint(json.dumps({\"call\": \"llm\", \"context\": {\"messages\": [{\"role\": \"user\", \"content\": prompt}]}}), flush=True)\ncompletion = json.loads(input())\nprint(json.dumps({\"call\": \"judge\", \"verdict\": \"true\", \"code\": completion.get(\"response\", \"\")}), flush=True)\njudge_result = json.loads(input())"
    },
    "meta": {
      "description": "Playground-tested Solo solver.",
      "contributorNetworkItemId": "cn_01JSTAGE200000000000000042"
    },
    "createdAt": "2026-05-18T12:34:56Z",
    "updatedAt": "2026-05-18T12:34:56Z"
  }
}
```

`createdAt` remains the creation time of this pair's stable formal entry. `updatedAt` changes only when a new logical replacement is accepted, not when the same idempotency key is replayed.

## Errors

| HTTP | Code | When |
| :---: | :--- | :--- |
| `400` | `MALFORMED_BODY` | The JSON body, content type, or top-level request shape is invalid. |
| `400` | `IDEMPOTENCY_KEY_REQUIRED` | `idempotencyKey` is missing or empty. |
| `400` | `IDEMPOTENCY_KEY_TOO_LONG` | `idempotencyKey` exceeds 200 UTF-8 bytes. |
| `403` | `EMAIL_NOT_VERIFIED` | The caller's email is not verified. |
| `403` | `ENROLL_REQUIRED` | The caller has not completed enrollment. |
| `403` | `TEAM_REQUIRED` | The caller does not currently have an active Stage 2 team. |
| `403` | `TEAM_OWNER_REQUIRED` | The caller is an active team member rather than its owner. |
| `403` | `SUBMISSION_WINDOW_NOT_OPEN` | The formal submission window has not opened. |
| `403` | `SUBMISSION_WINDOW_CLOSED` | The formal submission window has closed. |
| `409` | `IDEMPOTENCY_CONFLICT` | The same key was already used with a different request. |
| `409` | `SUBMISSION_CONFIGURATION_UNAVAILABLE` | The competition currently exposes no valid track or model selection. |
| `413` | `FILE_TOO_LARGE` | `payload.solverCode` exceeds the live UTF-8 byte limit. |
| `422` | `SUBMISSION_PAYLOAD_INVALID` | `payload` is invalid, contains an unknown field, or uses a track or model outside the current catalog. |
| `422` | `SUBMISSION_META_INVALID` | `meta` is invalid or references an unavailable or incompatible Contributor Network item. |

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