What is Idempotency?

Idempotency is the property of an API operation where executing the same request multiple times results in the same outcome as executing it once. If a request succeeds but the client never receives the response — because of a timeout, network drop, crash, or restarts — you can safely retry it without duplicating side-effect operations. Imagine your system is creating a payout to a mobile money user. You send the request, but the network drops before you get the response. Without idempotency, retrying could result in two payouts — sending the same funds twice to the user.
(You definitely don’t want this 😀)
With idempotency, you attach a unique Idempotency-Key to the request:
  • If the first attempt succeeded, Monime returns the original result when you retry.
  • If the first attempt never reached Monime, the request is processed normally.

How it Works in Monime

When you send a request to Monime with an idempotency key, something fascinating happens that sets it apart from most payment APIs. Your key doesn’t just exist in some global namespace where it could collide with keys from other spaces. Instead, we evaluate your key within the context of the specific Space your request is targeting, creating what we call space-scoped idempotency. This means that if you’re managing payments for multiple businesses through different Spaces, you can use the exact same key pattern across all of them without any risk of collision or confusion. Think about what this means in practice. Suppose you’re building a payroll platform that serves several companies, and suppose your model requires that each company have an associating Space on Monime so you delegate financial services to Monime. Now, when Company A creates a payout with Idempotency-Key: payout-12345 for their employee Aminata, and Company B simultaneously creates a payout with the same Idempotency-Key: payout-456 for their employee Musa, these operations remain completely independent. They’re not just different transactions; they exist in entirely separate idempotency universes. This Space-scoped design means you can use consistent, meaningful idempotency key patterns across your entire platform without maintaining complex namespacing schemes or worrying about key collisions between different clients. Protection happens in two layers.
  • High-speed cache (24 hours): Monime stores the key and its result in a fast-access cache for one day, instantly returning the original response for any matching retry during that window.
  • Last-mile enforcement: Unlike most financial APIs, protection doesn’t disappear when the cache expires. Even after 24 hours, retries hitting the internal service undergo transaction-level deduplication—so whether a retry comes after five seconds or five days, duplicates are still prevented.
Unlike many other systems, Monime does not cache API errors — failed requests bypass the idempotency mechanism. This lets you retry after transient issues, like service unavailability, or bad request without having the need to generate a new key.
We also have some conflict detection mechanism that protects you from a particular class of bugs. When you send a request with an idempotency key that’s already been used, Monime doesn’t just blindly return the previous result. Instead, it first checks whether you’re sending the exact same request. If you accidentally reuse an idempotency key but with different parameters—say, you meant to create a SLE 100 payout but accidentally sent SLE 10 with a recycled key — Monime will immediately return a 409 Conflict error with the reason idempotency_key_in_use. This validation happens at both the URL level and the request body level, ensuring that an idempotency key truly represents one and only one specific intention. Without this protection, a simple key generation bug in your code could lead to wildly incorrect financial operations being silently accepted.
Mismatch detection (same key, different request) is limited to the 24-hour retention period; beyond that, a retry won’t trigger a 409.
By combining Space-scoping, dual-layer protection, and strict validation, we turn the uncertainty of distributed systems into predictable, safe payment operations—no accidental duplicates, no guesswork.

How to Use It in Monime

Using idempotency in Monime is straightforward. Include an Idempotency-Key header with every POST request that creates resources. The key should be unique to each logical operation you want to perform:
curl --request POST \
  --url https://api.monime.io/v1/payouts \
  --header 'Authorization: Bearer <access-token>' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: <idempotency-key>' \
  --header 'Monime-Space-Id: <monime-space-id>' \
  --data '{
  "amount": {
    "currency": "SLE",
    "value": 100
  },
  "destination": {
    "type": "momo",
    "providerId": "m17",
    "phoneNumber": "078000111"
  }
}'

Generating Idempotency Keys

Choose a key generation strategy that fits your use case:
  • UUID Approach (Recommended for most cases):
import { v4 as uuidv4 } from 'uuid';

const idempotencyKey = uuidv4(); // "550e8400-e29b-41d4-a716-446655440000"

Implementing Retry Logic

With idempotency, you can safely retry failed requests without fear of duplicates:
async function createPayoutWithRetry(data, idempotencyKey, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.monime.io/v1/payouts', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${ACCESS_TOKEN}`,
          'Monime-Space-Id': SPACE_ID,
          'Idempotency-Key': idempotencyKey,  // Same key for all retries
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });

      if (response.ok) {
        return await response.json();
      }

      // Handle 409 Conflict specifically
      if (response.status === 409) {
        const error = await response.json();
        if (error.reason === 'idempotency_key_in_use') {
          throw new Error('Idempotency key already used with different request');
        }
      }

      // Retry on network errors or 5xx responses
      if (response.status >= 500) {
        console.log(`Attempt ${attempt} failed, retrying...`);
        await sleep(Math.pow(2, attempt) * 1000); // Exponential backoff
        continue;
      }
      throw new Error(`Request failed: ${response.status}`);

    } catch (error) {
      if (attempt === maxRetries) throw error;
      await sleep(Math.pow(2, attempt) * 1000);
    }
  }
}

Conflict Error Response

If you reuse an idempotency key with a different endpoint or payload, Monime will reject the request with the following error:
{
    "success": false,
    "messages": [],
    "error": {
        "code": 409,
        "reason": "idempotency_key_in_use",
        "message": "Conflict: Idempotency key reused with a non-identical request.",
        "details": []
    }
}

Best Practices for Idempotency

  • Generation and Use
    1. Use a unique key per logical operation. UUIDv4 works well, or use a deterministic key (UUIDv5) from a stable business ref (e.g., payout:) when you want automatic retries across services.
    2. Keep keys above 25 and below 64 characters. When a stable identifier exceeds this, convert it to a UUIDv5 so it remains compatible and deterministic.
    3. Send the exact same request on retry. Method, URL, headers, and JSON body must match byte-for-byte (including field order if your client doesn’t canonicalize).
  • Store & Retry
    1. Persist keys client-side alongside your business operation ID. This lets you retry confidently after crashes or timeouts.
    2. Leverage the retention window. Cache retention is 24 hours; beyond that Monime still enforces last-mile idempotency, but plan to reconcile using your business refs if you expect very late retries.
    3. GET resource on uncertainty. If you don’t know the outcome, prefer fetching the resource before issuing a new creation; 404 means it was never created.
  • Handle Outcomes
    1. On success: reuse the same key if you must confirm; you’ll receive the original response.
    2. On 409 Conflict: your key was used for a different request; generate a new key and fix the caller logic.
    3. On errors (5xx/timeout): retry with the same key. Errors aren’t cached, so you’re not forced to issue a new key.
  • Security
    1. Avoid putting sensitive information in the key. Treat it as an identifier that is safe to log and helful for tracing.