# Publish a cheatsheet

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

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

Publishes an immutable snapshot of one owned private Stage 1 cheatsheet to the Contributor Network. The source cheatsheet becomes read-only until its publication is withdrawn.

This operation does not create or replace the team's formal competition submission.

## Request fields

| Field | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `competitionId` | string | Yes | Use the Stage 1 competition ID shown in the example. |
| `kind` | string | Yes | Must be `cheatsheet`. |
| `source` | [Cheatsheet source](#cheatsheet-source) | Yes | Private cheatsheet revision to publish. |
| `remark` | string | No | Publication note of at most 5,000 characters. |
| `idempotencyKey` | string | Yes | Client-generated retry key, at most 200 UTF-8 bytes after trimming. |

`track`, `modelId`, `parentContributorNetworkItemId`, and other source shapes are not accepted by this Stage 1 operation.

### Cheatsheet source

```ts
type CheatsheetSource = {
  from: "cheatsheet";
  cheatsheetId: string;
  updatedAt: string;
};
```

`cheatsheetId` identifies an owned item returned by the [private cheatsheet library](./cheatsheets.md). `updatedAt` must exactly match that item's current value; this prevents a concurrent edit from publishing content the caller did not review.

The server copies the source title and content into the immutable publication. It also derives the lineage parent from the source's immutable `sourceContributorNetworkItemId`; callers cannot replace or remove that provenance during publication.

## Idempotency

Generate one `idempotencyKey` for the intended publication. Reuse it only when retrying the same normalized request after a timeout or uncertain response.

The first successful publication returns `201 Created`. An exact replay returns the original item with `200 OK` and does not create another public item, public code, lineage edge, or quota charge. Reusing the key with a different source revision, remark, competition, or kind returns `409 IDEMPOTENCY_CONFLICT`.

## Example request

```bash
SAIR_IDEMPOTENCY_KEY="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": "mathematics-distillation-challenge-equational-theories-stage1",
    "kind": "cheatsheet",
    "source": {
      "from": "cheatsheet",
      "cheatsheetId": "cs_01JSTAGE100000000000000001",
      "updatedAt": "2026-04-12T08:21:30Z"
    },
    "remark": "Adds a reusable prime-modulus strategy.",
    "idempotencyKey": "'"$SAIR_IDEMPOTENCY_KEY"'"
  }'
```

## Response

Returns the complete item documented by [Get a Shared Cheatsheet](./get-contributor-cheatsheet.md). On the first successful request, the source cheatsheet's `publishedContributorNetworkItemId` becomes the returned item ID.

```json
{
  "ok": true,
  "data": {
    "id": "cn_01JSTAGE100000000000000001",
    "publicCode": "EQT01-000042",
    "competitionId": "mathematics-distillation-challenge-equational-theories-stage1",
    "kind": "cheatsheet",
    "title": "Prime modulus shortcuts",
    "remark": "Adds a reusable prime-modulus strategy.",
    "author": {
      "sairId": "U-9f7e1c00",
      "displayName": "alice",
      "avatarUrl": null
    },
    "contentBytes": 85,
    "contentSha256": "c91039d66e6d249aa522e530d982e56573972f9d00b37e1cd35971a3a4235a29",
    "favoriteCount": 0,
    "viewerHasFavorited": false,
    "commentCount": 0,
    "directChildCount": 0,
    "publishedAt": "2026-04-12T09:00:00Z",
    "content": "When the modulus is prime, use Fermat's little theorem before expanding the equation.",
    "lineage": {
      "depth": 0,
      "parent": null,
      "root": {
        "id": "cn_01JSTAGE100000000000000001",
        "publicCode": "EQT01-000042",
        "available": true
      }
    }
  }
}
```

## Errors

| HTTP | Code | When |
| :---: | :--- | :--- |
| `400` | `MALFORMED_BODY` | The JSON body is malformed, has an invalid top-level shape, or contains an unknown top-level field. |
| `400` | `IDEMPOTENCY_KEY_REQUIRED` | `idempotencyKey` is missing or empty. |
| `400` | `IDEMPOTENCY_KEY_TOO_LONG` | `idempotencyKey` exceeds 200 UTF-8 bytes. |
| `403` | `PUBLISH_CAP_EXCEEDED` | The account has no remaining active-publication capacity for this competition. |
| `404` | `NOT_FOUND` | The source cheatsheet is missing or is not owned by the calling account. |
| `409` | `CHEATSHEET_ALREADY_PUBLISHED` | The source already has an active Contributor Network publication. |
| `409` | `DUPLICATE_CONTENT` | The account already has an active Stage 1 item with identical published content. |
| `409` | `IDEMPOTENCY_CONFLICT` | The key was already used for a different publication request. |
| `409` | `SOURCE_REVISION_CONFLICT` | `source.updatedAt` no longer matches the private cheatsheet. |
| `422` | `RESOURCE_FIELD_INVALID` | A competition, kind, source, remark, or lineage value is invalid. |
| `429` | `DAILY_RESOURCE_LIMIT_EXCEEDED` | The account exhausted the UTC-day Contributor Network creation quota for this competition. |

Read the current active cap from [Get my participation](./get-my-participation.md). Withdrawing an item releases active capacity but does not restore the daily creation quota. See [Errors](../../../errors.md) for shared authentication, scope, and rate-limit errors.
