PopNowDocs de la API
Construir con la API
Contenido principal

Idempotencia

Reintenta tras un timeout sin generar dos veces ni pagar dos veces.

Una clave de idempotencia es una etiqueta que le pones a un trabajo, como «renderizar el video del pedido 8814». Si la misma solicitud etiquetada llega dos veces a PopNow, el trabajo se hace una sola vez.

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

Por qué la necesitas

Un timeout te deja adivinando: quizá la solicitud nunca llegó, o quizá llegó y solo se perdió la respuesta. Sin clave, o te rindes (y el usuario espera para nada) o reintentas (y quizá generas y pagas dos veces). Con clave, reintentar siempre es seguro: obtienes una tarea nueva o la que se creó hace un momento.

Cómo se comporta

  • Repetir con la misma clave desde la misma clave de API devuelve la tarea original en su estado actual: ni segunda tarea, ni segunda retención, ni segundo cobro.
  • La clave queda ligada a lo que enviaste: model, todo el input y callBackUrl. El orden de los campos y los espacios no importan, pero cualquier cambio real sí. Una solicitud modificada con una clave vieja devuelve 409; dale una clave nueva.
  • Una clave se recuerda 24 horas desde su primer uso. Pasado ese tiempo, la misma clave crea una tarea nueva que se cobra, así que detén los reintentos automáticos mucho antes.
  • Las claves tienen hasta 128 caracteres y solo tienen que ser únicas dentro de tu cuenta. Otra clave de API de tu cuenta no puede reutilizarla: recibe 409 sin el ID de la tarea original.
EndpointRepetir con la misma clave…
POST /api/v1/jobs/createTaskdevuelve la tarea original
POST /api/v1/jobs/retrydevuelve la tarea que creó ese reintento (la clave se limita a la tarea de origen)
POST /v1/videosdevuelve la tarea original
POST /api/v1/assetsdevuelve el recurso ya guardado (aquí la cabecera es obligatoria)

Los endpoints de lectura y los que emiten enlaces de subida o descarga ignoran la cabecera. jobs/purge no la necesita: repetirlo ya es inofensivo.

Cómo elegir claves

  • Una clave por trabajo, no por intento HTTP. Cada reintento debe reutilizarla.
  • Derívala de algo que ya guardas: order-8814-render-1 se puede recalcular tras un fallo; un UUID aleatorio no, salvo que lo guardes antes.
  • Si usas un UUID, genéralo antes del primer intento y guárdalo junto al trabajo al que pertenece.

Nunca generes la clave dentro del bucle de reintentos

// 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);

Conflictos

Todos estos casos devuelven code: 409:

Qué pasó¿Reintentar?
Llegaron a la vez dos solicitudes con la misma claveSí: espera unos cientos de milisegundos y reenvía con la misma clave
La clave ya se usó para otra solicitudNo: la solicitud cambió y necesita una clave nueva
Otra clave de API de tu cuenta ya usó esta claveNo: reintenta con la clave de API que hizo el primer intento

Tu código no debería decidir según msg, así que permite uno o dos reintentos de 409 y después muestra el error a una persona.

Ejemplo completo

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',
);

En esta página