# Custom problems

Manage the calling account's private equational implication problems for Mathematics Distillation Stage 2 practice runs. Use their IDs in the `problemIds` field documented by [Create a Playground Run](./create-playground-run.md).

Each problem asks whether Equation 1 implies Equation 2 over all magmas:

- `lhsText` is the complete Equation 1 hypothesis.
- `rhsText` is the complete Equation 2 goal.

The `lhs` and `rhs` field names are historical. They do not mean the two sides of one equation, and the texts are equation syntax rather than Lean source. Both `*` and `◇` are accepted as the magma operation symbol.

Custom problems are immutable practice inputs, not formal competition submissions. Only the owner can read or delete them. Existing Playground Runs keep a snapshot, so later deletion does not change their results or history.

## Custom problem fields

| Field | Type | Description |
| :--- | :--- | :--- |
| `id` | string | Stable problem ID to send in `problemIds`. |
| `lhsName` | string | Display name for Equation 1. |
| `lhsText` | string | Complete Equation 1 hypothesis. |
| `rhsName` | string | Display name for Equation 2. |
| `rhsText` | string | Complete Equation 2 goal. |
| `isCustom` | boolean | Always `true` for this resource. |
| `createdAt` | string | ISO 8601 UTC creation time. |

## List custom problems

```http
GET /api/public/v1/lean-problems
```

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

Returns only problems owned by the calling account, ordered by creation time from newest to oldest. Follow the shared [cursor traversal rules](../../../pagination.md).

### Query parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `cursor` | string | No | Opaque cursor from the previous `nextCursor`. |
| `limit` | integer | No | Page size. Default `25`, maximum `100`. |

### Response fields

| Field | Type | Description |
| :--- | :--- | :--- |
| `items` | [CustomProblem](#custom-problem-fields)[] | Custom problems on this page. |
| `nextCursor` | string \| null | Cursor for the next page, or `null` after the final page. |
| `storedTotal` | integer | Number of custom problems currently stored by the calling account. |
| `storageLimit` | integer | Current per-account custom-problem storage limit. |

```json
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "prob_01JSTAGE200000000000000004",
        "lhsName": "Commutativity",
        "lhsText": "x * y = y * x",
        "rhsName": "Reverse commutativity",
        "rhsText": "y * x = x * y",
        "isCustom": true,
        "createdAt": "2026-08-31T09:12:45Z"
      }
    ],
    "nextCursor": null,
    "storedTotal": 7,
    "storageLimit": 1000
  }
}
```

## Get a custom problem

```http
GET /api/public/v1/lean-problems/{problemId}
```

**Scope**: `playground.read`.

Returns one owned [CustomProblem](#custom-problem-fields). A missing problem and a problem owned by another account both return `404 NOT_FOUND`.

### Path parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `problemId` | string | Yes | Custom problem ID. |

## Create a custom problem

```http
POST /api/public/v1/lean-problems
Content-Type: application/json
```

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

Creates an immutable custom problem for the calling account.

### Request fields

| Field | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `lhsName` | string | No | Display name for Equation 1. After trimming, maximum 100 characters. Defaults to `Custom LHS` when omitted or blank. |
| `lhsText` | string | Yes | Complete Equation 1 hypothesis. After trimming, 1–2,000 characters. |
| `rhsName` | string | No | Display name for Equation 2. After trimming, maximum 100 characters. Defaults to `Custom RHS` when omitted or blank. |
| `rhsText` | string | Yes | Complete Equation 2 goal. After trimming, 1–2,000 characters. |

```bash
curl -X POST "https://api.sair.foundation/api/public/v1/lean-problems" \
  -H "Authorization: Bearer $SAIR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "lhsName": "Commutativity",
    "lhsText": "x * y = y * x",
    "rhsName": "Reverse commutativity",
    "rhsText": "y * x = x * y"
  }'
```

The service identifies duplicates by the trimmed `lhsText` and `rhsText` pair within the calling account:

- A new pair returns `201 Created`, consumes one storage slot, and counts toward the [daily creation quota](../../../rate-limiting.md#daily-resource-creation).
- An existing pair returns `200 OK` with the existing problem. It does not replace the stored names or consume another storage slot or creation-quota unit.

Both statuses return the complete [CustomProblem](#custom-problem-fields).

## Delete a custom problem

```http
DELETE /api/public/v1/lean-problems/{problemId}
```

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

Deletes an owned custom problem and returns `204 No Content`. The deletion also removes the problem ID from the owner's saved custom problem sets. It releases one storage slot but does not restore that day's [creation quota](../../../rate-limiting.md#daily-resource-creation). Existing Playground Run snapshots remain unchanged.

### Path parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| `problemId` | string | Yes | Custom problem ID. |

## Errors

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

| HTTP | Code | When |
| :---: | :--- | :--- |
| `403` | `CUSTOM_PROBLEM_CAP_EXCEEDED` | The account has reached the `storageLimit` returned by the list endpoint. |
| `404` | `NOT_FOUND` | The problem is missing or is not owned by the calling account. |
| `422` | `RESOURCE_FIELD_INVALID` | A field or pagination parameter is invalid. |
| `429` | `DAILY_RESOURCE_LIMIT_EXCEEDED` | The account exhausted the UTC-day custom-problem creation quota. |
