# 수학도장(MathDojang) 문제 등록 API — LLM 안내서

이 문서는 AI 어시스턴트가 사용자를 대신해 수학 문제를 등록할 수 있도록 작성되었습니다.

> 참고: MCP를 지원하는 클라이언트(ChatGPT 커넥터, Claude 등)는 `https://mathdojang.com/api/mcp`를
> MCP 커넥터로 연결하면 아래 REST 절차 없이 도구 호출만으로 같은 작업을 할 수 있습니다.
> 이 경우 본문·해설 형식 규칙(4장)만 그대로 지키면 됩니다.

## 1. 인증

모든 요청에 발급받은 API 키를 Bearer 토큰으로 넣으세요 (30일 유효):

```
Authorization: Bearer mdk_...
```

키가 동작하는지 먼저 확인하려면: `GET https://mathdojang.com/api/auth/me` — 200이면 유효합니다.
이 키로는 다음 작업만 가능합니다:

- `GET https://mathdojang.com/api/auth/me`: 내 정보 확인
- `POST https://mathdojang.com/api/uploads/sessions`: 이미지 업로드 세션 생성 (아래 4장)
- `GET https://mathdojang.com/api/uploads/sessions/{id}`: 업로드 세션의 이미지 목록 조회
- `POST https://mathdojang.com/api/uploads/images`: 검증 가능한 이미지 자산과 presigned PUT URL 생성 (파일 바이트를 직접 PUT할 수 있는 환경 전용)
- `POST https://mathdojang.com/api/uploads/images/{assetId}/finalize`: PUT한 이미지의 실제 바이트 검증 완료
- `POST https://mathdojang.com/api/uploads/content-images`: 구형 직접 업로드 호환 경로
- `POST https://mathdojang.com/api/problems`: 문제 등록

> MCP 클라이언트에서 `/api/mcp`에 같은 API 키를 연결하면 위 REST 허용 경로 목록과 별개로
> 커뮤니티 도구도 사용할 수 있습니다. 커뮤니티 게시글 목록·상세 조회·작성,
> 본인 일반 글 수정·삭제가 가능하며, 모더레이터 계정은 공지 작성·수정·삭제도 가능합니다.
> API 키로 `/api/posts` REST 쓰기 경로를 직접 호출하는 것은 허용되지 않습니다.

## 2. 문제 등록 — POST https://mathdojang.com/api/problems

Content-Type: application/json. 필드 규칙:

| 필드 | 타입 | 규칙 |
|---|---|---|
| `title` | string | 필수, 1~100자 |
| `body` | string | 필수, 문제 본문 리치 HTML (아래 3장), 최대 40000자 |
| `answerType` | `"number"` \| `"choice"` | 필수. 단답형(숫자) / 5지선다 |
| `answer` | string | 필수. number형: 정답 값. choice형: `"3"` 또는 복수 정답 `"1,3"` (1~5, 중복 불가) |
| `choices` | string[5] | choice형일 때 필수, 정확히 5개. 각 보기는 일반 텍스트와 `$...$` 인라인 수식 조각을 함께 쓰는 문자열 |
| `difficulty` | int | 필수, 1~30 (클수록 어려움). **정하기 전에 난이도 선택 안내 공지를 읽으세요 (아래 6장)** |
| `subject` | enum | 필수, 아래 과목 코드 |
| `unit` | enum | 필수, **선택한 과목에 속한** 단원 코드 |
| `category` | enum | 선택, 미지정 시 `"csat"`. `"csat"`(수능형) / `"school"`(내신형) / `"olympiad"`(경시형) / `"etc"`(기타문제). `"past"`(기출문제)는 운영진 전용이며 기출 시험·학년도·시행년도 태그가 함께 필요합니다(축당 1개) |
| `solution` | string | 필수, 해설 리치 HTML (본문과 동일 형식) |
| `tagIds` | int[] | 선택, 최대 50개. 카탈로그: `GET https://mathdojang.com/api/problems/tags/all` (인증 불필요) |
| `attestOriginal` | true | 필수. 자작 문제임을 확약 — **반드시 사용자에게 확인받은 뒤** true로 보내세요 |
| `attestCopyright` | true | 필수. 저작권 침해가 없음을 확약 — 위와 동일 |

`imageKeys`·`solutionImageKeys`는 legacy 갤러리 필드이므로 LLM 흐름에서는 보내지 마세요. 에디터 이미지는 아래 4장의 인라인 이미지 업로드 절차를 사용해 `body`·`solution` HTML 안에 넣습니다.

### 객관식 보기 수식

- REST API의 `choices`는 일반 텍스트와 `$...$` 수식 조각을 함께 사용합니다.
- 루트는 `"$\sqrt{3}$"`, 분수는 `"$\frac{1}{2}$"`, 지수는 `"$3^{1/2}$"`로 보냅니다.
- 문장과 수식을 섞을 수 있습니다: `"속력은 $\sqrt{3}\,\mathrm{m/s}$이다."`
- MCP 도구의 `create_problem`·`update_problem`은 각 보기를 `"\sqrt{3}"`처럼 하나의 순수 인라인 LaTeX 식으로 받으며, 서버가 `$...$`로 정규화합니다.

### 과목·단원 코드

| subject | 과목 | 허용 unit |
|---|---|---|
| `math1` | 수1 | `exp_log` (지수함수와 로그함수), `trig` (삼각함수), `sequence` (수열) |
| `math2` | 수2 | `limit_continuity` (함수의 극한과 연속), `differentiation` (미분), `integration` (적분) |
| `calculus` | 미적분 | `sequence_limit` (수열의 극한), `differential_calculus` (미분법), `integral_calculus` (적분법) |
| `prob_stat` | 확통 | `counting` (경우의 수), `probability` (확률), `statistics` (통계) |
| `geometry` | 기하 | `conics` (이차곡선), `plane_vector` (평면벡터), `space_geometry` (공간도형과 공간좌표) |
| `middle_math` | 중등수학 | `middle_number_operations` (수와 연산), `middle_change_relations` (변화와 관계), `middle_geometry_measurement` (도형과 측정), `middle_data_probability` (자료와 가능성) |
| `common_math1` | 공통수학1 | `common1_polynomial` (다항식), `common1_equation_inequality` (방정식과 부등식), `common1_counting` (경우의 수), `common1_matrix` (행렬) |
| `common_math2` | 공통수학2 | `common2_coordinate_geometry` (도형의 방정식), `common2_sets_propositions` (집합과 명제), `common2_function_graph` (함수와 그래프) |
| `discrete_math` | 이산수학 | `discrete_graph` (그래프 이론) |
| `other` | 기타 | `competition_integer` (경시대회-정수), `competition_algebra` (경시대회-대수), `competition_geometry` (경시대회-기하), `competition_combinatorics` (경시대회-조합), `other_general` (기타) |

## 3. 본문·해설 HTML 형식 (중요)

`body`와 `solution`은 수학도장 에디터와 호환되는 다음 HTML이어야 합니다.

### 에디터 서식

- 문단·제목: `<p>`, `<h2>`, `<h3>`
- 강조·링크: `<strong>`, `<em>`, `<a href="https://...">`
- 목록·인용: `<ul>`, `<ol>`, `<li>`, `<blockquote>`
- 줄바꿈: `<br>`
- 보기·조건 상자: 시험지처럼 네모 상자로 묶이는 영역은 `<div data-box="보기"><p>ㄱ. ...</p><p>ㄴ. ...</p></div>`로 감쌉니다. `data-box` 값이 라벨로 상자 상단 테두리에 표시됩니다(꺾쇠 `< >`는 자동 부가 — 라벨에 넣지 마세요). 라벨 없는 민짜 상자(조건·증명 과정)는 `data-box=""`. 상자 중첩은 불가합니다.
- 수식 안의 (가)(나)(다) 빈칸 상자는 `data-box`가 아니라 LaTeX `\boxed{\,\text{(가)}\,}`로 표기합니다.
- `class`는 보내지 마세요. 이미지 폭을 제외한 임의 `style`도 제거됩니다.
- **수식은 반드시** 아래 형식만 사용합니다. `$...$`, `\(...\)`, MathML 등은 렌더링되지 않습니다:
  - 인라인 수식: `<span data-math="inline" data-latex="a^2+b^2"></span>` (태그 내용은 비움)
  - 블록(별도 줄) 수식: `<div data-math="block" data-latex="\int_0^1 x\,dx"></div>` (`<p>` 밖에 배치)
- `data-latex` 값은 KaTeX 호환 LaTeX이며 HTML 속성이므로 `"`는 `&quot;`, `<`는 `&lt;`로 이스케이프합니다.

## 4. 이미지 넣기 — 업로드 세션 (권장)

대화에 첨부된 이미지는 당신이 직접 R2로 업로드할 수 없습니다. 대신 **업로드 세션**을 만들어
사용자가 브라우저에서 올리게 하고, 완료 후 조회한 `publicUrl`을 본문에 넣으세요.

1. 업로드 세션을 생성합니다 (12시간 유효, 세션당 최대 10장):

```bash
curl -X POST "https://mathdojang.com/api/uploads/sessions" -H "Authorization: Bearer mdk_..."
```

```json
{ "success": true, "data": { "id": "...", "pageUrl": "https://mathdojang.com/llm-upload/...", "expiresAt": "..." } }
```

2. `pageUrl`을 사용자에게 전달하며 안내하세요: "이 링크를 열어 문제에 넣을 이미지를 올려 주세요
   (수학도장 로그인 필요). 다 올리셨으면 알려 주세요." — 링크는 반드시 응답의 `pageUrl` 그대로 사용합니다.
3. 사용자가 완료를 알리면 세션을 조회해 이미지 목록을 받습니다:

```bash
curl "https://mathdojang.com/api/uploads/sessions/{id}" -H "Authorization: Bearer mdk_..."
```

```json
{
  "success": true,
  "data": {
    "id": "...",
    "expiresAt": "...",
    "expired": false,
    "images": [{ "id": "...", "publicUrl": "https://public-image-url..." }]
  }
}
```

4. 각 `publicUrl`을 본문 또는 해설 HTML에 넣습니다:

```html
<figure data-figure="" data-align="center" style="width:60%"><img src="PUBLIC_URL" alt="이미지 설명" loading="lazy"><figcaption>선택 설명</figcaption></figure>
```

- `data-align`은 `left`, `center`, `right` 중 하나입니다.
- `style`은 `width:20%`부터 `width:100%`까지만 사용합니다. 고정값을 쓰지 말고
  그림 성격에 맞춰 정하세요 — 수직선·좁은 도형 25~40%, 일반 도형·그래프 50~75%,
  표·와이드 그래프 85~100%. 사용자가 에디터에서 드래그로 조정할 수 있는 값과
  같으므로, 애매하면 넉넉한 쪽보다 실제 그림 비율에 가까운 쪽을 고르세요.
- 조회로 받은 `publicUrl` 외의 이미지 URL을 만들거나 추측하지 마세요. `images`가 비어 있으면
  사용자에게 업로드 상태를 다시 확인하세요.
- 어떤 이미지가 어떤 위치에 들어갈지는 사용자와 대화로 확인하세요 (조회 순서 = 업로드 순서).
- 인라인 에디터 이미지는 legacy `imageKeys`·`solutionImageKeys`에 넣지 마세요.
- 세션이 만료되면(`expired: true`) 새 이미지는 못 올리지만 이미 올린 이미지 조회는 계속 됩니다.
  더 올려야 하면 새 세션을 만드세요.
### 파일 바이트를 직접 PUT할 수 있는 환경

WebP 변환을 우선 시도하되 변환 실패만으로 업로드를 중단하지 마세요. PNG, JPEG, WebP, GIF를
모두 허용하며, 변환 후 실제 파일의 MIME과 정확한 바이트 수를 신고해야 합니다.

1. `POST https://mathdojang.com/api/uploads/images`에 다음처럼 요청합니다.

```json
{
  "purpose": "content",
  "variants": [{ "name": "content", "contentType": "image/png", "size": 1024 }]
}
```

2. 응답의 `assetId`를 보관하고 `variants[0].uploadUrl`로 정확히 1,024바이트를 PUT합니다.
   `Content-Type`은 신고한 `image/png`와 정확히 같아야 하고
   `Cache-Control: public, max-age=31536000, immutable` 헤더도 정확히 보내야 합니다.
   응답이 200 또는 204인지 확인합니다.
3. `POST https://mathdojang.com/api/uploads/images/{assetId}/finalize`를 호출합니다. 서버가 실제 바이트,
   MIME, 크기, 디코딩 가능 여부와 치수를 검증합니다.
4. finalize 성공 응답의 `variants[].publicUrl`만 본문·해설에 넣습니다. finalize 전에 URL을
   삽입하지 마세요.

기존 `POST https://mathdojang.com/api/uploads/content-images`로 만든 pending URL을 바로 첨부하면 저장
경로가 기회적으로 finalize하는 호환 동작은 유지되지만, 새 클라이언트는 이에 의존하지 않습니다.

## 5. 응답 형식

- 성공: HTTP 201, `{ "success": true, "data": { "id": "...", ... } }` — 등록된 문제는 임시제출(draft) 상태가 됩니다. 사용자가 사이트에서 `https://mathdojang.com/problems/{id}` 미리보기로 확인·수정한 뒤 "검토 요청"을 눌러야 검수 대기열에 들어갑니다.
- 실패: `{ "success": false, "error": { "code": "...", "message": "...", "fieldErrors": { "필드": "사유" } } }`
  - 401 `API_KEY_INVALID`: 키가 만료·폐기됨 — 사용자에게 새 키 발급을 요청하세요.
  - 400 `VALIDATION`: `fieldErrors`를 보고 해당 필드를 고쳐 재시도하세요.

## 6. 참고 자료 — 운영 공지 (인증 불필요)

출제 기준·난이도 배분 등은 운영 공지로 안내됩니다. 시작하기 전에:

1. 공지 목록 조회: `GET https://mathdojang.com/api/posts?category=notice` — 응답의 `data.items[]`에서 `id`와 `title` 확인
2. **제목을 보고 출제에 필요한 공지라면** 상세를 읽으세요: `GET https://mathdojang.com/api/posts/{id}` — `data.body`가 본문(HTML)
3. 특히 **"문제 난이도 선택 안내"** 공지는 `difficulty` 값을 정하기 전에 반드시 읽고 그 기준을 따르세요.

## 7. 요청 예시

```bash
curl -X POST "https://mathdojang.com/api/problems" \
  -H "Authorization: Bearer mdk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "지수방정식의 해",
    "body": "<p>방정식 <span data-math=\"inline\" data-latex=\"2^{x+1}=16\"></span>을 만족시키는 실수 <span data-math=\"inline\" data-latex=\"x\"></span>의 값을 구하시오.</p>",
    "answerType": "number",
    "answer": "3",
    "difficulty": 3,
    "subject": "math1",
    "unit": "exp_log",
    "solution": "<p><span data-math=\"inline\" data-latex=\"2^{x+1}=2^4\"></span>이므로 <span data-math=\"inline\" data-latex=\"x+1=4\"></span>, 따라서 <span data-math=\"inline\" data-latex=\"x=3\"></span>이다.</p>",
    "attestOriginal": true,
    "attestCopyright": true
  }'
```

## 8. 진행 방법 권장사항

1. 공지 목록에서 필요한 공지(특히 난이도 선택 안내)를 읽으세요.
2. 사용자와 대화하며 에디터 호환 HTML로 문제 본문과 해설을 완성하세요.
3. 문제에 이미지가 필요하면 업로드 세션을 만들어 pageUrl을 안내하고, 사용자의 완료 신호 후
   세션 조회로 받은 publicUrl을 본문·해설에 넣으세요 (4장).
4. 등록 전 최종 JSON을 사용자에게 보여 주세요.
5. 자작·저작권 확약 두 가지를 각각 확인받은 뒤에만 true로 보내세요.
6. `POST https://mathdojang.com/api/problems`로 등록하고 성공·실패 결과를 사용자에게 알리세요.
7. 등록 성공 시 문제는 임시제출(draft) 상태입니다 — 사용자에게 `https://mathdojang.com/problems/{id}`에서 미리보기로 확인하고, 필요하면 수정한 뒤 "검토 요청" 버튼으로 검수를 신청하라고 안내하세요.
8. HTTP 요청을 직접 보낼 수 없는 환경이면, 사용자가 터미널에 붙여넣을 수 있는 완성된 curl 명령을 만들어 주세요.
