API 레퍼런스

중복 과금 방지

멱등성은 재시도한 요청이 한 번만 실행된다는 뜻입니다. 재시도할 때 같은 Idempotency-Key를 보내면, 첫 시도가 유실된 것처럼 보였더라도 한 번만 과금됩니다.

타임아웃된 요청은 실패한 요청이 아닙니다. 생성이 이미 진행 중일 수 있으며, 무작정 재시도하면 두 번째 생성이 시작되고 — 과금도 됩니다. Idempotency-Key를 보내면 게이트웨이는 요청을 한 번만 실행하고, 24시간 동안 같은 응답을 재전송합니다.

#키를 보내야 할 때

보내야 함

  • 모든 비동기 제출 — 동영상, 이미지 작업, 음악, 3D. 비용이 큰 요청입니다.
  • 워커가 자동으로 재시도하는 모든 비스트리밍 호출.
  • 장애 후 작업을 다시 전달할 수 있는 큐 뒤의 호출.

생략

  • 스트리밍 요청 — 헤더가 400으로 거부되며, 스트림은 재전송할 수 없습니다.
  • GET 폴링과 메타데이터 조회 — 반복해도 원래 무료이고 안전합니다.
  • 중복 응답 비용이 기록 관리 비용보다 적은 대화형 채팅.
submit.sh
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"
  }'

키는 자체 레코드에서 파생

재시도 루프 안에서 생성한 UUID는 의미가 없습니다 — 시도마다 새 식별자가 생깁니다. 처리 중인 비즈니스 레코드(주문 ID, 작업 ID)에서 파생해, 해당 작업의 모든 재시도가 같은 키를 갖게 하세요.

#키 매칭 방식

명시적으로 켜야 하며 자동 적용되지 않음

헤더가 없으면 아무것도 달라지지 않습니다. 키 없이 재시도하면 별도로 과금되는 두 번째 요청이 됩니다.

쓰기 메서드에만 적용

GET, HEAD, OPTIONS는 이 헤더를 무시합니다 — 작업 폴링은 원래 반복해도 안전합니다.

범위: 사용자 + 키 + 메서드 + 경로

같은 값이라도 다른 API 키나 다른 엔드포인트에서 쓰면 별개의 요청입니다.

본문도 식별 기준에 포함됨

경로, 쿼리, 본문을 함께 해시합니다. 페이로드를 바꾼 채 키를 재사용하면 덮어쓰지 않고 409를 반환합니다.

스트리밍과 함께 사용 불가

스트리밍 응답은 재전송용으로 저장할 수 없으므로 stream:true와 키를 함께 보내면 400으로 거부됩니다.

실패 시 차단

원장을 사용할 수 없으면 보호 없이 실행하지 않고 503으로 거부합니다.

키는 최대 255자입니다. 더 길면 요청이 실행되기 전에 400으로 거부됩니다.

#결과와 상태 코드

재전송된 응답에는 X-Idempotent-Replayed: true와 원래 상태 코드가 담깁니다 — 대조할 중복이 아니라 정상 응답으로 처리하세요. 모든 거부는 평면 가드 오류 구조를 사용합니다.

HTTP코드의미조치 방법
400IDEMPOTENCY_STREAM_UNSUPPORTED스트리밍 요청에 Idempotency-Key를 보냈습니다.스트리밍 호출에서는 이 헤더를 빼거나, 응답 재전송 보호가 필요하면 stream:false로 설정하세요.
400IDEMPOTENCY_BODY_UNREADABLE멱등 원장에 기록할 본문 해시를 계산할 수 없습니다.일반 JSON 또는 multipart 본문을 보내세요 — 버퍼링할 수 없는 청크 본문은 거부됩니다.
409IDEMPOTENCY_PAYLOAD_CONFLICT이 키는 이미 다른 본문, 경로 또는 쿼리에 사용되었습니다.새 키를 생성하세요. 키는 재시도 루프 전체가 아니라 정확히 하나의 요청을 식별합니다.
409IDEMPOTENCY_IN_PROGRESS이 키를 사용한 첫 요청이 아직 실행 중입니다.Retry-After(2초)만큼 기다린 뒤 같은 키로 재시도하세요 — 원래 응답을 받게 됩니다.
409IDEMPOTENCY_RESULT_UNREPLAYABLE원래 요청은 완료되었지만 응답이 봉인되어 재전송할 수 없습니다.무작정 다시 제출하지 마세요. 먼저 작업 엔드포인트나 자체 기록으로 대조하세요.
503IDEMPOTENCY_UNAVAILABLE멱등 원장을 사용할 수 없어, 이중 과금 위험을 피하려고 가드가 요청을 차단(fail closed)합니다.백오프를 적용해 재시도하세요 — 요청은 실행되지 않았습니다.

#재시도 루프

작업 하나에 키 하나를 두고 모든 시도에서 재사용하며, 409 처리 중은 "실패"가 아니라 "대기"로 처리합니다.

submit_once.py
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부터 저장

작업을 제출됨으로 표시하는 트랜잭션 안에서 task_id를 데이터베이스에 기록하세요. 키는 제출을 보호할 뿐이며, 응답을 잃어버린 뒤에는 자체 기록만이 여러분을 보호합니다.

비동기 작업 수명 주기 →

아직 해결되지 않았나요?

요청 ID나 오류 메시지를 보내 주시면 확인해 드리겠습니다.