# Solver template library

Manage the calling account's private Mathematics Distillation Stage 2 solver drafts. Each template stores one Python implementation of the Stage 2 solver protocol for either the `solo` or `marathon` track.

The library is independent of formal submissions. It may contain multiple templates, while the [formal submission endpoint](./submit-solver.md) stores one current solver for each `(track, modelId)` selection by copying `solverCode`. Updating or deleting a template does not change a formal submission or an existing Playground Run.

## Lifecycle

| State | Editable | Behavior |
| :--- | :---: | :--- |
| Private | Yes | The owner may read, update, delete, test, or publish the template. |
| Published | No | `publishedContributorNetworkItemId` identifies the active Contributor Network item. Withdraw that item before updating or deleting the source template. |
| Withdrawn | Yes | The private template remains, and `publishedContributorNetworkItemId` returns to `null`. It may be edited and published again. |

`sourceContributorNetworkItemId` records the Stage 2 `solver-template` item from which a template was derived. The referenced item must be active and visible when the template is created. The field is then immutable. The source may use another track or model: lineage does not set the new template's execution configuration. A later publication derives its parent from this field and therefore requires that item to remain active; see [Publish a solver template](./publish-contributor-solver.md).

An account may store at most 20 templates in each track, including published templates. Titles are unique within the account and track after trimming. Deleting a private template releases library capacity but does not restore that day's [creation quota](../../../rate-limiting.md#daily-resource-creation).

## Solver template fields

List responses return `SolverTemplateSummary`; item, create, and update responses return `SolverTemplate`, which adds `solverCode`.

| Field | Type | Included | Description |
| :--- | :--- | :--- | :--- |
| `id` | string | All responses | Stable solver template ID. |
| `title` | string | All responses | Trimmed template title. |
| `track` | string | All responses | Immutable Stage 2 track ID. |
| `sourceContributorNetworkItemId` | string \| null | All responses | Contributor Network item recorded when this template was derived, or `null`. |
| `publishedContributorNetworkItemId` | string \| null | All responses | Active item published from this template, or `null`. |
| `createdAt` | string | All responses | ISO 8601 UTC creation time. |
| `updatedAt` | string | All responses | ISO 8601 UTC time of the latest title or `solverCode` update. |
| `solverCode` | string | Item responses only | Python source implementing the Stage 2 solver protocol. Omitted from list responses. |

## List solver templates

```http
GET /api/public/v1/solver-templates
```

**Scope**: `playground.read`. This endpoint is cursor-paginated.

Returns only templates owned by the calling account, ordered by creation time from newest to oldest. Updating a template does not move it to the front of the list. Follow the shared [cursor traversal rules](../../../pagination.md).

### Query parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `track` | string | No | Exact, case-sensitive track ID returned by [Get competition detail](./get-competition-detail.md). Omit to include every track. |
| `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-fields)[] | Template summaries without `solverCode`. |
| `nextCursor` | string \| null | Cursor for the next page, or `null` after the final page. |

```json
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "sst_01JSTAGE200000000000000003",
        "title": "Equivalence prover",
        "track": "solo",
        "sourceContributorNetworkItemId": "cn_01JSTAGE200000000000000002",
        "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 owned [SolverTemplate](#solver-template-fields), including `solverCode`. A missing template and a template owned by another account both return `404 NOT_FOUND`.

### Path parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `templateId` | string | Yes | Solver template ID. |

## Create a solver template

```http
POST /api/public/v1/solver-templates
Content-Type: application/json
```

**Scope**: `playground.write`.

Creates a private solver template for the calling account.

### Request fields

| Field | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `title` | string | Yes | Title after trimming: 1–200 characters and unique within the selected track. |
| `solverCode` | string | Yes | Python source containing at least one non-whitespace character. Its UTF-8 byte length must not exceed the Stage 2 `submissionSpec.limits.maxBytes` returned by [Get competition detail](./get-competition-detail.md). |
| `track` | string | Yes | Exact, case-sensitive track ID returned by the competition detail catalog. |
| `sourceContributorNetworkItemId` | string | No | Active, visible `solver-template` item from this competition. The reference is immutable after creation. |

```bash
curl -X POST "https://api.sair.foundation/api/public/v1/solver-templates" \
  -H "Authorization: Bearer $SAIR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "title": "Equivalence prover",
    "solverCode": "import json\ncontext = json.loads(input())\nprint(json.dumps({\"call\": \"judge\", \"verdict\": \"true\", \"code\": \"by rfl\"}), flush=True)\njudge_result = json.loads(input())",
    "track": "solo",
    "sourceContributorNetworkItemId": "cn_01JSTAGE200000000000000002"
  }'
```

Returns `201 Created` with the created [SolverTemplate](#solver-template-fields), including `solverCode`.

## Update a solver template

```http
PATCH /api/public/v1/solver-templates/{templateId}
Content-Type: application/json
```

**Scope**: `playground.write`.

Partially updates an owned private template. Include `title`, `solverCode`, or both; an empty request object returns `422 RESOURCE_FIELD_INVALID`. `track` and `sourceContributorNetworkItemId` cannot be changed.

The title and `solverCode` constraints are the same as for [Create a solver template](#create-a-solver-template). A published source returns `403 SOLVER_TEMPLATE_LOCKED`; withdraw its Contributor Network item before editing it.

### Path parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `templateId` | string | Yes | Solver template ID. |

### Request fields

| Field | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `title` | string | No | Replacement title. |
| `solverCode` | string | No | Replacement Stage 2 solver source. |

```bash
curl -X PATCH "https://api.sair.foundation/api/public/v1/solver-templates/$TEMPLATE_ID" \
  -H "Authorization: Bearer $SAIR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "title": "Equivalence prover v2"
  }'
```

Returns `200 OK` with the updated [SolverTemplate](#solver-template-fields), including `solverCode`.

## Delete a solver template

```http
DELETE /api/public/v1/solver-templates/{templateId}
```

**Scope**: `playground.write`.

Deletes an owned private template and returns `204 No Content`. Deletion does not alter a formal submission or an existing Playground Run. A published source returns `403 SOLVER_TEMPLATE_LOCKED`; withdraw its Contributor Network item first.

### Path parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `templateId` | string | Yes | Solver template ID. |

## Errors

See [Errors](../../../errors.md). Endpoint-specific errors are:

| HTTP | Code | When |
| :---: | :--- | :--- |
| `403` | `SOLVER_TEMPLATE_LOCKED` | The template has an active Contributor Network publication. |
| `403` | `SOLVER_TEMPLATE_CAP_EXCEEDED` | The account already stores 20 templates in the selected track. |
| `404` | `NOT_FOUND` | The template is missing or is not owned by the calling account. |
| `409` | `DUPLICATE_TITLE` | Another owned template in the same track has the requested trimmed title. |
| `422` | `RESOURCE_FIELD_INVALID` | A field or filter is invalid, the update is empty, or the source Contributor Network item is not an active Stage 2 solver template. |
| `429` | `DAILY_RESOURCE_LIMIT_EXCEEDED` | The account exhausted the UTC-day solver-template creation quota. |
