# 오류와 멱등 키

> Pumati MCP 오류 코드와, 멱등 키로 안전하게 다시 시도하는 방법.

실패한 호출은 도구 오류(`isError: true`)이고 본문은 JSON이에요.

```
{ "error": "INVALID_INPUT", "message": "invalid fields", "fields": ["ends_at"] }
```

도구 입력 스키마에 맞지 않는 인자(빠진 필드, 범위를 벗어난 값, `status: verified_by_human`처럼 허용되지 않는 값)도 `INVALID_INPUT`이에요. `fields`는 고쳐야 할 입력을 점으로 이은 경로(`bonus.amount_each`, `options.1.label`)로 알려 주고 `INVALID_INPUT`일 때만 있어요. `message`는 무엇이 잘못됐는지 영어로 적어요.

| 코드 | 뜻과 해결 방법 |
| --- | --- |
| `INVALID_INPUT` | 입력이 없거나, 범위를 벗어났거나, 허용되지 않는 값이에요. `fields`에 어느 입력인지, 알 수 있으면 `message`에 이유가 나와요(예: 이미지가 올바른 base64가 아니거나 PNG·JPEG·WebP가 아님). 고쳐서 다시 부르세요. |
| `NOT_FOUND` | 이 연결이 쓸 수 있는 앱 안에 그런 앱·미션·빌드·피드백이 없거나(다른 앱은 없는 것과 똑같이 보여요), 그런 이름의 도구가 없어요. |
| `FORBIDDEN` | 이 연결이 그 앱에 해당 권한이 없거나, 새 앱을 만들 수 없거나, 철회됐거나(다시 연결하세요), 계정이 정지됐어요. |
| `SUBSCRIPTION_REQUIRED` | 계정에 사용 중인 Indie·Team 요금제가 없어요. pumati.app/dev/credits에서 고르세요. |
| `INVALID_STATE` | 미션 상태가 이 동작을 허용하지 않아요. 예: 이미 게시한 미션을 다시 게시. |
| `DRAFT_CHANGED` | `expected_revision` 뒤에 초안이 바뀌었어요. `get_test`로 다시 읽고 새 버전으로 게시하세요. |
| `REVISION_CHANGED` | 검수한 `revision_no` 뒤에 피드백이 수정됐어요. `get_feedback`으로 다시 읽고 새 버전을 검수하세요. |
| `CLAIMED` | 지금 Pumati 검수자가 이 피드백을 보고 있어요. 나중에 다시 하거나 검수자에게 맡기세요. |
| `SPEND_LIMIT` | 예산이 이 연결의 남은 지출 한도를 넘어요. 다시 연결해 한도를 올리거나 더 작게 게시하세요. |
| `INSUFFICIENT_CREDITS` | 앱 소유자 잔액으로 예산을 채울 수 없어요. pumati.app/dev/credits에서 충전하세요. |
| `BUILD_REQUIRED` | 대상 플랫폼에 연결된 빌드가 없어요. 빌드를 등록하고 `build_ids`에 넣으세요. |
| `BUILD_NOT_APPROVED` | 연결된 iOS 빌드가 아직 Apple 베타 심사를 기다리고 있어요. |
| `BUILD_EXPIRES_TOO_SOON` | 연결된 빌드가 미션 마감 전에 만료돼요. 더 일찍 끝내거나 새 빌드를 연결하세요. |
| `IDEMPOTENCY_MISMATCH` | 이 `idempotency_key`는 다른 입력에 쓰였어요. 새 키를 쓰세요. |
| `IDEMPOTENCY_NOT_GRANTED` | 이 키로 만든 앱을 이 연결이 쓸 수 없어요. 새 키를 쓰세요. |
| `INTERNAL` | 서버에서 문제가 생겼어요. 같은 `idempotency_key`로 다시 시도하세요. |

## 멱등 키

모든 작성·게시 도구는 선택 입력 `idempotency_key`를 받아요. `A-Z a-z 0-9 . _ : -` 1~128자예요. 키는 **계정과 보낸 OAuth 클라이언트** 단위로 구분돼요. 다른 클라이언트(예: Claude Code 다음 Codex)에서 같은 키를 보내면 새 요청이라 새로 만들어요. 같은 클라이언트로 다시 로그인하면 키가 유지되지만, 서버를 지웠다가 다시 추가하는 등 새로 등록한 클라이언트는 다른 클라이언트로 봐요.

- 같은 키로 같은 입력을 보내면 다시 실행하지 않고 처음 결과를 돌려줘요. 시간 초과 뒤 다시 보내도 두 번 게시되지 않아요.
- 같은 키로 다른 입력을 보내면 `IDEMPOTENCY_MISMATCH`예요. 새 요청에는 새 키를 쓰세요.
- 실패한 호출은 아무것도 저장하지 않으니 같은 키로 다시 시도해도 돼요.
- 같은 키로 동시에 보낸 호출은 첫 호출을 기다렸다가 그 결과를 돌려줘요.

의도가 드러나는 키를 쓰세요. 예: `publish-<mission_id>-<revision>`.

## 초안 버전

초안에는 `draft_revision`이 있어요. `publish_test`는 마지막으로 본 버전을 `expected_revision`으로 받고, 그 뒤 초안이 바뀌었으면(예: 웹에서 수정) `DRAFT_CHANGED`로 실패해요. `get_test`로 초안을 다시 확인하고 새 버전으로 게시하세요.
