# 모두CBT 오픈 API 가이드

내 공부 자료를 AI로 문제로 만들어 모두CBT 암기장에 등록하는 방법입니다. 사람과 AI가 함께 읽도록 썼습니다.

> **AI에게:** 이 문서를 끝까지 읽은 뒤, [1. 한눈에 보기]의 순서대로 진행하세요. 문항은 [4. 문항 JSON 형식]과 [5~7. 작성 규칙]을 **반드시** 지켜야 화면에서 깨지지 않습니다. 확실하지 않은 사실·수치는 지어내지 말고, 등록 전에 항상 `POST /open/v1/validate`로 검사하세요.

- 문서 주소(마크다운): `https://api.moducbt.com/open/v1/docs`
- 형식·한도(JSON): `https://api.moducbt.com/open/v1/schema`
- 키 발급·붙여넣기 등록 화면: `https://www.moducbt.com/me/ai-import`

---

## 1. 한눈에 보기

1. **키 준비** — 모두CBT 로그인 → 마이페이지 `AI로 문제 등록` → `API · AI 에이전트` 탭에서 키(`mcbt_…`)를 발급합니다.
2. **어디에 넣을지 정하기** — 사용자에게 물어봅니다.
   - **새 암기장**을 만들지, **기존 암기장에 시험지를 추가**할지.
   - 기존이면 `GET /open/v1/categories`로 목록을 보여 주고 고르게 한 뒤, `GET /open/v1/categories/{id}`로 이미 있는 시험지(회차) 제목을 확인해 **같은 회차를 중복 등록하지 않게** 합니다.
3. **문항 만들기** — 이 문서의 형식·작성 규칙대로 JSON을 만듭니다. (그림이 있으면 `POST /open/v1/images`로 먼저 올립니다.)
4. **검사** — `POST /open/v1/validate`. `issues`가 있으면 문항 번호별 사유대로 고쳐 `valid: true`가 될 때까지 반복합니다.
5. **등록** — 가장 쉬운 방법은 **`POST /open/v1/import` 한 번**입니다(새 암기장 또는 기존 암기장 + 시험지 여러 개 + 실전 모의고사 설정까지). 시험지 1개씩 넣으려면 `POST /open/v1/categories/{id}/exams`.
   - 시험지(회차) 1개에 최대 **300문항**, 한 요청에 시험지 10개·문항 합계 300개까지. 넘으면 나눠서 보냅니다.
6. **(객관식, 선택) 실전 모의고사 설정** — 과목 구성·과락·합격 점수·제한 시간을 정하면 "실전 모의고사"로 실제 시험처럼 풀 수 있습니다. [8. 실전 모의고사 설정] 참고.
7. **(선택) 공개** — 사용자가 원할 때만 `POST /open/v1/categories/{id}/visibility`. [10. 공개 전환과 저작권]을 먼저 확인합니다.

> 키 없이 쓰는 방법도 있습니다. AI가 만든 JSON을 `https://www.moducbt.com/me/ai-import`의 `붙여넣기로 등록` 탭에 붙여 넣으면 됩니다.

---

## 1-1. MCP로 연결하기 (가장 쉬운 방법)

MCP를 지원하는 AI 도구라면 HTTP 요청을 직접 만들 필요 없이 **도구로** 씁니다. 주소는 `https://api.moducbt.com/mcp` 하나이고, 연결 방법은 두 가지입니다.

- **모두CBT 로그인으로 연결 (claude.ai·ChatGPT 등 커넥터):** 앱의 커넥터(또는 "사용자 지정 커넥터") 추가에서 주소만 넣으면 모두CBT 로그인 화면이 열리고, **허용**을 누르면 연결됩니다. API 키가 필요 없습니다. 연결 해제는 모두CBT `AI로 문제 등록 → API` 탭의 "연결된 앱"에서 합니다.
- **API 키로 연결 (Claude Code, Cursor, Codex 등):** 아래처럼 `X-API-Key` 헤더에 키를 넣습니다.

```bash
# Claude Code
claude mcp add --transport http moducbt https://api.moducbt.com/mcp --header "X-API-Key: mcbt_여기에_키"
```
```json
// Cursor (.cursor/mcp.json) · Claude Desktop 등 헤더를 지원하는 설정
{ "mcpServers": { "moducbt": { "url": "https://api.moducbt.com/mcp", "headers": { "X-API-Key": "mcbt_여기에_키" } } } }
```
헤더를 못 넣는 클라이언트는 `npx mcp-remote https://api.moducbt.com/mcp --header "X-API-Key: mcbt_여기에_키"` 로 연결합니다.

| 도구 | 하는 일 |
|---|---|
| `get_guide` | 이 문서 읽기 (먼저 호출) |
| `get_exam` · `update_questions` · `add_questions` · `delete_questions` | 기존 시험지 보기 · 문항 고치기 · 추가 · 삭제(확인 후) |
| `list_revisions` · `restore_revision` | 수정 기록 · 되돌리기 |
| `list_categories` · `get_category` | 내 암기장 목록 · 시험지 목록/실전 모의고사 설정 |
| `validate_questions` | 저장 없이 문항 검사 (issues·warnings) |
| `import_exams` | 새/기존 암기장 + 시험지 여러 개 + 실전 모의고사 한 번에 |
| `create_exam` · `set_real_exam` · `set_visibility` | 시험지 1개 · 실전 모의고사 설정 · 공개 전환 |
| `upload_image` | 이미지(base64) 올려 url 받기 |

도구 입력은 아래 REST 본문과 같습니다. 실패하면 `isError` 결과에 한국어 사유가 옵니다 — 읽고 고쳐서 다시 호출하세요.

---

## 2. 인증

- 기본 주소: `https://api.moducbt.com`
- 헤더: `X-API-Key: mcbt_...` (또는 `Authorization: Bearer mcbt_...`)
- `Content-Type: application/json`
- 키는 비밀번호처럼 다룹니다. 채팅·로그·저장소에 그대로 남기지 마세요. 새면 화면에서 폐기하고 새로 발급합니다.

---

## 3. 엔드포인트

| 요청 | 설명 |
|---|---|
| `GET /open/v1/schema` | 문항 형식·한도 (인증 불필요) |
| `GET /open/v1/docs` | 이 문서 (마크다운, 인증 불필요) |
| `GET /open/v1/categories` | 내 암기장 목록(최근 100개) |
| `GET /open/v1/categories/{id}` | 암기장 1개 — 시험지 목록(examId·제목·문항 수)·실전 모의고사 설정 |
| `POST /open/v1/import` | **한 번에 등록** — 새/기존 암기장 + 시험지 여러 개 + 실전 모의고사 설정 |
| `POST /open/v1/categories` | 암기장 만들기 — 항상 비공개 |
| `POST /open/v1/images` | 이미지 올리기(자동 압축) → 주소 받기 |
| `POST /open/v1/validate` | 저장 없이 문항 검사 |
| `POST /open/v1/categories/{id}/exams` | 시험지(회차) + 문항 등록 |
| `POST /open/v1/categories/{id}/real-exam` | (객관식) 실전 모의고사 설정·해제 |
| `GET /open/v1/exams/{examId}` | 시험지 문항 보기(questionId·번호 포함) |
| `POST /open/v1/exams/{examId}/questions/update` | 문항 고치기(바꿀 칸만) |
| `POST /open/v1/exams/{examId}/questions/add` | 문항 추가(시험지 끝) |
| `POST /open/v1/exams/{examId}/questions/delete` | 문항 삭제(`confirm: true` 필수) |
| `GET /open/v1/exams/{examId}/revisions` · `POST /open/v1/revisions/{id}/restore` | 수정 기록 · 되돌리기 |
| `POST /open/v1/categories/{id}/visibility` | 공개·비공개 전환 |

### 한 번에 등록 (권장)
새 암기장을 만들면서 시험지 2개를 넣고 실전 모의고사까지 설정하는 예:
```json
POST /open/v1/import
{
  "category": { "name": "내 전기기능사 필기", "examType": "OBJECTIVE" },
  "exams": [
    { "title": "1회차", "questions": [ ... ] },
    { "title": "2회차", "questions": [ ... ] }
  ],
  "realExam": {
    "subjects": [
      { "name": "전기이론", "numberFrom": 1, "numberTo": 20, "cutoffScore": 40 },
      { "name": "전기기기", "numberFrom": 21, "numberTo": 40, "cutoffScore": 40 },
      { "name": "전기설비", "numberFrom": 41, "numberTo": 60, "cutoffScore": 40 }
    ],
    "overallPassScore": 60,
    "timeLimitMinutes": 60
  }
}
```
**기존 암기장에 시험지를 추가**할 때는 `category` 대신 `"categoryId": 123`을 보냅니다(암기장 유형은 그 암기장을 따릅니다).
- 모든 문항을 먼저 검사하고, 틀린 곳이 있으면 **아무것도 만들지 않고** `issues: [{ exam, index, message }]`(시험지 번호·문항 번호)를 돌려줍니다.
- 응답: `{ ok, category: { id, created }, exams: [{ examId, title, questionCount, url }], realExam? }`
- 드물게 중간에 멈추면(하루 한도 등) 앞에서 저장된 시험지는 `createdExams`로 알려 줍니다. **나머지만** `categoryId`로 다시 보내세요.

### 암기장 1개 보기
```json
GET /open/v1/categories/123
→ { ok, category: { id, name, examType, isPublic, url,
    exams: [{ examId, title, questionCount }], realExam: {...} | null } }
```

### 암기장 만들기
```json
POST /open/v1/categories
{ "name": "내 전기기능사 요약 문제", "examType": "OBJECTIVE", "description": "선택" }
```
- `examType`: `"OBJECTIVE"`(객관식·필기) 또는 `"SUBJECTIVE"`(주관식·실기). **한 암기장에는 한 유형만** 넣을 수 있습니다.
- 이름이 이미 있으면 `(2)`, `(3)`…이 자동으로 붙습니다. "필기 기출문제", "기출복원"처럼 공식 암기장과 헷갈리는 이름은 쓸 수 없습니다. 응답: `{ ok, category: { id, name, examType, isPublic: false, url } }`

### 이미지 올리기
```bash
curl -s -X POST https://api.moducbt.com/open/v1/images \
  -H "X-API-Key: $KEY" -F "file=@circuit.png"
```
- `multipart/form-data`의 `file` 필드로 **한 번에 1장**, 원본 최대 15MB (JPG·PNG·WebP·GIF·AVIF).
- 큰 사진도 그대로 보내면 됩니다. 서버가 **긴 변 1600px·약 500KB 이하의 WebP로 자동 압축**하고, 위치정보(EXIF)는 지웁니다.
- 응답: `{ ok, image: { url, width, height, bytes, originalBytes, resized } }` → `url`을 문항의 `images` 또는 `explanationImages`에 넣습니다.
- 이미지를 여러 장 올린 뒤 시험지를 등록하는 순서입니다. 쓰지 않은 이미지는 문항에 연결되지 않습니다.
- 이미지 업로드는 **API 키로만** 됩니다(로그인 쿠키 불가). 문항에는 이 API가 돌려준 `url`만 그대로 넣으세요.

### 검사(저장 안 함)
```json
POST /open/v1/validate
{ "examType": "OBJECTIVE", "questions": [ ... ] }
```
응답: `{ ok, valid, questionCount, issues: [{ index, message }], warnings: [{ index, message }] }` — `index`는 1부터 시작하는 문항 번호(0은 전체).
- `issues`는 **반드시 고쳐야 등록되는 오류**입니다: 형식, 수식(KaTeX로 실제로 그려 보고 실패하면 알려 줌), `$…$`·`<tex>` 밖 수식, 같은 시험지 안 중복 문항, 한 문항 안 같은 보기, base64, script 글자 등.
- `warnings`는 **등록은 되지만 고치면 더 좋아지는 것**입니다: 해설이 보기 번호로 답을 가리킴, 정답 위치 쏠림, 주관식 답안 항목이 한 줄에 붙음. 사용자에게 알려 주거나 고쳐서 다시 검사하세요.

### 시험지 등록
```json
POST /open/v1/categories/{id}/exams
{ "title": "1회차", "questions": [ ... ] }
```
- 제목 앞에 항상 `[암기장 이름]`이 자동으로 붙고(직접 붙일 필요 없음), 겹치면 `(2)`가 붙습니다. 응답의 `exam.title`이 실제 저장된 제목입니다.
- 하나라도 형식 오류가 있으면 **아무것도 저장하지 않고** 400과 `issues`를 돌려줍니다.
- **같은 암기장에 같은 제목이 이미 있으면 409**와 `existingExamId`를 돌려줍니다. 응답을 못 받고 다시 보낸 경우라면 이미 저장된 것이니 `GET /open/v1/categories/{id}`로 확인하세요. 일부러 한 벌 더 만들 때만 `"allowDuplicateTitle": true`를 보냅니다.
- **요청은 하나씩 순서대로** 보내세요. 이전 쓰기 요청이 끝나기 전에 보내면 429가 납니다.
- 응답: `{ ok, exam: { examId, title, questionCount, url } }`

### 공개·비공개 전환
```json
POST /open/v1/categories/{id}/visibility
{ "isPublic": true, "acknowledgeCopyright": true }
```
- 공개하려면 `acknowledgeCopyright: true`가 **필수**입니다. 비공개로 되돌릴 때는 `{ "isPublic": false }`.
- 공개·비공개는 **암기장을 만든 사람만** 바꿀 수 있습니다(공동편집자 불가).

---

## 4. 문항 JSON 형식

```json
{
  "questions": [
    {
      "type": "OBJECTIVE",
      "question": "옴의 법칙에서 전류 I를 구하는 식은?",
      "choices": ["<tex>V/R</tex>", "<tex>VR</tex>", "<tex>R/V</tex>", "<tex>V+R</tex>"],
      "answer": 1,
      "explanation": "전류는 전압에 비례하고 저항에 반비례하므로 <tex>I = V/R</tex> 이다."
    },
    {
      "type": "SUBJECTIVE",
      "question": "산업안전보건법상 안전보건표지의 종류 4가지를 쓰시오.",
      "answer": "① 금지표지\n② 경고표지\n③ 지시표지\n④ 안내표지",
      "explanation": "선택. 모범답안 아래에 [해설]로 붙습니다."
    }
  ]
}
```

| 필드 | 객관식(OBJECTIVE) | 주관식(SUBJECTIVE) |
|---|---|---|
| `type` | `"OBJECTIVE"` | `"SUBJECTIVE"` |
| `question` | 필수, 최대 5,000자 | 필수 |
| `choices` | 필수, 보기 2~6개 문자열 배열(권장 4개) | 사용 안 함 |
| `answer` | 필수, **정답 보기 번호(1부터 시작하는 정수)** | 필수, **모범답안 텍스트** |
| `explanation` | 선택, 해설 | 선택, 모범답안 아래 `[해설]`로 붙음 |
| `images` | 선택, 문제 그림 주소 배열(최대 5개) | 같음 |
| `explanationImages` | 선택, 해설 그림 주소 배열(최대 5개) | 같음 |

- **그림은 `POST /open/v1/images`로 먼저 올리고 받은 주소만** `images`/`explanationImages`에 넣습니다. 다른 사이트 주소, 다른 사람이 올린 주소는 거부됩니다.
- **이미지를 base64 문자열(`data:image/...;base64,…`)로 본문에 넣지 마세요.** 검사에서 거부됩니다.
- 그림은 문제 글 아래에 표시됩니다. 문제 글에 "다음 그림에서…"처럼 그림을 가리키는 말을 넣으세요.
- 표·수식으로 쓸 수 있는 것(간단한 표, 공식)은 그림보다 `<table>`·`<tex>`가 선명하고 검색도 됩니다.

---

## 5. 화면 서식 규칙 (에디터 특성)

모두CBT는 문항을 **HTML 조각**으로 저장하고 그대로 그립니다. 아래 규칙을 지키면 화면에서 깔끔하게 보입니다.

- **줄바꿈은 `\n`** — 서버가 `<br>`로 바꿉니다. 블록 사이(질문과 보기·조건 사이)는 `\n\n`으로 한 줄 더 띄우세요.
- **허용 태그:** `b strong i em u s sup sub br p div span ul ol li table thead tbody tr th td code pre blockquote hr tex`. 나머지 태그와 모든 속성(style·class·링크·이미지)은 **지워집니다**. 표 칸 합치기(`colspan`, `rowspan`)만 허용됩니다.
- **꺾쇠:** 허용 태그가 아닌 `<`는 서버가 글자로 보존합니다(예: `A<B<C`, `<Ctrl>+C`). `<tex>` 안의 `<`·`>`는 `\lt`·`\gt`로 바뀝니다. 헷갈리지 않게 처음부터 `&lt;` `&gt;` 또는 수식 `<tex>a \lt b</tex>`로 쓰는 것을 권합니다.
- **표·목록 태그 앞뒤 줄바꿈**은 서버가 지웁니다(표 안에 빈 줄이 생기지 않게). 표는 한 줄로 이어 써도 됩니다.
- **표**는 `<table>`로 만들 수 있습니다. 3열 이하가 휴대폰에서 읽기 좋습니다.
  ```
  <table><tr><th>구분</th><th>값</th></tr><tr><td>정격전압</td><td>220V</td></tr></table>
  ```
- **화학식·단위 첨자:** 아래첨자 `<sub>`, 위첨자 `<sup>`. 예) `CO<sub>2</sub>`, `H<sub>2</sub>SO<sub>4</sub>`, `m<sup>2</sup>`, `10<sup>-3</sup>`
- **번호 기호:** 순서·빈칸·항목 기호는 `① ② ③ ④ …`로 통일합니다(`a b c`, `ㄱ ㄴ ㄷ`, `(1)(2)` → `①②③`). 빈칸은 `(①)`, `(②)`로 표시합니다.
- **조건·보기가 있는 문제의 권장 구조:**
  ```
  (질문 문장)\n\n[조건] 전압 220V, 저항 10Ω\n\n① …\n② …
  ```
- `&nbsp;` 같은 공백 엔티티로 줄을 맞추지 마세요. 줄마다 `\n`으로 나눕니다.

---

## 6. 수식 규칙 (KaTeX)

수식은 `<tex>…</tex>` 태그 안에 쓴 **KaTeX(LaTeX 문법)만** 수식으로 그려집니다.

| 해야 할 것 | 하지 말 것 |
|---|---|
| `<tex>\dfrac{V}{R}</tex>` | `$\dfrac{V}{R}$`, `\(…\)`, `$$…$$` — 인식되지 않아 기호가 그대로 보임 |
| 한 줄 수식. 여러 식은 `<tex>`를 여러 개로 나누고 사이에 글 | `\begin{aligned}`, `\\` 줄바꿈 같은 여러 줄 환경 |
| 천 단위 콤마 `1{,}000{,}000` | `1,000,000` (수식 안에서 간격이 깨짐) |
| 단위는 태그 밖: `<tex>v = 6.54</tex> m/s` | 단위까지 전부 태그 안에 넣기 |
| 부등호는 `\lt`, `\gt`, `\le`, `\ge` | 태그 안에 `<`, `>` 그대로 |
| 한글 설명은 태그 밖에 | `\text{…}` 안에 긴 한글 문장 |

**⚠️ JSON 안에서는 백슬래시를 두 번 써야 합니다.** JSON 문자열에서 `\f`, `\t`, `\b`, `\n`, `\r`는 제어문자로 바뀌어 `\frac`, `\times`, `\beta`가 **조용히 깨집니다.**

```json
{ "question": "<tex>\\dfrac{1}{2} \\times 4 = 2</tex> 가 맞는가?" }
```
(화면에는 ½ × 4 = 2 로 보입니다. 검사 API가 한 번만 쓴 백슬래시를 찾아 알려 줍니다.)

**계산 문제 예시**
```
question: "전압 220V, 저항 10Ω 회로의 전류는 몇 A인가?"
choices:  ["11", "22", "2.2", "220"]   answer: 2
explanation: "<tex>I = \\dfrac{V}{R} = \\dfrac{220}{10} = 22</tex> A"
```

---

## 7. 좋은 암기장을 만드는 작성 규칙

**공통**
- 자료에 있는 내용만 씁니다. 수치·법령·기준값을 지어내지 마세요. 애매하면 그 문제는 빼세요.
- 문제는 짧고 분명하게. 판단에 필요한 조건·보기·구분은 **문제 안에** 넣습니다(해설로 빼면 문제가 성립하지 않습니다).
- 정답이 되는 용어는 **원문 표기 그대로** 씁니다. 임의 괄호 풀이·동의어를 덧붙이지 마세요. 예) `제노그램` ✅ / `제노그램(직업가계도)` ❌
- 같은 내용을 묻는 문제를 반복하지 마세요.

**객관식**
- 보기는 4개를 권장합니다. 정답 위치(1~4)를 고르게 섞고, 오답도 그럴듯하게 만드세요.
- 해설은 1~3문장으로 왜 그 답인지 씁니다. **"③번이 정답"처럼 보기 번호로 가리키지 말고 내용으로** 씁니다. 예) "전류는 저항에 반비례하므로 V/R 이다."
- 계산 문제는 입력 수치를 직접 정하고 **답을 한 번 더 계산해 검산**하세요. 보기 안에 정답이 정확히 하나만 있어야 합니다.
- 해설 서식: 첫 문장에 정답 근거를 쓰고, 핵심어 2~4곳만 `<b>`로 강조합니다. 오답 이유를 여러 개 적을 때는 `<ul><li>…</li></ul>`로 나눕니다.
- 정답이 둘 이상이거나(복수정답) 모두 정답인 문제는 `answer`에 대표 번호 하나만 넣고, 해설 첫 줄에 "복수정답: ①, ③"처럼 적습니다.
- 변수 첨자는 수식으로 씁니다: V₁ → `<tex>V_1</tex>`. 화학식·단위는 `<sub>`·`<sup>`(예: `CO<sub>2</sub>`, `m<sup>2</sup>`).

**주관식** — 모범답안은 **AI 쌤 채점 기준**이 됩니다.
- 핵심어 위주로 간결하게. 서술형 법령 문장은 "핵심 동사구"로 줄입니다. 예) "근로자가 관리감독자의 지휘에 따라 작업하도록 할 것" → "관리감독자 지휘에 따라 작업"
- **여러 항목은 한 줄에 하나씩** 번호를 붙입니다: `① 금지표지\n② 경고표지\n③ 지시표지`. 빈칸형은 `①: 31\n②: 4`처럼 빈칸 번호와 맞춥니다.
- "N가지 쓰시오"는 N개 이상을 씁니다(대체 정답이 있으면 함께).
- 채점 키워드(용어·수치·단위·순서)는 절대 바꾸지 않습니다. 줄여도 되는 것은 어미·조사·군더더기뿐입니다.
- 모범답안 칸에 "(각 2가지씩 작성)" 같은 안내 문구를 넣지 마세요.

---

## 8. 실전 모의고사 설정 (객관식)

실전 모의고사는 암기장의 시험지들에서 **과목별로 문제를 뽑아** 실제 시험처럼 풀고, **과목별 과락과 평균 합격 점수**로 합격·불합격을 알려 주는 기능입니다. 설정은 암기장을 만든 사람만 할 수 있습니다.

**방식 ① 번호 범위 (대부분 이걸 씁니다)** — 회차 시험지마다 전 과목이 순서대로 들어 있을 때. 예) 1회차 60문항 중 1~20번 = 1과목, 21~40번 = 2과목…
```json
POST /open/v1/categories/123/real-exam
{
  "subjects": [
    { "name": "전기이론", "numberFrom": 1,  "numberTo": 20, "cutoffScore": 40 },
    { "name": "전기기기", "numberFrom": 21, "numberTo": 40, "cutoffScore": 40 },
    { "name": "전기설비", "numberFrom": 41, "numberTo": 60, "cutoffScore": 40 }
  ],
  "overallPassScore": 60,
  "timeLimitMinutes": 60
}
```
- 문항 번호는 시험지에 넣은 **순서(1부터)** 입니다. 그래서 **모든 회차 시험지를 같은 과목 순서·같은 문항 수로** 만들어야 합니다.
- 각 과목은 모든 회차의 해당 번호 범위에서 `questionCount`개(기본 = 범위 안 문항 수)를 뽑습니다.
- 출제에 쓸 시험지는 기본으로 **이 암기장의 모든 시험지**입니다. 일부만 쓰려면 `"examIds": [시험지 id…]`를 함께 보냅니다.

**방식 ② 과목별 시험지** — 과목마다 시험지를 따로 만들었을 때. 예) "전기이론 문제은행", "전기기기 문제은행"
```json
{
  "subjects": [
    { "name": "전기이론", "examIds": [501], "questionCount": 20, "cutoffScore": 40 },
    { "name": "전기기기", "examIds": [502, 503], "questionCount": 20, "cutoffScore": 40 }
  ],
  "overallPassScore": 60
}
```

| 필드 | 뜻 | 기본값·범위 |
|---|---|---|
| `subjects[].name` | 과목명 | 필수, 40자 |
| `subjects[].numberFrom`·`numberTo` | (방식 ①) 과목 문항 번호 범위 | 1 이상, 시작 ≤ 끝 |
| `subjects[].examIds` | (방식 ②) 과목 출제 시험지 id | 이 암기장 안의 시험지만 |
| `subjects[].questionCount` | 과목당 출제 수 | ① 기본 = 범위 크기 / ② 필수, 1~200 |
| `subjects[].cutoffScore` | 과목 과락(100점 만점, 미만이면 과락) | 기본 40 |
| `overallPassScore` | 전체 평균 합격 점수 | 기본 60 |
| `timeLimitMinutes` | 제한 시간(분) | 선택, 1~600 |
| `notice` | 응시자 안내문(평문) | 선택, 300자 |

- 과목은 1~10개. 한 설정 안에서는 **한 가지 방식만** 씁니다.
- 끄려면 `{ "off": true }`. 현재 설정은 `GET /open/v1/categories/{id}`의 `realExam`에서 봅니다.
- ⚠️ 설정은 **저장할 때의 시험지 목록**을 기준으로 합니다. 나중에 시험지를 추가하면 이 설정을 **한 번 더 보내야** 새 시험지가 출제에 들어갑니다(시험지 등록 응답에 `realExamNotice`로 알려 줍니다). `import`에 `realExam`을 같이 보내면 자동으로 다시 저장됩니다.
- 응답의 `warnings`(예: 어떤 과목의 문항이 모자람)를 사용자에게 알려 주세요.
- **실제 시험 기준(과목 구성·과락·합격 점수·시간)을 모르면 지어내지 말고 사용자에게 물어보세요.** 많은 국가기술자격 필기는 과목당 40점 미만 과락·평균 60점 이상 합격이지만, 시험마다 다릅니다.

---

## 8-1. 기존 시험지 고치기

이미 등록한 시험지의 문제를 고치거나, 문항을 더하거나 지울 수 있습니다. 내가 만든 시험지(또는 공동편집 암기장의 시험지)만, 공식 암기장은 불가합니다.

1. `GET /open/v1/categories/{id}` → `exams`에서 시험지 id 확인
2. `GET /open/v1/exams/{examId}` → 문항마다 `questionId`·`number`와 등록 때와 같은 형식의 내용
3. 고치기 — **바꿀 칸만** 보냅니다. 문항 id가 그대로라 학생 풀이 기록·북마크·오답노트가 유지됩니다.
```json
POST /open/v1/exams/29070/questions/update
{ "questions": [
  { "questionId": 1201, "explanation": "전류는 전압에 비례하고 저항에 반비례하므로 <tex>I = V/R</tex> 이다." },
  { "questionId": 1203, "choices": ["2", "4", "6", "8"], "answer": 2 }
] }
```
- 합쳐진 결과를 새 등록과 같은 규칙으로 검사하고, 하나라도 틀리면 **아무것도 바꾸지 않습니다**. 한 번에 100개까지.
- 바꾸기 전 내용은 백업됩니다. `GET …/revisions`로 기록을 보고 `POST /open/v1/revisions/{id}/restore`로 되돌립니다.
4. 추가 — `POST …/questions/add { "questions": [...] }` (시험지 끝에 붙음, 한도는 새 등록과 같음)
5. 삭제 — `POST …/questions/delete { "questionIds": [...], "confirm": true }`. **되돌릴 수 없고 학생 풀이 기록도 사라지므로 반드시 사용자에게 먼저 확인**하세요. `confirm` 없이 보내면 영향(풀이 기록 수)만 알려 줍니다. 내용만 바꿀 땐 삭제 대신 고치기를 쓰세요.
- 실전 모의고사(번호 범위)를 쓰는 암기장에 문항을 추가·삭제하면 번호 범위가 달라질 수 있어요. 필요하면 실전 모의고사 설정을 다시 저장하세요.

## 9. 한도와 오류 처리

| 항목 | 한도 |
|---|---|
| 시험지 1개당 문항 | 300문항 |
| 한 번에 등록(import) | 시험지 10개 · 문항 합계 300개 |
| 암기장 1개당 시험지 | 300개 |
| 하루 등록 | 2,000문항 · 암기장 20개 |
| 호출 | 등록 분당 10회 · 시간당 120회 · 조회 분당 60회 |
| 문항 1개(문제+보기+답+해설 합계) | 10,000자 |
| 하루 등록 글자 수 | 3,000,000자 |
| 요청 크기 | 2MB (이미지는 15MB) |
| 이미지 | 분당 30장 · 하루 300장 · 한 번에 1장 |
| API 키 | 계정당 5개 |

| 응답 | 뜻 | 할 일 |
|---|---|---|
| `400` + `issues` | 문항 형식 오류 — 아무것도 저장 안 됨 | 번호별 사유대로 고쳐 다시 보냄 |
| `401` | 키 없음·틀림·폐기 | 키 확인, 필요하면 재발급 |
| `403` | 내 암기장이 아님 / 저작권 게시중단 암기장 / 실전 모의고사는 만든 사람만 | 내 암기장 id 사용 |
| `413` | 요청이 너무 큼 | 문항을 나눠 보냄 |
| `409` | 같은 암기장에 같은 제목의 시험지가 이미 있음 | 이미 저장됐는지 확인. 새 제목을 쓰거나 `allowDuplicateTitle: true` |
| `415` | JSON 이 아닌 본문 | `Content-Type: application/json` 으로 보냄 |
| `429` | 호출 제한 · 이전 요청 처리 중 | **60초 기다린 뒤**(처리 중이면 응답 받은 뒤) 다시 시도 |
| `503` | 이미지 업로드 일시 중지 | 잠시 후 다시 시도, 급하면 그림 없이 등록 |

모든 오류는 한국어 `message`를 담고 있습니다. AI는 `message`와 `issues`를 읽고 스스로 고치세요.

---

## 10. 공개 전환과 저작권

> ⚠️ **공개 전환 시 저작권에 유의해 주세요. 교재·강의·타 사이트의 문제와 해설을 그대로 옮긴 암기장은 공개할 수 없으며, 저작권 신고가 들어오면 게시가 중단되고 이용이 제한될 수 있어요.**

- API로 만든 암기장은 **항상 비공개**로 시작합니다. 비공개 암기장은 나만 볼 수 있습니다.
- 공개는 사용자가 원할 때만, `acknowledgeCopyright: true`와 함께 따로 요청합니다.
- 직접 정리한 노트, 강의 필기처럼 **내가 쓸 권리가 있는 자료**로 만드세요. 교재·타 사이트 문제를 그대로 옮기는 것은 비공개라도 권하지 않습니다.
- 저작권 신고로 게시가 중단된 암기장은 다시 공개할 수 없습니다(`403`).

---

## 11. 전체 예시 (curl)

```bash
KEY="mcbt_여기에_발급받은_키"

# 1) 암기장 만들기
curl -s -X POST https://api.moducbt.com/open/v1/categories \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"name":"내 전기기능사 요약 문제","examType":"OBJECTIVE"}'

# 2) (그림이 있으면) 이미지 올리기 → 응답의 image.url 을 문항 images 에
curl -s -X POST https://api.moducbt.com/open/v1/images \
  -H "X-API-Key: $KEY" -F "file=@circuit.png"

# 3) 검사
curl -s -X POST https://api.moducbt.com/open/v1/validate \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d @questions.json

# 4) 등록 ({id}는 1번 응답의 category.id)
curl -s -X POST https://api.moducbt.com/open/v1/categories/{id}/exams \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"title":"1회차","questions":[{"type":"OBJECTIVE","question":"전압 220V, 저항 10Ω 회로의 전류는 몇 A인가?","choices":["11","22","2.2","220"],"answer":2,"explanation":"<tex>I = \\dfrac{220}{10} = 22</tex> A"}]}'
```

---

## 12. AI에게 맡길 때 쓰는 지시문 예시

```
모두CBT 오픈 API로 내 암기장에 문제를 등록해 줘.
1) https://api.moducbt.com/open/v1/docs 를 먼저 끝까지 읽고 그 규칙을 지켜.
2) 인증 헤더: X-API-Key: mcbt_…
3) 새 암기장을 만들지, 기존 암기장(GET /open/v1/categories)에 시험지를 추가할지 먼저 나한테 물어봐.
   아래 자료로 문제를 만들어 validate 로 검사한 뒤 POST /open/v1/import 로 등록해.
   객관식이면 실전 모의고사 설정(과목·과락·합격 점수·시간)을 할지도 물어보고, 기준은 지어내지 마.
   그림이 필요한 문제는 POST /open/v1/images 로 그림을 먼저 올리고 받은 url 을 images 에 넣어.
4) 한 시험지는 최대 300문항, 429면 60초 기다렸다 재시도.
5) 공개 전환은 내가 요청할 때만. 끝나면 암기장 주소와 등록한 문항 수를 알려 줘.
[자료]
(여기에 자료)
```
