API 레퍼런스
중복 과금 방지
멱등성은 재시도한 요청이 한 번만 실행된다는 뜻입니다. 재시도할 때 같은 Idempotency-Key를 보내면, 첫 시도가 유실된 것처럼 보였더라도 한 번만 과금됩니다.
타임아웃된 요청은 실패한 요청이 아닙니다. 생성이 이미 진행 중일 수 있으며, 무작정 재시도하면 두 번째 생성이 시작되고 — 과금도 됩니다. Idempotency-Key를 보내면 게이트웨이는 요청을 한 번만 실행하고, 24시간 동안 같은 응답을 재전송합니다.
#키를 보내야 할 때
보내야 함
- 모든 비동기 제출 — 동영상, 이미지 작업, 음악, 3D. 비용이 큰 요청입니다.
- 워커가 자동으로 재시도하는 모든 비스트리밍 호출.
- 장애 후 작업을 다시 전달할 수 있는 큐 뒤의 호출.
생략
- 스트리밍 요청 — 헤더가 400으로 거부되며, 스트림은 재전송할 수 없습니다.
- GET 폴링과 메타데이터 조회 — 반복해도 원래 무료이고 안전합니다.
- 중복 응답 비용이 기록 관리 비용보다 적은 대화형 채팅.
curl https://www.aisplice.store/v1/videos \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"model": "doubao-seedance-2.5",
"prompt": "A drone shot of a lighthouse at dawn"
}'키는 자체 레코드에서 파생
#키 매칭 방식
명시적으로 켜야 하며 자동 적용되지 않음
헤더가 없으면 아무것도 달라지지 않습니다. 키 없이 재시도하면 별도로 과금되는 두 번째 요청이 됩니다.
쓰기 메서드에만 적용
GET, HEAD, OPTIONS는 이 헤더를 무시합니다 — 작업 폴링은 원래 반복해도 안전합니다.
범위: 사용자 + 키 + 메서드 + 경로
같은 값이라도 다른 API 키나 다른 엔드포인트에서 쓰면 별개의 요청입니다.
본문도 식별 기준에 포함됨
경로, 쿼리, 본문을 함께 해시합니다. 페이로드를 바꾼 채 키를 재사용하면 덮어쓰지 않고 409를 반환합니다.
스트리밍과 함께 사용 불가
스트리밍 응답은 재전송용으로 저장할 수 없으므로 stream:true와 키를 함께 보내면 400으로 거부됩니다.
실패 시 차단
원장을 사용할 수 없으면 보호 없이 실행하지 않고 503으로 거부합니다.
키는 최대 255자입니다. 더 길면 요청이 실행되기 전에 400으로 거부됩니다.
#결과와 상태 코드
재전송된 응답에는 X-Idempotent-Replayed: true와 원래 상태 코드가 담깁니다 — 대조할 중복이 아니라 정상 응답으로 처리하세요. 모든 거부는 평면 가드 오류 구조를 사용합니다.
| HTTP | 코드 | 의미 | 조치 방법 |
|---|---|---|---|
| 400 | IDEMPOTENCY_STREAM_UNSUPPORTED | 스트리밍 요청에 Idempotency-Key를 보냈습니다. | 스트리밍 호출에서는 이 헤더를 빼거나, 응답 재전송 보호가 필요하면 stream:false로 설정하세요. |
| 400 | IDEMPOTENCY_BODY_UNREADABLE | 멱등 원장에 기록할 본문 해시를 계산할 수 없습니다. | 일반 JSON 또는 multipart 본문을 보내세요 — 버퍼링할 수 없는 청크 본문은 거부됩니다. |
| 409 | IDEMPOTENCY_PAYLOAD_CONFLICT | 이 키는 이미 다른 본문, 경로 또는 쿼리에 사용되었습니다. | 새 키를 생성하세요. 키는 재시도 루프 전체가 아니라 정확히 하나의 요청을 식별합니다. |
| 409 | IDEMPOTENCY_IN_PROGRESS | 이 키를 사용한 첫 요청이 아직 실행 중입니다. | Retry-After(2초)만큼 기다린 뒤 같은 키로 재시도하세요 — 원래 응답을 받게 됩니다. |
| 409 | IDEMPOTENCY_RESULT_UNREPLAYABLE | 원래 요청은 완료되었지만 응답이 봉인되어 재전송할 수 없습니다. | 무작정 다시 제출하지 마세요. 먼저 작업 엔드포인트나 자체 기록으로 대조하세요. |
| 503 | IDEMPOTENCY_UNAVAILABLE | 멱등 원장을 사용할 수 없어, 이중 과금 위험을 피하려고 가드가 요청을 차단(fail closed)합니다. | 백오프를 적용해 재시도하세요 — 요청은 실행되지 않았습니다. |
#재시도 루프
작업 하나에 키 하나를 두고 모든 시도에서 재사용하며, 409 처리 중은 "실패"가 아니라 "대기"로 처리합니다.
import time, uuid, requests
def submit_once(job_id: str, body: dict, api_key: str, tries: int = 5):
# Same key for every attempt of this job — that is what makes it safe.
key = str(uuid.uuid5(uuid.NAMESPACE_URL, f"video-job:{job_id}"))
for attempt in range(tries):
response = requests.post(
f"https://www.aisplice.store/v1/videos",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": key,
},
json=body,
timeout=60,
)
if response.status_code == 409:
code = response.json().get("code", "")
if code == "IDEMPOTENCY_IN_PROGRESS":
time.sleep(int(response.headers.get("Retry-After", 2)))
continue
# PAYLOAD_CONFLICT or RESULT_UNREPLAYABLE: a human decision,
# never a silent resubmit.
raise RuntimeError(response.text)
if response.status_code >= 500:
time.sleep(2 ** attempt)
continue
response.raise_for_status()
replayed = response.headers.get("X-Idempotent-Replayed") == "true"
return response.json(), replayed
raise TimeoutError(f"submit for {job_id} did not settle")재시도하기 전에 작업 ID부터 저장
아직 해결되지 않았나요?
요청 ID나 오류 메시지를 보내 주시면 확인해 드리겠습니다.