Pular para o conteúdo

Idempotência

Redes falham. Se o seu POST para criar um agendamento estourar o tempo, você não sabe se ele foi gravado. Com Idempotency-Key, pode repetir sem medo de agendar duas vezes.

Hoje, dois endpoints aceitam o header Idempotency-Key:

Endpoint Operação
POST /api/salons/{salonId}/appointments CreateAppointment — criar agendamento
POST /api/salons/{salonId}/customers CreateCustomer — cadastrar cliente

Nos outros endpoints o header é ignorado. As ações sobre um agendamento que já existe não criam nada: remarcar de novo para o mesmo horário dá o mesmo resultado, e cancelar ou mudar o status de novo responde 409 (transição inválida) se a primeira tentativa já tinha dado certo — na dúvida, confira com GET /api/salons/{salonId}/appointments/{appointmentId}. Quando outro endpoint passar a aceitar a chave, isso vai aparecer na referência e no changelog.

Gere uma chave única por operação — um UUID v4 serve — e reenvie a mesma chave com o mesmo corpo em todas as tentativas daquela operação.

Janela do terminal
CHAVE=$(uuidgen) # gere uma vez por operação e guarde junto com o pedido
curl -s -i "https://api.pegavez.com.br/api/salons/$SALON_ID/customers" \
-H "Authorization: Bearer $PEGAVEZ_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $CHAVE" \
-d '{ "name": "Maria Souza", "phone": "+5511999998888" }'

A chave tem de 1 a 255 caracteres visíveis (ASCII de ! a ~, sem espaço) e vem num único header. Fora disso, a resposta é 400 com code: "idempotency_key_invalid".

Situação Resposta
Primeira vez com a chave Processa normalmente e guarda a resposta.
Mesma chave, mesmo corpo, primeira já terminou Devolve a resposta guardada (mesmo status, corpo e Location) com o header Idempotency-Replayed: true, sem executar de novo.
Mesma chave, primeira ainda em andamento 409 com code: "idempotency_in_progress": espere alguns segundos e repita. Se a primeira não terminar em 2 minutos (ex.: o servidor caiu no meio) sem ter gravado nada, a repetição com o mesmo corpo assume a chave e roda.
Mesma chave, a primeira gravou mas não respondeu e passaram 2 minutos 409 com code: "idempotency_unknown_result": a operação chegou a gravar, mas a resposta se perdeu. Nada é executado de novo. Consulte o recurso (ex.: liste os agendamentos do dia ou busque o cliente pelo telefone); se ele não estiver lá, repita com uma chave nova.
Mesma chave com corpo diferente ou em outro endpoint 422 com code: "idempotency_mismatch": a chave já foi usada para outra operação.
Chave inválida (vazia, com espaço, longa demais, header repetido) 400 com code: "idempotency_key_invalid".
Sem o header Processa normalmente, sem proteção contra repetição.
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: /api/salons/3f6c2b1e-8d4a-4c4e-9b7a-2a5d0e9f1c11/customers/c2d8b4e6-7a1f-4e9c-b3d5-8f6a0e2c4b17
Idempotency-Replayed: true
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.10",
"title": "Uma requisição com esta Idempotency-Key ainda está em andamento. Tente de novo em instantes.",
"status": 409,
"code": "idempotency_in_progress",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}
  • Ficam guardadas as respostas 2xx e 4xx, menos 409 e 429. Um 400 de validação, por exemplo, volta igual se você repetir a mesma chave com o mesmo corpo: corrija o corpo e use uma chave nova.
  • Não ficam guardados 409, 429, 5xx e erros de regra de negócio (como horário ocupado): a chave é apagada e a próxima tentativa com ela roda de novo, do zero.
  • Se a sua conexão cair depois que o Pega Vez gravou, a resposta fica guardada mesmo assim: repita com a mesma chave para recebê-la.
  • Num erro inesperado no meio, a chave fica em andamento (409 idempotency_in_progress) por até 2 minutos, para não criar em dobro. Passado esse prazo, o Pega Vez sabe se a primeira tentativa chegou a gravar: se não gravou, a repetição roda; se gravou, mas a resposta se perdeu, a repetição recebe 409 idempotency_unknown_result e não executa de novo — nesse caso não há resposta guardada para devolver, então confira o recurso com um GET.
  • Validade de 24 horas. A chave e a resposta ficam guardadas por 24 h a partir da primeira requisição. Depois disso, a mesma chave é tratada como nova.
  • Por token. As chaves de um token não colidem com as de outro, nem com as do painel.
  • O corpo é comparado byte a byte (por um hash SHA-256). Para repetir, reenvie exatamente os mesmos bytes; não monte o JSON de novo com outra ordem de campos ou outro horário calculado.
  • Uma chave por operação. Não reaproveite a chave de um agendamento para outro, mesmo que os dados sejam parecidos.
import { randomUUID } from 'node:crypto';
async function criarAgendamento(salonId, dados, tentativas = 4) {
const chave = randomUUID();
const corpo = JSON.stringify(dados); // mesmo corpo em todas as tentativas
let ultima;
for (let i = 0; i < tentativas; i++) {
try {
ultima = await fetch(`https://api.pegavez.com.br/api/salons/${salonId}/appointments`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PEGAVEZ_TOKEN}`,
'Content-Type': 'application/json',
'Idempotency-Key': chave,
},
body: corpo,
signal: AbortSignal.timeout(15_000),
});
} catch {
await esperar(2 ** i * 1000); // rede ou timeout: repete com a mesma chave
continue;
}
if (ultima.status === 429) {
await esperar(Number(ultima.headers.get('Retry-After') ?? 5) * 1000);
continue;
}
if (ultima.status >= 500 || (await codigo(ultima)) === 'idempotency_in_progress') {
await esperar(2 ** i * 1000);
continue;
}
return ultima; // 2xx (talvez repetida: Idempotency-Replayed) ou erro definitivo (400, 403, 409 de horário, 422...)
}
if (ultima) return ultima;
throw new Error('Não foi possível falar com a API do Pega Vez.');
}
// Lê o "code" do Problem Details sem consumir o corpo da resposta original.
async function codigo(resposta) {
if (resposta.status !== 409) return undefined;
const problema = await resposta.clone().json().catch(() => ({}));
return problema.code;
}
const esperar = (ms) => new Promise((r) => setTimeout(r, ms));