Skip to content

Guides

Idempotency

Retry article creation as often as you like — with an Idempotency-Key you'll never pay twice for the same article.

Why it matters

Creating an article costs credits. If your request times out or the connection drops, you can't tell whether we received it. Retrying blindly could create — and charge — a second article. An idempotency key makes the retry safe.

Using Idempotency-Key

Send a unique value (up to 100 characters) in the Idempotency-Key header of POST /articles. A UUID works well, or a key derived from your own data such as post-4711-de.

Shell
curl https://reblogger.ai/api/v1/articles \
  -H "Authorization: Bearer $REBLOGGER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: post-4711-de" \
  -d '{ "source": { "type": "url", "url": "https://example.com/post-4711" }, "language": "de" }'

Behavior

  • The first request creates the generation and returns 202 Accepted.
  • Any later request with the same key returns the original generation with 200 OK and the header Idempotent-Replayed: true — no new article, no new charge.
  • Concurrent requests with the same key are safe: exactly one generation is created.
  • Keys are scoped to your account (shared across your API keys) and don't expire.
  • The body of a replayed request is not compared — reuse a key only for a retry of the same request.

Only POST /articles needs a key. GET, PATCH and DELETE are naturally idempotent. In the MCP server, pass idempotency_key to generate_article.

A safe retry loop

async function createArticle(body, attempts = 4) {
  const key = crypto.randomUUID(); // one key for all attempts of this article
  for (let i = 0; i < attempts; i++) {
    try {
      const res = await fetch('https://reblogger.ai/api/v1/articles', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.REBLOGGER_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': key,
        },
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(15_000),
      });
      if (res.status === 429 || res.status >= 500) throw new Error(`retryable ${res.status}`);
      return await res.json(); // 202 or 200 (replay) — or a 4xx problem you should not retry
    } catch (err) {
      if (i === attempts - 1) throw err;
      await new Promise((r) => setTimeout(r, 2 ** i * 1000));
    }
  }
}