Skip to content
Sign in Waitlist
Docs/Guides/Retry a request without duplicates

Retry a request without duplicates

When a response gets lost (the network dropped, the request timed out), you cannot tell whether Praeko received it. Send the Idempotency-Key header and the server runs the request only once: a retry gets the same response as the first attempt.

Without the header, nothing changesExisting integrations keep working exactly as before. The header is optional on every route that accepts it.

Rules

Rule Value
Where it applies Authenticated POST, PATCH and DELETE routes under /v1 (API key or panel session). GET is never stored; PUT is already safe to repeat.
The key 1 to 255 visible ASCII characters, no spaces. Use a UUID. Anything else: 422 invalid_field.
Scope Workspace, route and key. The same key in another workspace, or on another route, is a different key. No response ever crosses workspaces.
Lifetime 24 hours after the first request.
Same request Same method, path, query and body (and the same X-Praeko-Contract): the stored response is replayed with Idempotent-Replayed: true.
Different request Same key with another method, path, query or body: 422 idempotency_key_reuse. Nothing runs.
First request still running 409 idempotency_in_progress, with Retry-After: 1. Retry in a moment.
Server error A 5xx response is not stored: the key is released and the next attempt really runs.
Client error A 4xx response (validation, 404, 409…) is stored and replayed. Fix the request and use a new key.
Large response Above 256 KB only the status is stored; a retry gets 409 idempotency_response_too_large. Read the resource with GET.
Orphaned request If the server dies mid-request, the reservation can be taken over after 2 minutes.
Not covered PUT /v1/media/:id/parts/:part and PUT /v1/media/:id/content (raw bytes, already safe to repeat: sending the same part again is fine). Creating (POST /v1/media) and completing (POST /v1/media/:id/complete) accept the key.

Authentication errors and the rate limit (401, 403, 429) are answered before the key is checked and are never stored.

The three errors

Status `code` What to do
422 idempotency_key_reuse The key was already used with another request. Generate a new key for the new request.
409 idempotency_in_progress The first request is still running. Wait for Retry-After and retry with the same key.
409 idempotency_response_too_large The original response was over 256 KB and was not stored. Read the resource with GET.

curl

First attempt, then the identical retry
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":"The bug that only showed up on Mondays"}'

# the network dropped and you do not know if it went through: repeat it EXACTLY, with the same key
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":"The bug that only showed up on Mondays"}'
# HTTP/2 200 … idempotent-replayed: true   (same body, only one idea on the board)

JavaScript with fetch

Retry with the same key
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function createPost(post) {
  const key = crypto.randomUUID(); // one key per request, the same on every attempt
  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 { /* network failure: retry with the same key */ }
  }
  throw new Error("no response after 3 attempts");
}

SDK (@praeko/sdk)

Every call that writes accepts the idempotencyKey option. With true, the SDK generates a UUID and resends the request with the same key on a network failure, a 429 or a 5xx. With a string, you pick the key (for example, one of your job ids, so it survives a process restart).

JavaScript
import { Praeko } from "@praeko/sdk";
const praeko = new Praeko({ url: "https://praeko.app", key: process.env.PRAEKO_KEY });

// 1) the SDK generates the UUID and retries the POST with the same key if the network fails
const post = await praeko.posts.create({ title, media: { video: video.id }, texts, items }, { idempotencyKey: true });

// 2) or pick the key yourself
await praeko.webhooks.create({ url: "https://example.com/hooks/praeko", idempotencyKey: `hook-${jobId}` });

Without idempotencyKey, the SDK never retries a POST, PATCH or DELETE (only GET and upload parts). These accept the option: media.create and media.complete, posts.create, adopt, update, cancel, publishNow and scheduleYoutube, webhooks.create, rotateSecret and redeliver, ideas.create and import, apiKeys.create, youtube.reply and instagram.reply.

CLI

Terminal
praeko publish-now <id> --idempotency-key K
praeko webhook create --url https://example.com/hooks/praeko --idempotency-key K

publish-now and webhook create send the header. The CLI does not retry on its own: the option is for when you run the command again, for example from a script with retries.

What was already safe to resend

Situation What to do
POST /v1/posts with your own id Send your own id in the body: a repeat returns 409 id_exists and nothing is duplicated. Works even without Idempotency-Key.
Repeated media part Safe: PUT /v1/media/:id/parts/:part overwrites the same part.
Repeated webhook delivery Praeko-Delivery is the same on every attempt: keep the ids you already handled and ignore repeats.
POST /v1/insights/import Idempotent: it upserts by network and id.
POST /v1/ideas/import Idempotent by each idea's old id (only ideas that do not exist yet are added).
Question or mistake on this page? contato@praeko.appView .md

Type to search pages, routes and error codes.