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.
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
#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.
| HTTP | Código | Significado | O que fazer |
|---|---|---|---|
| 400 | IDEMPOTENCY_STREAM_UNSUPPORTED | Idempotency-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. |
| 400 | IDEMPOTENCY_BODY_UNREADABLE | Nã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. |
| 409 | IDEMPOTENCY_PAYLOAD_CONFLICT | Esta 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. |
| 409 | IDEMPOTENCY_IN_PROGRESS | A 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. |
| 409 | IDEMPOTENCY_RESULT_UNREPLAYABLE | A 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. |
| 503 | IDEMPOTENCY_UNAVAILABLE | O 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".
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
Ainda com problemas?
Envie para nós o ID da requisição ou a mensagem de erro e vamos investigar.