Repetir uma requisição sem duplicar
Quando a resposta se perde (a rede caiu, o tempo esgotou), você não sabe se o Praeko recebeu o pedido. Mande o cabeçalho Idempotency-Key e o servidor executa o pedido uma vez só: a repetição recebe a mesma resposta da primeira.
Regras
| Regra | Valor |
|---|---|
| Onde vale | Rotas autenticadas de POST, PATCH e DELETE de /v1 (chave de API ou sessão do painel). GET nunca é guardado; PUT já é repetível por natureza. |
| A chave | De 1 a 255 caracteres ASCII visíveis, sem espaço. Use um UUID. Fora disso: 422 invalid_field. |
| Escopo | Workspace, rota e chave. A mesma chave em outro workspace, ou em outra rota, é outra chave. Nenhuma resposta passa de um workspace para outro. |
| Prazo | 24 horas depois do primeiro pedido. |
| Pedido igual | Mesmo método, caminho, query e corpo (e o mesmo X-Praeko-Contract): repete a resposta guardada, com Idempotent-Replayed: true. |
| Pedido diferente | Mesma chave com outro método, caminho, query ou corpo: 422 idempotency_key_reuse. Nada é executado. |
| Primeiro ainda em andamento | 409 idempotency_in_progress, com Retry-After: 1. Repita em instantes. |
| Erro do servidor | Resposta 5xx não é guardada: a chave fica livre e a nova tentativa executa de verdade. |
| Erro do cliente | Resposta 4xx (validação, 404, 409…) é guardada e repetida. Corrija o pedido e use uma chave nova. |
| Resposta grande | Acima de 256 KB só o status é guardado; repetir dá 409 idempotency_response_too_large. Consulte o recurso por GET. |
| Pedido órfão | Se o servidor morrer no meio do pedido, a reserva é retomada depois de 2 minutos. |
| Ficam de fora | PUT /v1/media/:id/parts/:part e PUT /v1/media/:id/content (bytes crus, já repetíveis: reenviar a mesma parte é seguro). A criação (POST /v1/media) e a conclusão (POST /v1/media/:id/complete) aceitam a chave. |
Os erros de autenticação e o limite de requisições (401, 403, 429) são respondidos antes da chave e nunca são guardados.
Os três erros
| Status | `code` | O que fazer |
|---|---|---|
| 422 | idempotency_key_reuse |
A chave já foi usada com outro pedido. Gere uma chave nova para o pedido novo. |
| 409 | idempotency_in_progress |
O primeiro pedido ainda roda. Espere o Retry-After e repita com a mesma chave. |
| 409 | idempotency_response_too_large |
A resposta original passou de 256 KB e não foi guardada. Leia o recurso por GET. |
curl
KEY=$(uuidgen)
curl -X POST https://praeko.app/v1/ideas \
-H "Authorization: Bearer $PRAEKO_KEY" -H "X-Praeko-Contract: en" \
-H "Content-Type: application/json" -H "Idempotency-Key: $KEY" \
-d '{"title":"O bug que só aparecia na segunda-feira"}'
# a rede caiu e você não sabe se foi: repita IDÊNTICO, com a mesma chave
curl -i -X POST https://praeko.app/v1/ideas \
-H "Authorization: Bearer $PRAEKO_KEY" -H "X-Praeko-Contract: en" \
-H "Content-Type: application/json" -H "Idempotency-Key: $KEY" \
-d '{"title":"O bug que só aparecia na segunda-feira"}'
# HTTP/2 200 … idempotent-replayed: true (mesmo corpo, uma ideia só no quadro)JavaScript com fetch
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function criarPost(post) {
const key = crypto.randomUUID(); // uma chave por pedido, a mesma em todas as tentativas
for (let i = 0; i < 3; i++) {
try {
const r = await fetch("https://praeko.app/v1/posts", {
method: "POST",
headers: { authorization: `Bearer ${process.env.PRAEKO_KEY}`, "x-praeko-contract": "en", "content-type": "application/json", "idempotency-key": key },
body: JSON.stringify(post),
});
if (r.status === 409 && (await r.clone().json()).error.code === "idempotency_in_progress") { await sleep(1000); continue; }
return r;
} catch { /* a rede caiu: repete com a mesma chave */ }
}
throw new Error("sem resposta depois de 3 tentativas");
}SDK (@praeko/sdk)
Cada chamada que escreve aceita a opção idempotencyKey. Com true, o SDK gera um UUID e reenvia o pedido com a mesma chave se a rede falhar, ou se vier 429 ou 5xx. Com uma string, você escolhe a chave (por exemplo, a de um job seu, para sobreviver a um reinício do processo).
import { Praeko } from "@praeko/sdk";
const praeko = new Praeko({ url: "https://praeko.app", key: process.env.PRAEKO_KEY });
// 1) o SDK gera o UUID e repete o POST com a mesma chave se a rede falhar
const post = await praeko.posts.create({ title, media: { video: video.id }, texts, items }, { idempotencyKey: true });
// 2) ou escolha a chave
await praeko.webhooks.create({ url: "https://exemplo.com.br/hooks/praeko", idempotencyKey: `hook-${jobId}` });Sem idempotencyKey, o SDK não repete nenhum POST, PATCH ou DELETE (só GET e as partes de upload). Aceitam a opção: media.create e media.complete, posts.create, adopt, update, cancel, publishNow e scheduleYoutube, webhooks.create, rotateSecret e redeliver, ideas.create e import, apiKeys.create, youtube.reply e instagram.reply.
CLI
praeko publish-now <id> --idempotency-key K
praeko webhook create --url https://exemplo.com.br/hooks/praeko --idempotency-key Kpublish-now e webhook create mandam o cabeçalho. O CLI não repete sozinho: a opção serve para quem roda o comando de novo, por exemplo num script com tentativas.
O que já era seguro reenviar
| Situação | O que fazer |
|---|---|
POST /v1/posts com o seu id |
Mande o seu próprio id no corpo: repetido, volta 409 id_exists e nada é duplicado. Vale mesmo sem Idempotency-Key. |
| Parte de mídia repetida | Seguro: PUT /v1/media/:id/parts/:part sobrescreve a mesma parte. |
| Entrega de webhook repetida | O Praeko-Delivery é igual em todas as tentativas: guarde os ids já tratados e ignore os repetidos. |
POST /v1/insights/import |
Idempotente: faz upsert por rede e id. |
POST /v1/ideas/import |
Idempotente pelo id antigo de cada ideia (só entram as que ainda não existem). |