Referência da API

Evite cobranças duplicadas

Idempotência significa que uma requisição repetida é executada uma única vez: envie a mesma Idempotency-Key na nova tentativa e você é cobrado uma vez só, mesmo que a primeira tentativa só pareça ter se perdido.

Uma requisição que estourou o tempo limite não é uma requisição com falha. A geração pode já estar em andamento, e uma nova tentativa às cegas inicia — e cobra — uma segunda. Envie uma Idempotency-Key e o gateway executa a requisição uma vez e depois reproduz a mesma resposta por 24 horas.

#Quando enviar uma chave

Envie

  • Todo envio assíncrono — vídeo, tarefa de imagem, música, 3D. Esses são os caros.
  • Qualquer chamada sem streaming que o seu worker repete automaticamente.
  • Chamadas atrás de uma fila que pode reentregar um trabalho após uma falha.

Dispense

  • Requisições de streaming — o cabeçalho é rejeitado com 400, um stream não pode ser reproduzido.
  • Consultas GET e leituras de metadados — repeti-las já é grátis e seguro.
  • Chat interativo, em que uma resposta duplicada custa menos que o controle.
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"
  }'

Derive a chave do seu próprio registro

Um UUID gerado dentro do loop de novas tentativas anula o propósito — cada tentativa ganha uma nova identidade. Derive-o da linha de negócio que você está atendendo (ID do pedido, ID do job) para que toda nova tentativa desse job leve a mesma chave.

#Como as chaves são comparadas

Opcional, nunca automático

Sem o cabeçalho, nada muda. Uma nova tentativa sem chave é uma segunda requisição cobrada.

Só métodos de escrita

GET, HEAD e OPTIONS ignoram o cabeçalho — consultar uma tarefa já é seguro de repetir.

Escopo: usuário + chave + método + rota

O mesmo valor com outra chave de API, ou em outro endpoint, é uma operação diferente.

O corpo faz parte da identidade

Caminho, query e corpo entram juntos no hash. Reutilizar uma chave com o payload alterado retorna 409; não sobrescreve.

Incompatível com streaming

Uma resposta em stream não pode ser armazenada para reprodução, então stream:true junto com uma chave é rejeitado com 400.

Falha fechada

Se o registro estiver indisponível, a requisição é recusada com 503 em vez de ser executada sem proteção.

As chaves têm no máximo 255 caracteres. Qualquer coisa maior é rejeitada com 400 antes de a requisição ser executada.

#Resultados e códigos de status

Uma resposta reproduzida traz X-Idempotent-Replayed: true e o status original — trate-a como a resposta, não como uma duplicata a conciliar. Toda rejeição usa o envelope plano da verificação prévia.

HTTPCódigoSignificadoO que fazer
400IDEMPOTENCY_STREAM_UNSUPPORTEDIdempotency-Key foi enviada em uma requisição de streaming.Remova o cabeçalho nas chamadas de streaming ou defina stream:false quando precisar de proteção contra repetição.
400IDEMPOTENCY_BODY_UNREADABLENão foi possível gerar o hash do corpo para o registro de idempotência.Envie um corpo JSON ou multipart normal — corpos chunked que não podem ser armazenados em buffer são rejeitados.
409IDEMPOTENCY_PAYLOAD_CONFLICTEsta chave já foi usada com outro corpo, caminho ou query.Gere uma nova chave. Uma chave identifica uma requisição exata, não um ciclo de novas tentativas.
409IDEMPOTENCY_IN_PROGRESSA primeira requisição com esta chave ainda está em execução.Aguarde o Retry-After (2s) e tente novamente com a mesma chave — você receberá a resposta original.
409IDEMPOTENCY_RESULT_UNREPLAYABLEA requisição original terminou, mas a resposta foi selada e não pode ser reproduzida.Não reenvie às cegas. Faça primeiro a conciliação pelo endpoint da tarefa ou pelos seus próprios registros.
503IDEMPOTENCY_UNAVAILABLEO registro de idempotência está indisponível, então a proteção falha fechada em vez de arriscar uma cobrança dupla.Tente novamente com backoff — a requisição não foi executada.

#Loop de novas tentativas

Uma chave por job, reutilizada em todas as tentativas, com o 409 em andamento tratado como "aguardar", não como "falhar".

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

Persista o id da tarefa antes de tentar qualquer coisa de novo

Grave o task_id no seu banco de dados na mesma transação que marca o job como enviado. A chave protege o envio; só o seu próprio registro protege você depois que a resposta se perde.

Ciclo de vida das tarefas assíncronas →

Ainda com problemas?

Envie para nós o ID da requisição ou a mensagem de erro e vamos investigar.