PopNowAPI 문서
API로 만들기
본문

멱등성

타임아웃 뒤에도 두 번 생성되거나 두 번 과금될 걱정 없이 재시도하기.

멱등 키는 "주문 8814의 영상 렌더링"처럼 한 가지 일에 붙이는 이름표입니다. 같은 이름표가 붙은 요청이 PopNow에 두 번 도착해도 일은 한 번만 처리됩니다.

POST /api/v1/jobs/createTask
Idempotency-Key: order-8814-render-1

왜 필요한가

타임아웃이 나면 판단하기 어렵습니다. 요청이 아예 도착하지 않았을 수도 있고, 도착했는데 응답만 사라졌을 수도 있습니다. 키가 없으면 포기하거나(사용자는 헛되이 기다림) 재시도하거나(두 번 생성되고 두 번 과금될 수 있음) 둘 중 하나입니다. 키가 있으면 재시도는 언제나 안전합니다. 새 작업이 생기거나, 방금 만든 작업을 돌려받거나 둘 중 하나입니다.

동작 방식

  • 같은 API 키에서 같은 키로 다시 보내면 원래 작업이 현재 상태로 돌아옵니다. 작업도, 홀드도, 과금도 늘지 않습니다.
  • 키는 보낸 내용(model, input 전체, callBackUrl)에 묶입니다. 필드 순서나 공백은 상관없지만 실제로 내용이 바뀌면 다릅니다. 바뀐 요청을 예전 키로 보내면 409가 오니 새 키를 쓰세요.
  • 키는 처음 쓴 때부터 24시간 기억됩니다. 그 뒤에는 같은 키라도 새로 과금되는 작업이 만들어지니 자동 재시도는 그보다 훨씬 전에 멈추세요.
  • 키는 128자까지이며 계정 안에서만 고유하면 됩니다. 계정의 다른 API 키는 이 키를 다시 쓸 수 없으며, 원래 작업 ID 없이 409를 받습니다.
엔드포인트같은 키로 다시 보내면…
POST /api/v1/jobs/createTask원래 작업을 돌려줌
POST /api/v1/jobs/retry그 재시도로 만든 작업을 돌려줌(키 범위는 원본 작업별)
POST /v1/videos원래 작업을 돌려줌
POST /api/v1/assets이미 저장된 에셋을 돌려줌(여기서는 헤더가 필수)

읽기 엔드포인트와 업로드·다운로드 링크를 만드는 엔드포인트는 이 헤더를 무시합니다. jobs/purge에는 필요 없습니다. 반복해서 호출해도 이미 무해합니다.

키 고르기

  • HTTP 시도마다가 아니라 일 하나에 키 하나입니다. 재시도할 때마다 그 키를 다시 쓰세요.
  • 이미 저장해 둔 값에서 만드세요. order-8814-render-1은 프로세스가 죽은 뒤에도 다시 계산할 수 있지만, 무작위 UUID는 먼저 저장해 두지 않으면 되찾을 수 없습니다.
  • UUID를 쓴다면 첫 시도 전에 만들고, 해당 작업과 함께 저장해 두세요.

재시도 루프 안에서 키를 만들지 마세요

// Wrong: a new key per attempt defeats the whole mechanism
for (let i = 0; i < 3; i++) await createTask(body, crypto.randomUUID());

// Right: one key, created once
const key = crypto.randomUUID();
for (let i = 0; i < 3; i++) await createTask(body, key);

충돌

다음은 모두 code: 409를 돌려줍니다.

무슨 일이 있었나재시도?
같은 키의 요청 두 개가 동시에 도착예. 몇백 밀리초 기다렸다가 같은 키로 다시 보내기
그 키가 다른 요청에 이미 쓰임아니요. 요청이 바뀌었으니 새 키가 필요
계정의 다른 API 키가 이 키를 이미 씀아니요. 처음 시도한 API 키로 재시도

msg로 분기하면 안 되므로 409는 한두 번만 재시도하고 그 뒤에는 사람이 볼 수 있게 오류를 드러내세요.

전체 예시

JavaScript
const RETRYABLE = new Set([429, 500, 503, 50301]); // business codes from the body

async function createTaskOnce(body, idempotencyKey) {
  for (let attempt = 0; attempt < 4; attempt++) {
    try {
      const res = await fetch('https://api.popnow.ai/api/v1/jobs/createTask', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.POPNOW_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': idempotencyKey,
        },
        body: JSON.stringify(body),
      });
      const json = await res.json();
      if (json.code === 200) return json.data; // a new task, or the original one
      if (!RETRYABLE.has(json.code)) throw new Error(`${json.code} ${json.msg} (${json.request_id})`);
    } catch (err) {
      if (!(err instanceof TypeError)) throw err; // network error: safe to retry with the same key
    }
    await new Promise((r) => setTimeout(r, 2 ** attempt * 500 + Math.random() * 300));
  }
  throw new Error('createTask did not succeed after 4 attempts');
}

const task = await createTaskOnce(
  { model: 'alibaba/z-image-turbo/text-to-image', input: { prompt: 'A paper boat on a calm lake' } },
  'order-8814-render-1',
);

이 페이지에서