PopNowAPI 文件
用 API 開發
主要內容

冪等

逾時後安心重試,不會重複生成,也不會重複扣點。

冪等鍵是你為一件工作貼上的標籤,例如「為訂單 8814 算圖」。同一個貼了標籤的請求就算送到 PopNow 兩次,這件工作也只會做一次。

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

為什麼需要它

逾時會讓你進退兩難:可能請求根本沒送到,也可能送到了只是回應遺失。沒有冪等鍵,你只能放棄(讓使用者白等)或重試(可能生成兩次、扣點兩次)。有了它,重試永遠是安全的:要嘛建立新任務,要嘛拿回剛才建立的那一個。

運作方式

  • 同一把 API 金鑰用同一個鍵重送,拿回的是原本的任務及其目前狀態——不會多出任務、多保留或多扣點。
  • 鍵與你送出的內容綁定:model、整個 input 與 callBackUrl。欄位順序與空白無所謂,但內容真的改了就不行。用舊鍵送出修改過的請求會回傳 409;請改用新鍵。
  • 鍵從第一次使用起保留 24 小時。超過之後,同一個鍵會建立一個新的、要收費的任務,所以自動重試要在那之前提早停止。
  • 鍵最長 128 個字元,只需在你的帳戶內唯一。帳戶中另一把 API 金鑰無法重用它:會收到 409,且不會回傳原任務的 ID。
端點用同一個鍵重送時…
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',
);

本頁內容