> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sajuapi.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 에러

> 에러 코드와 처리 방법

## 에러 응답 형식

모든 에러는 다음 구조를 따릅니다:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "error_code",
    "message": "사람이 읽을 수 있는 설명",
    "hint": "해결 방법 (선택)",
    "field": "specific.field.path (선택)"
  },
  "meta": {
    "requestId": "req_abc123"
  }
}
```

<Tip>
  지원팀에 문의할 때 항상 `requestId`를 포함하세요 - 문제를 빠르게 디버깅하는 데 도움이 됩니다.
</Tip>

## HTTP 상태 코드

| 상태  | 의미      | 일반적인 원인             |
| --- | ------- | ------------------- |
| 200 | 성공      | 요청이 성공적으로 완료됨       |
| 400 | 잘못된 요청  | 잘못된 JSON, 누락된 필드    |
| 401 | 인증 실패   | 잘못되거나 누락된 API 키     |
| 403 | 접근 금지   | 권한 부족               |
| 404 | 찾을 수 없음 | 존재하지 않는 엔드포인트       |
| 429 | 요청 과다   | 요청 제한 초과            |
| 500 | 서버 에러   | 내부 에러 (지원팀 문의)      |
| 503 | 서비스 불가  | AI 제공자 장애 (나중에 재시도) |

## 에러 코드

### 인증 에러 (401)

| 코드                  | 설명           | 해결 방법                   |
| ------------------- | ------------ | ----------------------- |
| `missing_api_key`   | 요청에 API 키 없음 | `X-API-Key` 헤더 추가       |
| `invalid_api_key`   | 키가 존재하지 않음   | 키 형식 확인 (bs\_live\_xxx) |
| `expired_api_key`   | 키가 폐기됨       | 새 키 생성                  |
| `suspended_api_key` | 남용으로 키 정지됨   | 지원팀 문의                  |

```json theme={null}
{
  "error": {
    "code": "invalid_api_key",
    "message": "제공된 API 키가 유효하지 않습니다",
    "hint": "API 키는 'bs_live_' 또는 'bs_test_'로 시작합니다"
  }
}
```

### 유효성 검사 에러 (400)

| 코드              | 설명           | 해결 방법                   |
| --------------- | ------------ | ----------------------- |
| `invalid_json`  | 잘못된 JSON 형식  | JSON 문법 확인              |
| `missing_field` | 필수 필드 누락     | 지정된 필드 추가               |
| `invalid_field` | 필드 타입/형식 오류  | 필드 요구사항 확인              |
| `invalid_saju`  | 사주 구조 오류     | 사주 객체 형식 참고             |
| `invalid_model` | 알 수 없는 모델 지정 | haiku, sonnet, gpt4o 사용 |
| `invalid_date`  | 날짜 형식 오류     | YYYY-MM-DD 형식 사용        |

```json theme={null}
{
  "error": {
    "code": "missing_field",
    "message": "필수 필드 'saju.pillars.day'가 누락되었습니다",
    "field": "saju.pillars.day"
  }
}
```

### 요청 제한 에러 (429)

| 코드                     | 설명          | 해결 방법          |
| ---------------------- | ----------- | -------------- |
| `rate_limited`         | 요청 과다       | 대기 후 재시도       |
| `daily_limit_exceeded` | 일일 할당량 도달   | 플랜 업그레이드 또는 대기 |
| `cost_limit_exceeded`  | 월간 지출 한도 도달 | 대시보드에서 한도 증가   |

```json theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "요청 제한 초과. 42초 후에 다시 시도하세요.",
    "retryAfter": 42
  }
}
```

### 서버 에러 (5xx)

| 코드                    | 설명           | 해결 방법           |
| --------------------- | ------------ | --------------- |
| `internal_error`      | 예상치 못한 서버 에러 | 백오프와 함께 재시도     |
| `ai_provider_error`   | AI API 실패    | 재시도 또는 다른 모델 시도 |
| `timeout`             | 요청 시간 초과     | 더 간단한 요청으로 재시도  |
| `service_unavailable` | 일시적 장애       | 상태 페이지 확인, 재시도  |

```json theme={null}
{
  "error": {
    "code": "ai_provider_error",
    "message": "AI 제공자가 에러를 반환했습니다",
    "hint": "다시 시도하거나 다른 모델을 사용하세요"
  }
}
```

## 에러 처리 모범 사례

### JavaScript/TypeScript

```typescript theme={null}
class SajuClientError extends Error {
  constructor(
    public code: string,
    message: string,
    public requestId?: string,
    public retryAfter?: number
  ) {
    super(message);
    this.name = 'SajuClientError';
  }
}

async function callAPI(endpoint: string, body: object) {
  const response = await fetch(`https://api.sajuapi.dev${endpoint}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.BITHUMB_API_KEY!
    },
    body: JSON.stringify(body)
  });

  const data = await response.json();

  if (!data.success) {
    throw new SajuClientError(
      data.error.code,
      data.error.message,
      data.meta?.requestId,
      data.error.retryAfter
    );
  }

  return data;
}

// 에러 처리와 함께 사용
try {
  const fortune = await callAPI('/api/daily-fortune', { saju, userName });
  console.log(fortune.data);
} catch (error) {
  if (error instanceof SajuClientError) {
    switch (error.code) {
      case 'rate_limited':
        await sleep(error.retryAfter! * 1000);
        // 재시도...
        break;
      case 'invalid_api_key':
        console.error('API 키 설정을 확인하세요');
        break;
      default:
        console.error(`API 에러 [${error.code}]: ${error.message}`);
    }
  }
}
```

### Python

```python theme={null}
class SajuClientError(Exception):
    def __init__(self, code: str, message: str, request_id: str = None, retry_after: int = None):
        self.code = code
        self.message = message
        self.request_id = request_id
        self.retry_after = retry_after
        super().__init__(message)

def call_api(endpoint: str, body: dict):
    response = requests.post(
        f'https://api.sajuapi.dev{endpoint}',
        headers={
            'Content-Type': 'application/json',
            'X-API-Key': os.environ['BITHUMB_API_KEY']
        },
        json=body
    )

    data = response.json()

    if not data.get('success'):
        error = data.get('error', {})
        raise SajuClientError(
            code=error.get('code', 'unknown'),
            message=error.get('message', '알 수 없는 에러'),
            request_id=data.get('meta', {}).get('requestId'),
            retry_after=error.get('retryAfter')
        )

    return data

# 사용법
try:
    fortune = call_api('/api/daily-fortune', {'saju': saju, 'userName': '홍길동'})
except SajuClientError as e:
    if e.code == 'rate_limited':
        time.sleep(e.retry_after)
        # 재시도...
    else:
        print(f'API 에러 [{e.code}]: {e.message}')
        print(f'요청 ID: {e.request_id}')
```

## 디버깅 팁

1. **requestId 확인**: 모든 에러에 고유 ID가 포함됩니다 - 지원 티켓에 포함하세요
2. **field 확인**: 유효성 검사 에러는 종종 어떤 필드가 잘못되었는지 지정합니다
3. **hint 읽기**: 많은 에러에 해결 방법에 대한 힌트가 포함됩니다
4. **상태 페이지 확인**: 5xx 에러의 경우 [status.sajuapi.dev](https://status.sajuapi.dev) 확인
