Referencia de la API

Evitar cobros dobles

La idempotencia significa que una solicitud reintentada se ejecuta una sola vez: envía la misma Idempotency-Key en el reintento y se te cobrará una sola vez, aunque el primer intento solo pareciera perdido.

Una solicitud con timeout no es una solicitud fallida. Puede que la generación ya esté en marcha, y un reintento a ciegas inicia — y factura — una segunda. Envía una Idempotency-Key y el gateway ejecuta la solicitud una sola vez y luego reproduce la misma respuesta durante 24 horas.

#Cuándo enviar una clave

Envíala

  • Todo envío asíncrono — video, tarea de imagen, música, 3D. Son los más caros.
  • Cualquier llamada sin streaming que tu worker reintente automáticamente.
  • Llamadas detrás de una cola que puede volver a entregar un trabajo tras una caída.

Omítela

  • Solicitudes con streaming — el encabezado se rechaza con 400; un stream no se puede reproducir.
  • Consultas GET y lecturas de metadatos — repetirlas ya es gratis y seguro.
  • Chat interactivo en el que una respuesta duplicada cuesta menos que la gestión adicional.
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"
  }'

Deriva la clave de tu propio registro

Un UUID generado dentro del bucle de reintentos anula su propósito — cada intento recibe una identidad nueva. Derívala de la fila de negocio que estás procesando (id de pedido, id de trabajo) para que todos los reintentos de ese trabajo lleven la misma clave.

#Cómo se comparan las claves

Opcional, nunca automático

Sin el encabezado no cambia nada. Un reintento sin clave es una segunda solicitud facturable.

Solo métodos de escritura

GET, HEAD y OPTIONS ignoran el encabezado — consultar una tarea ya es seguro de repetir.

Ámbito: usuario + clave + método + ruta

El mismo valor con otra clave de API, o en otro endpoint, es una operación distinta.

El cuerpo forma parte de la identidad

La ruta, la query y el cuerpo se hashean juntos. Reutilizar una clave con un payload modificado devuelve 409; no lo sobrescribe.

No compatible con streaming

Una respuesta en streaming no se puede almacenar para reproducirla, así que stream:true junto con una clave se rechaza con 400.

Falla en modo cerrado

Si el registro no está disponible, la solicitud se rechaza con 503 en lugar de ejecutarse sin protección.

Las claves tienen como máximo 255 caracteres. Cualquier clave más larga se rechaza con 400 antes de ejecutar la solicitud.

#Resultados y códigos de estado

Una respuesta reproducida incluye X-Idempotent-Replayed: true y el estado original — trátala como la respuesta, no como un duplicado que conciliar. Todos los rechazos usan el envoltorio plano del control previo.

HTTPCódigoSignificadoQué hacer
400IDEMPOTENCY_STREAM_UNSUPPORTEDSe envió Idempotency-Key en una solicitud con streaming.Quita el encabezado en las llamadas con streaming, o usa stream:false cuando necesites protección contra repeticiones.
400IDEMPOTENCY_BODY_UNREADABLENo se pudo calcular el hash del cuerpo para el registro de idempotencia.Envía un cuerpo JSON o multipart normal — se rechazan los cuerpos chunked que no se pueden almacenar en búfer.
409IDEMPOTENCY_PAYLOAD_CONFLICTEsta clave ya se usó con otro cuerpo, otra ruta u otra query.Genera una clave nueva. Una clave identifica una solicitud exacta, no un bucle de reintentos.
409IDEMPOTENCY_IN_PROGRESSLa primera solicitud con esta clave sigue en ejecución.Espera el Retry-After (2s) y reintenta con la misma clave — obtendrás la respuesta original.
409IDEMPOTENCY_RESULT_UNREPLAYABLELa solicitud original terminó, pero su respuesta quedó sellada y no se puede reproducir.No reenvíes a ciegas. Concilia primero desde el endpoint de la tarea o con tus propios registros.
503IDEMPOTENCY_UNAVAILABLEEl registro de idempotencia no está disponible, así que el control falla en modo cerrado en lugar de arriesgarse a un cobro doble.Reintenta con backoff — la solicitud no se ejecutó.

#Bucle de reintentos

Una clave por trabajo, reutilizada en cada intento, y el 409 en curso tratado como "esperar", no como "fallo".

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")

Guarda el id de la tarea antes de reintentar nada

Escribe task_id en tu base de datos en la misma transacción que marca el trabajo como enviado. La clave protege el envío; solo tu propio registro te protege si se pierde la respuesta.

Ciclo de vida de las tareas asíncronas →

¿Sigues con el problema?

Envíanos el ID de solicitud o el mensaje de error y lo revisaremos.