Operações de escrita na Vendaze API suportam idempotência via header Idempotency-Key. Isso permite que você reenvie uma requisição com segurança caso não tenha recebido resposta, sem risco de criar registros duplicados.
Como usar
Inclua o header Idempotency-Key com uma string única gerada por você:
curl -X POST https://api.vendaze.com/v1/deals \
-H "Authorization: Bearer SEU_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7f8a9b2c-3d4e-5f60-a1b2-c3d4e5f60718" \
-d '{
"name": "Contrato Anual Acme",
"pipeline_id": "uuid-do-pipeline",
"stage_id": "uuid-do-stage",
"currency": "BRL",
"price_cts": 1200000
}'
Se você enviar a mesma requisição novamente com o mesmo Idempotency-Key, a API retorna a resposta original sem processar novamente.
A chave deve ser:
- Uma string de até 255 caracteres
- Única por operação (recomendado: UUID v4)
- Gerada pelo seu sistema, não reutilizada entre operações distintas
import { randomUUID } from 'crypto';
const idempotencyKey = randomUUID();
Comportamento por cenário
| Cenário | Comportamento |
|---|
| Primeira requisição | Processa normalmente e armazena a resposta. |
| Requisição repetida com mesma chave e mesmo body | Retorna a resposta original sem reprocessar. |
| Requisição repetida com mesma chave e body diferente | Retorna 422 com código idempotency_conflict. |
| Requisição repetida após 24 horas | Chave expirada. Processa como nova requisição. |
Por que PATCH e DELETE são naturalmente idempotentes
Endpoints PATCH e DELETE não precisam do header Idempotency-Key porque são idempotentes por definição:
- PATCH: aplicar a mesma atualização duas vezes produz o mesmo resultado. Definir
full_name como "Ana Costa" uma segunda vez não altera o registro.
- DELETE: deletar um registro que já foi deletado retorna
404. O estado final (registro não existe) é o mesmo independente de quantas vezes você chame.
O header Idempotency-Key só é necessário para operações POST, onde cada chamada sem chave criaria um novo registro.
Endpoints que suportam idempotência
O header Idempotency-Key é aceito em todos os endpoints de escrita:
POST /v1/people
POST /v1/companies
POST /v1/deals
Padrão recomendado para criações críticas
import { randomUUID } from 'crypto';
async function createDeal(payload, accessToken) {
// Gere a chave uma vez por operação lógica, antes da primeira tentativa
const idempotencyKey = randomUUID();
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await fetch('https://api.vendaze.com/v1/deals', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(payload),
});
if (res.ok) return res.json();
// Não retentar em erros de validação ou erros de cliente
if (res.status >= 400 && res.status < 500) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
// Retentar em falhas de rede ou 5xx
if (res.status >= 500) {
await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
continue;
}
} catch (err) {
if (attempt === 2) throw err;
await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}
}
Rastreando chaves de idempotência no banco de dados
Para operações onde você precisa de trilha de auditoria ou quer correlacionar seus registros internos com respostas da API, armazene a chave de idempotência junto ao recurso criado:
CREATE TABLE deal_creations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
idempotency_key UUID NOT NULL UNIQUE,
vendaze_deal_id UUID,
payload JSONB NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
resolved_at TIMESTAMPTZ
);
async function createDealWithTracking(payload, accessToken) {
const idempotencyKey = randomUUID();
await db.dealCreations.insert({
idempotency_key: idempotencyKey,
payload,
status: 'pending',
});
try {
const res = await fetch('https://api.vendaze.com/v1/deals', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(payload),
});
const deal = await res.json();
await db.dealCreations.update(
{ idempotency_key: idempotencyKey },
{ status: 'success', vendaze_deal_id: deal.data.id, resolved_at: new Date() },
);
return deal;
} catch (err) {
await db.dealCreations.update(
{ idempotency_key: idempotencyKey },
{ status: 'failed', resolved_at: new Date() },
);
throw err;
}
}
A chave de idempotência persiste por 24 horas. Use sempre um UUID v4 gerado no momento da
operação. Nunca reutilize chaves entre operações diferentes.
Conflito de chave
Se você enviar uma chave já usada com um body diferente, a API retorna:
{
"error": {
"code": "idempotency_conflict",
"message": "An idempotency key was reused with a different request body.",
"request_id": "uuid"
}
}
Isso protege contra bugs onde a mesma chave é acidentalmente reutilizada para duas operações distintas.