# Publish a saved solution

```http
POST /api/public/v1/contributor-network/items
Content-Type: application/json
```

**Scope**: `contributor-network.write`.

Publishes an immutable snapshot of one exact Lean Kernel Playground Solution revision. The complete Workspace becomes public and the private Solution is publication-locked until withdrawal. This does not create or replace a formal submission.

Call [Preview a saved solution publication](./preview-contributor-publication.md) first so the user can inspect the current snapshot. The implemented write contract identifies that revision through `source`; it does not accept a nested `confirmation` object.

## Request fields

| Field | Type, requirement, and meaning |
| :--- | :--- |
| `competitionId` | **Required string.** Must be `lean-kernel-challenge`. |
| `kind` | **Required string.** Must be `lean-kernel-package`. |
| `source` | **Required [`LeanKernelSolutionSource`](#solution-source).** Owned Solution revision to publish. |
| `parentContributorNetworkItemId` | **Optional string.** Direct active parent package. |
| `remark` | **Optional string.** Publication note of at most 5,000 characters. |
| `idempotencyKey` | **Required string.** Retry key of at most 200 UTF-8 bytes after trimming. |

`track` and `modelId` must be omitted for Lean Kernel packages.

### Solution source

```ts
type LeanKernelSolutionSource = {
  from: "lean-kernel-solution";
  solutionId: string;
  solutionRevision: number;
};
```

`solutionRevision` must be the Solution's exact current positive revision.

## Idempotency

The first successful publication and every exact retry return `201 Created` with the same item. Reusing the key with a different source, parent, remark, Competition, or kind returns `409 IDEMPOTENCY_CONFLICT`.

After the item is withdrawn, the old key must not be used for a new publication. Obtain a fresh preview and use a new key.

## Example request

```bash
SAIR_IDEMPOTENCY_KEY="lkc-publish-$(openssl rand -hex 16)"

curl -X POST \
  "https://api.sair.foundation/api/public/v1/contributor-network/items" \
  -H "Authorization: Bearer $SAIR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "competitionId": "lean-kernel-challenge",
    "kind": "lean-kernel-package",
    "source": {
      "from": "lean-kernel-solution",
      "solutionId": "17",
      "solutionRevision": 2
    },
    "remark": "Reusable Fibonacci proof package.",
    "idempotencyKey": "'"$SAIR_IDEMPOTENCY_KEY"'"
  }'
```

## Response

The response contains the package summary fields, plus publication transition metadata. It does not include `workspaceFiles`; use [Get a shared package](./get-contributor-package.md) for source content.

```json
{
  "ok": true,
  "data": {
    "id": "cn_8fc20f8a7a3c4bd99506b5d51a6c905e",
    "publicCode": "LKC01-000017",
    "competitionId": "lean-kernel-challenge",
    "kind": "lean-kernel-package",
    "title": "baseline",
    "remark": "Reusable Fibonacci proof package.",
    "author": {
      "sairId": "U-9f7e1c00",
      "displayName": "alice"
    },
    "track": null,
    "modelId": null,
    "favoriteCount": 0,
    "viewerHasFavorited": false,
    "publishedAt": "2026-08-31T09:00:00Z",
    "problemId": "fib",
    "approvedRevision": "7a0abb4a80d635740a91aed1894f714dfbb82edd",
    "solutionRevision": 2,
    "executionSnapshotId": "snapshot-17",
    "templateVersion": "7a0abb4a80d635740a91aed1894f714dfbb82edd",
    "toolchainVersion": "leanprover/lean4:v4.33.1",
    "manifestVersion": "lean-kernel-user-workspace-v1",
    "workspaceSha256": "7d5307e5c809eeb01a95cf0ca6da2d452b5f94bd78e2a2fbc0ea706210e58ea8",
    "workspaceSizeBytes": 241,
    "packageFilename": "baseline.zip",
    "packageFileCount": 2,
    "packageSizeBytes": 432,
    "packageSha256": "5c376eac90aa2d027dddadbd707cfdf105095926336953ae30aecba377b1ef06",
    "downloadUrl": "/api/public/v1/contributor-network/items/cn_8fc20f8a7a3c4bd99506b5d51a6c905e/download",
    "parent": null,
    "published": true,
    "publicItemId": "cn_8fc20f8a7a3c4bd99506b5d51a6c905e",
    "publishCap": 50,
    "capRemaining": 49
  }
}
```

Successful responses use `Content-Type: application/json`.

## Errors

| Error | When |
| :--- | :--- |
| `403 ENROLL_REQUIRED` | The account has not completed Competition enrollment. |
| `403 PLAYGROUND_NOT_STARTED` | The Playground has not opened. |
| `403 PLAYGROUND_READ_ONLY` | The Playground is closed or disabled for new publications. |
| `403 PUBLISH_CAP_EXCEEDED` | No active-publication capacity remains. |
| `404 NOT_FOUND` | The Competition, Solution, or requested parent is unavailable. |
| `409 REVISION_CONFLICT` | The requested Solution revision is no longer current. |
| `409 IDEMPOTENCY_CONFLICT` | The key was used for a different publication intent. |
| `409 SOLUTION_PUBLICATION_LOCKED` | The Solution is already publishing or published. |
| `422 RESOURCE_FIELD_INVALID` | A discriminator, source, key, remark, parent, or kind-specific field is invalid. |
| `429 DAILY_RESOURCE_LIMIT_EXCEEDED` | The UTC-day publication quota is exhausted. |
| `500 PUBLICATION_FAILED` | A consistent publication transition could not be completed or recovered. |
| `502 PLATFORM_UNAVAILABLE` | Competition, lifecycle, enrollment, or capacity authority is unavailable. |
| `502 PLAYGROUND_CONFIGURATION_INVALID` | Playground lifecycle configuration is invalid. |
| `503 PUBLICATION_STORE_BUSY` | The publication store is temporarily busy; retry the same intent with the same key. |

Read capacity from [Get my participation](./get-my-participation.md). See [Errors](../../../errors.md) for shared authentication, scope, and rate-limit errors.
