# Repetir uma requisição sem duplicar

> O cabeçalho Idempotency-Key da API do Praeko: repetir um POST, PATCH ou DELETE depois de uma falha de rede sem criar dois posts, duas mídias ou dois webhooks. Regras, erros, curl, SDK e CLI.

Fonte: https://praeko.app/docs/guides/idempotency

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.

> **Sem o cabeçalho, nada muda** Quem já usa a API continua exatamente como estava. O cabeçalho é opcional em toda rota que o aceita.

## 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

**Primeira vez, e a repetição idêntica**

```bash
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

**Repetir com a mesma chave**

```js
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).

**JavaScript**

```js
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

**Terminal**

```bash
praeko publish-now <id> --idempotency-key K
praeko webhook create --url https://exemplo.com.br/hooks/praeko --idempotency-key K
```

`publish-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). |
