Idempotency and retries

An Idempotency-Key header on every POST: retry after a network failure without creating anything twice nor using credits twice, for 24 hours.

api.memojin.com/v1 · Contract 1.0.0-beta.2 · Beta

Sections · Idempotency

A network failure or a timeout does not tell you whether your request went through. To retry without creating anything twice nor using credits twice, add the Idempotency-Key header to every POST.

Example

POST/v1/contents/{contentId}/memocardsScope memocards:write
Request
curl -X POST "https://api.memojin.com/v1/contents/3f1c2b0e-8d4a-4f7e-9a51-3c2d1e0f9b87/memocards" \
  -H "Authorization: Bearer $MEMOJIN_API_KEY" \
  -H "Idempotency-Key: 6f1c2b0e-8d4a-4f7e-9a51-3c2d1e0f9b87" \
  -H "Content-Type: application/json" \
  -d '{
  "front": "Where does photosynthesis take place?",
  "back": "In the chloroplasts, mainly in the leaves."
}'
Response 201
{
  "id": "c5e8a1b2-3d4f-4a6b-9c8d-7e6f5a4b3c21",
  "contentId": "3f1c2b0e-8d4a-4f7e-9a51-3c2d1e0f9b87",
  "front": "Where does photosynthesis take place?",
  "back": "In the chloroplasts, mainly in the leaves.",
  "explanation": null,
  "retention": 0.82,
  "lastReviewedAt": "2026-10-03T18:12:00.000Z",
  "createdAt": "2026-10-01T14:05:00.000Z",
  "updatedAt": "2026-10-01T14:05:00.000Z"
}

Rules

  • One key per operation: 1 to 255 printable ASCII characters, preferably a UUID v4. Generate it before the first attempt and keep it with the operation (in your database, in your job queue) for all its retries.
  • For 24 hours, the same key with the same request (same method, same path, same body) replays the first response as is, with Idempotent-Replayed: true: nothing is created again, no credit is used again.
  • The same key with another request: 422 idempotency_key_reused. The same request still running: 409 idempotency_in_progress, retry after Retry-After (1 s).
  • Responses stored and replayed: successes (2xx), 400, 404 and 422. Not stored, so that a retry really runs again: 402, 409, 429 and 5xx errors.
  • Keys belong to your organization and to the environment: a key used in test never replays a live response.
  • GET requests do not need one; PATCH and DELETE are protected by If-Match.

Retrying with the same key

import { randomUUID } from 'node:crypto';

const key = randomUUID(); // kept with the operation, reused on every retry
for (const wait of [0, 1000, 5000, 30000]) {
  await new Promise((r) => setTimeout(r, wait));
  try {
    const res = await fetch('https://api.memojin.com/v1/contents/3f1c2b0e-8d4a-4f7e-9a51-3c2d1e0f9b87/memocards', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.MEMOJIN_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': key,
      },
      body: JSON.stringify(card),
      signal: AbortSignal.timeout(30_000),
    });
    if (res.status === 409 || res.status === 429) {
      await new Promise((r) => setTimeout(r, Number(res.headers.get('Retry-After') ?? 1) * 1000));
      continue;
    }
    if (res.status < 500) return await res.json(); // 201, a replay, or an error to fix
  } catch {
    // network error or timeout: retry with the SAME key
  }
}