멱등성(Idempotency)은 동일한 요청을 여러 번 보내도 결과가 동일하게 유지되는 특성입니다.
네트워크 오류나 타임아웃으로 인한 재시도 시 중복 작업을 방지합니다.
중복 방지 동일한 운세가 두 번 생성되는 것을 방지
안전한 재시도 타임아웃 시 안심하고 재시도 가능
사용 방법
Idempotency-Key 헤더
Idempotency-Key 헤더에 고유한 값을 포함하여 요청합니다:
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"
}'
키 생성 권장 방식
// 권장: 의미 있는 조합으로 생성
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 / 1.1 200 OK
Content-Type : application/json
X-Request-Id : req_abc123
재시도 요청 (멱등성 적용)
HTTP / 1.1 200 OK
Content-Type : application/json
X-Request-Id : req_def456
Idempotent-Replayed : true
Idempotent-Replayed: true 헤더가 있으면 이전 결과가 재사용된 것입니다.
키 요구사항
항목 요구사항 길이 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/ ✓ DELETE /v1/profiles/ ✓ POST /v1/fortunes ✓ POST /v1/fortunes/batch ✓ DELETE /v1/fortunes/ ✓ POST /v1/webhooks ✓ DELETE /v1/webhooks/ ✓ GET /v1/* - (GET은 기본적으로 멱등)
에러 처리
키 재사용 오류 (다른 요청)
동일한 키로 다른 요청을 보내면 409 오류가 발생합니다:
{
"status" : 409 ,
"type" : "/errors/conflict" ,
"title" : "Conflict" ,
"detail" : "Idempotency key already used for a different request" ,
"instance" : "/v1/fortunes"
}
처리 중 재요청
이전 요청이 아직 처리 중일 때 같은 키로 재요청하면:
{
"status" : 409 ,
"type" : "/errors/conflict" ,
"title" : "Conflict" ,
"detail" : "Previous request with this idempotency key is still in progress" ,
"retry_after" : 5
}
유효하지 않은 키
{
"status" : 400 ,
"type" : "/errors/validation" ,
"title" : "Bad Request" ,
"detail" : "Idempotency key must be 10-64 characters long"
}
구현 예시
JavaScript/TypeScript
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
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
권장사항
의미 있는 키 사용 작업 내용을 반영하는 키를 사용하면 디버깅이 쉽습니다.
예: create-profile-user123-20250115
클라이언트에서 생성 서버가 아닌 클라이언트에서 키를 생성해야
재시도 시 동일한 키를 사용할 수 있습니다.
키 저장 재시도가 필요한 경우를 대비해
요청과 함께 키를 저장해 두세요.
24시간 제한 인지 키는 24시간 후 만료됩니다.
장기 작업은 새로운 키를 사용하세요.