Idempotência
Como funciona
Escolha uma chave única por operação (um UUID, ou um ID seu, como um número de pedido) e envie como Idempotency-Key. Por 24 horas, a mesma chave na mesma rota devolve de novo o primeiro resultado, com Idempotent-Replayed: true, em vez de refazer o trabalho.
- A mesma chave com um corpo diferente responde 422 idempotency_conflict.
- A mesma chave enquanto a primeira requisição ainda roda responde 409 conflict: espere e tente de novo.
- Uma requisição que falhou não é guardada, então tentar de novo com a mesma chave faz o trabalho.
- As chaves valem para o espaço de trabalho, quem chama, a rota e o caminho dela. Quem chama é a chave de API (ou, no app, o membro conectado): duas chaves de API que enviam o mesmo Idempotency-Key nunca reproduzem as respostas uma da outra, e uma chave de API girada é um novo chamador. A mesma chave nas rotas de dois códigos diferentes também não colide.
- PATCH e DELETE não aceitam chave: repeti-los deixa o mesmo estado.
As repetições nunca levam um segredo
A resposta guardada nunca inclui um segredo. Um POST que devolve um (registrar um endpoint de webhook, e criar ou girar uma chave de API no app) leva o segredo só na primeira resposta, com secretAlreadyShown: false. Uma repetição dentro das 24 horas responde o mesmo objeto com secret: null e secretAlreadyShown: true.
Se a primeira resposta se perdeu, o objeto ainda existe (o ID dele está na repetição). Gire o segredo do endpoint no app (ou apague o endpoint e registre de novo), ou gire a chave de API, para obter um novo segredo.
{ "id": "whe_3k8m2q9x7c4v1b6n", "url": "https://hooks.example.com/dotwick", "secret": null, "secretAlreadyShown": true}Tentando de novo com segurança
import { randomUUID } from "node:crypto";// one key per logical operation, reused on every retry of itconst idempotencyKey = randomUUID();for (let attempt = 1; attempt <= 5; attempt++) { const res = await fetch("https://api.dotwick.com/v1/codes", { method: "POST", headers: { Authorization: `Bearer ${process.env.DOTWICK_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, }, body: JSON.stringify({ name: "Spring menu", content: { type: "url", url: "https://example.com/menu" } }), }); if (res.ok) break; // a replay answers the first result with Idempotent-Replayed: true if (res.status !== 429 && res.status < 500) throw new Error((await res.json()).error.message); const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt); await new Promise((r) => setTimeout(r, wait * 1000));}Rotas que aceitam uma chave
Todo POST exceto o upload multipart POST /v1/assets.
| Método | Caminho | Escopo | Finalidade |
|---|---|---|---|
| POST | /v1/codes | codes:write | Cria um códigoidempotente |
| POST | /v1/codes/move | codes:write | Move códigos para uma pastaidempotente |
| POST | /v1/codes/move-domain | codes:write | Move códigos para outro domínioidempotente |
| POST | /v1/codes/tags | codes:write | Adiciona ou remove tags em vários códigosidempotente |
| POST | /v1/codes/{id}/duplicate | codes:write | Duplica um códigoidempotente |
| POST | /v1/codes/bulk | codes:write | Cria até 500 códigos em uma única execuçãoidempotente |
| POST | /v1/bulk-imports/{id}/cancel | codes:write | Cancela uma execução na fila ou em andamentoidempotente |
| POST | /v1/bulk-imports/{id}/retry | codes:write | Tenta de novo as linhas com falha de uma execuçãoidempotente |
| POST | /v1/analytics/exports | analytics:read | Escaneamentos em CSV, preparados em segundo planoidempotente |
| POST | /v1/reports | analytics:read | Gera um relatório de estatísticas em PDF ou CSVidempotente |
| POST | /v1/exports | codes:read | Exporta vários códigos em um único ZIPidempotente |
| POST | /v1/tags | tags:write | Cria uma tagidempotente |
| POST | /v1/folders | folders:write | Cria uma pastaidempotente |
| POST | /v1/templates | templates:write | Cria um modeloidempotente |
| POST | /v1/webhooks | webhooks:write | Registra um endpoint e os tipos de eventoidempotente |
| POST | /v1/webhooks/{id}/test | webhooks:write | Envia um evento webhook.testidempotente |
| POST | /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver | webhooks:write | Envia uma entrega de novoidempotente |
| POST | /v1/webhooks/{id}/recover | webhooks:write | Reenvia as entregas com falhaidempotente |
| POST | /v1/hooks | webhooks:write | Inscreve um hook em um eventoidempotente |
| POST | /v1/cards | cards:write | Cria um cartão de visitaidempotente |
| POST | /v1/cards/{id}/deactivate | cards:write | Desativa um cartão de visitaidempotente |
| POST | /v1/conversions | conversions:write | Registra uma conversãoidempotente |
| POST | /v1/nfc/tags | codes:write | Registra uma tag NFC em um códigoidempotente |