> ## 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.

# 멱등성 (Idempotency)

> 안전한 재시도를 위한 멱등성 키 사용법

## 개요

멱등성(Idempotency)은 동일한 요청을 여러 번 보내도 결과가 동일하게 유지되는 특성입니다.
네트워크 오류나 타임아웃으로 인한 재시도 시 중복 작업을 방지합니다.

<CardGroup cols={2}>
  <Card title="중복 방지" icon="copy">
    동일한 운세가 두 번 생성되는 것을 방지
  </Card>

  <Card title="안전한 재시도" icon="rotate">
    타임아웃 시 안심하고 재시도 가능
  </Card>
</CardGroup>

***

## 사용 방법

### Idempotency-Key 헤더

`Idempotency-Key` 헤더에 고유한 값을 포함하여 요청합니다:

```bash theme={null}
curl -X POST https://api.sajuapi.dev/v1/fortunes \
  -H "X-API-Key: bs_live_xxx" \
  -H "Idempotency-Key: fortune-2025-01-15-prf_abc123-daily" \
  -H "Content-Type: application/json" \
  -d '{
    "profile_id": "prf_abc123",
    "type": "daily"
  }'
```

### 키 생성 권장 방식

```javascript theme={null}
// 권장: 의미 있는 조합으로 생성
const idempotencyKey = `${operation}-${date}-${profileId}-${type}`;
// 예: "fortune-2025-01-15-prf_abc123-daily"

// 또는 UUID 사용
const idempotencyKey = crypto.randomUUID();
// 예: "550e8400-e29b-41d4-a716-446655440000"
```

***

## 동작 방식

### 첫 번째 요청

```
Client                          Server
  │                               │
  │  POST /v1/fortunes            │
  │  Idempotency-Key: xxx         │
  │ ─────────────────────────────►│
  │                               │
  │                               │  처리 및 결과 저장
  │                               │
  │  200 OK                       │
  │  { fortune data }             │
  │ ◄─────────────────────────────│
```

### 동일 키로 재요청

```
Client                          Server
  │                               │
  │  POST /v1/fortunes            │
  │  Idempotency-Key: xxx         │
  │ ─────────────────────────────►│
  │                               │
  │                               │  저장된 결과 반환
  │                               │  (재처리 없음)
  │                               │
  │  200 OK                       │
  │  Idempotent-Replayed: true    │
  │  { 동일한 fortune data }       │
  │ ◄─────────────────────────────│
```

***

## 응답 헤더

### 첫 번째 요청

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_abc123
```

### 재시도 요청 (멱등성 적용)

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_def456
Idempotent-Replayed: true
```

<Note>
  `Idempotent-Replayed: true` 헤더가 있으면 이전 결과가 재사용된 것입니다.
</Note>

***

## 키 요구사항

| 항목    | 요구사항               |
| ----- | ------------------ |
| 길이    | 10-64자             |
| 문자    | 영문, 숫자, 하이픈, 언더스코어 |
| 유효 기간 | 24시간               |
| 대소문자  | 구분함                |

### 유효한 키 예시

```
fortune-2025-01-15-prf_abc123
550e8400-e29b-41d4-a716-446655440000
create-profile-user12345-20250115
```

### 무효한 키 예시

```
abc         # 너무 짧음 (10자 미만)
key with spaces  # 공백 포함
키-한글      # 한글 포함
```

***

## 지원 엔드포인트

멱등성 키는 상태를 변경하는 (POST, PUT, DELETE) 엔드포인트에서 지원됩니다:

| 엔드포인트                    | 멱등성 지원            |
| ------------------------ | ----------------- |
| POST /v1/profiles        | ✓                 |
| PUT /v1/profiles/{id}    | ✓                 |
| DELETE /v1/profiles/{id} | ✓                 |
| POST /v1/fortunes        | ✓                 |
| POST /v1/fortunes/batch  | ✓                 |
| DELETE /v1/fortunes/{id} | ✓                 |
| POST /v1/webhooks        | ✓                 |
| DELETE /v1/webhooks/{id} | ✓                 |
| GET /v1/\*               | - (GET은 기본적으로 멱등) |

***

## 에러 처리

### 키 재사용 오류 (다른 요청)

동일한 키로 다른 요청을 보내면 409 오류가 발생합니다:

```json theme={null}
{
  "status": 409,
  "type": "/errors/conflict",
  "title": "Conflict",
  "detail": "Idempotency key already used for a different request",
  "instance": "/v1/fortunes"
}
```

### 처리 중 재요청

이전 요청이 아직 처리 중일 때 같은 키로 재요청하면:

```json theme={null}
{
  "status": 409,
  "type": "/errors/conflict",
  "title": "Conflict",
  "detail": "Previous request with this idempotency key is still in progress",
  "retry_after": 5
}
```

### 유효하지 않은 키

```json theme={null}
{
  "status": 400,
  "type": "/errors/validation",
  "title": "Bad Request",
  "detail": "Idempotency key must be 10-64 characters long"
}
```

***

## 구현 예시

### JavaScript/TypeScript

```typescript theme={null}
import { v4 as uuidv4 } from 'uuid';

class SajuClient {
  private apiKey: string;
  private baseUrl = 'https://api.sajuapi.dev';

  async createFortune(profileId: string, options?: {
    type?: string;
    model?: string;
    idempotencyKey?: string;
  }) {
    const idempotencyKey = options?.idempotencyKey ||
      `fortune-${new Date().toISOString().split('T')[0]}-${profileId}`;

    const response = await fetch(`${this.baseUrl}/v1/fortunes`, {
      method: 'POST',
      headers: {
        'X-API-Key': this.apiKey,
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotencyKey
      },
      body: JSON.stringify({
        profile_id: profileId,
        type: options?.type || 'daily',
        model: options?.model || 'haiku'
      })
    });

    const isReplayed = response.headers.get('Idempotent-Replayed') === 'true';
    const data = await response.json();

    return { data, isReplayed };
  }
}
```

### Python

```python theme={null}
import uuid
from datetime import date

class SajuClient:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.sajuapi.dev"

    def create_fortune(
        self,
        profile_id: str,
        fortune_type: str = "daily",
        idempotency_key: str = None
    ):
        if idempotency_key is None:
            idempotency_key = f"fortune-{date.today()}-{profile_id}"

        response = requests.post(
            f"{self.base_url}/v1/fortunes",
            headers={
                "X-API-Key": self.api_key,
                "Idempotency-Key": idempotency_key
            },
            json={
                "profile_id": profile_id,
                "type": fortune_type
            }
        )

        is_replayed = response.headers.get("Idempotent-Replayed") == "true"
        return response.json(), is_replayed
```

***

## 권장사항

<CardGroup cols={2}>
  <Card title="의미 있는 키 사용" icon="key">
    작업 내용을 반영하는 키를 사용하면 디버깅이 쉽습니다.
    예: `create-profile-user123-20250115`
  </Card>

  <Card title="클라이언트에서 생성" icon="laptop">
    서버가 아닌 클라이언트에서 키를 생성해야
    재시도 시 동일한 키를 사용할 수 있습니다.
  </Card>

  <Card title="키 저장" icon="database">
    재시도가 필요한 경우를 대비해
    요청과 함께 키를 저장해 두세요.
  </Card>

  <Card title="24시간 제한 인지" icon="clock">
    키는 24시간 후 만료됩니다.
    장기 작업은 새로운 키를 사용하세요.
  </Card>
</CardGroup>
